Move client-specific files to 'src/client' (#7902)
authorQuentin Bazin <quent42340@gmail.com>
Wed, 28 Nov 2018 19:01:49 +0000 (20:01 +0100)
committerSmallJoker <SmallJoker@users.noreply.github.com>
Wed, 28 Nov 2018 19:01:49 +0000 (20:01 +0100)
Update Android.mk
Remove 'src/client' from include_directories

135 files changed:
.gitignore
build/android/jni/Android.mk
src/CMakeLists.txt
src/camera.cpp [deleted file]
src/camera.h [deleted file]
src/client.cpp [deleted file]
src/client.h [deleted file]
src/client/CMakeLists.txt
src/client/camera.cpp [new file with mode: 0644]
src/client/camera.h [new file with mode: 0644]
src/client/client.cpp [new file with mode: 0644]
src/client/client.h [new file with mode: 0644]
src/client/clientenvironment.cpp [new file with mode: 0644]
src/client/clientenvironment.h [new file with mode: 0644]
src/client/clientmap.cpp [new file with mode: 0644]
src/client/clientmap.h [new file with mode: 0644]
src/client/clientmedia.cpp [new file with mode: 0644]
src/client/clientmedia.h [new file with mode: 0644]
src/client/clientobject.cpp [new file with mode: 0644]
src/client/clientobject.h [new file with mode: 0644]
src/client/clouds.cpp [new file with mode: 0644]
src/client/clouds.h [new file with mode: 0644]
src/client/content_cao.cpp [new file with mode: 0644]
src/client/content_cao.h [new file with mode: 0644]
src/client/content_cso.cpp [new file with mode: 0644]
src/client/content_cso.h [new file with mode: 0644]
src/client/content_mapblock.cpp [new file with mode: 0644]
src/client/content_mapblock.h [new file with mode: 0644]
src/client/filecache.cpp [new file with mode: 0644]
src/client/filecache.h [new file with mode: 0644]
src/client/fontengine.cpp [new file with mode: 0644]
src/client/fontengine.h [new file with mode: 0644]
src/client/game.cpp [new file with mode: 0644]
src/client/game.h [new file with mode: 0644]
src/client/guiscalingfilter.cpp [new file with mode: 0644]
src/client/guiscalingfilter.h [new file with mode: 0644]
src/client/imagefilters.cpp [new file with mode: 0644]
src/client/imagefilters.h [new file with mode: 0644]
src/client/keycode.cpp [new file with mode: 0644]
src/client/keycode.h [new file with mode: 0644]
src/client/localplayer.cpp [new file with mode: 0644]
src/client/localplayer.h [new file with mode: 0644]
src/client/mapblock_mesh.cpp [new file with mode: 0644]
src/client/mapblock_mesh.h [new file with mode: 0644]
src/client/mesh.cpp [new file with mode: 0644]
src/client/mesh.h [new file with mode: 0644]
src/client/mesh_generator_thread.cpp [new file with mode: 0644]
src/client/mesh_generator_thread.h [new file with mode: 0644]
src/client/meshgen/collector.cpp
src/client/minimap.cpp [new file with mode: 0644]
src/client/minimap.h [new file with mode: 0644]
src/client/particles.cpp [new file with mode: 0644]
src/client/particles.h [new file with mode: 0644]
src/client/render/core.cpp
src/client/render/interlaced.cpp
src/client/render/stereo.cpp
src/client/shader.cpp [new file with mode: 0644]
src/client/shader.h [new file with mode: 0644]
src/client/sky.cpp [new file with mode: 0644]
src/client/sky.h [new file with mode: 0644]
src/client/wieldmesh.cpp [new file with mode: 0644]
src/client/wieldmesh.h [new file with mode: 0644]
src/clientenvironment.cpp [deleted file]
src/clientenvironment.h [deleted file]
src/clientmap.cpp [deleted file]
src/clientmap.h [deleted file]
src/clientmedia.cpp [deleted file]
src/clientmedia.h [deleted file]
src/clientobject.cpp [deleted file]
src/clientobject.h [deleted file]
src/clouds.cpp [deleted file]
src/clouds.h [deleted file]
src/collision.cpp
src/content_cao.cpp [deleted file]
src/content_cao.h [deleted file]
src/content_cso.cpp [deleted file]
src/content_cso.h [deleted file]
src/content_mapblock.cpp [deleted file]
src/content_mapblock.h [deleted file]
src/filecache.cpp [deleted file]
src/filecache.h [deleted file]
src/fontengine.cpp [deleted file]
src/fontengine.h [deleted file]
src/game.cpp [deleted file]
src/game.h [deleted file]
src/gui/guiChatConsole.cpp
src/gui/guiConfirmRegistration.cpp
src/gui/guiEngine.cpp
src/gui/guiFormSpecMenu.cpp
src/gui/guiKeyChangeMenu.h
src/gui/guiPasswordChange.cpp
src/gui/guiTable.cpp
src/guiscalingfilter.cpp [deleted file]
src/guiscalingfilter.h [deleted file]
src/imagefilters.cpp [deleted file]
src/imagefilters.h [deleted file]
src/itemdef.cpp
src/keycode.cpp [deleted file]
src/keycode.h [deleted file]
src/localplayer.cpp [deleted file]
src/localplayer.h [deleted file]
src/main.cpp
src/mapblock.cpp
src/mapblock_mesh.cpp [deleted file]
src/mapblock_mesh.h [deleted file]
src/mesh.cpp [deleted file]
src/mesh.h [deleted file]
src/mesh_generator_thread.cpp [deleted file]
src/mesh_generator_thread.h [deleted file]
src/minimap.cpp [deleted file]
src/minimap.h [deleted file]
src/network/clientopcodes.h
src/network/clientpackethandler.cpp
src/nodedef.cpp
src/particles.cpp [deleted file]
src/particles.h [deleted file]
src/script/cpp_api/s_base.cpp
src/script/cpp_api/s_client.cpp
src/script/cpp_api/s_security.cpp
src/script/lua_api/l_camera.cpp
src/script/lua_api/l_client.cpp
src/script/lua_api/l_env.cpp
src/script/lua_api/l_localplayer.cpp
src/script/lua_api/l_minimap.cpp
src/script/lua_api/l_particles.cpp
src/script/lua_api/l_particles_local.cpp
src/script/scripting_client.cpp
src/shader.cpp [deleted file]
src/shader.h [deleted file]
src/sky.cpp [deleted file]
src/sky.h [deleted file]
src/unittest/test_keycode.cpp
src/wieldmesh.cpp [deleted file]
src/wieldmesh.h [deleted file]
util/travis/clang-format-whitelist.txt

index dd06dfb308d8ce31c599d70fef910ed9eaa6e243..d5cb9b6ea7d065a00003630e66c971358d50b168 100644 (file)
@@ -56,6 +56,7 @@ debug.txt
 
 ## Other files generated by minetest
 screenshot_*.png
+testbm.txt
 
 ## Doxygen files
 doc/Doxyfile
@@ -95,6 +96,7 @@ cmake-build-release/
 cmake_config.h
 cmake_config_githash.h
 CMakeDoxy*
+compile_commands.json
 
 ## Android build files
 build/android/src/main/assets
index e5cf7349cf925aeba192c1d7f94a306096704b40..364278f68b99944396ff0e31446d0038253dc9af 100644 (file)
@@ -113,20 +113,20 @@ LOCAL_C_INCLUDES := \
 
 LOCAL_SRC_FILES := \
                jni/src/ban.cpp                           \
-               jni/src/camera.cpp                        \
+               jni/src/client/camera.cpp                 \
                jni/src/mapgen/cavegen.cpp                \
                jni/src/chat.cpp                          \
-               jni/src/client.cpp                        \
-               jni/src/clientenvironment.cpp             \
+               jni/src/client/client.cpp                 \
+               jni/src/client/clientenvironment.cpp      \
                jni/src/clientiface.cpp                   \
-               jni/src/clientmap.cpp                     \
-               jni/src/clientmedia.cpp                   \
-               jni/src/clientobject.cpp                  \
-               jni/src/clouds.cpp                        \
+               jni/src/client/clientmap.cpp              \
+               jni/src/client/clientmedia.cpp            \
+               jni/src/client/clientobject.cpp           \
+               jni/src/client/clouds.cpp                 \
                jni/src/collision.cpp                     \
-               jni/src/content_cao.cpp                   \
-               jni/src/content_cso.cpp                   \
-               jni/src/content_mapblock.cpp              \
+               jni/src/client/content_cao.cpp            \
+               jni/src/client/content_cso.cpp            \
+               jni/src/client/content_mapblock.cpp       \
                jni/src/content_mapnode.cpp               \
                jni/src/content_nodemeta.cpp              \
                jni/src/content_sao.cpp                   \
@@ -147,10 +147,10 @@ LOCAL_SRC_FILES := \
                jni/src/emerge.cpp                        \
                jni/src/environment.cpp                   \
                jni/src/face_position_cache.cpp           \
-               jni/src/filecache.cpp                     \
+               jni/src/client/filecache.cpp              \
                jni/src/filesys.cpp                       \
-               jni/src/fontengine.cpp                    \
-               jni/src/game.cpp                          \
+               jni/src/client/fontengine.cpp             \
+               jni/src/client/game.cpp                   \
                jni/src/genericobject.cpp                 \
                jni/src/gettext.cpp                       \
                jni/src/gui/guiChatConsole.cpp            \
@@ -162,7 +162,7 @@ LOCAL_SRC_FILES := \
                jni/src/gui/guiKeyChangeMenu.cpp          \
                jni/src/gui/guiPasswordChange.cpp         \
                jni/src/gui/guiTable.cpp                  \
-               jni/src/guiscalingfilter.cpp              \
+               jni/src/client/guiscalingfilter.cpp              \
                jni/src/gui/guiVolumeChange.cpp           \
                jni/src/gui/intlGUIEditBox.cpp            \
                jni/src/gui/modalMenu.cpp                 \
@@ -170,20 +170,20 @@ LOCAL_SRC_FILES := \
                jni/src/gui/touchscreengui.cpp            \
                jni/src/httpfetch.cpp                     \
                jni/src/hud.cpp                           \
-               jni/src/imagefilters.cpp                  \
+               jni/src/client/imagefilters.cpp                  \
                jni/src/inventory.cpp                     \
                jni/src/inventorymanager.cpp              \
                jni/src/itemdef.cpp                       \
                jni/src/itemstackmetadata.cpp             \
-               jni/src/keycode.cpp                       \
+               jni/src/client/keycode.cpp                       \
                jni/src/light.cpp                         \
-               jni/src/localplayer.cpp                   \
+               jni/src/client/localplayer.cpp                   \
                jni/src/log.cpp                           \
                jni/src/main.cpp                          \
                jni/src/map.cpp                           \
                jni/src/map_settings_manager.cpp          \
                jni/src/mapblock.cpp                      \
-               jni/src/mapblock_mesh.cpp                 \
+               jni/src/client/mapblock_mesh.cpp                 \
                jni/src/mapgen/mapgen.cpp                 \
                jni/src/mapgen/mapgen_carpathian.cpp      \
                jni/src/mapgen/mapgen_flat.cpp            \
@@ -195,14 +195,14 @@ LOCAL_SRC_FILES := \
                jni/src/mapgen/mapgen_valleys.cpp         \
                jni/src/mapnode.cpp                       \
                jni/src/mapsector.cpp                     \
-               jni/src/mesh.cpp                          \
-               jni/src/mesh_generator_thread.cpp         \
+               jni/src/client/mesh.cpp                   \
+               jni/src/client/mesh_generator_thread.cpp  \
                jni/src/metadata.cpp                      \
                jni/src/mapgen/mg_biome.cpp               \
                jni/src/mapgen/mg_decoration.cpp          \
                jni/src/mapgen/mg_ore.cpp                 \
                jni/src/mapgen/mg_schematic.cpp           \
-               jni/src/minimap.cpp                       \
+               jni/src/client/minimap.cpp                \
                jni/src/modchannels.cpp                   \
                jni/src/nameidmapping.cpp                 \
                jni/src/nodedef.cpp                       \
@@ -211,7 +211,7 @@ LOCAL_SRC_FILES := \
                jni/src/noise.cpp                         \
                jni/src/objdef.cpp                        \
                jni/src/object_properties.cpp             \
-               jni/src/particles.cpp                     \
+               jni/src/client/particles.cpp                     \
                jni/src/pathfinder.cpp                    \
                jni/src/player.cpp                        \
                jni/src/porting_android.cpp               \
@@ -229,8 +229,8 @@ LOCAL_SRC_FILES := \
                jni/src/serverenvironment.cpp             \
                jni/src/serverlist.cpp                    \
                jni/src/serverobject.cpp                  \
-               jni/src/shader.cpp                        \
-               jni/src/sky.cpp                           \
+               jni/src/client/shader.cpp                 \
+               jni/src/client/sky.cpp                    \
                jni/src/staticobject.cpp                  \
                jni/src/tileanimation.cpp                 \
                jni/src/translation.cpp                   \
@@ -275,7 +275,7 @@ LOCAL_SRC_FILES := \
                jni/src/unittest/test_voxelalgorithms.cpp \
                jni/src/unittest/test_voxelmanipulator.cpp \
                jni/src/settings.cpp                      \
-               jni/src/wieldmesh.cpp                     \
+               jni/src/client/wieldmesh.cpp              \
                jni/src/client/meshgen/collector.cpp      \
                jni/src/client/clientlauncher.cpp         \
                jni/src/client/gameui.cpp                 \
index c0832d0c2fb33c4038ebefead28ab42cc25eb69d..8218d586c85a27556279f4540d8fbc2d28301786 100644 (file)
@@ -397,12 +397,14 @@ set(common_SRCS
        genericobject.cpp
        gettext.cpp
        httpfetch.cpp
+       hud.cpp
        inventory.cpp
        inventorymanager.cpp
        itemdef.cpp
        itemstackmetadata.cpp
        light.cpp
        log.cpp
+       main.cpp
        map.cpp
        map_settings_manager.cpp
        mapblock.cpp
@@ -482,34 +484,6 @@ set(client_SRCS
        ${gui_SRCS}
        ${client_network_SRCS}
        ${client_irrlicht_changes_SRCS}
-       camera.cpp
-       client.cpp
-       clientenvironment.cpp
-       clientmap.cpp
-       clientmedia.cpp
-       clientobject.cpp
-       clouds.cpp
-       content_cao.cpp
-       content_cso.cpp
-       content_mapblock.cpp
-       convert_json.cpp
-       filecache.cpp
-       fontengine.cpp
-       game.cpp
-       guiscalingfilter.cpp
-       hud.cpp
-       imagefilters.cpp
-       keycode.cpp
-       localplayer.cpp
-       main.cpp
-       mapblock_mesh.cpp
-       mesh.cpp
-       mesh_generator_thread.cpp
-       minimap.cpp
-       particles.cpp
-       shader.cpp
-       sky.cpp
-       wieldmesh.cpp
        ${client_SCRIPT_SRCS}
        ${UNITTEST_CLIENT_SRCS}
 )
@@ -518,7 +492,6 @@ list(SORT client_SRCS)
 # Server sources
 set(server_SRCS
        ${common_SRCS}
-       main.cpp
 )
 list(SORT server_SRCS)
 
@@ -829,10 +802,10 @@ if(BUILD_CLIENT)
        endif()
 
        if(USE_FREETYPE)
-               install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../fonts" DESTINATION "${SHAREDIR}" 
+               install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../fonts" DESTINATION "${SHAREDIR}"
                                FILES_MATCHING PATTERN "*.ttf" PATTERN "*.txt")
        else()
-               install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../fonts" DESTINATION "${SHAREDIR}" 
+               install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../fonts" DESTINATION "${SHAREDIR}"
                                FILES_MATCHING PATTERN "*.png" PATTERN "*.xml")
        endif()
 
diff --git a/src/camera.cpp b/src/camera.cpp
deleted file mode 100644 (file)
index 1bbdb56..0000000
+++ /dev/null
@@ -1,658 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 "camera.h"
-#include "debug.h"
-#include "client.h"
-#include "map.h"
-#include "clientmap.h"     // MapDrawControl
-#include "player.h"
-#include <cmath>
-#include "client/renderingengine.h"
-#include "settings.h"
-#include "wieldmesh.h"
-#include "noise.h"         // easeCurve
-#include "sound.h"
-#include "event.h"
-#include "nodedef.h"
-#include "util/numeric.h"
-#include "constants.h"
-#include "fontengine.h"
-#include "script/scripting_client.h"
-
-#define CAMERA_OFFSET_STEP 200
-#define WIELDMESH_OFFSET_X 55.0f
-#define WIELDMESH_OFFSET_Y -35.0f
-
-Camera::Camera(MapDrawControl &draw_control, Client *client):
-       m_draw_control(draw_control),
-       m_client(client)
-{
-       scene::ISceneManager *smgr = RenderingEngine::get_scene_manager();
-       // note: making the camera node a child of the player node
-       // would lead to unexpected behaviour, so we don't do that.
-       m_playernode = smgr->addEmptySceneNode(smgr->getRootSceneNode());
-       m_headnode = smgr->addEmptySceneNode(m_playernode);
-       m_cameranode = smgr->addCameraSceneNode(smgr->getRootSceneNode());
-       m_cameranode->bindTargetAndRotation(true);
-
-       // This needs to be in its own scene manager. It is drawn after
-       // all other 3D scene nodes and before the GUI.
-       m_wieldmgr = smgr->createNewSceneManager();
-       m_wieldmgr->addCameraSceneNode();
-       m_wieldnode = new WieldMeshSceneNode(m_wieldmgr, -1, false);
-       m_wieldnode->setItem(ItemStack(), m_client);
-       m_wieldnode->drop(); // m_wieldmgr grabbed it
-
-       /* TODO: Add a callback function so these can be updated when a setting
-        *       changes.  At this point in time it doesn't matter (e.g. /set
-        *       is documented to change server settings only)
-        *
-        * 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.
-        */
-       m_cache_fall_bobbing_amount = g_settings->getFloat("fall_bobbing_amount");
-       m_cache_view_bobbing_amount = g_settings->getFloat("view_bobbing_amount");
-       // 45 degrees is the lowest FOV that doesn't cause the server to treat this
-       // as a zoom FOV and load world beyond the set server limits.
-       m_cache_fov                 = std::fmax(g_settings->getFloat("fov"), 45.0f);
-       m_arm_inertia               = g_settings->getBool("arm_inertia");
-       m_nametags.clear();
-}
-
-Camera::~Camera()
-{
-       m_wieldmgr->drop();
-}
-
-bool Camera::successfullyCreated(std::string &error_message)
-{
-       if (!m_playernode) {
-               error_message = "Failed to create the player scene node";
-       } else if (!m_headnode) {
-               error_message = "Failed to create the head scene node";
-       } else if (!m_cameranode) {
-               error_message = "Failed to create the camera scene node";
-       } else if (!m_wieldmgr) {
-               error_message = "Failed to create the wielded item scene manager";
-       } else if (!m_wieldnode) {
-               error_message = "Failed to create the wielded item scene node";
-       } else {
-               error_message.clear();
-       }
-
-       if (g_settings->getBool("enable_client_modding")) {
-               m_client->getScript()->on_camera_ready(this);
-       }
-       return error_message.empty();
-}
-
-// Returns the fractional part of x
-inline f32 my_modf(f32 x)
-{
-       double dummy;
-       return modf(x, &dummy);
-}
-
-void Camera::step(f32 dtime)
-{
-       if(m_view_bobbing_fall > 0)
-       {
-               m_view_bobbing_fall -= 3 * dtime;
-               if(m_view_bobbing_fall <= 0)
-                       m_view_bobbing_fall = -1; // Mark the effect as finished
-       }
-
-       bool was_under_zero = m_wield_change_timer < 0;
-       m_wield_change_timer = MYMIN(m_wield_change_timer + dtime, 0.125);
-
-       if (m_wield_change_timer >= 0 && was_under_zero)
-               m_wieldnode->setItem(m_wield_item_next, m_client);
-
-       if (m_view_bobbing_state != 0)
-       {
-               //f32 offset = dtime * m_view_bobbing_speed * 0.035;
-               f32 offset = dtime * m_view_bobbing_speed * 0.030;
-               if (m_view_bobbing_state == 2) {
-                       // Animation is getting turned off
-                       if (m_view_bobbing_anim < 0.25) {
-                               m_view_bobbing_anim -= offset;
-                       } else if (m_view_bobbing_anim > 0.75) {
-                               m_view_bobbing_anim += offset;
-                       }
-
-                       if (m_view_bobbing_anim < 0.5) {
-                               m_view_bobbing_anim += offset;
-                               if (m_view_bobbing_anim > 0.5)
-                                       m_view_bobbing_anim = 0.5;
-                       } else {
-                               m_view_bobbing_anim -= offset;
-                               if (m_view_bobbing_anim < 0.5)
-                                       m_view_bobbing_anim = 0.5;
-                       }
-
-                       if (m_view_bobbing_anim <= 0 || m_view_bobbing_anim >= 1 ||
-                                       fabs(m_view_bobbing_anim - 0.5) < 0.01) {
-                               m_view_bobbing_anim = 0;
-                               m_view_bobbing_state = 0;
-                       }
-               }
-               else {
-                       float was = m_view_bobbing_anim;
-                       m_view_bobbing_anim = my_modf(m_view_bobbing_anim + offset);
-                       bool step = (was == 0 ||
-                                       (was < 0.5f && m_view_bobbing_anim >= 0.5f) ||
-                                       (was > 0.5f && m_view_bobbing_anim <= 0.5f));
-                       if(step) {
-                               m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::VIEW_BOBBING_STEP));
-                       }
-               }
-       }
-
-       if (m_digging_button != -1) {
-               f32 offset = dtime * 3.5f;
-               float m_digging_anim_was = m_digging_anim;
-               m_digging_anim += offset;
-               if (m_digging_anim >= 1)
-               {
-                       m_digging_anim = 0;
-                       m_digging_button = -1;
-               }
-               float lim = 0.15;
-               if(m_digging_anim_was < lim && m_digging_anim >= lim)
-               {
-                       if (m_digging_button == 0) {
-                               m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::CAMERA_PUNCH_LEFT));
-                       } else if(m_digging_button == 1) {
-                               m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::CAMERA_PUNCH_RIGHT));
-                       }
-               }
-       }
-}
-
-static inline v2f dir(const v2f &pos_dist)
-{
-       f32 x = pos_dist.X - WIELDMESH_OFFSET_X;
-       f32 y = pos_dist.Y - WIELDMESH_OFFSET_Y;
-
-       f32 x_abs = std::fabs(x);
-       f32 y_abs = std::fabs(y);
-
-       if (x_abs >= y_abs) {
-               y *= (1.0f / x_abs);
-               x /= x_abs;
-       }
-
-       if (y_abs >= x_abs) {
-               x *= (1.0f / y_abs);
-               y /= y_abs;
-       }
-
-       return v2f(std::fabs(x), std::fabs(y));
-}
-
-void Camera::addArmInertia(f32 player_yaw)
-{
-       m_cam_vel.X = std::fabs(rangelim(m_last_cam_pos.X - player_yaw,
-               -100.0f, 100.0f) / 0.016f) * 0.01f;
-       m_cam_vel.Y = std::fabs((m_last_cam_pos.Y - m_camera_direction.Y) / 0.016f);
-       f32 gap_X = std::fabs(WIELDMESH_OFFSET_X - m_wieldmesh_offset.X);
-       f32 gap_Y = std::fabs(WIELDMESH_OFFSET_Y - m_wieldmesh_offset.Y);
-
-       if (m_cam_vel.X > 1.0f || m_cam_vel.Y > 1.0f) {
-               /*
-                   The arm moves relative to the camera speed,
-                   with an acceleration factor.
-               */
-
-               if (m_cam_vel.X > 1.0f) {
-                       if (m_cam_vel.X > m_cam_vel_old.X)
-                               m_cam_vel_old.X = m_cam_vel.X;
-
-                       f32 acc_X = 0.12f * (m_cam_vel.X - (gap_X * 0.1f));
-                       m_wieldmesh_offset.X += m_last_cam_pos.X < player_yaw ? acc_X : -acc_X;
-
-                       if (m_last_cam_pos.X != player_yaw)
-                               m_last_cam_pos.X = player_yaw;
-
-                       m_wieldmesh_offset.X = rangelim(m_wieldmesh_offset.X,
-                               WIELDMESH_OFFSET_X - 7.0f, WIELDMESH_OFFSET_X + 7.0f);
-               }
-
-               if (m_cam_vel.Y > 1.0f) {
-                       if (m_cam_vel.Y > m_cam_vel_old.Y)
-                               m_cam_vel_old.Y = m_cam_vel.Y;
-
-                       f32 acc_Y = 0.12f * (m_cam_vel.Y - (gap_Y * 0.1f));
-                       m_wieldmesh_offset.Y +=
-                               m_last_cam_pos.Y > m_camera_direction.Y ? acc_Y : -acc_Y;
-
-                       if (m_last_cam_pos.Y != m_camera_direction.Y)
-                               m_last_cam_pos.Y = m_camera_direction.Y;
-
-                       m_wieldmesh_offset.Y = rangelim(m_wieldmesh_offset.Y,
-                               WIELDMESH_OFFSET_Y - 10.0f, WIELDMESH_OFFSET_Y + 5.0f);
-               }
-
-               m_arm_dir = dir(m_wieldmesh_offset);
-       } else {
-               /*
-                   Now the arm gets back to its default position when the camera stops,
-                   following a vector, with a smooth deceleration factor.
-               */
-
-               f32 dec_X = 0.12f * (m_cam_vel_old.X * (1.0f +
-                       (1.0f - m_arm_dir.X))) * (gap_X / 20.0f);
-
-               f32 dec_Y = 0.06f * (m_cam_vel_old.Y * (1.0f +
-                       (1.0f - m_arm_dir.Y))) * (gap_Y / 15.0f);
-
-               if (gap_X < 0.1f)
-                       m_cam_vel_old.X = 0.0f;
-
-               m_wieldmesh_offset.X -=
-                       m_wieldmesh_offset.X > WIELDMESH_OFFSET_X ? dec_X : -dec_X;
-
-               if (gap_Y < 0.1f)
-                       m_cam_vel_old.Y = 0.0f;
-
-               m_wieldmesh_offset.Y -=
-                       m_wieldmesh_offset.Y > WIELDMESH_OFFSET_Y ? dec_Y : -dec_Y;
-       }
-}
-
-void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_reload_ratio)
-{
-       // Get player position
-       // Smooth the movement when walking up stairs
-       v3f old_player_position = m_playernode->getPosition();
-       v3f player_position = player->getPosition();
-       if (player->isAttached && player->parent)
-               player_position = player->parent->getPosition();
-       //if(player->touching_ground && player_position.Y > old_player_position.Y)
-       if(player->touching_ground &&
-                       player_position.Y > old_player_position.Y)
-       {
-               f32 oldy = old_player_position.Y;
-               f32 newy = player_position.Y;
-               f32 t = std::exp(-23 * frametime);
-               player_position.Y = oldy * t + newy * (1-t);
-       }
-
-       // Set player node transformation
-       m_playernode->setPosition(player_position);
-       m_playernode->setRotation(v3f(0, -1 * player->getYaw(), 0));
-       m_playernode->updateAbsolutePosition();
-
-       // Get camera tilt timer (hurt animation)
-       float cameratilt = fabs(fabs(player->hurt_tilt_timer-0.75)-0.75);
-
-       // Fall bobbing animation
-       float fall_bobbing = 0;
-       if(player->camera_impact >= 1 && m_camera_mode < CAMERA_MODE_THIRD)
-       {
-               if(m_view_bobbing_fall == -1) // Effect took place and has finished
-                       player->camera_impact = m_view_bobbing_fall = 0;
-               else if(m_view_bobbing_fall == 0) // Initialize effect
-                       m_view_bobbing_fall = 1;
-
-               // Convert 0 -> 1 to 0 -> 1 -> 0
-               fall_bobbing = m_view_bobbing_fall < 0.5 ? m_view_bobbing_fall * 2 : -(m_view_bobbing_fall - 0.5) * 2 + 1;
-               // Smoothen and invert the above
-               fall_bobbing = sin(fall_bobbing * 0.5 * M_PI) * -1;
-               // Amplify according to the intensity of the impact
-               fall_bobbing *= (1 - rangelim(50 / player->camera_impact, 0, 1)) * 5;
-
-               fall_bobbing *= m_cache_fall_bobbing_amount;
-       }
-
-       // Calculate players eye offset for different camera modes
-       v3f PlayerEyeOffset = player->getEyeOffset();
-       if (m_camera_mode == CAMERA_MODE_FIRST)
-               PlayerEyeOffset += player->eye_offset_first;
-       else
-               PlayerEyeOffset += player->eye_offset_third;
-
-       // Set head node transformation
-       m_headnode->setPosition(PlayerEyeOffset+v3f(0,cameratilt*-player->hurt_tilt_strength+fall_bobbing,0));
-       m_headnode->setRotation(v3f(player->getPitch(), 0, cameratilt*player->hurt_tilt_strength));
-       m_headnode->updateAbsolutePosition();
-
-       // Compute relative camera position and target
-       v3f rel_cam_pos = v3f(0,0,0);
-       v3f rel_cam_target = v3f(0,0,1);
-       v3f rel_cam_up = v3f(0,1,0);
-
-       if (m_cache_view_bobbing_amount != 0.0f && m_view_bobbing_anim != 0.0f &&
-               m_camera_mode < CAMERA_MODE_THIRD) {
-               f32 bobfrac = my_modf(m_view_bobbing_anim * 2);
-               f32 bobdir = (m_view_bobbing_anim < 0.5) ? 1.0 : -1.0;
-
-               #if 1
-               f32 bobknob = 1.2;
-               f32 bobtmp = sin(pow(bobfrac, bobknob) * M_PI);
-               //f32 bobtmp2 = cos(pow(bobfrac, bobknob) * M_PI);
-
-               v3f bobvec = v3f(
-                       0.3 * bobdir * sin(bobfrac * M_PI),
-                       -0.28 * bobtmp * bobtmp,
-                       0.);
-
-               //rel_cam_pos += 0.2 * bobvec;
-               //rel_cam_target += 0.03 * bobvec;
-               //rel_cam_up.rotateXYBy(0.02 * bobdir * bobtmp * M_PI);
-               float f = 1.0;
-               f *= m_cache_view_bobbing_amount;
-               rel_cam_pos += bobvec * f;
-               //rel_cam_target += 0.995 * bobvec * f;
-               rel_cam_target += bobvec * f;
-               rel_cam_target.Z -= 0.005 * bobvec.Z * f;
-               //rel_cam_target.X -= 0.005 * bobvec.X * f;
-               //rel_cam_target.Y -= 0.005 * bobvec.Y * f;
-               rel_cam_up.rotateXYBy(-0.03 * bobdir * bobtmp * M_PI * f);
-               #else
-               f32 angle_deg = 1 * bobdir * sin(bobfrac * M_PI);
-               f32 angle_rad = angle_deg * M_PI / 180;
-               f32 r = 0.05;
-               v3f off = v3f(
-                       r * sin(angle_rad),
-                       r * (cos(angle_rad) - 1),
-                       0);
-               rel_cam_pos += off;
-               //rel_cam_target += off;
-               rel_cam_up.rotateXYBy(angle_deg);
-               #endif
-
-       }
-
-       // Compute absolute camera position and target
-       m_headnode->getAbsoluteTransformation().transformVect(m_camera_position, rel_cam_pos);
-       m_headnode->getAbsoluteTransformation().rotateVect(m_camera_direction, rel_cam_target - rel_cam_pos);
-
-       v3f abs_cam_up;
-       m_headnode->getAbsoluteTransformation().rotateVect(abs_cam_up, rel_cam_up);
-
-       // Seperate camera position for calculation
-       v3f my_cp = m_camera_position;
-
-       // Reposition the camera for third person view
-       if (m_camera_mode > CAMERA_MODE_FIRST)
-       {
-               if (m_camera_mode == CAMERA_MODE_THIRD_FRONT)
-                       m_camera_direction *= -1;
-
-               my_cp.Y += 2;
-
-               // Calculate new position
-               bool abort = false;
-               for (int i = BS; i <= BS * 2.75; i++) {
-                       my_cp.X = m_camera_position.X + m_camera_direction.X * -i;
-                       my_cp.Z = m_camera_position.Z + m_camera_direction.Z * -i;
-                       if (i > 12)
-                               my_cp.Y = m_camera_position.Y + (m_camera_direction.Y * -i);
-
-                       // Prevent camera positioned inside nodes
-                       const NodeDefManager *nodemgr = m_client->ndef();
-                       MapNode n = m_client->getEnv().getClientMap()
-                               .getNodeNoEx(floatToInt(my_cp, BS));
-
-                       const ContentFeatures& features = nodemgr->get(n);
-                       if (features.walkable) {
-                               my_cp.X += m_camera_direction.X*-1*-BS/2;
-                               my_cp.Z += m_camera_direction.Z*-1*-BS/2;
-                               my_cp.Y += m_camera_direction.Y*-1*-BS/2;
-                               abort = true;
-                               break;
-                       }
-               }
-
-               // If node blocks camera position don't move y to heigh
-               if (abort && my_cp.Y > player_position.Y+BS*2)
-                       my_cp.Y = player_position.Y+BS*2;
-       }
-
-       // Update offset if too far away from the center of the map
-       m_camera_offset.X += CAMERA_OFFSET_STEP*
-                       (((s16)(my_cp.X/BS) - m_camera_offset.X)/CAMERA_OFFSET_STEP);
-       m_camera_offset.Y += CAMERA_OFFSET_STEP*
-                       (((s16)(my_cp.Y/BS) - m_camera_offset.Y)/CAMERA_OFFSET_STEP);
-       m_camera_offset.Z += CAMERA_OFFSET_STEP*
-                       (((s16)(my_cp.Z/BS) - m_camera_offset.Z)/CAMERA_OFFSET_STEP);
-
-       // Set camera node transformation
-       m_cameranode->setPosition(my_cp-intToFloat(m_camera_offset, BS));
-       m_cameranode->setUpVector(abs_cam_up);
-       // *100.0 helps in large map coordinates
-       m_cameranode->setTarget(my_cp-intToFloat(m_camera_offset, BS) + 100 * m_camera_direction);
-
-       // update the camera position in third-person mode to render blocks behind player
-       // and correctly apply liquid post FX.
-       if (m_camera_mode != CAMERA_MODE_FIRST)
-               m_camera_position = my_cp;
-
-       // Get FOV
-       f32 fov_degrees;
-       // Disable zoom with zoom FOV = 0
-       if (player->getPlayerControl().zoom && player->getZoomFOV() > 0.001f) {
-               fov_degrees = player->getZoomFOV();
-       } else {
-               fov_degrees = m_cache_fov;
-       }
-       fov_degrees = rangelim(fov_degrees, 1.0f, 160.0f);
-
-       // FOV and aspect ratio
-       const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize();
-       m_aspect = (f32) window_size.X / (f32) window_size.Y;
-       m_fov_y = fov_degrees * M_PI / 180.0;
-       // Increase vertical FOV on lower aspect ratios (<16:10)
-       m_fov_y *= MYMAX(1.0, MYMIN(1.4, sqrt(16./10. / m_aspect)));
-       m_fov_x = 2 * atan(m_aspect * tan(0.5 * m_fov_y));
-       m_cameranode->setAspectRatio(m_aspect);
-       m_cameranode->setFOV(m_fov_y);
-
-       if (m_arm_inertia)
-               addArmInertia(player->getYaw());
-
-       // Position the wielded item
-       //v3f wield_position = v3f(45, -35, 65);
-       v3f wield_position = v3f(m_wieldmesh_offset.X, m_wieldmesh_offset.Y, 65);
-       //v3f wield_rotation = v3f(-100, 120, -100);
-       v3f wield_rotation = v3f(-100, 120, -100);
-       wield_position.Y += fabs(m_wield_change_timer)*320 - 40;
-       if(m_digging_anim < 0.05 || m_digging_anim > 0.5)
-       {
-               f32 frac = 1.0;
-               if(m_digging_anim > 0.5)
-                       frac = 2.0 * (m_digging_anim - 0.5);
-               // This value starts from 1 and settles to 0
-               f32 ratiothing = std::pow((1.0f - tool_reload_ratio), 0.5f);
-               //f32 ratiothing2 = pow(ratiothing, 0.5f);
-               f32 ratiothing2 = (easeCurve(ratiothing*0.5))*2.0;
-               wield_position.Y -= frac * 25.0 * pow(ratiothing2, 1.7f);
-               //wield_position.Z += frac * 5.0 * ratiothing2;
-               wield_position.X -= frac * 35.0 * pow(ratiothing2, 1.1f);
-               wield_rotation.Y += frac * 70.0 * pow(ratiothing2, 1.4f);
-               //wield_rotation.X -= frac * 15.0 * pow(ratiothing2, 1.4f);
-               //wield_rotation.Z += frac * 15.0 * pow(ratiothing2, 1.0f);
-       }
-       if (m_digging_button != -1)
-       {
-               f32 digfrac = m_digging_anim;
-               wield_position.X -= 50 * sin(pow(digfrac, 0.8f) * M_PI);
-               wield_position.Y += 24 * sin(digfrac * 1.8 * M_PI);
-               wield_position.Z += 25 * 0.5;
-
-               // Euler angles are PURE EVIL, so why not use quaternions?
-               core::quaternion quat_begin(wield_rotation * core::DEGTORAD);
-               core::quaternion quat_end(v3f(80, 30, 100) * core::DEGTORAD);
-               core::quaternion quat_slerp;
-               quat_slerp.slerp(quat_begin, quat_end, sin(digfrac * M_PI));
-               quat_slerp.toEuler(wield_rotation);
-               wield_rotation *= core::RADTODEG;
-       } else {
-               f32 bobfrac = my_modf(m_view_bobbing_anim);
-               wield_position.X -= sin(bobfrac*M_PI*2.0) * 3.0;
-               wield_position.Y += sin(my_modf(bobfrac*2.0)*M_PI) * 3.0;
-       }
-       m_wieldnode->setPosition(wield_position);
-       m_wieldnode->setRotation(wield_rotation);
-
-       m_wieldnode->setColor(player->light_color);
-
-       // Set render distance
-       updateViewingRange();
-
-       // If the player is walking, swimming, or climbing,
-       // view bobbing is enabled and free_move is off,
-       // start (or continue) the view bobbing animation.
-       const v3f &speed = player->getSpeed();
-       const bool movement_XZ = hypot(speed.X, speed.Z) > BS;
-       const bool movement_Y = fabs(speed.Y) > BS;
-
-       const bool walking = movement_XZ && player->touching_ground;
-       const bool swimming = (movement_XZ || player->swimming_vertical) && player->in_liquid;
-       const bool climbing = movement_Y && player->is_climbing;
-       if ((walking || swimming || climbing) &&
-                       (!g_settings->getBool("free_move") || !m_client->checkLocalPrivilege("fly"))) {
-               // Start animation
-               m_view_bobbing_state = 1;
-               m_view_bobbing_speed = MYMIN(speed.getLength(), 70);
-       }
-       else if (m_view_bobbing_state == 1)
-       {
-               // Stop animation
-               m_view_bobbing_state = 2;
-               m_view_bobbing_speed = 60;
-       }
-}
-
-void Camera::updateViewingRange()
-{
-       f32 viewing_range = g_settings->getFloat("viewing_range");
-       f32 near_plane = g_settings->getFloat("near_plane");
-
-       m_draw_control.wanted_range = std::fmin(adjustDist(viewing_range, getFovMax()), 4000);
-       m_cameranode->setNearValue(rangelim(near_plane, 0.0f, 0.5f) * BS);
-       if (m_draw_control.range_all) {
-               m_cameranode->setFarValue(100000.0);
-               return;
-       }
-       m_cameranode->setFarValue((viewing_range < 2000) ? 2000 * BS : viewing_range * BS);
-}
-
-void Camera::setDigging(s32 button)
-{
-       if (m_digging_button == -1)
-               m_digging_button = button;
-}
-
-void Camera::wield(const ItemStack &item)
-{
-       if (item.name != m_wield_item_next.name ||
-                       item.metadata != m_wield_item_next.metadata) {
-               m_wield_item_next = item;
-               if (m_wield_change_timer > 0)
-                       m_wield_change_timer = -m_wield_change_timer;
-               else if (m_wield_change_timer == 0)
-                       m_wield_change_timer = -0.001;
-       }
-}
-
-void Camera::drawWieldedTool(irr::core::matrix4* translation)
-{
-       // Clear Z buffer so that the wielded tool stay in front of world geometry
-       m_wieldmgr->getVideoDriver()->clearZBuffer();
-
-       // Draw the wielded node (in a separate scene manager)
-       scene::ICameraSceneNode* cam = m_wieldmgr->getActiveCamera();
-       cam->setAspectRatio(m_cameranode->getAspectRatio());
-       cam->setFOV(72.0*M_PI/180.0);
-       cam->setNearValue(10);
-       cam->setFarValue(1000);
-       if (translation != NULL)
-       {
-               irr::core::matrix4 startMatrix = cam->getAbsoluteTransformation();
-               irr::core::vector3df focusPoint = (cam->getTarget()
-                               - cam->getAbsolutePosition()).setLength(1)
-                               + cam->getAbsolutePosition();
-
-               irr::core::vector3df camera_pos =
-                               (startMatrix * *translation).getTranslation();
-               cam->setPosition(camera_pos);
-               cam->setTarget(focusPoint);
-       }
-       m_wieldmgr->drawAll();
-}
-
-void Camera::drawNametags()
-{
-       core::matrix4 trans = m_cameranode->getProjectionMatrix();
-       trans *= m_cameranode->getViewMatrix();
-
-       for (std::list<Nametag *>::const_iterator
-                       i = m_nametags.begin();
-                       i != m_nametags.end(); ++i) {
-               Nametag *nametag = *i;
-               if (nametag->nametag_color.getAlpha() == 0) {
-                       // Enforce hiding nametag,
-                       // because if freetype is enabled, a grey
-                       // shadow can remain.
-                       continue;
-               }
-               v3f pos = nametag->parent_node->getAbsolutePosition() + nametag->nametag_pos * BS;
-               f32 transformed_pos[4] = { pos.X, pos.Y, pos.Z, 1.0f };
-               trans.multiplyWith1x4Matrix(transformed_pos);
-               if (transformed_pos[3] > 0) {
-                       std::wstring nametag_colorless =
-                               unescape_translate(utf8_to_wide(nametag->nametag_text));
-                       core::dimension2d<u32> textsize =
-                               g_fontengine->getFont()->getDimension(
-                               nametag_colorless.c_str());
-                       f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f :
-                               core::reciprocal(transformed_pos[3]);
-                       v2u32 screensize = RenderingEngine::get_video_driver()->getScreenSize();
-                       v2s32 screen_pos;
-                       screen_pos.X = screensize.X *
-                               (0.5 * transformed_pos[0] * zDiv + 0.5) - textsize.Width / 2;
-                       screen_pos.Y = screensize.Y *
-                               (0.5 - transformed_pos[1] * zDiv * 0.5) - textsize.Height / 2;
-                       core::rect<s32> size(0, 0, textsize.Width, textsize.Height);
-                       g_fontengine->getFont()->draw(
-                               translate_string(utf8_to_wide(nametag->nametag_text)).c_str(),
-                               size + screen_pos, nametag->nametag_color);
-               }
-       }
-}
-
-Nametag *Camera::addNametag(scene::ISceneNode *parent_node,
-               const std::string &nametag_text, video::SColor nametag_color,
-               const v3f &pos)
-{
-       Nametag *nametag = new Nametag(parent_node, nametag_text, nametag_color, pos);
-       m_nametags.push_back(nametag);
-       return nametag;
-}
-
-void Camera::removeNametag(Nametag *nametag)
-{
-       m_nametags.remove(nametag);
-       delete nametag;
-}
diff --git a/src/camera.h b/src/camera.h
deleted file mode 100644 (file)
index 88de357..0000000
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include "inventory.h"
-#include "client/tile.h"
-#include <ICameraSceneNode.h>
-#include <ISceneNode.h>
-#include <list>
-
-class LocalPlayer;
-struct MapDrawControl;
-class Client;
-class WieldMeshSceneNode;
-
-struct Nametag {
-       Nametag(scene::ISceneNode *a_parent_node,
-                       const std::string &a_nametag_text,
-                       const video::SColor &a_nametag_color,
-                       const v3f &a_nametag_pos):
-               parent_node(a_parent_node),
-               nametag_text(a_nametag_text),
-               nametag_color(a_nametag_color),
-               nametag_pos(a_nametag_pos)
-       {
-       }
-       scene::ISceneNode *parent_node;
-       std::string nametag_text;
-       video::SColor nametag_color;
-       v3f nametag_pos;
-};
-
-enum CameraMode {CAMERA_MODE_FIRST, CAMERA_MODE_THIRD, CAMERA_MODE_THIRD_FRONT};
-
-/*
-       Client camera class, manages the player and camera scene nodes, the viewing distance
-       and performs view bobbing etc. It also displays the wielded tool in front of the
-       first-person camera.
-*/
-class Camera
-{
-public:
-       Camera(MapDrawControl &draw_control, Client *client);
-       ~Camera();
-
-       // Get camera scene node.
-       // It has the eye transformation, pitch and view bobbing applied.
-       inline scene::ICameraSceneNode* getCameraNode() const
-       {
-               return m_cameranode;
-       }
-
-       // Get the camera position (in absolute scene coordinates).
-       // This has view bobbing applied.
-       inline v3f getPosition() const
-       {
-               return m_camera_position;
-       }
-
-       // Get the camera direction (in absolute camera coordinates).
-       // This has view bobbing applied.
-       inline v3f getDirection() const
-       {
-               return m_camera_direction;
-       }
-
-       // Get the camera offset
-       inline v3s16 getOffset() const
-       {
-               return m_camera_offset;
-       }
-
-       // Horizontal field of view
-       inline f32 getFovX() const
-       {
-               return m_fov_x;
-       }
-
-       // Vertical field of view
-       inline f32 getFovY() const
-       {
-               return m_fov_y;
-       }
-
-       // Get maximum of getFovX() and getFovY()
-       inline f32 getFovMax() const
-       {
-               return MYMAX(m_fov_x, m_fov_y);
-       }
-
-       // Checks if the constructor was able to create the scene nodes
-       bool successfullyCreated(std::string &error_message);
-
-       // Step the camera: updates the viewing range and view bobbing.
-       void step(f32 dtime);
-
-       // Update the camera from the local player's position.
-       // busytime is used to adjust the viewing range.
-       void update(LocalPlayer* player, f32 frametime, f32 busytime,
-                       f32 tool_reload_ratio);
-
-       // Update render distance
-       void updateViewingRange();
-
-       // Start digging animation
-       // Pass 0 for left click, 1 for right click
-       void setDigging(s32 button);
-
-       // Replace the wielded item mesh
-       void wield(const ItemStack &item);
-
-       // Draw the wielded tool.
-       // This has to happen *after* the main scene is drawn.
-       // Warning: This clears the Z buffer.
-       void drawWieldedTool(irr::core::matrix4* translation=NULL);
-
-       // Toggle the current camera mode
-       void toggleCameraMode() {
-               if (m_camera_mode == CAMERA_MODE_FIRST)
-                       m_camera_mode = CAMERA_MODE_THIRD;
-               else if (m_camera_mode == CAMERA_MODE_THIRD)
-                       m_camera_mode = CAMERA_MODE_THIRD_FRONT;
-               else
-                       m_camera_mode = CAMERA_MODE_FIRST;
-       }
-
-       // Set the current camera mode
-       inline void setCameraMode(CameraMode mode)
-       {
-               m_camera_mode = mode;
-       }
-
-       //read the current camera mode
-       inline CameraMode getCameraMode()
-       {
-               return m_camera_mode;
-       }
-
-       Nametag *addNametag(scene::ISceneNode *parent_node,
-               const std::string &nametag_text, video::SColor nametag_color,
-               const v3f &pos);
-
-       void removeNametag(Nametag *nametag);
-
-       const std::list<Nametag *> &getNametags() { return m_nametags; }
-
-       void drawNametags();
-
-       inline void addArmInertia(f32 player_yaw);
-
-private:
-       // Nodes
-       scene::ISceneNode *m_playernode = nullptr;
-       scene::ISceneNode *m_headnode = nullptr;
-       scene::ICameraSceneNode *m_cameranode = nullptr;
-
-       scene::ISceneManager *m_wieldmgr = nullptr;
-       WieldMeshSceneNode *m_wieldnode = nullptr;
-
-       // draw control
-       MapDrawControl& m_draw_control;
-
-       Client *m_client;
-
-       // Absolute camera position
-       v3f m_camera_position;
-       // Absolute camera direction
-       v3f m_camera_direction;
-       // Camera offset
-       v3s16 m_camera_offset;
-
-       v2f m_wieldmesh_offset = v2f(55.0f, -35.0f);
-       v2f m_arm_dir;
-       v2f m_cam_vel;
-       v2f m_cam_vel_old;
-       v2f m_last_cam_pos;
-
-       // Field of view and aspect ratio stuff
-       f32 m_aspect = 1.0f;
-       f32 m_fov_x = 1.0f;
-       f32 m_fov_y = 1.0f;
-
-       // View bobbing animation frame (0 <= m_view_bobbing_anim < 1)
-       f32 m_view_bobbing_anim = 0.0f;
-       // If 0, view bobbing is off (e.g. player is standing).
-       // If 1, view bobbing is on (player is walking).
-       // If 2, view bobbing is getting switched off.
-       s32 m_view_bobbing_state = 0;
-       // Speed of view bobbing animation
-       f32 m_view_bobbing_speed = 0.0f;
-       // Fall view bobbing
-       f32 m_view_bobbing_fall = 0.0f;
-
-       // Digging animation frame (0 <= m_digging_anim < 1)
-       f32 m_digging_anim = 0.0f;
-       // If -1, no digging animation
-       // If 0, left-click digging animation
-       // If 1, right-click digging animation
-       s32 m_digging_button = -1;
-
-       // Animation when changing wielded item
-       f32 m_wield_change_timer = 0.125f;
-       ItemStack m_wield_item_next;
-
-       CameraMode m_camera_mode = CAMERA_MODE_FIRST;
-
-       f32 m_cache_fall_bobbing_amount;
-       f32 m_cache_view_bobbing_amount;
-       f32 m_cache_fov;
-       bool m_arm_inertia;
-
-       std::list<Nametag *> m_nametags;
-};
diff --git a/src/client.cpp b/src/client.cpp
deleted file mode 100644 (file)
index 17ee3a1..0000000
+++ /dev/null
@@ -1,1970 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 <iostream>
-#include <algorithm>
-#include <sstream>
-#include <cmath>
-#include <IFileSystem.h>
-#include "client.h"
-#include "network/clientopcodes.h"
-#include "network/connection.h"
-#include "network/networkpacket.h"
-#include "threading/mutex_auto_lock.h"
-#include "client/clientevent.h"
-#include "client/gameui.h"
-#include "client/renderingengine.h"
-#include "client/sound.h"
-#include "client/tile.h"
-#include "util/auth.h"
-#include "util/directiontables.h"
-#include "util/pointedthing.h"
-#include "util/serialize.h"
-#include "util/string.h"
-#include "util/srp.h"
-#include "filesys.h"
-#include "mapblock_mesh.h"
-#include "mapblock.h"
-#include "minimap.h"
-#include "modchannels.h"
-#include "content/mods.h"
-#include "profiler.h"
-#include "shader.h"
-#include "gettext.h"
-#include "clientmap.h"
-#include "clientmedia.h"
-#include "version.h"
-#include "database/database-sqlite3.h"
-#include "serialization.h"
-#include "guiscalingfilter.h"
-#include "script/scripting_client.h"
-#include "game.h"
-#include "chatmessage.h"
-#include "translation.h"
-
-extern gui::IGUIEnvironment* guienv;
-
-/*
-       Client
-*/
-
-Client::Client(
-               const char *playername,
-               const std::string &password,
-               const std::string &address_name,
-               MapDrawControl &control,
-               IWritableTextureSource *tsrc,
-               IWritableShaderSource *shsrc,
-               IWritableItemDefManager *itemdef,
-               NodeDefManager *nodedef,
-               ISoundManager *sound,
-               MtEventManager *event,
-               bool ipv6,
-               GameUI *game_ui
-):
-       m_tsrc(tsrc),
-       m_shsrc(shsrc),
-       m_itemdef(itemdef),
-       m_nodedef(nodedef),
-       m_sound(sound),
-       m_event(event),
-       m_mesh_update_thread(this),
-       m_env(
-               new ClientMap(this, control, 666),
-               tsrc, this
-       ),
-       m_particle_manager(&m_env),
-       m_con(new con::Connection(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, ipv6, this)),
-       m_address_name(address_name),
-       m_server_ser_ver(SER_FMT_VER_INVALID),
-       m_last_chat_message_sent(time(NULL)),
-       m_password(password),
-       m_chosen_auth_mech(AUTH_MECHANISM_NONE),
-       m_media_downloader(new ClientMediaDownloader()),
-       m_state(LC_Created),
-       m_game_ui(game_ui),
-       m_modchannel_mgr(new ModChannelMgr())
-{
-       // Add local player
-       m_env.setLocalPlayer(new LocalPlayer(this, playername));
-
-       if (g_settings->getBool("enable_minimap")) {
-               m_minimap = new Minimap(this);
-       }
-       m_cache_save_interval = g_settings->getU16("server_map_save_interval");
-
-       m_modding_enabled = g_settings->getBool("enable_client_modding");
-       // Only create the client script environment if client modding is enabled
-       if (m_modding_enabled) {
-               m_script = new ClientScripting(this);
-               m_env.setScript(m_script);
-               m_script->setEnv(&m_env);
-       }
-}
-
-void Client::loadMods()
-{
-       // Don't load mods twice
-       if (m_mods_loaded) {
-               return;
-       }
-
-       // If client modding is not enabled, don't load client-provided CSM mods or
-       // builtin.
-       if (!m_modding_enabled) {
-               warningstream << "Client side mods are disabled by configuration." << std::endl;
-               return;
-       }
-
-       // Load builtin
-       scanModIntoMemory(BUILTIN_MOD_NAME, getBuiltinLuaPath());
-       m_script->loadModFromMemory(BUILTIN_MOD_NAME);
-
-       // If the server has disabled client-provided CSM mod loading, don't load
-       // client-provided CSM mods. Builtin is loaded so needs verfying.
-       if (checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOAD_CLIENT_MODS)) {
-               warningstream << "Client side mods are disabled by server." << std::endl;
-               // If builtin integrity is wrong, disconnect user
-               if (!checkBuiltinIntegrity()) {
-                       // @TODO disconnect user
-               }
-               return;
-       }
-
-       ClientModConfiguration modconf(getClientModsLuaPath());
-       m_mods = modconf.getMods();
-       // complain about mods with unsatisfied dependencies
-       if (!modconf.isConsistent()) {
-               modconf.printUnsatisfiedModsError();
-       }
-
-       // Print mods
-       infostream << "Client Loading mods: ";
-       for (const ModSpec &mod : m_mods)
-               infostream << mod.name << " ";
-       infostream << std::endl;
-
-       // Load and run "mod" scripts
-       for (const ModSpec &mod : m_mods) {
-               if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) {
-                       throw ModError("Error loading mod \"" + mod.name +
-                               "\": Mod name does not follow naming conventions: "
-                                       "Only characters [a-z0-9_] are allowed.");
-               }
-               scanModIntoMemory(mod.name, mod.path);
-       }
-
-       // Load and run "mod" scripts
-       for (const ModSpec &mod : m_mods)
-               m_script->loadModFromMemory(mod.name);
-
-       // Run a callback when mods are loaded
-       m_script->on_mods_loaded();
-       m_mods_loaded = true;
-}
-
-bool Client::checkBuiltinIntegrity()
-{
-       // @TODO
-       return true;
-}
-
-void Client::scanModSubfolder(const std::string &mod_name, const std::string &mod_path,
-                       std::string mod_subpath)
-{
-       std::string full_path = mod_path + DIR_DELIM + mod_subpath;
-       std::vector<fs::DirListNode> mod = fs::GetDirListing(full_path);
-       for (const fs::DirListNode &j : mod) {
-               std::string filename = j.name;
-               if (j.dir) {
-                       scanModSubfolder(mod_name, mod_path, mod_subpath
-                                       + filename + DIR_DELIM);
-                       continue;
-               }
-               std::replace( mod_subpath.begin(), mod_subpath.end(), DIR_DELIM_CHAR, '/');
-               m_mod_files[mod_name + ":" + mod_subpath + filename] = full_path  + filename;
-       }
-}
-
-const std::string &Client::getBuiltinLuaPath()
-{
-       static const std::string builtin_dir = porting::path_share + DIR_DELIM + "builtin";
-       return builtin_dir;
-}
-
-const std::string &Client::getClientModsLuaPath()
-{
-       static const std::string clientmods_dir = porting::path_share + DIR_DELIM + "clientmods";
-       return clientmods_dir;
-}
-
-const std::vector<ModSpec>& Client::getMods() const
-{
-       static std::vector<ModSpec> client_modspec_temp;
-       return client_modspec_temp;
-}
-
-const ModSpec* Client::getModSpec(const std::string &modname) const
-{
-       return NULL;
-}
-
-void Client::Stop()
-{
-       m_shutdown = true;
-       if (m_modding_enabled)
-               m_script->on_shutdown();
-       //request all client managed threads to stop
-       m_mesh_update_thread.stop();
-       // Save local server map
-       if (m_localdb) {
-               infostream << "Local map saving ended." << std::endl;
-               m_localdb->endSave();
-       }
-
-       if (m_modding_enabled)
-               delete m_script;
-}
-
-bool Client::isShutdown()
-{
-       return m_shutdown || !m_mesh_update_thread.isRunning();
-}
-
-Client::~Client()
-{
-       m_shutdown = true;
-       m_con->Disconnect();
-
-       deleteAuthData();
-
-       m_mesh_update_thread.stop();
-       m_mesh_update_thread.wait();
-       while (!m_mesh_update_thread.m_queue_out.empty()) {
-               MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx();
-               delete r.mesh;
-       }
-
-
-       delete m_inventory_from_server;
-
-       // Delete detached inventories
-       for (auto &m_detached_inventorie : m_detached_inventories) {
-               delete m_detached_inventorie.second;
-       }
-
-       // cleanup 3d model meshes on client shutdown
-       while (RenderingEngine::get_mesh_cache()->getMeshCount() != 0) {
-               scene::IAnimatedMesh *mesh = RenderingEngine::get_mesh_cache()->getMeshByIndex(0);
-
-               if (mesh)
-                       RenderingEngine::get_mesh_cache()->removeMesh(mesh);
-       }
-
-       delete m_minimap;
-       delete m_media_downloader;
-}
-
-void Client::connect(Address address, bool is_local_server)
-{
-       initLocalMapSaving(address, m_address_name, is_local_server);
-
-       m_con->SetTimeoutMs(0);
-       m_con->Connect(address);
-}
-
-void Client::step(float dtime)
-{
-       // Limit a bit
-       if (dtime > 2.0)
-               dtime = 2.0;
-
-       m_animation_time += dtime;
-       if(m_animation_time > 60.0)
-               m_animation_time -= 60.0;
-
-       m_time_of_day_update_timer += dtime;
-
-       ReceiveAll();
-
-       /*
-               Packet counter
-       */
-       {
-               float &counter = m_packetcounter_timer;
-               counter -= dtime;
-               if(counter <= 0.0)
-               {
-                       counter = 20.0;
-
-                       infostream << "Client packetcounter (" << m_packetcounter_timer
-                                       << "):"<<std::endl;
-                       m_packetcounter.print(infostream);
-                       m_packetcounter.clear();
-               }
-       }
-
-       // UGLY hack to fix 2 second startup delay caused by non existent
-       // server client startup synchronization in local server or singleplayer mode
-       static bool initial_step = true;
-       if (initial_step) {
-               initial_step = false;
-       }
-       else if(m_state == LC_Created) {
-               if (m_is_registration_confirmation_state) {
-                       // Waiting confirmation
-                       return;
-               }
-               float &counter = m_connection_reinit_timer;
-               counter -= dtime;
-               if(counter <= 0.0) {
-                       counter = 2.0;
-
-                       LocalPlayer *myplayer = m_env.getLocalPlayer();
-                       FATAL_ERROR_IF(myplayer == NULL, "Local player not found in environment.");
-
-                       sendInit(myplayer->getName());
-               }
-
-               // Not connected, return
-               return;
-       }
-
-       /*
-               Do stuff if connected
-       */
-
-       /*
-               Run Map's timers and unload unused data
-       */
-       const float map_timer_and_unload_dtime = 5.25;
-       if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) {
-               ScopeProfiler sp(g_profiler, "Client: map timer and unload");
-               std::vector<v3s16> deleted_blocks;
-               m_env.getMap().timerUpdate(map_timer_and_unload_dtime,
-                       g_settings->getFloat("client_unload_unused_data_timeout"),
-                       g_settings->getS32("client_mapblock_limit"),
-                       &deleted_blocks);
-
-               /*
-                       Send info to server
-                       NOTE: This loop is intentionally iterated the way it is.
-               */
-
-               std::vector<v3s16>::iterator i = deleted_blocks.begin();
-               std::vector<v3s16> sendlist;
-               for(;;) {
-                       if(sendlist.size() == 255 || i == deleted_blocks.end()) {
-                               if(sendlist.empty())
-                                       break;
-                               /*
-                                       [0] u16 command
-                                       [2] u8 count
-                                       [3] v3s16 pos_0
-                                       [3+6] v3s16 pos_1
-                                       ...
-                               */
-
-                               sendDeletedBlocks(sendlist);
-
-                               if(i == deleted_blocks.end())
-                                       break;
-
-                               sendlist.clear();
-                       }
-
-                       sendlist.push_back(*i);
-                       ++i;
-               }
-       }
-
-       /*
-               Send pending messages on out chat queue
-       */
-       if (!m_out_chat_queue.empty() && canSendChatMessage()) {
-               sendChatMessage(m_out_chat_queue.front());
-               m_out_chat_queue.pop();
-       }
-
-       /*
-               Handle environment
-       */
-       // Control local player (0ms)
-       LocalPlayer *player = m_env.getLocalPlayer();
-       assert(player);
-       player->applyControl(dtime, &m_env);
-
-       // Step environment
-       m_env.step(dtime);
-       m_sound->step(dtime);
-
-       /*
-               Get events
-       */
-       while (m_env.hasClientEnvEvents()) {
-               ClientEnvEvent envEvent = m_env.getClientEnvEvent();
-
-               if (envEvent.type == CEE_PLAYER_DAMAGE) {
-                       u8 damage = envEvent.player_damage.amount;
-
-                       if (envEvent.player_damage.send_to_server)
-                               sendDamage(damage);
-
-                       // Add to ClientEvent queue
-                       ClientEvent *event = new ClientEvent();
-                       event->type = CE_PLAYER_DAMAGE;
-                       event->player_damage.amount = damage;
-                       m_client_event_queue.push(event);
-               }
-       }
-
-       /*
-               Print some info
-       */
-       float &counter = m_avg_rtt_timer;
-       counter += dtime;
-       if(counter >= 10) {
-               counter = 0.0;
-               // connectedAndInitialized() is true, peer exists.
-               float avg_rtt = getRTT();
-               infostream << "Client: average rtt: " << avg_rtt << std::endl;
-       }
-
-       /*
-               Send player position to server
-       */
-       {
-               float &counter = m_playerpos_send_timer;
-               counter += dtime;
-               if((m_state == LC_Ready) && (counter >= m_recommended_send_interval))
-               {
-                       counter = 0.0;
-                       sendPlayerPos();
-               }
-       }
-
-       /*
-               Replace updated meshes
-       */
-       {
-               int num_processed_meshes = 0;
-               while (!m_mesh_update_thread.m_queue_out.empty())
-               {
-                       num_processed_meshes++;
-
-                       MinimapMapblock *minimap_mapblock = NULL;
-                       bool do_mapper_update = true;
-
-                       MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx();
-                       MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(r.p);
-                       if (block) {
-                               // Delete the old mesh
-                               delete block->mesh;
-                               block->mesh = nullptr;
-
-                               if (r.mesh) {
-                                       minimap_mapblock = r.mesh->moveMinimapMapblock();
-                                       if (minimap_mapblock == NULL)
-                                               do_mapper_update = false;
-
-                                       bool is_empty = true;
-                                       for (int l = 0; l < MAX_TILE_LAYERS; l++)
-                                               if (r.mesh->getMesh(l)->getMeshBufferCount() != 0)
-                                                       is_empty = false;
-
-                                       if (is_empty)
-                                               delete r.mesh;
-                                       else
-                                               // Replace with the new mesh
-                                               block->mesh = r.mesh;
-                               }
-                       } else {
-                               delete r.mesh;
-                       }
-
-                       if (m_minimap && do_mapper_update)
-                               m_minimap->addBlock(r.p, minimap_mapblock);
-
-                       if (r.ack_block_to_server) {
-                               /*
-                                       Acknowledge block
-                                       [0] u8 count
-                                       [1] v3s16 pos_0
-                               */
-                               sendGotBlocks(r.p);
-                       }
-               }
-
-               if (num_processed_meshes > 0)
-                       g_profiler->graphAdd("num_processed_meshes", num_processed_meshes);
-       }
-
-       /*
-               Load fetched media
-       */
-       if (m_media_downloader && m_media_downloader->isStarted()) {
-               m_media_downloader->step(this);
-               if (m_media_downloader->isDone()) {
-                       delete m_media_downloader;
-                       m_media_downloader = NULL;
-               }
-       }
-
-       /*
-               If the server didn't update the inventory in a while, revert
-               the local inventory (so the player notices the lag problem
-               and knows something is wrong).
-       */
-       if (m_inventory_from_server) {
-               float interval = 10.0f;
-               float count_before = std::floor(m_inventory_from_server_age / interval);
-
-               m_inventory_from_server_age += dtime;
-
-               float count_after = std::floor(m_inventory_from_server_age / interval);
-
-               if (count_after != count_before) {
-                       // Do this every <interval> seconds after TOCLIENT_INVENTORY
-                       // Reset the locally changed inventory to the authoritative inventory
-                       m_env.getLocalPlayer()->inventory = *m_inventory_from_server;
-                       m_inventory_updated = true;
-               }
-       }
-
-       /*
-               Update positions of sounds attached to objects
-       */
-       {
-               for (auto &m_sounds_to_object : m_sounds_to_objects) {
-                       int client_id = m_sounds_to_object.first;
-                       u16 object_id = m_sounds_to_object.second;
-                       ClientActiveObject *cao = m_env.getActiveObject(object_id);
-                       if (!cao)
-                               continue;
-                       m_sound->updateSoundPosition(client_id, cao->getPosition());
-               }
-       }
-
-       /*
-               Handle removed remotely initiated sounds
-       */
-       m_removed_sounds_check_timer += dtime;
-       if(m_removed_sounds_check_timer >= 2.32) {
-               m_removed_sounds_check_timer = 0;
-               // Find removed sounds and clear references to them
-               std::vector<s32> removed_server_ids;
-               for (std::unordered_map<s32, int>::iterator i = m_sounds_server_to_client.begin();
-                               i != m_sounds_server_to_client.end();) {
-                       s32 server_id = i->first;
-                       int client_id = i->second;
-                       ++i;
-                       if(!m_sound->soundExists(client_id)) {
-                               m_sounds_server_to_client.erase(server_id);
-                               m_sounds_client_to_server.erase(client_id);
-                               m_sounds_to_objects.erase(client_id);
-                               removed_server_ids.push_back(server_id);
-                       }
-               }
-
-               // Sync to server
-               if(!removed_server_ids.empty()) {
-                       sendRemovedSounds(removed_server_ids);
-               }
-       }
-
-       m_mod_storage_save_timer -= dtime;
-       if (m_mod_storage_save_timer <= 0.0f) {
-               verbosestream << "Saving registered mod storages." << std::endl;
-               m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval");
-               for (std::unordered_map<std::string, ModMetadata *>::const_iterator
-                               it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) {
-                       if (it->second->isModified()) {
-                               it->second->save(getModStoragePath());
-                       }
-               }
-       }
-
-       // Write server map
-       if (m_localdb && m_localdb_save_interval.step(dtime,
-                       m_cache_save_interval)) {
-               m_localdb->endSave();
-               m_localdb->beginSave();
-       }
-}
-
-bool Client::loadMedia(const std::string &data, const std::string &filename)
-{
-       // Silly irrlicht's const-incorrectness
-       Buffer<char> data_rw(data.c_str(), data.size());
-
-       std::string name;
-
-       const char *image_ext[] = {
-               ".png", ".jpg", ".bmp", ".tga",
-               ".pcx", ".ppm", ".psd", ".wal", ".rgb",
-               NULL
-       };
-       name = removeStringEnd(filename, image_ext);
-       if (!name.empty()) {
-               verbosestream<<"Client: Attempting to load image "
-               <<"file \""<<filename<<"\""<<std::endl;
-
-               io::IFileSystem *irrfs = RenderingEngine::get_filesystem();
-               video::IVideoDriver *vdrv = RenderingEngine::get_video_driver();
-
-               // Create an irrlicht memory file
-               io::IReadFile *rfile = irrfs->createMemoryReadFile(
-                               *data_rw, data_rw.getSize(), "_tempreadfile");
-
-               FATAL_ERROR_IF(!rfile, "Could not create irrlicht memory file.");
-
-               // Read image
-               video::IImage *img = vdrv->createImageFromFile(rfile);
-               if (!img) {
-                       errorstream<<"Client: Cannot create image from data of "
-                                       <<"file \""<<filename<<"\""<<std::endl;
-                       rfile->drop();
-                       return false;
-               }
-
-               m_tsrc->insertSourceImage(filename, img);
-               img->drop();
-               rfile->drop();
-               return true;
-       }
-
-       const char *sound_ext[] = {
-               ".0.ogg", ".1.ogg", ".2.ogg", ".3.ogg", ".4.ogg",
-               ".5.ogg", ".6.ogg", ".7.ogg", ".8.ogg", ".9.ogg",
-               ".ogg", NULL
-       };
-       name = removeStringEnd(filename, sound_ext);
-       if (!name.empty()) {
-               verbosestream<<"Client: Attempting to load sound "
-               <<"file \""<<filename<<"\""<<std::endl;
-               m_sound->loadSoundData(name, data);
-               return true;
-       }
-
-       const char *model_ext[] = {
-               ".x", ".b3d", ".md2", ".obj",
-               NULL
-       };
-
-       name = removeStringEnd(filename, model_ext);
-       if (!name.empty()) {
-               verbosestream<<"Client: Storing model into memory: "
-                               <<"\""<<filename<<"\""<<std::endl;
-               if(m_mesh_data.count(filename))
-                       errorstream<<"Multiple models with name \""<<filename.c_str()
-                                       <<"\" found; replacing previous model"<<std::endl;
-               m_mesh_data[filename] = data;
-               return true;
-       }
-
-       const char *translate_ext[] = {
-               ".tr", NULL
-       };
-       name = removeStringEnd(filename, translate_ext);
-       if (!name.empty()) {
-               verbosestream << "Client: Loading translation: "
-                               << "\"" << filename << "\"" << std::endl;
-               g_translations->loadTranslation(data);
-               return true;
-       }
-
-       errorstream << "Client: Don't know how to load file \""
-               << filename << "\"" << std::endl;
-       return false;
-}
-
-// Virtual methods from con::PeerHandler
-void Client::peerAdded(con::Peer *peer)
-{
-       infostream << "Client::peerAdded(): peer->id="
-                       << peer->id << std::endl;
-}
-void Client::deletingPeer(con::Peer *peer, bool timeout)
-{
-       infostream << "Client::deletingPeer(): "
-                       "Server Peer is getting deleted "
-                       << "(timeout=" << timeout << ")" << std::endl;
-
-       if (timeout) {
-               m_access_denied = true;
-               m_access_denied_reason = gettext("Connection timed out.");
-       }
-}
-
-/*
-       u16 command
-       u16 number of files requested
-       for each file {
-               u16 length of name
-               string name
-       }
-*/
-void Client::request_media(const std::vector<std::string> &file_requests)
-{
-       std::ostringstream os(std::ios_base::binary);
-       writeU16(os, TOSERVER_REQUEST_MEDIA);
-       size_t file_requests_size = file_requests.size();
-
-       FATAL_ERROR_IF(file_requests_size > 0xFFFF, "Unsupported number of file requests");
-
-       // Packet dynamicly resized
-       NetworkPacket pkt(TOSERVER_REQUEST_MEDIA, 2 + 0);
-
-       pkt << (u16) (file_requests_size & 0xFFFF);
-
-       for (const std::string &file_request : file_requests) {
-               pkt << file_request;
-       }
-
-       Send(&pkt);
-
-       infostream << "Client: Sending media request list to server ("
-                       << file_requests.size() << " files. packet size)" << std::endl;
-}
-
-void Client::initLocalMapSaving(const Address &address,
-               const std::string &hostname,
-               bool is_local_server)
-{
-       if (!g_settings->getBool("enable_local_map_saving") || is_local_server) {
-               return;
-       }
-
-       const std::string world_path = porting::path_user
-               + DIR_DELIM + "worlds"
-               + DIR_DELIM + "server_"
-               + hostname + "_" + std::to_string(address.getPort());
-
-       fs::CreateAllDirs(world_path);
-
-       m_localdb = new MapDatabaseSQLite3(world_path);
-       m_localdb->beginSave();
-       actionstream << "Local map saving started, map will be saved at '" << world_path << "'" << std::endl;
-}
-
-void Client::ReceiveAll()
-{
-       u64 start_ms = porting::getTimeMs();
-       for(;;)
-       {
-               // Limit time even if there would be huge amounts of data to
-               // process
-               if(porting::getTimeMs() > start_ms + 100)
-                       break;
-
-               try {
-                       Receive();
-                       g_profiler->graphAdd("client_received_packets", 1);
-               }
-               catch(con::NoIncomingDataException &e) {
-                       break;
-               }
-               catch(con::InvalidIncomingDataException &e) {
-                       infostream<<"Client::ReceiveAll(): "
-                                       "InvalidIncomingDataException: what()="
-                                       <<e.what()<<std::endl;
-               }
-       }
-}
-
-void Client::Receive()
-{
-       NetworkPacket pkt;
-       m_con->Receive(&pkt);
-       ProcessData(&pkt);
-}
-
-inline void Client::handleCommand(NetworkPacket* pkt)
-{
-       const ToClientCommandHandler& opHandle = toClientCommandTable[pkt->getCommand()];
-       (this->*opHandle.handler)(pkt);
-}
-
-/*
-       sender_peer_id given to this shall be quaranteed to be a valid peer
-*/
-void Client::ProcessData(NetworkPacket *pkt)
-{
-       ToClientCommand command = (ToClientCommand) pkt->getCommand();
-       u32 sender_peer_id = pkt->getPeerId();
-
-       //infostream<<"Client: received command="<<command<<std::endl;
-       m_packetcounter.add((u16)command);
-
-       /*
-               If this check is removed, be sure to change the queue
-               system to know the ids
-       */
-       if(sender_peer_id != PEER_ID_SERVER) {
-               infostream << "Client::ProcessData(): Discarding data not "
-                       "coming from server: peer_id=" << sender_peer_id
-                       << std::endl;
-               return;
-       }
-
-       // Command must be handled into ToClientCommandHandler
-       if (command >= TOCLIENT_NUM_MSG_TYPES) {
-               infostream << "Client: Ignoring unknown command "
-                       << command << std::endl;
-               return;
-       }
-
-       /*
-        * Those packets are handled before m_server_ser_ver is set, it's normal
-        * But we must use the new ToClientConnectionState in the future,
-        * as a byte mask
-        */
-       if(toClientCommandTable[command].state == TOCLIENT_STATE_NOT_CONNECTED) {
-               handleCommand(pkt);
-               return;
-       }
-
-       if(m_server_ser_ver == SER_FMT_VER_INVALID) {
-               infostream << "Client: Server serialization"
-                               " format invalid or not initialized."
-                               " Skipping incoming command=" << command << std::endl;
-               return;
-       }
-
-       /*
-         Handle runtime commands
-       */
-
-       handleCommand(pkt);
-}
-
-void Client::Send(NetworkPacket* pkt)
-{
-       m_con->Send(PEER_ID_SERVER,
-               serverCommandFactoryTable[pkt->getCommand()].channel,
-               pkt,
-               serverCommandFactoryTable[pkt->getCommand()].reliable);
-}
-
-// 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;
-       s32 pitch        = myplayer->getPitch() * 100;
-       s32 yaw          = myplayer->getYaw() * 100;
-       u32 keyPressed   = myplayer->keyPressed;
-       // scaled by 80, so that pi can fit into a u8
-       u8 fov           = clientMap->getCameraFov() * 80;
-       u8 wanted_range  = MYMIN(255,
-                       std::ceil(clientMap->getControl().wanted_range / MAP_BLOCKSIZE));
-
-       v3s32 position(pf.X, pf.Y, pf.Z);
-       v3s32 speed(sf.X, sf.Y, sf.Z);
-
-       /*
-               Format:
-               [0] v3s32 position*100
-               [12] v3s32 speed*100
-               [12+12] s32 pitch*100
-               [12+12+4] s32 yaw*100
-               [12+12+4+4] u32 keyPressed
-               [12+12+4+4+4] u8 fov*80
-               [12+12+4+4+4+1] u8 ceil(wanted_range / MAP_BLOCKSIZE)
-       */
-       *pkt << position << speed << pitch << yaw << keyPressed;
-       *pkt << fov << wanted_range;
-}
-
-void Client::interact(u8 action, const PointedThing& pointed)
-{
-       if(m_state != LC_Ready) {
-               errorstream << "Client::interact() "
-                               "Canceled (not connected)"
-                               << std::endl;
-               return;
-       }
-
-       LocalPlayer *myplayer = m_env.getLocalPlayer();
-       if (myplayer == NULL)
-               return;
-
-       /*
-               [0] u16 command
-               [2] u8 action
-               [3] u16 item
-               [5] u32 length of the next item (plen)
-               [9] serialized PointedThing
-               [9 + plen] player position information
-               actions:
-               0: start digging (from undersurface) or use
-               1: stop digging (all parameters ignored)
-               2: digging completed
-               3: place block or item (to abovesurface)
-               4: use item
-               5: perform secondary action of item
-       */
-
-       NetworkPacket pkt(TOSERVER_INTERACT, 1 + 2 + 0);
-
-       pkt << action;
-       pkt << (u16)getPlayerItem();
-
-       std::ostringstream tmp_os(std::ios::binary);
-       pointed.serialize(tmp_os);
-
-       pkt.putLongString(tmp_os.str());
-
-       writePlayerPos(myplayer, &m_env.getClientMap(), &pkt);
-
-       Send(&pkt);
-}
-
-void Client::deleteAuthData()
-{
-       if (!m_auth_data)
-               return;
-
-       switch (m_chosen_auth_mech) {
-               case AUTH_MECHANISM_FIRST_SRP:
-                       break;
-               case AUTH_MECHANISM_SRP:
-               case AUTH_MECHANISM_LEGACY_PASSWORD:
-                       srp_user_delete((SRPUser *) m_auth_data);
-                       m_auth_data = NULL;
-                       break;
-               case AUTH_MECHANISM_NONE:
-                       break;
-       }
-       m_chosen_auth_mech = AUTH_MECHANISM_NONE;
-}
-
-
-AuthMechanism Client::choseAuthMech(const u32 mechs)
-{
-       if (mechs & AUTH_MECHANISM_SRP)
-               return AUTH_MECHANISM_SRP;
-
-       if (mechs & AUTH_MECHANISM_FIRST_SRP)
-               return AUTH_MECHANISM_FIRST_SRP;
-
-       if (mechs & AUTH_MECHANISM_LEGACY_PASSWORD)
-               return AUTH_MECHANISM_LEGACY_PASSWORD;
-
-       return AUTH_MECHANISM_NONE;
-}
-
-void Client::sendInit(const std::string &playerName)
-{
-       NetworkPacket pkt(TOSERVER_INIT, 1 + 2 + 2 + (1 + playerName.size()));
-
-       // we don't support network compression yet
-       u16 supp_comp_modes = NETPROTO_COMPRESSION_NONE;
-
-       pkt << (u8) SER_FMT_VER_HIGHEST_READ << (u16) supp_comp_modes;
-       pkt << (u16) CLIENT_PROTOCOL_VERSION_MIN << (u16) CLIENT_PROTOCOL_VERSION_MAX;
-       pkt << playerName;
-
-       Send(&pkt);
-}
-
-void Client::promptConfirmRegistration(AuthMechanism chosen_auth_mechanism)
-{
-       m_chosen_auth_mech = chosen_auth_mechanism;
-       m_is_registration_confirmation_state = true;
-}
-
-void Client::confirmRegistration()
-{
-       m_is_registration_confirmation_state = false;
-       startAuth(m_chosen_auth_mech);
-}
-
-void Client::startAuth(AuthMechanism chosen_auth_mechanism)
-{
-       m_chosen_auth_mech = chosen_auth_mechanism;
-
-       switch (chosen_auth_mechanism) {
-               case AUTH_MECHANISM_FIRST_SRP: {
-                       // send srp verifier to server
-                       std::string verifier;
-                       std::string salt;
-                       generate_srp_verifier_and_salt(getPlayerName(), m_password,
-                               &verifier, &salt);
-
-                       NetworkPacket resp_pkt(TOSERVER_FIRST_SRP, 0);
-                       resp_pkt << salt << verifier << (u8)((m_password.empty()) ? 1 : 0);
-
-                       Send(&resp_pkt);
-                       break;
-               }
-               case AUTH_MECHANISM_SRP:
-               case AUTH_MECHANISM_LEGACY_PASSWORD: {
-                       u8 based_on = 1;
-
-                       if (chosen_auth_mechanism == AUTH_MECHANISM_LEGACY_PASSWORD) {
-                               m_password = translate_password(getPlayerName(), m_password);
-                               based_on = 0;
-                       }
-
-                       std::string playername_u = lowercase(getPlayerName());
-                       m_auth_data = srp_user_new(SRP_SHA256, SRP_NG_2048,
-                               getPlayerName().c_str(), playername_u.c_str(),
-                               (const unsigned char *) m_password.c_str(),
-                               m_password.length(), NULL, NULL);
-                       char *bytes_A = 0;
-                       size_t len_A = 0;
-                       SRP_Result res = srp_user_start_authentication(
-                               (struct SRPUser *) m_auth_data, NULL, NULL, 0,
-                               (unsigned char **) &bytes_A, &len_A);
-                       FATAL_ERROR_IF(res != SRP_OK, "Creating local SRP user failed.");
-
-                       NetworkPacket resp_pkt(TOSERVER_SRP_BYTES_A, 0);
-                       resp_pkt << std::string(bytes_A, len_A) << based_on;
-                       Send(&resp_pkt);
-                       break;
-               }
-               case AUTH_MECHANISM_NONE:
-                       break; // not handled in this method
-       }
-}
-
-void Client::sendDeletedBlocks(std::vector<v3s16> &blocks)
-{
-       NetworkPacket pkt(TOSERVER_DELETEDBLOCKS, 1 + sizeof(v3s16) * blocks.size());
-
-       pkt << (u8) blocks.size();
-
-       for (const v3s16 &block : blocks) {
-               pkt << block;
-       }
-
-       Send(&pkt);
-}
-
-void Client::sendGotBlocks(v3s16 block)
-{
-       NetworkPacket pkt(TOSERVER_GOTBLOCKS, 1 + 6);
-       pkt << (u8) 1 << block;
-       Send(&pkt);
-}
-
-void Client::sendRemovedSounds(std::vector<s32> &soundList)
-{
-       size_t server_ids = soundList.size();
-       assert(server_ids <= 0xFFFF);
-
-       NetworkPacket pkt(TOSERVER_REMOVED_SOUNDS, 2 + server_ids * 4);
-
-       pkt << (u16) (server_ids & 0xFFFF);
-
-       for (int sound_id : soundList)
-               pkt << sound_id;
-
-       Send(&pkt);
-}
-
-void Client::sendNodemetaFields(v3s16 p, const std::string &formname,
-               const StringMap &fields)
-{
-       size_t fields_size = fields.size();
-
-       FATAL_ERROR_IF(fields_size > 0xFFFF, "Unsupported number of nodemeta fields");
-
-       NetworkPacket pkt(TOSERVER_NODEMETA_FIELDS, 0);
-
-       pkt << p << formname << (u16) (fields_size & 0xFFFF);
-
-       StringMap::const_iterator it;
-       for (it = fields.begin(); it != fields.end(); ++it) {
-               const std::string &name = it->first;
-               const std::string &value = it->second;
-               pkt << name;
-               pkt.putLongString(value);
-       }
-
-       Send(&pkt);
-}
-
-void Client::sendInventoryFields(const std::string &formname,
-               const StringMap &fields)
-{
-       size_t fields_size = fields.size();
-       FATAL_ERROR_IF(fields_size > 0xFFFF, "Unsupported number of inventory fields");
-
-       NetworkPacket pkt(TOSERVER_INVENTORY_FIELDS, 0);
-       pkt << formname << (u16) (fields_size & 0xFFFF);
-
-       StringMap::const_iterator it;
-       for (it = fields.begin(); it != fields.end(); ++it) {
-               const std::string &name  = it->first;
-               const std::string &value = it->second;
-               pkt << name;
-               pkt.putLongString(value);
-       }
-
-       Send(&pkt);
-}
-
-void Client::sendInventoryAction(InventoryAction *a)
-{
-       std::ostringstream os(std::ios_base::binary);
-
-       a->serialize(os);
-
-       // Make data buffer
-       std::string s = os.str();
-
-       NetworkPacket pkt(TOSERVER_INVENTORY_ACTION, s.size());
-       pkt.putRawString(s.c_str(),s.size());
-
-       Send(&pkt);
-}
-
-bool Client::canSendChatMessage() const
-{
-       u32 now = time(NULL);
-       float time_passed = now - m_last_chat_message_sent;
-
-       float virt_chat_message_allowance = m_chat_message_allowance + time_passed *
-                       (CLIENT_CHAT_MESSAGE_LIMIT_PER_10S / 8.0f);
-
-       if (virt_chat_message_allowance < 1.0f)
-               return false;
-
-       return true;
-}
-
-void Client::sendChatMessage(const std::wstring &message)
-{
-       const s16 max_queue_size = g_settings->getS16("max_out_chat_queue_size");
-       if (canSendChatMessage()) {
-               u32 now = time(NULL);
-               float time_passed = now - m_last_chat_message_sent;
-               m_last_chat_message_sent = time(NULL);
-
-               m_chat_message_allowance += time_passed * (CLIENT_CHAT_MESSAGE_LIMIT_PER_10S / 8.0f);
-               if (m_chat_message_allowance > CLIENT_CHAT_MESSAGE_LIMIT_PER_10S)
-                       m_chat_message_allowance = CLIENT_CHAT_MESSAGE_LIMIT_PER_10S;
-
-               m_chat_message_allowance -= 1.0f;
-
-               NetworkPacket pkt(TOSERVER_CHAT_MESSAGE, 2 + message.size() * sizeof(u16));
-
-               pkt << message;
-
-               Send(&pkt);
-       } else if (m_out_chat_queue.size() < (u16) max_queue_size || max_queue_size == -1) {
-               m_out_chat_queue.push(message);
-       } else {
-               infostream << "Could not queue chat message because maximum out chat queue size ("
-                               << max_queue_size << ") is reached." << std::endl;
-       }
-}
-
-void Client::clearOutChatQueue()
-{
-       m_out_chat_queue = std::queue<std::wstring>();
-}
-
-void Client::sendChangePassword(const std::string &oldpassword,
-        const std::string &newpassword)
-{
-       LocalPlayer *player = m_env.getLocalPlayer();
-       if (player == NULL)
-               return;
-
-       // get into sudo mode and then send new password to server
-       m_password = oldpassword;
-       m_new_password = newpassword;
-       startAuth(choseAuthMech(m_sudo_auth_methods));
-}
-
-
-void Client::sendDamage(u8 damage)
-{
-       NetworkPacket pkt(TOSERVER_DAMAGE, sizeof(u8));
-       pkt << damage;
-       Send(&pkt);
-}
-
-void Client::sendRespawn()
-{
-       NetworkPacket pkt(TOSERVER_RESPAWN, 0);
-       Send(&pkt);
-}
-
-void Client::sendReady()
-{
-       NetworkPacket pkt(TOSERVER_CLIENT_READY,
-                       1 + 1 + 1 + 1 + 2 + sizeof(char) * strlen(g_version_hash));
-
-       pkt << (u8) VERSION_MAJOR << (u8) VERSION_MINOR << (u8) VERSION_PATCH
-               << (u8) 0 << (u16) strlen(g_version_hash);
-
-       pkt.putRawString(g_version_hash, (u16) strlen(g_version_hash));
-       Send(&pkt);
-}
-
-void Client::sendPlayerPos()
-{
-       LocalPlayer *myplayer = m_env.getLocalPlayer();
-       if (!myplayer)
-               return;
-
-       ClientMap &map = m_env.getClientMap();
-
-       u8 camera_fov    = map.getCameraFov();
-       u8 wanted_range  = map.getControl().wanted_range;
-
-       // Save bandwidth by only updating position when something changed
-       if(myplayer->last_position        == myplayer->getPosition() &&
-                       myplayer->last_speed        == myplayer->getSpeed()    &&
-                       myplayer->last_pitch        == myplayer->getPitch()    &&
-                       myplayer->last_yaw          == myplayer->getYaw()      &&
-                       myplayer->last_keyPressed   == myplayer->keyPressed    &&
-                       myplayer->last_camera_fov   == camera_fov              &&
-                       myplayer->last_wanted_range == wanted_range)
-               return;
-
-       myplayer->last_position     = myplayer->getPosition();
-       myplayer->last_speed        = myplayer->getSpeed();
-       myplayer->last_pitch        = myplayer->getPitch();
-       myplayer->last_yaw          = myplayer->getYaw();
-       myplayer->last_keyPressed   = myplayer->keyPressed;
-       myplayer->last_camera_fov   = camera_fov;
-       myplayer->last_wanted_range = wanted_range;
-
-       NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1);
-
-       writePlayerPos(myplayer, &map, &pkt);
-
-       Send(&pkt);
-}
-
-void Client::sendPlayerItem(u16 item)
-{
-       LocalPlayer *myplayer = m_env.getLocalPlayer();
-       if (!myplayer)
-               return;
-
-       NetworkPacket pkt(TOSERVER_PLAYERITEM, 2);
-
-       pkt << item;
-
-       Send(&pkt);
-}
-
-void Client::removeNode(v3s16 p)
-{
-       std::map<v3s16, MapBlock*> modified_blocks;
-
-       try {
-               m_env.getMap().removeNodeAndUpdate(p, modified_blocks);
-       }
-       catch(InvalidPositionException &e) {
-       }
-
-       for (const auto &modified_block : modified_blocks) {
-               addUpdateMeshTaskWithEdge(modified_block.first, false, true);
-       }
-}
-
-/**
- * Helper function for Client Side Modding
- * CSM restrictions are applied there, this should not be used for core engine
- * @param p
- * @param is_valid_position
- * @return
- */
-MapNode Client::getNode(v3s16 p, bool *is_valid_position)
-{
-       if (checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOOKUP_NODES)) {
-               v3s16 ppos = floatToInt(m_env.getLocalPlayer()->getPosition(), BS);
-               if ((u32) ppos.getDistanceFrom(p) > m_csm_restriction_noderange) {
-                       *is_valid_position = false;
-                       return {};
-               }
-       }
-       return m_env.getMap().getNodeNoEx(p, is_valid_position);
-}
-
-void Client::addNode(v3s16 p, MapNode n, bool remove_metadata)
-{
-       //TimeTaker timer1("Client::addNode()");
-
-       std::map<v3s16, MapBlock*> modified_blocks;
-
-       try {
-               //TimeTaker timer3("Client::addNode(): addNodeAndUpdate");
-               m_env.getMap().addNodeAndUpdate(p, n, modified_blocks, remove_metadata);
-       }
-       catch(InvalidPositionException &e) {
-       }
-
-       for (const auto &modified_block : modified_blocks) {
-               addUpdateMeshTaskWithEdge(modified_block.first, false, true);
-       }
-}
-
-void Client::setPlayerControl(PlayerControl &control)
-{
-       LocalPlayer *player = m_env.getLocalPlayer();
-       assert(player);
-       player->control = control;
-}
-
-void Client::selectPlayerItem(u16 item)
-{
-       m_playeritem = item;
-       m_inventory_updated = true;
-       sendPlayerItem(item);
-}
-
-// Returns true if the inventory of the local player has been
-// updated from the server. If it is true, it is set to false.
-bool Client::getLocalInventoryUpdated()
-{
-       bool updated = m_inventory_updated;
-       m_inventory_updated = false;
-       return updated;
-}
-
-// Copies the inventory of the local player to parameter
-void Client::getLocalInventory(Inventory &dst)
-{
-       LocalPlayer *player = m_env.getLocalPlayer();
-       assert(player);
-       dst = player->inventory;
-}
-
-Inventory* Client::getInventory(const InventoryLocation &loc)
-{
-       switch(loc.type){
-       case InventoryLocation::UNDEFINED:
-       {}
-       break;
-       case InventoryLocation::CURRENT_PLAYER:
-       {
-               LocalPlayer *player = m_env.getLocalPlayer();
-               assert(player);
-               return &player->inventory;
-       }
-       break;
-       case InventoryLocation::PLAYER:
-       {
-               // Check if we are working with local player inventory
-               LocalPlayer *player = m_env.getLocalPlayer();
-               if (!player || strcmp(player->getName(), loc.name.c_str()) != 0)
-                       return NULL;
-               return &player->inventory;
-       }
-       break;
-       case InventoryLocation::NODEMETA:
-       {
-               NodeMetadata *meta = m_env.getMap().getNodeMetadata(loc.p);
-               if(!meta)
-                       return NULL;
-               return meta->getInventory();
-       }
-       break;
-       case InventoryLocation::DETACHED:
-       {
-               if (m_detached_inventories.count(loc.name) == 0)
-                       return NULL;
-               return m_detached_inventories[loc.name];
-       }
-       break;
-       default:
-               FATAL_ERROR("Invalid inventory location type.");
-               break;
-       }
-       return NULL;
-}
-
-void Client::inventoryAction(InventoryAction *a)
-{
-       /*
-               Send it to the server
-       */
-       sendInventoryAction(a);
-
-       /*
-               Predict some local inventory changes
-       */
-       a->clientApply(this, this);
-
-       // Remove it
-       delete a;
-}
-
-float Client::getAnimationTime()
-{
-       return m_animation_time;
-}
-
-int Client::getCrackLevel()
-{
-       return m_crack_level;
-}
-
-v3s16 Client::getCrackPos()
-{
-       return m_crack_pos;
-}
-
-void Client::setCrack(int level, v3s16 pos)
-{
-       int old_crack_level = m_crack_level;
-       v3s16 old_crack_pos = m_crack_pos;
-
-       m_crack_level = level;
-       m_crack_pos = pos;
-
-       if(old_crack_level >= 0 && (level < 0 || pos != old_crack_pos))
-       {
-               // remove old crack
-               addUpdateMeshTaskForNode(old_crack_pos, false, true);
-       }
-       if(level >= 0 && (old_crack_level < 0 || pos != old_crack_pos))
-       {
-               // add new crack
-               addUpdateMeshTaskForNode(pos, false, true);
-       }
-}
-
-u16 Client::getHP()
-{
-       LocalPlayer *player = m_env.getLocalPlayer();
-       assert(player);
-       return player->hp;
-}
-
-bool Client::getChatMessage(std::wstring &res)
-{
-       if (m_chat_queue.empty())
-               return false;
-
-       ChatMessage *chatMessage = m_chat_queue.front();
-       m_chat_queue.pop();
-
-       res = L"";
-
-       switch (chatMessage->type) {
-               case CHATMESSAGE_TYPE_RAW:
-               case CHATMESSAGE_TYPE_ANNOUNCE:
-               case CHATMESSAGE_TYPE_SYSTEM:
-                       res = chatMessage->message;
-                       break;
-               case CHATMESSAGE_TYPE_NORMAL: {
-                       if (!chatMessage->sender.empty())
-                               res = L"<" + chatMessage->sender + L"> " + chatMessage->message;
-                       else
-                               res = chatMessage->message;
-                       break;
-               }
-               default:
-                       break;
-       }
-
-       delete chatMessage;
-       return true;
-}
-
-void Client::typeChatMessage(const std::wstring &message)
-{
-       // Discard empty line
-       if (message.empty())
-               return;
-
-       // If message was consumed by script API, don't send it to server
-       if (m_modding_enabled && m_script->on_sending_message(wide_to_utf8(message)))
-               return;
-
-       // Send to others
-       sendChatMessage(message);
-
-       // Show locally
-       if (message[0] != L'/') {
-               // compatibility code
-               if (m_proto_ver < 29) {
-                       LocalPlayer *player = m_env.getLocalPlayer();
-                       assert(player);
-                       std::wstring name = narrow_to_wide(player->getName());
-                       pushToChatQueue(new ChatMessage(CHATMESSAGE_TYPE_NORMAL, message, name));
-               }
-       }
-}
-
-void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server, bool urgent)
-{
-       // Check if the block exists to begin with. In the case when a non-existing
-       // neighbor is automatically added, it may not. In that case we don't want
-       // to tell the mesh update thread about it.
-       MapBlock *b = m_env.getMap().getBlockNoCreateNoEx(p);
-       if (b == NULL)
-               return;
-
-       m_mesh_update_thread.updateBlock(&m_env.getMap(), p, ack_to_server, urgent);
-}
-
-void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server, bool urgent)
-{
-       try{
-               addUpdateMeshTask(blockpos, ack_to_server, urgent);
-       }
-       catch(InvalidPositionException &e){}
-
-       // Leading edge
-       for (int i=0;i<6;i++)
-       {
-               try{
-                       v3s16 p = blockpos + g_6dirs[i];
-                       addUpdateMeshTask(p, false, urgent);
-               }
-               catch(InvalidPositionException &e){}
-       }
-}
-
-void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool urgent)
-{
-       {
-               v3s16 p = nodepos;
-               infostream<<"Client::addUpdateMeshTaskForNode(): "
-                               <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
-                               <<std::endl;
-       }
-
-       v3s16 blockpos          = getNodeBlockPos(nodepos);
-       v3s16 blockpos_relative = blockpos * MAP_BLOCKSIZE;
-
-       try{
-               addUpdateMeshTask(blockpos, ack_to_server, urgent);
-       }
-       catch(InvalidPositionException &e) {}
-
-       // 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){}
-       }
-}
-
-ClientEvent *Client::getClientEvent()
-{
-       FATAL_ERROR_IF(m_client_event_queue.empty(),
-                       "Cannot getClientEvent, queue is empty.");
-
-       ClientEvent *event = m_client_event_queue.front();
-       m_client_event_queue.pop();
-       return event;
-}
-
-bool Client::connectedToServer()
-{
-       return m_con->Connected();
-}
-
-const Address Client::getServerAddress()
-{
-       return m_con->GetPeerAddress(PEER_ID_SERVER);
-}
-
-float Client::mediaReceiveProgress()
-{
-       if (m_media_downloader)
-               return m_media_downloader->getProgress();
-
-       return 1.0; // downloader only exists when not yet done
-}
-
-typedef struct TextureUpdateArgs {
-       gui::IGUIEnvironment *guienv;
-       u64 last_time_ms;
-       u16 last_percent;
-       const wchar_t* text_base;
-       ITextureSource *tsrc;
-} TextureUpdateArgs;
-
-void texture_update_progress(void *args, u32 progress, u32 max_progress)
-{
-               TextureUpdateArgs* targs = (TextureUpdateArgs*) args;
-               u16 cur_percent = ceil(progress / (double) max_progress * 100.);
-
-               // update the loading menu -- if neccessary
-               bool do_draw = false;
-               u64 time_ms = targs->last_time_ms;
-               if (cur_percent != targs->last_percent) {
-                       targs->last_percent = cur_percent;
-                       time_ms = porting::getTimeMs();
-                       // only draw when the user will notice something:
-                       do_draw = (time_ms - targs->last_time_ms > 100);
-               }
-
-               if (do_draw) {
-                       targs->last_time_ms = time_ms;
-                       std::basic_stringstream<wchar_t> strm;
-                       strm << targs->text_base << " " << targs->last_percent << "%...";
-                       RenderingEngine::draw_load_screen(strm.str(), targs->guienv, targs->tsrc, 0,
-                               72 + (u16) ((18. / 100.) * (double) targs->last_percent), true);
-               }
-}
-
-void Client::afterContentReceived()
-{
-       infostream<<"Client::afterContentReceived() started"<<std::endl;
-       assert(m_itemdef_received); // pre-condition
-       assert(m_nodedef_received); // pre-condition
-       assert(mediaReceived()); // pre-condition
-
-       const wchar_t* text = wgettext("Loading textures...");
-
-       // Clear cached pre-scaled 2D GUI images, as this cache
-       // might have images with the same name but different
-       // content from previous sessions.
-       guiScalingCacheClear();
-
-       // Rebuild inherited images and recreate textures
-       infostream<<"- Rebuilding images and textures"<<std::endl;
-       RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 70);
-       m_tsrc->rebuildImagesAndTextures();
-       delete[] text;
-
-       // Rebuild shaders
-       infostream<<"- Rebuilding shaders"<<std::endl;
-       text = wgettext("Rebuilding shaders...");
-       RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 71);
-       m_shsrc->rebuildShaders();
-       delete[] text;
-
-       // Update node aliases
-       infostream<<"- Updating node aliases"<<std::endl;
-       text = wgettext("Initializing nodes...");
-       RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 72);
-       m_nodedef->updateAliases(m_itemdef);
-       for (const auto &path : getTextureDirs())
-               m_nodedef->applyTextureOverrides(path + DIR_DELIM + "override.txt");
-       m_nodedef->setNodeRegistrationStatus(true);
-       m_nodedef->runNodeResolveCallbacks();
-       delete[] text;
-
-       // Update node textures and assign shaders to each tile
-       infostream<<"- Updating node textures"<<std::endl;
-       TextureUpdateArgs tu_args;
-       tu_args.guienv = guienv;
-       tu_args.last_time_ms = porting::getTimeMs();
-       tu_args.last_percent = 0;
-       tu_args.text_base =  wgettext("Initializing nodes");
-       tu_args.tsrc = m_tsrc;
-       m_nodedef->updateTextures(this, texture_update_progress, &tu_args);
-       delete[] tu_args.text_base;
-
-       // Start mesh update thread after setting up content definitions
-       infostream<<"- Starting mesh update thread"<<std::endl;
-       m_mesh_update_thread.start();
-
-       m_state = LC_Ready;
-       sendReady();
-
-       if (g_settings->getBool("enable_client_modding")) {
-               m_script->on_client_ready(m_env.getLocalPlayer());
-       }
-
-       text = wgettext("Done!");
-       RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 100);
-       infostream<<"Client::afterContentReceived() done"<<std::endl;
-       delete[] text;
-}
-
-// returns the Round Trip Time
-// if the RTT did not become updated within 2 seconds, e.g. before timing out,
-// it returns the expired time instead
-float Client::getRTT()
-{
-       float avg_rtt = m_con->getPeerStat(PEER_ID_SERVER, con::AVG_RTT);
-       float time_from_last_rtt =
-               m_con->getPeerStat(PEER_ID_SERVER, con::TIMEOUT_COUNTER);
-       if (avg_rtt + 2.0f > time_from_last_rtt)
-               return avg_rtt;
-       return time_from_last_rtt;
-}
-
-float Client::getCurRate()
-{
-       return (m_con->getLocalStat(con::CUR_INC_RATE) +
-                       m_con->getLocalStat(con::CUR_DL_RATE));
-}
-
-void Client::makeScreenshot()
-{
-       irr::video::IVideoDriver *driver = RenderingEngine::get_video_driver();
-       irr::video::IImage* const raw_image = driver->createScreenShot();
-
-       if (!raw_image)
-               return;
-
-       time_t t = time(NULL);
-       struct tm *tm = localtime(&t);
-
-       char timetstamp_c[64];
-       strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", tm);
-
-       std::string filename_base = g_settings->get("screenshot_path")
-                       + DIR_DELIM
-                       + std::string("screenshot_")
-                       + std::string(timetstamp_c);
-       std::string filename_ext = "." + g_settings->get("screenshot_format");
-       std::string filename;
-
-       u32 quality = (u32)g_settings->getS32("screenshot_quality");
-       quality = MYMIN(MYMAX(quality, 0), 100) / 100.0 * 255;
-
-       // Try to find a unique filename
-       unsigned serial = 0;
-
-       while (serial < SCREENSHOT_MAX_SERIAL_TRIES) {
-               filename = filename_base + (serial > 0 ? ("_" + itos(serial)) : "") + filename_ext;
-               std::ifstream tmp(filename.c_str());
-               if (!tmp.good())
-                       break;  // File did not apparently exist, we'll go with it
-               serial++;
-       }
-
-       if (serial == SCREENSHOT_MAX_SERIAL_TRIES) {
-               infostream << "Could not find suitable filename for screenshot" << std::endl;
-       } else {
-               irr::video::IImage* const image =
-                               driver->createImage(video::ECF_R8G8B8, raw_image->getDimension());
-
-               if (image) {
-                       raw_image->copyTo(image);
-
-                       std::ostringstream sstr;
-                       if (driver->writeImageToFile(image, filename.c_str(), quality)) {
-                               sstr << "Saved screenshot to '" << filename << "'";
-                       } else {
-                               sstr << "Failed to save screenshot '" << filename << "'";
-                       }
-                       pushToChatQueue(new ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
-                                       narrow_to_wide(sstr.str())));
-                       infostream << sstr.str() << std::endl;
-                       image->drop();
-               }
-       }
-
-       raw_image->drop();
-}
-
-bool Client::shouldShowMinimap() const
-{
-       return !m_minimap_disabled_by_server;
-}
-
-void Client::pushToEventQueue(ClientEvent *event)
-{
-       m_client_event_queue.push(event);
-}
-
-void Client::showMinimap(const bool show)
-{
-       m_game_ui->showMinimap(show);
-}
-
-// IGameDef interface
-// Under envlock
-IItemDefManager* Client::getItemDefManager()
-{
-       return m_itemdef;
-}
-const NodeDefManager* Client::getNodeDefManager()
-{
-       return m_nodedef;
-}
-ICraftDefManager* Client::getCraftDefManager()
-{
-       return NULL;
-       //return m_craftdef;
-}
-ITextureSource* Client::getTextureSource()
-{
-       return m_tsrc;
-}
-IShaderSource* Client::getShaderSource()
-{
-       return m_shsrc;
-}
-
-u16 Client::allocateUnknownNodeId(const std::string &name)
-{
-       errorstream << "Client::allocateUnknownNodeId(): "
-                       << "Client cannot allocate node IDs" << std::endl;
-       FATAL_ERROR("Client allocated unknown node");
-
-       return CONTENT_IGNORE;
-}
-ISoundManager* Client::getSoundManager()
-{
-       return m_sound;
-}
-MtEventManager* Client::getEventManager()
-{
-       return m_event;
-}
-
-ParticleManager* Client::getParticleManager()
-{
-       return &m_particle_manager;
-}
-
-scene::IAnimatedMesh* Client::getMesh(const std::string &filename, bool cache)
-{
-       StringMap::const_iterator it = m_mesh_data.find(filename);
-       if (it == m_mesh_data.end()) {
-               errorstream << "Client::getMesh(): Mesh not found: \"" << filename
-                       << "\"" << std::endl;
-               return NULL;
-       }
-       const std::string &data    = it->second;
-
-       // Create the mesh, remove it from cache and return it
-       // This allows unique vertex colors and other properties for each instance
-       Buffer<char> data_rw(data.c_str(), data.size()); // Const-incorrect Irrlicht
-       io::IReadFile *rfile   = RenderingEngine::get_filesystem()->createMemoryReadFile(
-                       *data_rw, data_rw.getSize(), filename.c_str());
-       FATAL_ERROR_IF(!rfile, "Could not create/open RAM file");
-
-       scene::IAnimatedMesh *mesh = RenderingEngine::get_scene_manager()->getMesh(rfile);
-       rfile->drop();
-       mesh->grab();
-       if (!cache)
-               RenderingEngine::get_mesh_cache()->removeMesh(mesh);
-       return mesh;
-}
-
-const std::string* Client::getModFile(const std::string &filename)
-{
-       StringMap::const_iterator it = m_mod_files.find(filename);
-       if (it == m_mod_files.end()) {
-               errorstream << "Client::getModFile(): File not found: \"" << filename
-                       << "\"" << std::endl;
-               return NULL;
-       }
-       return &it->second;
-}
-
-bool Client::registerModStorage(ModMetadata *storage)
-{
-       if (m_mod_storages.find(storage->getModName()) != m_mod_storages.end()) {
-               errorstream << "Unable to register same mod storage twice. Storage name: "
-                               << storage->getModName() << std::endl;
-               return false;
-       }
-
-       m_mod_storages[storage->getModName()] = storage;
-       return true;
-}
-
-void Client::unregisterModStorage(const std::string &name)
-{
-       std::unordered_map<std::string, ModMetadata *>::const_iterator it =
-               m_mod_storages.find(name);
-       if (it != m_mod_storages.end()) {
-               // Save unconditionaly on unregistration
-               it->second->save(getModStoragePath());
-               m_mod_storages.erase(name);
-       }
-}
-
-std::string Client::getModStoragePath() const
-{
-       return porting::path_user + DIR_DELIM + "client" + DIR_DELIM + "mod_storage";
-}
-
-/*
- * Mod channels
- */
-
-bool Client::joinModChannel(const std::string &channel)
-{
-       if (m_modchannel_mgr->channelRegistered(channel))
-               return false;
-
-       NetworkPacket pkt(TOSERVER_MODCHANNEL_JOIN, 2 + channel.size());
-       pkt << channel;
-       Send(&pkt);
-
-       m_modchannel_mgr->joinChannel(channel, 0);
-       return true;
-}
-
-bool Client::leaveModChannel(const std::string &channel)
-{
-       if (!m_modchannel_mgr->channelRegistered(channel))
-               return false;
-
-       NetworkPacket pkt(TOSERVER_MODCHANNEL_LEAVE, 2 + channel.size());
-       pkt << channel;
-       Send(&pkt);
-
-       m_modchannel_mgr->leaveChannel(channel, 0);
-       return true;
-}
-
-bool Client::sendModChannelMessage(const std::string &channel, const std::string &message)
-{
-       if (!m_modchannel_mgr->canWriteOnChannel(channel))
-               return false;
-
-       if (message.size() > STRING_MAX_LEN) {
-               warningstream << "ModChannel message too long, dropping before sending "
-                               << " (" << message.size() << " > " << STRING_MAX_LEN << ", channel: "
-                               << channel << ")" << std::endl;
-               return false;
-       }
-
-       // @TODO: do some client rate limiting
-       NetworkPacket pkt(TOSERVER_MODCHANNEL_MSG, 2 + channel.size() + 2 + message.size());
-       pkt << channel << message;
-       Send(&pkt);
-       return true;
-}
-
-ModChannel* Client::getModChannel(const std::string &channel)
-{
-       return m_modchannel_mgr->getModChannel(channel);
-}
diff --git a/src/client.h b/src/client.h
deleted file mode 100644 (file)
index 68a832d..0000000
+++ /dev/null
@@ -1,608 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include "clientenvironment.h"
-#include "irrlichttypes_extrabloated.h"
-#include <ostream>
-#include <map>
-#include <set>
-#include <vector>
-#include <unordered_set>
-#include "clientobject.h"
-#include "gamedef.h"
-#include "inventorymanager.h"
-#include "localplayer.h"
-#include "client/hud.h"
-#include "particles.h"
-#include "mapnode.h"
-#include "tileanimation.h"
-#include "mesh_generator_thread.h"
-#include "network/address.h"
-#include "network/peerhandler.h"
-#include <fstream>
-
-#define CLIENT_CHAT_MESSAGE_LIMIT_PER_10S 10.0f
-
-struct ClientEvent;
-struct MeshMakeData;
-struct ChatMessage;
-class MapBlockMesh;
-class IWritableTextureSource;
-class IWritableShaderSource;
-class IWritableItemDefManager;
-class ISoundManager;
-class NodeDefManager;
-//class IWritableCraftDefManager;
-class ClientMediaDownloader;
-struct MapDrawControl;
-class ModChannelMgr;
-class MtEventManager;
-struct PointedThing;
-class MapDatabase;
-class Minimap;
-struct MinimapMapblock;
-class Camera;
-class NetworkPacket;
-namespace con {
-class Connection;
-}
-
-enum LocalClientState {
-       LC_Created,
-       LC_Init,
-       LC_Ready
-};
-
-/*
-       Packet counter
-*/
-
-class PacketCounter
-{
-public:
-       PacketCounter() = default;
-
-       void add(u16 command)
-       {
-               std::map<u16, u16>::iterator n = m_packets.find(command);
-               if(n == m_packets.end())
-               {
-                       m_packets[command] = 1;
-               }
-               else
-               {
-                       n->second++;
-               }
-       }
-
-       void clear()
-       {
-               for (auto &m_packet : m_packets) {
-                       m_packet.second = 0;
-               }
-       }
-
-       void print(std::ostream &o)
-       {
-               for (const auto &m_packet : m_packets) {
-                       o << "cmd "<< m_packet.first <<" count "<< m_packet.second << std::endl;
-               }
-       }
-
-private:
-       // command, count
-       std::map<u16, u16> m_packets;
-};
-
-class ClientScripting;
-class GameUI;
-
-class Client : public con::PeerHandler, public InventoryManager, public IGameDef
-{
-public:
-       /*
-               NOTE: Nothing is thread-safe here.
-       */
-
-       Client(
-                       const char *playername,
-                       const std::string &password,
-                       const std::string &address_name,
-                       MapDrawControl &control,
-                       IWritableTextureSource *tsrc,
-                       IWritableShaderSource *shsrc,
-                       IWritableItemDefManager *itemdef,
-                       NodeDefManager *nodedef,
-                       ISoundManager *sound,
-                       MtEventManager *event,
-                       bool ipv6,
-                       GameUI *game_ui
-       );
-
-       ~Client();
-       DISABLE_CLASS_COPY(Client);
-
-       // Load local mods into memory
-       void scanModSubfolder(const std::string &mod_name, const std::string &mod_path,
-                               std::string mod_subpath);
-       inline void scanModIntoMemory(const std::string &mod_name, const std::string &mod_path)
-       {
-               scanModSubfolder(mod_name, mod_path, "");
-       }
-
-       /*
-        request all threads managed by client to be stopped
-        */
-       void Stop();
-
-
-       bool isShutdown();
-
-       /*
-               The name of the local player should already be set when
-               calling this, as it is sent in the initialization.
-       */
-       void connect(Address address, bool is_local_server);
-
-       /*
-               Stuff that references the environment is valid only as
-               long as this is not called. (eg. Players)
-               If this throws a PeerNotFoundException, the connection has
-               timed out.
-       */
-       void step(float dtime);
-
-       /*
-        * Command Handlers
-        */
-
-       void handleCommand(NetworkPacket* pkt);
-
-       void handleCommand_Null(NetworkPacket* pkt) {};
-       void handleCommand_Deprecated(NetworkPacket* pkt);
-       void handleCommand_Hello(NetworkPacket* pkt);
-       void handleCommand_AuthAccept(NetworkPacket* pkt);
-       void handleCommand_AcceptSudoMode(NetworkPacket* pkt);
-       void handleCommand_DenySudoMode(NetworkPacket* pkt);
-       void handleCommand_AccessDenied(NetworkPacket* pkt);
-       void handleCommand_RemoveNode(NetworkPacket* pkt);
-       void handleCommand_AddNode(NetworkPacket* pkt);
-       void handleCommand_BlockData(NetworkPacket* pkt);
-       void handleCommand_Inventory(NetworkPacket* pkt);
-       void handleCommand_TimeOfDay(NetworkPacket* pkt);
-       void handleCommand_ChatMessage(NetworkPacket *pkt);
-       void handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt);
-       void handleCommand_ActiveObjectMessages(NetworkPacket* pkt);
-       void handleCommand_Movement(NetworkPacket* pkt);
-       void handleCommand_HP(NetworkPacket* pkt);
-       void handleCommand_Breath(NetworkPacket* pkt);
-       void handleCommand_MovePlayer(NetworkPacket* pkt);
-       void handleCommand_DeathScreen(NetworkPacket* pkt);
-       void handleCommand_AnnounceMedia(NetworkPacket* pkt);
-       void handleCommand_Media(NetworkPacket* pkt);
-       void handleCommand_NodeDef(NetworkPacket* pkt);
-       void handleCommand_ItemDef(NetworkPacket* pkt);
-       void handleCommand_PlaySound(NetworkPacket* pkt);
-       void handleCommand_StopSound(NetworkPacket* pkt);
-       void handleCommand_FadeSound(NetworkPacket *pkt);
-       void handleCommand_Privileges(NetworkPacket* pkt);
-       void handleCommand_InventoryFormSpec(NetworkPacket* pkt);
-       void handleCommand_DetachedInventory(NetworkPacket* pkt);
-       void handleCommand_ShowFormSpec(NetworkPacket* pkt);
-       void handleCommand_SpawnParticle(NetworkPacket* pkt);
-       void handleCommand_AddParticleSpawner(NetworkPacket* pkt);
-       void handleCommand_DeleteParticleSpawner(NetworkPacket* pkt);
-       void handleCommand_HudAdd(NetworkPacket* pkt);
-       void handleCommand_HudRemove(NetworkPacket* pkt);
-       void handleCommand_HudChange(NetworkPacket* pkt);
-       void handleCommand_HudSetFlags(NetworkPacket* pkt);
-       void handleCommand_HudSetParam(NetworkPacket* pkt);
-       void handleCommand_HudSetSky(NetworkPacket* pkt);
-       void handleCommand_CloudParams(NetworkPacket* pkt);
-       void handleCommand_OverrideDayNightRatio(NetworkPacket* pkt);
-       void handleCommand_LocalPlayerAnimations(NetworkPacket* pkt);
-       void handleCommand_EyeOffset(NetworkPacket* pkt);
-       void handleCommand_UpdatePlayerList(NetworkPacket* pkt);
-       void handleCommand_ModChannelMsg(NetworkPacket *pkt);
-       void handleCommand_ModChannelSignal(NetworkPacket *pkt);
-       void handleCommand_SrpBytesSandB(NetworkPacket *pkt);
-       void handleCommand_FormspecPrepend(NetworkPacket *pkt);
-       void handleCommand_CSMRestrictionFlags(NetworkPacket *pkt);
-
-       void ProcessData(NetworkPacket *pkt);
-
-       void Send(NetworkPacket* pkt);
-
-       void interact(u8 action, const PointedThing& pointed);
-
-       void sendNodemetaFields(v3s16 p, const std::string &formname,
-               const StringMap &fields);
-       void sendInventoryFields(const std::string &formname,
-               const StringMap &fields);
-       void sendInventoryAction(InventoryAction *a);
-       void sendChatMessage(const std::wstring &message);
-       void clearOutChatQueue();
-       void sendChangePassword(const std::string &oldpassword,
-               const std::string &newpassword);
-       void sendDamage(u8 damage);
-       void sendRespawn();
-       void sendReady();
-
-       ClientEnvironment& getEnv() { return m_env; }
-       ITextureSource *tsrc() { return getTextureSource(); }
-       ISoundManager *sound() { return getSoundManager(); }
-       static const std::string &getBuiltinLuaPath();
-       static const std::string &getClientModsLuaPath();
-
-       const std::vector<ModSpec> &getMods() const override;
-       const ModSpec* getModSpec(const std::string &modname) const override;
-
-       // Causes urgent mesh updates (unlike Map::add/removeNodeWithEvent)
-       void removeNode(v3s16 p);
-
-       /**
-        * Helper function for Client Side Modding
-        * CSM restrictions are applied there, this should not be used for core engine
-        * @param p
-        * @param is_valid_position
-        * @return
-        */
-       MapNode getNode(v3s16 p, bool *is_valid_position);
-       void addNode(v3s16 p, MapNode n, bool remove_metadata = true);
-
-       void setPlayerControl(PlayerControl &control);
-
-       void selectPlayerItem(u16 item);
-       u16 getPlayerItem() const
-       { return m_playeritem; }
-
-       // Returns true if the inventory of the local player has been
-       // updated from the server. If it is true, it is set to false.
-       bool getLocalInventoryUpdated();
-       // Copies the inventory of the local player to parameter
-       void getLocalInventory(Inventory &dst);
-
-       /* InventoryManager interface */
-       Inventory* getInventory(const InventoryLocation &loc) override;
-       void inventoryAction(InventoryAction *a) override;
-
-       const std::list<std::string> &getConnectedPlayerNames()
-       {
-               return m_env.getPlayerNames();
-       }
-
-       float getAnimationTime();
-
-       int getCrackLevel();
-       v3s16 getCrackPos();
-       void setCrack(int level, v3s16 pos);
-
-       u16 getHP();
-
-       bool checkPrivilege(const std::string &priv) const
-       { return (m_privileges.count(priv) != 0); }
-
-       const std::unordered_set<std::string> &getPrivilegeList() const
-       { return m_privileges; }
-
-       bool getChatMessage(std::wstring &message);
-       void typeChatMessage(const std::wstring& message);
-
-       u64 getMapSeed(){ return m_map_seed; }
-
-       void addUpdateMeshTask(v3s16 blockpos, bool ack_to_server=false, bool urgent=false);
-       // Including blocks at appropriate edges
-       void addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server=false, bool urgent=false);
-       void addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server=false, bool urgent=false);
-
-       void updateCameraOffset(v3s16 camera_offset)
-       { m_mesh_update_thread.m_camera_offset = camera_offset; }
-
-       bool hasClientEvents() const { return !m_client_event_queue.empty(); }
-       // Get event from queue. If queue is empty, it triggers an assertion failure.
-       ClientEvent * getClientEvent();
-
-       bool accessDenied() const { return m_access_denied; }
-
-       bool reconnectRequested() const { return m_access_denied_reconnect; }
-
-       void setFatalError(const std::string &reason)
-       {
-               m_access_denied = true;
-               m_access_denied_reason = reason;
-       }
-
-       // Renaming accessDeniedReason to better name could be good as it's used to
-       // disconnect client when CSM failed.
-       const std::string &accessDeniedReason() const { return m_access_denied_reason; }
-
-       bool itemdefReceived()
-       { return m_itemdef_received; }
-       bool nodedefReceived()
-       { return m_nodedef_received; }
-       bool mediaReceived()
-       { return !m_media_downloader; }
-
-       u8 getProtoVersion()
-       { return m_proto_ver; }
-
-       bool connectedToServer();
-       void confirmRegistration();
-       bool m_is_registration_confirmation_state = false;
-       bool m_simple_singleplayer_mode;
-
-       float mediaReceiveProgress();
-
-       void afterContentReceived();
-
-       float getRTT();
-       float getCurRate();
-
-       Minimap* getMinimap() { return m_minimap; }
-       void setCamera(Camera* camera) { m_camera = camera; }
-
-       Camera* getCamera () { return m_camera; }
-
-       bool shouldShowMinimap() const;
-
-       // IGameDef interface
-       IItemDefManager* getItemDefManager() override;
-       const NodeDefManager* getNodeDefManager() override;
-       ICraftDefManager* getCraftDefManager() override;
-       ITextureSource* getTextureSource();
-       virtual IShaderSource* getShaderSource();
-       u16 allocateUnknownNodeId(const std::string &name) override;
-       virtual ISoundManager* getSoundManager();
-       MtEventManager* getEventManager();
-       virtual ParticleManager* getParticleManager();
-       bool checkLocalPrivilege(const std::string &priv)
-       { return checkPrivilege(priv); }
-       virtual scene::IAnimatedMesh* getMesh(const std::string &filename, bool cache = false);
-       const std::string* getModFile(const std::string &filename);
-
-       std::string getModStoragePath() const override;
-       bool registerModStorage(ModMetadata *meta) override;
-       void unregisterModStorage(const std::string &name) override;
-
-       // The following set of functions is used by ClientMediaDownloader
-       // Insert a media file appropriately into the appropriate manager
-       bool loadMedia(const std::string &data, const std::string &filename);
-       // Send a request for conventional media transfer
-       void request_media(const std::vector<std::string> &file_requests);
-
-       LocalClientState getState() { return m_state; }
-
-       void makeScreenshot();
-
-       inline void pushToChatQueue(ChatMessage *cec)
-       {
-               m_chat_queue.push(cec);
-       }
-
-       ClientScripting *getScript() { return m_script; }
-       const bool moddingEnabled() const { return m_modding_enabled; }
-       const bool modsLoaded() const { return m_mods_loaded; }
-
-       void pushToEventQueue(ClientEvent *event);
-
-       void showMinimap(bool show = true);
-
-       const Address getServerAddress();
-
-       const std::string &getAddressName() const
-       {
-               return m_address_name;
-       }
-
-       inline bool checkCSMRestrictionFlag(CSMRestrictionFlags flag) const
-       {
-               return m_csm_restriction_flags & flag;
-       }
-
-       u32 getCSMNodeRangeLimit() const
-       {
-               return m_csm_restriction_noderange;
-       }
-
-       inline std::unordered_map<u32, u32> &getHUDTranslationMap()
-       {
-               return m_hud_server_to_client;
-       }
-
-       bool joinModChannel(const std::string &channel) override;
-       bool leaveModChannel(const std::string &channel) override;
-       bool sendModChannelMessage(const std::string &channel,
-                       const std::string &message) override;
-       ModChannel *getModChannel(const std::string &channel) override;
-
-       const std::string &getFormspecPrepend() const
-       {
-               return m_env.getLocalPlayer()->formspec_prepend;
-       }
-private:
-       void loadMods();
-       bool checkBuiltinIntegrity();
-
-       // Virtual methods from con::PeerHandler
-       void peerAdded(con::Peer *peer) override;
-       void deletingPeer(con::Peer *peer, bool timeout) override;
-
-       void initLocalMapSaving(const Address &address,
-                       const std::string &hostname,
-                       bool is_local_server);
-
-       void ReceiveAll();
-       void Receive();
-
-       void sendPlayerPos();
-       // Send the item number 'item' as player item to the server
-       void sendPlayerItem(u16 item);
-
-       void deleteAuthData();
-       // helper method shared with clientpackethandler
-       static AuthMechanism choseAuthMech(const u32 mechs);
-
-       void sendInit(const std::string &playerName);
-       void promptConfirmRegistration(AuthMechanism chosen_auth_mechanism);
-       void startAuth(AuthMechanism chosen_auth_mechanism);
-       void sendDeletedBlocks(std::vector<v3s16> &blocks);
-       void sendGotBlocks(v3s16 block);
-       void sendRemovedSounds(std::vector<s32> &soundList);
-
-       // Helper function
-       inline std::string getPlayerName()
-       { return m_env.getLocalPlayer()->getName(); }
-
-       bool canSendChatMessage() const;
-
-       float m_packetcounter_timer = 0.0f;
-       float m_connection_reinit_timer = 0.1f;
-       float m_avg_rtt_timer = 0.0f;
-       float m_playerpos_send_timer = 0.0f;
-       IntervalLimiter m_map_timer_and_unload_interval;
-
-       IWritableTextureSource *m_tsrc;
-       IWritableShaderSource *m_shsrc;
-       IWritableItemDefManager *m_itemdef;
-       NodeDefManager *m_nodedef;
-       ISoundManager *m_sound;
-       MtEventManager *m_event;
-
-
-       MeshUpdateThread m_mesh_update_thread;
-       ClientEnvironment m_env;
-       ParticleManager m_particle_manager;
-       std::unique_ptr<con::Connection> m_con;
-       std::string m_address_name;
-       Camera *m_camera = nullptr;
-       Minimap *m_minimap = nullptr;
-       bool m_minimap_disabled_by_server = false;
-       // Server serialization version
-       u8 m_server_ser_ver;
-
-       // Used version of the protocol with server
-       // Values smaller than 25 only mean they are smaller than 25,
-       // and aren't accurate. We simply just don't know, because
-       // the server didn't send the version back then.
-       // If 0, server init hasn't been received yet.
-       u8 m_proto_ver = 0;
-
-       u16 m_playeritem = 0;
-       bool m_inventory_updated = false;
-       Inventory *m_inventory_from_server = nullptr;
-       float m_inventory_from_server_age = 0.0f;
-       PacketCounter m_packetcounter;
-       // Block mesh animation parameters
-       float m_animation_time = 0.0f;
-       int m_crack_level = -1;
-       v3s16 m_crack_pos;
-       // 0 <= m_daynight_i < DAYNIGHT_CACHE_COUNT
-       //s32 m_daynight_i;
-       //u32 m_daynight_ratio;
-       std::queue<std::wstring> m_out_chat_queue;
-       u32 m_last_chat_message_sent;
-       float m_chat_message_allowance = 5.0f;
-       std::queue<ChatMessage *> m_chat_queue;
-
-       // The authentication methods we can use to enter sudo mode (=change password)
-       u32 m_sudo_auth_methods;
-
-       // The seed returned by the server in TOCLIENT_INIT is stored here
-       u64 m_map_seed = 0;
-
-       // Auth data
-       std::string m_playername;
-       std::string m_password;
-       // If set, this will be sent (and cleared) upon a TOCLIENT_ACCEPT_SUDO_MODE
-       std::string m_new_password;
-       // Usable by auth mechanisms.
-       AuthMechanism m_chosen_auth_mech;
-       void *m_auth_data = nullptr;
-
-
-       bool m_access_denied = false;
-       bool m_access_denied_reconnect = false;
-       std::string m_access_denied_reason = "";
-       std::queue<ClientEvent *> m_client_event_queue;
-       bool m_itemdef_received = false;
-       bool m_nodedef_received = false;
-       bool m_mods_loaded = false;
-       ClientMediaDownloader *m_media_downloader;
-
-       // time_of_day speed approximation for old protocol
-       bool m_time_of_day_set = false;
-       float m_last_time_of_day_f = -1.0f;
-       float m_time_of_day_update_timer = 0.0f;
-
-       // An interval for generally sending object positions and stuff
-       float m_recommended_send_interval = 0.1f;
-
-       // Sounds
-       float m_removed_sounds_check_timer = 0.0f;
-       // Mapping from server sound ids to our sound ids
-       std::unordered_map<s32, int> m_sounds_server_to_client;
-       // And the other way!
-       std::unordered_map<int, s32> m_sounds_client_to_server;
-       // And relations to objects
-       std::unordered_map<int, u16> m_sounds_to_objects;
-
-       // CSM/client IDs to SSM/server IDs Mapping
-       // Map server particle spawner IDs to client IDs
-       std::unordered_map<u32, u32> m_particles_server_to_client;
-       // Map server hud ids to client hud ids
-       std::unordered_map<u32, u32> m_hud_server_to_client;
-
-       // Privileges
-       std::unordered_set<std::string> m_privileges;
-
-       // Detached inventories
-       // key = name
-       std::unordered_map<std::string, Inventory*> m_detached_inventories;
-
-       // Storage for mesh data for creating multiple instances of the same mesh
-       StringMap m_mesh_data;
-
-       StringMap m_mod_files;
-
-       // own state
-       LocalClientState m_state;
-
-       GameUI *m_game_ui;
-
-       // Used for saving server map to disk client-side
-       MapDatabase *m_localdb = nullptr;
-       IntervalLimiter m_localdb_save_interval;
-       u16 m_cache_save_interval;
-
-       ClientScripting *m_script = nullptr;
-       bool m_modding_enabled;
-       std::unordered_map<std::string, ModMetadata *> m_mod_storages;
-       float m_mod_storage_save_timer = 10.0f;
-       std::vector<ModSpec> m_mods;
-
-       bool m_shutdown = false;
-
-       // CSM restrictions byteflag
-       u64 m_csm_restriction_flags = CSMRestrictionFlags::CSM_RF_NONE;
-       u32 m_csm_restriction_noderange = 8;
-
-       std::unique_ptr<ModChannelMgr> m_modchannel_mgr;
-};
index 1cabe1b1134f168b91b137fce0f4250cf3b3c168..e24b73e807624fc797ae430f8883da01531654b4 100644 (file)
@@ -25,12 +25,37 @@ set(client_SRCS
        ${CMAKE_CURRENT_SOURCE_DIR}/render/plain.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/render/sidebyside.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/render/stereo.cpp
-       ${CMAKE_CURRENT_SOURCE_DIR}/renderingengine.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/camera.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/client.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/clientenvironment.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/clientlauncher.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/clientmap.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/clientmedia.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/clientobject.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/clouds.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/content_cao.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/content_cso.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/content_mapblock.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/filecache.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/fontengine.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/game.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/gameui.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/guiscalingfilter.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/hud.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/imagefilters.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/inputhandler.cpp
-       ${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp
        ${CMAKE_CURRENT_SOURCE_DIR}/joystick_controller.cpp
-       ${CMAKE_CURRENT_SOURCE_DIR}/hud.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/keycode.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/localplayer.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/mapblock_mesh.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/mesh.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/mesh_generator_thread.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/minimap.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/particles.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/renderingengine.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/shader.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/sky.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp
+       ${CMAKE_CURRENT_SOURCE_DIR}/wieldmesh.cpp
        PARENT_SCOPE
 )
diff --git a/src/client/camera.cpp b/src/client/camera.cpp
new file mode 100644 (file)
index 0000000..1bbdb56
--- /dev/null
@@ -0,0 +1,658 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 "camera.h"
+#include "debug.h"
+#include "client.h"
+#include "map.h"
+#include "clientmap.h"     // MapDrawControl
+#include "player.h"
+#include <cmath>
+#include "client/renderingengine.h"
+#include "settings.h"
+#include "wieldmesh.h"
+#include "noise.h"         // easeCurve
+#include "sound.h"
+#include "event.h"
+#include "nodedef.h"
+#include "util/numeric.h"
+#include "constants.h"
+#include "fontengine.h"
+#include "script/scripting_client.h"
+
+#define CAMERA_OFFSET_STEP 200
+#define WIELDMESH_OFFSET_X 55.0f
+#define WIELDMESH_OFFSET_Y -35.0f
+
+Camera::Camera(MapDrawControl &draw_control, Client *client):
+       m_draw_control(draw_control),
+       m_client(client)
+{
+       scene::ISceneManager *smgr = RenderingEngine::get_scene_manager();
+       // note: making the camera node a child of the player node
+       // would lead to unexpected behaviour, so we don't do that.
+       m_playernode = smgr->addEmptySceneNode(smgr->getRootSceneNode());
+       m_headnode = smgr->addEmptySceneNode(m_playernode);
+       m_cameranode = smgr->addCameraSceneNode(smgr->getRootSceneNode());
+       m_cameranode->bindTargetAndRotation(true);
+
+       // This needs to be in its own scene manager. It is drawn after
+       // all other 3D scene nodes and before the GUI.
+       m_wieldmgr = smgr->createNewSceneManager();
+       m_wieldmgr->addCameraSceneNode();
+       m_wieldnode = new WieldMeshSceneNode(m_wieldmgr, -1, false);
+       m_wieldnode->setItem(ItemStack(), m_client);
+       m_wieldnode->drop(); // m_wieldmgr grabbed it
+
+       /* TODO: Add a callback function so these can be updated when a setting
+        *       changes.  At this point in time it doesn't matter (e.g. /set
+        *       is documented to change server settings only)
+        *
+        * 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.
+        */
+       m_cache_fall_bobbing_amount = g_settings->getFloat("fall_bobbing_amount");
+       m_cache_view_bobbing_amount = g_settings->getFloat("view_bobbing_amount");
+       // 45 degrees is the lowest FOV that doesn't cause the server to treat this
+       // as a zoom FOV and load world beyond the set server limits.
+       m_cache_fov                 = std::fmax(g_settings->getFloat("fov"), 45.0f);
+       m_arm_inertia               = g_settings->getBool("arm_inertia");
+       m_nametags.clear();
+}
+
+Camera::~Camera()
+{
+       m_wieldmgr->drop();
+}
+
+bool Camera::successfullyCreated(std::string &error_message)
+{
+       if (!m_playernode) {
+               error_message = "Failed to create the player scene node";
+       } else if (!m_headnode) {
+               error_message = "Failed to create the head scene node";
+       } else if (!m_cameranode) {
+               error_message = "Failed to create the camera scene node";
+       } else if (!m_wieldmgr) {
+               error_message = "Failed to create the wielded item scene manager";
+       } else if (!m_wieldnode) {
+               error_message = "Failed to create the wielded item scene node";
+       } else {
+               error_message.clear();
+       }
+
+       if (g_settings->getBool("enable_client_modding")) {
+               m_client->getScript()->on_camera_ready(this);
+       }
+       return error_message.empty();
+}
+
+// Returns the fractional part of x
+inline f32 my_modf(f32 x)
+{
+       double dummy;
+       return modf(x, &dummy);
+}
+
+void Camera::step(f32 dtime)
+{
+       if(m_view_bobbing_fall > 0)
+       {
+               m_view_bobbing_fall -= 3 * dtime;
+               if(m_view_bobbing_fall <= 0)
+                       m_view_bobbing_fall = -1; // Mark the effect as finished
+       }
+
+       bool was_under_zero = m_wield_change_timer < 0;
+       m_wield_change_timer = MYMIN(m_wield_change_timer + dtime, 0.125);
+
+       if (m_wield_change_timer >= 0 && was_under_zero)
+               m_wieldnode->setItem(m_wield_item_next, m_client);
+
+       if (m_view_bobbing_state != 0)
+       {
+               //f32 offset = dtime * m_view_bobbing_speed * 0.035;
+               f32 offset = dtime * m_view_bobbing_speed * 0.030;
+               if (m_view_bobbing_state == 2) {
+                       // Animation is getting turned off
+                       if (m_view_bobbing_anim < 0.25) {
+                               m_view_bobbing_anim -= offset;
+                       } else if (m_view_bobbing_anim > 0.75) {
+                               m_view_bobbing_anim += offset;
+                       }
+
+                       if (m_view_bobbing_anim < 0.5) {
+                               m_view_bobbing_anim += offset;
+                               if (m_view_bobbing_anim > 0.5)
+                                       m_view_bobbing_anim = 0.5;
+                       } else {
+                               m_view_bobbing_anim -= offset;
+                               if (m_view_bobbing_anim < 0.5)
+                                       m_view_bobbing_anim = 0.5;
+                       }
+
+                       if (m_view_bobbing_anim <= 0 || m_view_bobbing_anim >= 1 ||
+                                       fabs(m_view_bobbing_anim - 0.5) < 0.01) {
+                               m_view_bobbing_anim = 0;
+                               m_view_bobbing_state = 0;
+                       }
+               }
+               else {
+                       float was = m_view_bobbing_anim;
+                       m_view_bobbing_anim = my_modf(m_view_bobbing_anim + offset);
+                       bool step = (was == 0 ||
+                                       (was < 0.5f && m_view_bobbing_anim >= 0.5f) ||
+                                       (was > 0.5f && m_view_bobbing_anim <= 0.5f));
+                       if(step) {
+                               m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::VIEW_BOBBING_STEP));
+                       }
+               }
+       }
+
+       if (m_digging_button != -1) {
+               f32 offset = dtime * 3.5f;
+               float m_digging_anim_was = m_digging_anim;
+               m_digging_anim += offset;
+               if (m_digging_anim >= 1)
+               {
+                       m_digging_anim = 0;
+                       m_digging_button = -1;
+               }
+               float lim = 0.15;
+               if(m_digging_anim_was < lim && m_digging_anim >= lim)
+               {
+                       if (m_digging_button == 0) {
+                               m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::CAMERA_PUNCH_LEFT));
+                       } else if(m_digging_button == 1) {
+                               m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::CAMERA_PUNCH_RIGHT));
+                       }
+               }
+       }
+}
+
+static inline v2f dir(const v2f &pos_dist)
+{
+       f32 x = pos_dist.X - WIELDMESH_OFFSET_X;
+       f32 y = pos_dist.Y - WIELDMESH_OFFSET_Y;
+
+       f32 x_abs = std::fabs(x);
+       f32 y_abs = std::fabs(y);
+
+       if (x_abs >= y_abs) {
+               y *= (1.0f / x_abs);
+               x /= x_abs;
+       }
+
+       if (y_abs >= x_abs) {
+               x *= (1.0f / y_abs);
+               y /= y_abs;
+       }
+
+       return v2f(std::fabs(x), std::fabs(y));
+}
+
+void Camera::addArmInertia(f32 player_yaw)
+{
+       m_cam_vel.X = std::fabs(rangelim(m_last_cam_pos.X - player_yaw,
+               -100.0f, 100.0f) / 0.016f) * 0.01f;
+       m_cam_vel.Y = std::fabs((m_last_cam_pos.Y - m_camera_direction.Y) / 0.016f);
+       f32 gap_X = std::fabs(WIELDMESH_OFFSET_X - m_wieldmesh_offset.X);
+       f32 gap_Y = std::fabs(WIELDMESH_OFFSET_Y - m_wieldmesh_offset.Y);
+
+       if (m_cam_vel.X > 1.0f || m_cam_vel.Y > 1.0f) {
+               /*
+                   The arm moves relative to the camera speed,
+                   with an acceleration factor.
+               */
+
+               if (m_cam_vel.X > 1.0f) {
+                       if (m_cam_vel.X > m_cam_vel_old.X)
+                               m_cam_vel_old.X = m_cam_vel.X;
+
+                       f32 acc_X = 0.12f * (m_cam_vel.X - (gap_X * 0.1f));
+                       m_wieldmesh_offset.X += m_last_cam_pos.X < player_yaw ? acc_X : -acc_X;
+
+                       if (m_last_cam_pos.X != player_yaw)
+                               m_last_cam_pos.X = player_yaw;
+
+                       m_wieldmesh_offset.X = rangelim(m_wieldmesh_offset.X,
+                               WIELDMESH_OFFSET_X - 7.0f, WIELDMESH_OFFSET_X + 7.0f);
+               }
+
+               if (m_cam_vel.Y > 1.0f) {
+                       if (m_cam_vel.Y > m_cam_vel_old.Y)
+                               m_cam_vel_old.Y = m_cam_vel.Y;
+
+                       f32 acc_Y = 0.12f * (m_cam_vel.Y - (gap_Y * 0.1f));
+                       m_wieldmesh_offset.Y +=
+                               m_last_cam_pos.Y > m_camera_direction.Y ? acc_Y : -acc_Y;
+
+                       if (m_last_cam_pos.Y != m_camera_direction.Y)
+                               m_last_cam_pos.Y = m_camera_direction.Y;
+
+                       m_wieldmesh_offset.Y = rangelim(m_wieldmesh_offset.Y,
+                               WIELDMESH_OFFSET_Y - 10.0f, WIELDMESH_OFFSET_Y + 5.0f);
+               }
+
+               m_arm_dir = dir(m_wieldmesh_offset);
+       } else {
+               /*
+                   Now the arm gets back to its default position when the camera stops,
+                   following a vector, with a smooth deceleration factor.
+               */
+
+               f32 dec_X = 0.12f * (m_cam_vel_old.X * (1.0f +
+                       (1.0f - m_arm_dir.X))) * (gap_X / 20.0f);
+
+               f32 dec_Y = 0.06f * (m_cam_vel_old.Y * (1.0f +
+                       (1.0f - m_arm_dir.Y))) * (gap_Y / 15.0f);
+
+               if (gap_X < 0.1f)
+                       m_cam_vel_old.X = 0.0f;
+
+               m_wieldmesh_offset.X -=
+                       m_wieldmesh_offset.X > WIELDMESH_OFFSET_X ? dec_X : -dec_X;
+
+               if (gap_Y < 0.1f)
+                       m_cam_vel_old.Y = 0.0f;
+
+               m_wieldmesh_offset.Y -=
+                       m_wieldmesh_offset.Y > WIELDMESH_OFFSET_Y ? dec_Y : -dec_Y;
+       }
+}
+
+void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_reload_ratio)
+{
+       // Get player position
+       // Smooth the movement when walking up stairs
+       v3f old_player_position = m_playernode->getPosition();
+       v3f player_position = player->getPosition();
+       if (player->isAttached && player->parent)
+               player_position = player->parent->getPosition();
+       //if(player->touching_ground && player_position.Y > old_player_position.Y)
+       if(player->touching_ground &&
+                       player_position.Y > old_player_position.Y)
+       {
+               f32 oldy = old_player_position.Y;
+               f32 newy = player_position.Y;
+               f32 t = std::exp(-23 * frametime);
+               player_position.Y = oldy * t + newy * (1-t);
+       }
+
+       // Set player node transformation
+       m_playernode->setPosition(player_position);
+       m_playernode->setRotation(v3f(0, -1 * player->getYaw(), 0));
+       m_playernode->updateAbsolutePosition();
+
+       // Get camera tilt timer (hurt animation)
+       float cameratilt = fabs(fabs(player->hurt_tilt_timer-0.75)-0.75);
+
+       // Fall bobbing animation
+       float fall_bobbing = 0;
+       if(player->camera_impact >= 1 && m_camera_mode < CAMERA_MODE_THIRD)
+       {
+               if(m_view_bobbing_fall == -1) // Effect took place and has finished
+                       player->camera_impact = m_view_bobbing_fall = 0;
+               else if(m_view_bobbing_fall == 0) // Initialize effect
+                       m_view_bobbing_fall = 1;
+
+               // Convert 0 -> 1 to 0 -> 1 -> 0
+               fall_bobbing = m_view_bobbing_fall < 0.5 ? m_view_bobbing_fall * 2 : -(m_view_bobbing_fall - 0.5) * 2 + 1;
+               // Smoothen and invert the above
+               fall_bobbing = sin(fall_bobbing * 0.5 * M_PI) * -1;
+               // Amplify according to the intensity of the impact
+               fall_bobbing *= (1 - rangelim(50 / player->camera_impact, 0, 1)) * 5;
+
+               fall_bobbing *= m_cache_fall_bobbing_amount;
+       }
+
+       // Calculate players eye offset for different camera modes
+       v3f PlayerEyeOffset = player->getEyeOffset();
+       if (m_camera_mode == CAMERA_MODE_FIRST)
+               PlayerEyeOffset += player->eye_offset_first;
+       else
+               PlayerEyeOffset += player->eye_offset_third;
+
+       // Set head node transformation
+       m_headnode->setPosition(PlayerEyeOffset+v3f(0,cameratilt*-player->hurt_tilt_strength+fall_bobbing,0));
+       m_headnode->setRotation(v3f(player->getPitch(), 0, cameratilt*player->hurt_tilt_strength));
+       m_headnode->updateAbsolutePosition();
+
+       // Compute relative camera position and target
+       v3f rel_cam_pos = v3f(0,0,0);
+       v3f rel_cam_target = v3f(0,0,1);
+       v3f rel_cam_up = v3f(0,1,0);
+
+       if (m_cache_view_bobbing_amount != 0.0f && m_view_bobbing_anim != 0.0f &&
+               m_camera_mode < CAMERA_MODE_THIRD) {
+               f32 bobfrac = my_modf(m_view_bobbing_anim * 2);
+               f32 bobdir = (m_view_bobbing_anim < 0.5) ? 1.0 : -1.0;
+
+               #if 1
+               f32 bobknob = 1.2;
+               f32 bobtmp = sin(pow(bobfrac, bobknob) * M_PI);
+               //f32 bobtmp2 = cos(pow(bobfrac, bobknob) * M_PI);
+
+               v3f bobvec = v3f(
+                       0.3 * bobdir * sin(bobfrac * M_PI),
+                       -0.28 * bobtmp * bobtmp,
+                       0.);
+
+               //rel_cam_pos += 0.2 * bobvec;
+               //rel_cam_target += 0.03 * bobvec;
+               //rel_cam_up.rotateXYBy(0.02 * bobdir * bobtmp * M_PI);
+               float f = 1.0;
+               f *= m_cache_view_bobbing_amount;
+               rel_cam_pos += bobvec * f;
+               //rel_cam_target += 0.995 * bobvec * f;
+               rel_cam_target += bobvec * f;
+               rel_cam_target.Z -= 0.005 * bobvec.Z * f;
+               //rel_cam_target.X -= 0.005 * bobvec.X * f;
+               //rel_cam_target.Y -= 0.005 * bobvec.Y * f;
+               rel_cam_up.rotateXYBy(-0.03 * bobdir * bobtmp * M_PI * f);
+               #else
+               f32 angle_deg = 1 * bobdir * sin(bobfrac * M_PI);
+               f32 angle_rad = angle_deg * M_PI / 180;
+               f32 r = 0.05;
+               v3f off = v3f(
+                       r * sin(angle_rad),
+                       r * (cos(angle_rad) - 1),
+                       0);
+               rel_cam_pos += off;
+               //rel_cam_target += off;
+               rel_cam_up.rotateXYBy(angle_deg);
+               #endif
+
+       }
+
+       // Compute absolute camera position and target
+       m_headnode->getAbsoluteTransformation().transformVect(m_camera_position, rel_cam_pos);
+       m_headnode->getAbsoluteTransformation().rotateVect(m_camera_direction, rel_cam_target - rel_cam_pos);
+
+       v3f abs_cam_up;
+       m_headnode->getAbsoluteTransformation().rotateVect(abs_cam_up, rel_cam_up);
+
+       // Seperate camera position for calculation
+       v3f my_cp = m_camera_position;
+
+       // Reposition the camera for third person view
+       if (m_camera_mode > CAMERA_MODE_FIRST)
+       {
+               if (m_camera_mode == CAMERA_MODE_THIRD_FRONT)
+                       m_camera_direction *= -1;
+
+               my_cp.Y += 2;
+
+               // Calculate new position
+               bool abort = false;
+               for (int i = BS; i <= BS * 2.75; i++) {
+                       my_cp.X = m_camera_position.X + m_camera_direction.X * -i;
+                       my_cp.Z = m_camera_position.Z + m_camera_direction.Z * -i;
+                       if (i > 12)
+                               my_cp.Y = m_camera_position.Y + (m_camera_direction.Y * -i);
+
+                       // Prevent camera positioned inside nodes
+                       const NodeDefManager *nodemgr = m_client->ndef();
+                       MapNode n = m_client->getEnv().getClientMap()
+                               .getNodeNoEx(floatToInt(my_cp, BS));
+
+                       const ContentFeatures& features = nodemgr->get(n);
+                       if (features.walkable) {
+                               my_cp.X += m_camera_direction.X*-1*-BS/2;
+                               my_cp.Z += m_camera_direction.Z*-1*-BS/2;
+                               my_cp.Y += m_camera_direction.Y*-1*-BS/2;
+                               abort = true;
+                               break;
+                       }
+               }
+
+               // If node blocks camera position don't move y to heigh
+               if (abort && my_cp.Y > player_position.Y+BS*2)
+                       my_cp.Y = player_position.Y+BS*2;
+       }
+
+       // Update offset if too far away from the center of the map
+       m_camera_offset.X += CAMERA_OFFSET_STEP*
+                       (((s16)(my_cp.X/BS) - m_camera_offset.X)/CAMERA_OFFSET_STEP);
+       m_camera_offset.Y += CAMERA_OFFSET_STEP*
+                       (((s16)(my_cp.Y/BS) - m_camera_offset.Y)/CAMERA_OFFSET_STEP);
+       m_camera_offset.Z += CAMERA_OFFSET_STEP*
+                       (((s16)(my_cp.Z/BS) - m_camera_offset.Z)/CAMERA_OFFSET_STEP);
+
+       // Set camera node transformation
+       m_cameranode->setPosition(my_cp-intToFloat(m_camera_offset, BS));
+       m_cameranode->setUpVector(abs_cam_up);
+       // *100.0 helps in large map coordinates
+       m_cameranode->setTarget(my_cp-intToFloat(m_camera_offset, BS) + 100 * m_camera_direction);
+
+       // update the camera position in third-person mode to render blocks behind player
+       // and correctly apply liquid post FX.
+       if (m_camera_mode != CAMERA_MODE_FIRST)
+               m_camera_position = my_cp;
+
+       // Get FOV
+       f32 fov_degrees;
+       // Disable zoom with zoom FOV = 0
+       if (player->getPlayerControl().zoom && player->getZoomFOV() > 0.001f) {
+               fov_degrees = player->getZoomFOV();
+       } else {
+               fov_degrees = m_cache_fov;
+       }
+       fov_degrees = rangelim(fov_degrees, 1.0f, 160.0f);
+
+       // FOV and aspect ratio
+       const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize();
+       m_aspect = (f32) window_size.X / (f32) window_size.Y;
+       m_fov_y = fov_degrees * M_PI / 180.0;
+       // Increase vertical FOV on lower aspect ratios (<16:10)
+       m_fov_y *= MYMAX(1.0, MYMIN(1.4, sqrt(16./10. / m_aspect)));
+       m_fov_x = 2 * atan(m_aspect * tan(0.5 * m_fov_y));
+       m_cameranode->setAspectRatio(m_aspect);
+       m_cameranode->setFOV(m_fov_y);
+
+       if (m_arm_inertia)
+               addArmInertia(player->getYaw());
+
+       // Position the wielded item
+       //v3f wield_position = v3f(45, -35, 65);
+       v3f wield_position = v3f(m_wieldmesh_offset.X, m_wieldmesh_offset.Y, 65);
+       //v3f wield_rotation = v3f(-100, 120, -100);
+       v3f wield_rotation = v3f(-100, 120, -100);
+       wield_position.Y += fabs(m_wield_change_timer)*320 - 40;
+       if(m_digging_anim < 0.05 || m_digging_anim > 0.5)
+       {
+               f32 frac = 1.0;
+               if(m_digging_anim > 0.5)
+                       frac = 2.0 * (m_digging_anim - 0.5);
+               // This value starts from 1 and settles to 0
+               f32 ratiothing = std::pow((1.0f - tool_reload_ratio), 0.5f);
+               //f32 ratiothing2 = pow(ratiothing, 0.5f);
+               f32 ratiothing2 = (easeCurve(ratiothing*0.5))*2.0;
+               wield_position.Y -= frac * 25.0 * pow(ratiothing2, 1.7f);
+               //wield_position.Z += frac * 5.0 * ratiothing2;
+               wield_position.X -= frac * 35.0 * pow(ratiothing2, 1.1f);
+               wield_rotation.Y += frac * 70.0 * pow(ratiothing2, 1.4f);
+               //wield_rotation.X -= frac * 15.0 * pow(ratiothing2, 1.4f);
+               //wield_rotation.Z += frac * 15.0 * pow(ratiothing2, 1.0f);
+       }
+       if (m_digging_button != -1)
+       {
+               f32 digfrac = m_digging_anim;
+               wield_position.X -= 50 * sin(pow(digfrac, 0.8f) * M_PI);
+               wield_position.Y += 24 * sin(digfrac * 1.8 * M_PI);
+               wield_position.Z += 25 * 0.5;
+
+               // Euler angles are PURE EVIL, so why not use quaternions?
+               core::quaternion quat_begin(wield_rotation * core::DEGTORAD);
+               core::quaternion quat_end(v3f(80, 30, 100) * core::DEGTORAD);
+               core::quaternion quat_slerp;
+               quat_slerp.slerp(quat_begin, quat_end, sin(digfrac * M_PI));
+               quat_slerp.toEuler(wield_rotation);
+               wield_rotation *= core::RADTODEG;
+       } else {
+               f32 bobfrac = my_modf(m_view_bobbing_anim);
+               wield_position.X -= sin(bobfrac*M_PI*2.0) * 3.0;
+               wield_position.Y += sin(my_modf(bobfrac*2.0)*M_PI) * 3.0;
+       }
+       m_wieldnode->setPosition(wield_position);
+       m_wieldnode->setRotation(wield_rotation);
+
+       m_wieldnode->setColor(player->light_color);
+
+       // Set render distance
+       updateViewingRange();
+
+       // If the player is walking, swimming, or climbing,
+       // view bobbing is enabled and free_move is off,
+       // start (or continue) the view bobbing animation.
+       const v3f &speed = player->getSpeed();
+       const bool movement_XZ = hypot(speed.X, speed.Z) > BS;
+       const bool movement_Y = fabs(speed.Y) > BS;
+
+       const bool walking = movement_XZ && player->touching_ground;
+       const bool swimming = (movement_XZ || player->swimming_vertical) && player->in_liquid;
+       const bool climbing = movement_Y && player->is_climbing;
+       if ((walking || swimming || climbing) &&
+                       (!g_settings->getBool("free_move") || !m_client->checkLocalPrivilege("fly"))) {
+               // Start animation
+               m_view_bobbing_state = 1;
+               m_view_bobbing_speed = MYMIN(speed.getLength(), 70);
+       }
+       else if (m_view_bobbing_state == 1)
+       {
+               // Stop animation
+               m_view_bobbing_state = 2;
+               m_view_bobbing_speed = 60;
+       }
+}
+
+void Camera::updateViewingRange()
+{
+       f32 viewing_range = g_settings->getFloat("viewing_range");
+       f32 near_plane = g_settings->getFloat("near_plane");
+
+       m_draw_control.wanted_range = std::fmin(adjustDist(viewing_range, getFovMax()), 4000);
+       m_cameranode->setNearValue(rangelim(near_plane, 0.0f, 0.5f) * BS);
+       if (m_draw_control.range_all) {
+               m_cameranode->setFarValue(100000.0);
+               return;
+       }
+       m_cameranode->setFarValue((viewing_range < 2000) ? 2000 * BS : viewing_range * BS);
+}
+
+void Camera::setDigging(s32 button)
+{
+       if (m_digging_button == -1)
+               m_digging_button = button;
+}
+
+void Camera::wield(const ItemStack &item)
+{
+       if (item.name != m_wield_item_next.name ||
+                       item.metadata != m_wield_item_next.metadata) {
+               m_wield_item_next = item;
+               if (m_wield_change_timer > 0)
+                       m_wield_change_timer = -m_wield_change_timer;
+               else if (m_wield_change_timer == 0)
+                       m_wield_change_timer = -0.001;
+       }
+}
+
+void Camera::drawWieldedTool(irr::core::matrix4* translation)
+{
+       // Clear Z buffer so that the wielded tool stay in front of world geometry
+       m_wieldmgr->getVideoDriver()->clearZBuffer();
+
+       // Draw the wielded node (in a separate scene manager)
+       scene::ICameraSceneNode* cam = m_wieldmgr->getActiveCamera();
+       cam->setAspectRatio(m_cameranode->getAspectRatio());
+       cam->setFOV(72.0*M_PI/180.0);
+       cam->setNearValue(10);
+       cam->setFarValue(1000);
+       if (translation != NULL)
+       {
+               irr::core::matrix4 startMatrix = cam->getAbsoluteTransformation();
+               irr::core::vector3df focusPoint = (cam->getTarget()
+                               - cam->getAbsolutePosition()).setLength(1)
+                               + cam->getAbsolutePosition();
+
+               irr::core::vector3df camera_pos =
+                               (startMatrix * *translation).getTranslation();
+               cam->setPosition(camera_pos);
+               cam->setTarget(focusPoint);
+       }
+       m_wieldmgr->drawAll();
+}
+
+void Camera::drawNametags()
+{
+       core::matrix4 trans = m_cameranode->getProjectionMatrix();
+       trans *= m_cameranode->getViewMatrix();
+
+       for (std::list<Nametag *>::const_iterator
+                       i = m_nametags.begin();
+                       i != m_nametags.end(); ++i) {
+               Nametag *nametag = *i;
+               if (nametag->nametag_color.getAlpha() == 0) {
+                       // Enforce hiding nametag,
+                       // because if freetype is enabled, a grey
+                       // shadow can remain.
+                       continue;
+               }
+               v3f pos = nametag->parent_node->getAbsolutePosition() + nametag->nametag_pos * BS;
+               f32 transformed_pos[4] = { pos.X, pos.Y, pos.Z, 1.0f };
+               trans.multiplyWith1x4Matrix(transformed_pos);
+               if (transformed_pos[3] > 0) {
+                       std::wstring nametag_colorless =
+                               unescape_translate(utf8_to_wide(nametag->nametag_text));
+                       core::dimension2d<u32> textsize =
+                               g_fontengine->getFont()->getDimension(
+                               nametag_colorless.c_str());
+                       f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f :
+                               core::reciprocal(transformed_pos[3]);
+                       v2u32 screensize = RenderingEngine::get_video_driver()->getScreenSize();
+                       v2s32 screen_pos;
+                       screen_pos.X = screensize.X *
+                               (0.5 * transformed_pos[0] * zDiv + 0.5) - textsize.Width / 2;
+                       screen_pos.Y = screensize.Y *
+                               (0.5 - transformed_pos[1] * zDiv * 0.5) - textsize.Height / 2;
+                       core::rect<s32> size(0, 0, textsize.Width, textsize.Height);
+                       g_fontengine->getFont()->draw(
+                               translate_string(utf8_to_wide(nametag->nametag_text)).c_str(),
+                               size + screen_pos, nametag->nametag_color);
+               }
+       }
+}
+
+Nametag *Camera::addNametag(scene::ISceneNode *parent_node,
+               const std::string &nametag_text, video::SColor nametag_color,
+               const v3f &pos)
+{
+       Nametag *nametag = new Nametag(parent_node, nametag_text, nametag_color, pos);
+       m_nametags.push_back(nametag);
+       return nametag;
+}
+
+void Camera::removeNametag(Nametag *nametag)
+{
+       m_nametags.remove(nametag);
+       delete nametag;
+}
diff --git a/src/client/camera.h b/src/client/camera.h
new file mode 100644 (file)
index 0000000..88de357
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "inventory.h"
+#include "client/tile.h"
+#include <ICameraSceneNode.h>
+#include <ISceneNode.h>
+#include <list>
+
+class LocalPlayer;
+struct MapDrawControl;
+class Client;
+class WieldMeshSceneNode;
+
+struct Nametag {
+       Nametag(scene::ISceneNode *a_parent_node,
+                       const std::string &a_nametag_text,
+                       const video::SColor &a_nametag_color,
+                       const v3f &a_nametag_pos):
+               parent_node(a_parent_node),
+               nametag_text(a_nametag_text),
+               nametag_color(a_nametag_color),
+               nametag_pos(a_nametag_pos)
+       {
+       }
+       scene::ISceneNode *parent_node;
+       std::string nametag_text;
+       video::SColor nametag_color;
+       v3f nametag_pos;
+};
+
+enum CameraMode {CAMERA_MODE_FIRST, CAMERA_MODE_THIRD, CAMERA_MODE_THIRD_FRONT};
+
+/*
+       Client camera class, manages the player and camera scene nodes, the viewing distance
+       and performs view bobbing etc. It also displays the wielded tool in front of the
+       first-person camera.
+*/
+class Camera
+{
+public:
+       Camera(MapDrawControl &draw_control, Client *client);
+       ~Camera();
+
+       // Get camera scene node.
+       // It has the eye transformation, pitch and view bobbing applied.
+       inline scene::ICameraSceneNode* getCameraNode() const
+       {
+               return m_cameranode;
+       }
+
+       // Get the camera position (in absolute scene coordinates).
+       // This has view bobbing applied.
+       inline v3f getPosition() const
+       {
+               return m_camera_position;
+       }
+
+       // Get the camera direction (in absolute camera coordinates).
+       // This has view bobbing applied.
+       inline v3f getDirection() const
+       {
+               return m_camera_direction;
+       }
+
+       // Get the camera offset
+       inline v3s16 getOffset() const
+       {
+               return m_camera_offset;
+       }
+
+       // Horizontal field of view
+       inline f32 getFovX() const
+       {
+               return m_fov_x;
+       }
+
+       // Vertical field of view
+       inline f32 getFovY() const
+       {
+               return m_fov_y;
+       }
+
+       // Get maximum of getFovX() and getFovY()
+       inline f32 getFovMax() const
+       {
+               return MYMAX(m_fov_x, m_fov_y);
+       }
+
+       // Checks if the constructor was able to create the scene nodes
+       bool successfullyCreated(std::string &error_message);
+
+       // Step the camera: updates the viewing range and view bobbing.
+       void step(f32 dtime);
+
+       // Update the camera from the local player's position.
+       // busytime is used to adjust the viewing range.
+       void update(LocalPlayer* player, f32 frametime, f32 busytime,
+                       f32 tool_reload_ratio);
+
+       // Update render distance
+       void updateViewingRange();
+
+       // Start digging animation
+       // Pass 0 for left click, 1 for right click
+       void setDigging(s32 button);
+
+       // Replace the wielded item mesh
+       void wield(const ItemStack &item);
+
+       // Draw the wielded tool.
+       // This has to happen *after* the main scene is drawn.
+       // Warning: This clears the Z buffer.
+       void drawWieldedTool(irr::core::matrix4* translation=NULL);
+
+       // Toggle the current camera mode
+       void toggleCameraMode() {
+               if (m_camera_mode == CAMERA_MODE_FIRST)
+                       m_camera_mode = CAMERA_MODE_THIRD;
+               else if (m_camera_mode == CAMERA_MODE_THIRD)
+                       m_camera_mode = CAMERA_MODE_THIRD_FRONT;
+               else
+                       m_camera_mode = CAMERA_MODE_FIRST;
+       }
+
+       // Set the current camera mode
+       inline void setCameraMode(CameraMode mode)
+       {
+               m_camera_mode = mode;
+       }
+
+       //read the current camera mode
+       inline CameraMode getCameraMode()
+       {
+               return m_camera_mode;
+       }
+
+       Nametag *addNametag(scene::ISceneNode *parent_node,
+               const std::string &nametag_text, video::SColor nametag_color,
+               const v3f &pos);
+
+       void removeNametag(Nametag *nametag);
+
+       const std::list<Nametag *> &getNametags() { return m_nametags; }
+
+       void drawNametags();
+
+       inline void addArmInertia(f32 player_yaw);
+
+private:
+       // Nodes
+       scene::ISceneNode *m_playernode = nullptr;
+       scene::ISceneNode *m_headnode = nullptr;
+       scene::ICameraSceneNode *m_cameranode = nullptr;
+
+       scene::ISceneManager *m_wieldmgr = nullptr;
+       WieldMeshSceneNode *m_wieldnode = nullptr;
+
+       // draw control
+       MapDrawControl& m_draw_control;
+
+       Client *m_client;
+
+       // Absolute camera position
+       v3f m_camera_position;
+       // Absolute camera direction
+       v3f m_camera_direction;
+       // Camera offset
+       v3s16 m_camera_offset;
+
+       v2f m_wieldmesh_offset = v2f(55.0f, -35.0f);
+       v2f m_arm_dir;
+       v2f m_cam_vel;
+       v2f m_cam_vel_old;
+       v2f m_last_cam_pos;
+
+       // Field of view and aspect ratio stuff
+       f32 m_aspect = 1.0f;
+       f32 m_fov_x = 1.0f;
+       f32 m_fov_y = 1.0f;
+
+       // View bobbing animation frame (0 <= m_view_bobbing_anim < 1)
+       f32 m_view_bobbing_anim = 0.0f;
+       // If 0, view bobbing is off (e.g. player is standing).
+       // If 1, view bobbing is on (player is walking).
+       // If 2, view bobbing is getting switched off.
+       s32 m_view_bobbing_state = 0;
+       // Speed of view bobbing animation
+       f32 m_view_bobbing_speed = 0.0f;
+       // Fall view bobbing
+       f32 m_view_bobbing_fall = 0.0f;
+
+       // Digging animation frame (0 <= m_digging_anim < 1)
+       f32 m_digging_anim = 0.0f;
+       // If -1, no digging animation
+       // If 0, left-click digging animation
+       // If 1, right-click digging animation
+       s32 m_digging_button = -1;
+
+       // Animation when changing wielded item
+       f32 m_wield_change_timer = 0.125f;
+       ItemStack m_wield_item_next;
+
+       CameraMode m_camera_mode = CAMERA_MODE_FIRST;
+
+       f32 m_cache_fall_bobbing_amount;
+       f32 m_cache_view_bobbing_amount;
+       f32 m_cache_fov;
+       bool m_arm_inertia;
+
+       std::list<Nametag *> m_nametags;
+};
diff --git a/src/client/client.cpp b/src/client/client.cpp
new file mode 100644 (file)
index 0000000..17ee3a1
--- /dev/null
@@ -0,0 +1,1970 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 <iostream>
+#include <algorithm>
+#include <sstream>
+#include <cmath>
+#include <IFileSystem.h>
+#include "client.h"
+#include "network/clientopcodes.h"
+#include "network/connection.h"
+#include "network/networkpacket.h"
+#include "threading/mutex_auto_lock.h"
+#include "client/clientevent.h"
+#include "client/gameui.h"
+#include "client/renderingengine.h"
+#include "client/sound.h"
+#include "client/tile.h"
+#include "util/auth.h"
+#include "util/directiontables.h"
+#include "util/pointedthing.h"
+#include "util/serialize.h"
+#include "util/string.h"
+#include "util/srp.h"
+#include "filesys.h"
+#include "mapblock_mesh.h"
+#include "mapblock.h"
+#include "minimap.h"
+#include "modchannels.h"
+#include "content/mods.h"
+#include "profiler.h"
+#include "shader.h"
+#include "gettext.h"
+#include "clientmap.h"
+#include "clientmedia.h"
+#include "version.h"
+#include "database/database-sqlite3.h"
+#include "serialization.h"
+#include "guiscalingfilter.h"
+#include "script/scripting_client.h"
+#include "game.h"
+#include "chatmessage.h"
+#include "translation.h"
+
+extern gui::IGUIEnvironment* guienv;
+
+/*
+       Client
+*/
+
+Client::Client(
+               const char *playername,
+               const std::string &password,
+               const std::string &address_name,
+               MapDrawControl &control,
+               IWritableTextureSource *tsrc,
+               IWritableShaderSource *shsrc,
+               IWritableItemDefManager *itemdef,
+               NodeDefManager *nodedef,
+               ISoundManager *sound,
+               MtEventManager *event,
+               bool ipv6,
+               GameUI *game_ui
+):
+       m_tsrc(tsrc),
+       m_shsrc(shsrc),
+       m_itemdef(itemdef),
+       m_nodedef(nodedef),
+       m_sound(sound),
+       m_event(event),
+       m_mesh_update_thread(this),
+       m_env(
+               new ClientMap(this, control, 666),
+               tsrc, this
+       ),
+       m_particle_manager(&m_env),
+       m_con(new con::Connection(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, ipv6, this)),
+       m_address_name(address_name),
+       m_server_ser_ver(SER_FMT_VER_INVALID),
+       m_last_chat_message_sent(time(NULL)),
+       m_password(password),
+       m_chosen_auth_mech(AUTH_MECHANISM_NONE),
+       m_media_downloader(new ClientMediaDownloader()),
+       m_state(LC_Created),
+       m_game_ui(game_ui),
+       m_modchannel_mgr(new ModChannelMgr())
+{
+       // Add local player
+       m_env.setLocalPlayer(new LocalPlayer(this, playername));
+
+       if (g_settings->getBool("enable_minimap")) {
+               m_minimap = new Minimap(this);
+       }
+       m_cache_save_interval = g_settings->getU16("server_map_save_interval");
+
+       m_modding_enabled = g_settings->getBool("enable_client_modding");
+       // Only create the client script environment if client modding is enabled
+       if (m_modding_enabled) {
+               m_script = new ClientScripting(this);
+               m_env.setScript(m_script);
+               m_script->setEnv(&m_env);
+       }
+}
+
+void Client::loadMods()
+{
+       // Don't load mods twice
+       if (m_mods_loaded) {
+               return;
+       }
+
+       // If client modding is not enabled, don't load client-provided CSM mods or
+       // builtin.
+       if (!m_modding_enabled) {
+               warningstream << "Client side mods are disabled by configuration." << std::endl;
+               return;
+       }
+
+       // Load builtin
+       scanModIntoMemory(BUILTIN_MOD_NAME, getBuiltinLuaPath());
+       m_script->loadModFromMemory(BUILTIN_MOD_NAME);
+
+       // If the server has disabled client-provided CSM mod loading, don't load
+       // client-provided CSM mods. Builtin is loaded so needs verfying.
+       if (checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOAD_CLIENT_MODS)) {
+               warningstream << "Client side mods are disabled by server." << std::endl;
+               // If builtin integrity is wrong, disconnect user
+               if (!checkBuiltinIntegrity()) {
+                       // @TODO disconnect user
+               }
+               return;
+       }
+
+       ClientModConfiguration modconf(getClientModsLuaPath());
+       m_mods = modconf.getMods();
+       // complain about mods with unsatisfied dependencies
+       if (!modconf.isConsistent()) {
+               modconf.printUnsatisfiedModsError();
+       }
+
+       // Print mods
+       infostream << "Client Loading mods: ";
+       for (const ModSpec &mod : m_mods)
+               infostream << mod.name << " ";
+       infostream << std::endl;
+
+       // Load and run "mod" scripts
+       for (const ModSpec &mod : m_mods) {
+               if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) {
+                       throw ModError("Error loading mod \"" + mod.name +
+                               "\": Mod name does not follow naming conventions: "
+                                       "Only characters [a-z0-9_] are allowed.");
+               }
+               scanModIntoMemory(mod.name, mod.path);
+       }
+
+       // Load and run "mod" scripts
+       for (const ModSpec &mod : m_mods)
+               m_script->loadModFromMemory(mod.name);
+
+       // Run a callback when mods are loaded
+       m_script->on_mods_loaded();
+       m_mods_loaded = true;
+}
+
+bool Client::checkBuiltinIntegrity()
+{
+       // @TODO
+       return true;
+}
+
+void Client::scanModSubfolder(const std::string &mod_name, const std::string &mod_path,
+                       std::string mod_subpath)
+{
+       std::string full_path = mod_path + DIR_DELIM + mod_subpath;
+       std::vector<fs::DirListNode> mod = fs::GetDirListing(full_path);
+       for (const fs::DirListNode &j : mod) {
+               std::string filename = j.name;
+               if (j.dir) {
+                       scanModSubfolder(mod_name, mod_path, mod_subpath
+                                       + filename + DIR_DELIM);
+                       continue;
+               }
+               std::replace( mod_subpath.begin(), mod_subpath.end(), DIR_DELIM_CHAR, '/');
+               m_mod_files[mod_name + ":" + mod_subpath + filename] = full_path  + filename;
+       }
+}
+
+const std::string &Client::getBuiltinLuaPath()
+{
+       static const std::string builtin_dir = porting::path_share + DIR_DELIM + "builtin";
+       return builtin_dir;
+}
+
+const std::string &Client::getClientModsLuaPath()
+{
+       static const std::string clientmods_dir = porting::path_share + DIR_DELIM + "clientmods";
+       return clientmods_dir;
+}
+
+const std::vector<ModSpec>& Client::getMods() const
+{
+       static std::vector<ModSpec> client_modspec_temp;
+       return client_modspec_temp;
+}
+
+const ModSpec* Client::getModSpec(const std::string &modname) const
+{
+       return NULL;
+}
+
+void Client::Stop()
+{
+       m_shutdown = true;
+       if (m_modding_enabled)
+               m_script->on_shutdown();
+       //request all client managed threads to stop
+       m_mesh_update_thread.stop();
+       // Save local server map
+       if (m_localdb) {
+               infostream << "Local map saving ended." << std::endl;
+               m_localdb->endSave();
+       }
+
+       if (m_modding_enabled)
+               delete m_script;
+}
+
+bool Client::isShutdown()
+{
+       return m_shutdown || !m_mesh_update_thread.isRunning();
+}
+
+Client::~Client()
+{
+       m_shutdown = true;
+       m_con->Disconnect();
+
+       deleteAuthData();
+
+       m_mesh_update_thread.stop();
+       m_mesh_update_thread.wait();
+       while (!m_mesh_update_thread.m_queue_out.empty()) {
+               MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx();
+               delete r.mesh;
+       }
+
+
+       delete m_inventory_from_server;
+
+       // Delete detached inventories
+       for (auto &m_detached_inventorie : m_detached_inventories) {
+               delete m_detached_inventorie.second;
+       }
+
+       // cleanup 3d model meshes on client shutdown
+       while (RenderingEngine::get_mesh_cache()->getMeshCount() != 0) {
+               scene::IAnimatedMesh *mesh = RenderingEngine::get_mesh_cache()->getMeshByIndex(0);
+
+               if (mesh)
+                       RenderingEngine::get_mesh_cache()->removeMesh(mesh);
+       }
+
+       delete m_minimap;
+       delete m_media_downloader;
+}
+
+void Client::connect(Address address, bool is_local_server)
+{
+       initLocalMapSaving(address, m_address_name, is_local_server);
+
+       m_con->SetTimeoutMs(0);
+       m_con->Connect(address);
+}
+
+void Client::step(float dtime)
+{
+       // Limit a bit
+       if (dtime > 2.0)
+               dtime = 2.0;
+
+       m_animation_time += dtime;
+       if(m_animation_time > 60.0)
+               m_animation_time -= 60.0;
+
+       m_time_of_day_update_timer += dtime;
+
+       ReceiveAll();
+
+       /*
+               Packet counter
+       */
+       {
+               float &counter = m_packetcounter_timer;
+               counter -= dtime;
+               if(counter <= 0.0)
+               {
+                       counter = 20.0;
+
+                       infostream << "Client packetcounter (" << m_packetcounter_timer
+                                       << "):"<<std::endl;
+                       m_packetcounter.print(infostream);
+                       m_packetcounter.clear();
+               }
+       }
+
+       // UGLY hack to fix 2 second startup delay caused by non existent
+       // server client startup synchronization in local server or singleplayer mode
+       static bool initial_step = true;
+       if (initial_step) {
+               initial_step = false;
+       }
+       else if(m_state == LC_Created) {
+               if (m_is_registration_confirmation_state) {
+                       // Waiting confirmation
+                       return;
+               }
+               float &counter = m_connection_reinit_timer;
+               counter -= dtime;
+               if(counter <= 0.0) {
+                       counter = 2.0;
+
+                       LocalPlayer *myplayer = m_env.getLocalPlayer();
+                       FATAL_ERROR_IF(myplayer == NULL, "Local player not found in environment.");
+
+                       sendInit(myplayer->getName());
+               }
+
+               // Not connected, return
+               return;
+       }
+
+       /*
+               Do stuff if connected
+       */
+
+       /*
+               Run Map's timers and unload unused data
+       */
+       const float map_timer_and_unload_dtime = 5.25;
+       if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) {
+               ScopeProfiler sp(g_profiler, "Client: map timer and unload");
+               std::vector<v3s16> deleted_blocks;
+               m_env.getMap().timerUpdate(map_timer_and_unload_dtime,
+                       g_settings->getFloat("client_unload_unused_data_timeout"),
+                       g_settings->getS32("client_mapblock_limit"),
+                       &deleted_blocks);
+
+               /*
+                       Send info to server
+                       NOTE: This loop is intentionally iterated the way it is.
+               */
+
+               std::vector<v3s16>::iterator i = deleted_blocks.begin();
+               std::vector<v3s16> sendlist;
+               for(;;) {
+                       if(sendlist.size() == 255 || i == deleted_blocks.end()) {
+                               if(sendlist.empty())
+                                       break;
+                               /*
+                                       [0] u16 command
+                                       [2] u8 count
+                                       [3] v3s16 pos_0
+                                       [3+6] v3s16 pos_1
+                                       ...
+                               */
+
+                               sendDeletedBlocks(sendlist);
+
+                               if(i == deleted_blocks.end())
+                                       break;
+
+                               sendlist.clear();
+                       }
+
+                       sendlist.push_back(*i);
+                       ++i;
+               }
+       }
+
+       /*
+               Send pending messages on out chat queue
+       */
+       if (!m_out_chat_queue.empty() && canSendChatMessage()) {
+               sendChatMessage(m_out_chat_queue.front());
+               m_out_chat_queue.pop();
+       }
+
+       /*
+               Handle environment
+       */
+       // Control local player (0ms)
+       LocalPlayer *player = m_env.getLocalPlayer();
+       assert(player);
+       player->applyControl(dtime, &m_env);
+
+       // Step environment
+       m_env.step(dtime);
+       m_sound->step(dtime);
+
+       /*
+               Get events
+       */
+       while (m_env.hasClientEnvEvents()) {
+               ClientEnvEvent envEvent = m_env.getClientEnvEvent();
+
+               if (envEvent.type == CEE_PLAYER_DAMAGE) {
+                       u8 damage = envEvent.player_damage.amount;
+
+                       if (envEvent.player_damage.send_to_server)
+                               sendDamage(damage);
+
+                       // Add to ClientEvent queue
+                       ClientEvent *event = new ClientEvent();
+                       event->type = CE_PLAYER_DAMAGE;
+                       event->player_damage.amount = damage;
+                       m_client_event_queue.push(event);
+               }
+       }
+
+       /*
+               Print some info
+       */
+       float &counter = m_avg_rtt_timer;
+       counter += dtime;
+       if(counter >= 10) {
+               counter = 0.0;
+               // connectedAndInitialized() is true, peer exists.
+               float avg_rtt = getRTT();
+               infostream << "Client: average rtt: " << avg_rtt << std::endl;
+       }
+
+       /*
+               Send player position to server
+       */
+       {
+               float &counter = m_playerpos_send_timer;
+               counter += dtime;
+               if((m_state == LC_Ready) && (counter >= m_recommended_send_interval))
+               {
+                       counter = 0.0;
+                       sendPlayerPos();
+               }
+       }
+
+       /*
+               Replace updated meshes
+       */
+       {
+               int num_processed_meshes = 0;
+               while (!m_mesh_update_thread.m_queue_out.empty())
+               {
+                       num_processed_meshes++;
+
+                       MinimapMapblock *minimap_mapblock = NULL;
+                       bool do_mapper_update = true;
+
+                       MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx();
+                       MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(r.p);
+                       if (block) {
+                               // Delete the old mesh
+                               delete block->mesh;
+                               block->mesh = nullptr;
+
+                               if (r.mesh) {
+                                       minimap_mapblock = r.mesh->moveMinimapMapblock();
+                                       if (minimap_mapblock == NULL)
+                                               do_mapper_update = false;
+
+                                       bool is_empty = true;
+                                       for (int l = 0; l < MAX_TILE_LAYERS; l++)
+                                               if (r.mesh->getMesh(l)->getMeshBufferCount() != 0)
+                                                       is_empty = false;
+
+                                       if (is_empty)
+                                               delete r.mesh;
+                                       else
+                                               // Replace with the new mesh
+                                               block->mesh = r.mesh;
+                               }
+                       } else {
+                               delete r.mesh;
+                       }
+
+                       if (m_minimap && do_mapper_update)
+                               m_minimap->addBlock(r.p, minimap_mapblock);
+
+                       if (r.ack_block_to_server) {
+                               /*
+                                       Acknowledge block
+                                       [0] u8 count
+                                       [1] v3s16 pos_0
+                               */
+                               sendGotBlocks(r.p);
+                       }
+               }
+
+               if (num_processed_meshes > 0)
+                       g_profiler->graphAdd("num_processed_meshes", num_processed_meshes);
+       }
+
+       /*
+               Load fetched media
+       */
+       if (m_media_downloader && m_media_downloader->isStarted()) {
+               m_media_downloader->step(this);
+               if (m_media_downloader->isDone()) {
+                       delete m_media_downloader;
+                       m_media_downloader = NULL;
+               }
+       }
+
+       /*
+               If the server didn't update the inventory in a while, revert
+               the local inventory (so the player notices the lag problem
+               and knows something is wrong).
+       */
+       if (m_inventory_from_server) {
+               float interval = 10.0f;
+               float count_before = std::floor(m_inventory_from_server_age / interval);
+
+               m_inventory_from_server_age += dtime;
+
+               float count_after = std::floor(m_inventory_from_server_age / interval);
+
+               if (count_after != count_before) {
+                       // Do this every <interval> seconds after TOCLIENT_INVENTORY
+                       // Reset the locally changed inventory to the authoritative inventory
+                       m_env.getLocalPlayer()->inventory = *m_inventory_from_server;
+                       m_inventory_updated = true;
+               }
+       }
+
+       /*
+               Update positions of sounds attached to objects
+       */
+       {
+               for (auto &m_sounds_to_object : m_sounds_to_objects) {
+                       int client_id = m_sounds_to_object.first;
+                       u16 object_id = m_sounds_to_object.second;
+                       ClientActiveObject *cao = m_env.getActiveObject(object_id);
+                       if (!cao)
+                               continue;
+                       m_sound->updateSoundPosition(client_id, cao->getPosition());
+               }
+       }
+
+       /*
+               Handle removed remotely initiated sounds
+       */
+       m_removed_sounds_check_timer += dtime;
+       if(m_removed_sounds_check_timer >= 2.32) {
+               m_removed_sounds_check_timer = 0;
+               // Find removed sounds and clear references to them
+               std::vector<s32> removed_server_ids;
+               for (std::unordered_map<s32, int>::iterator i = m_sounds_server_to_client.begin();
+                               i != m_sounds_server_to_client.end();) {
+                       s32 server_id = i->first;
+                       int client_id = i->second;
+                       ++i;
+                       if(!m_sound->soundExists(client_id)) {
+                               m_sounds_server_to_client.erase(server_id);
+                               m_sounds_client_to_server.erase(client_id);
+                               m_sounds_to_objects.erase(client_id);
+                               removed_server_ids.push_back(server_id);
+                       }
+               }
+
+               // Sync to server
+               if(!removed_server_ids.empty()) {
+                       sendRemovedSounds(removed_server_ids);
+               }
+       }
+
+       m_mod_storage_save_timer -= dtime;
+       if (m_mod_storage_save_timer <= 0.0f) {
+               verbosestream << "Saving registered mod storages." << std::endl;
+               m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval");
+               for (std::unordered_map<std::string, ModMetadata *>::const_iterator
+                               it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) {
+                       if (it->second->isModified()) {
+                               it->second->save(getModStoragePath());
+                       }
+               }
+       }
+
+       // Write server map
+       if (m_localdb && m_localdb_save_interval.step(dtime,
+                       m_cache_save_interval)) {
+               m_localdb->endSave();
+               m_localdb->beginSave();
+       }
+}
+
+bool Client::loadMedia(const std::string &data, const std::string &filename)
+{
+       // Silly irrlicht's const-incorrectness
+       Buffer<char> data_rw(data.c_str(), data.size());
+
+       std::string name;
+
+       const char *image_ext[] = {
+               ".png", ".jpg", ".bmp", ".tga",
+               ".pcx", ".ppm", ".psd", ".wal", ".rgb",
+               NULL
+       };
+       name = removeStringEnd(filename, image_ext);
+       if (!name.empty()) {
+               verbosestream<<"Client: Attempting to load image "
+               <<"file \""<<filename<<"\""<<std::endl;
+
+               io::IFileSystem *irrfs = RenderingEngine::get_filesystem();
+               video::IVideoDriver *vdrv = RenderingEngine::get_video_driver();
+
+               // Create an irrlicht memory file
+               io::IReadFile *rfile = irrfs->createMemoryReadFile(
+                               *data_rw, data_rw.getSize(), "_tempreadfile");
+
+               FATAL_ERROR_IF(!rfile, "Could not create irrlicht memory file.");
+
+               // Read image
+               video::IImage *img = vdrv->createImageFromFile(rfile);
+               if (!img) {
+                       errorstream<<"Client: Cannot create image from data of "
+                                       <<"file \""<<filename<<"\""<<std::endl;
+                       rfile->drop();
+                       return false;
+               }
+
+               m_tsrc->insertSourceImage(filename, img);
+               img->drop();
+               rfile->drop();
+               return true;
+       }
+
+       const char *sound_ext[] = {
+               ".0.ogg", ".1.ogg", ".2.ogg", ".3.ogg", ".4.ogg",
+               ".5.ogg", ".6.ogg", ".7.ogg", ".8.ogg", ".9.ogg",
+               ".ogg", NULL
+       };
+       name = removeStringEnd(filename, sound_ext);
+       if (!name.empty()) {
+               verbosestream<<"Client: Attempting to load sound "
+               <<"file \""<<filename<<"\""<<std::endl;
+               m_sound->loadSoundData(name, data);
+               return true;
+       }
+
+       const char *model_ext[] = {
+               ".x", ".b3d", ".md2", ".obj",
+               NULL
+       };
+
+       name = removeStringEnd(filename, model_ext);
+       if (!name.empty()) {
+               verbosestream<<"Client: Storing model into memory: "
+                               <<"\""<<filename<<"\""<<std::endl;
+               if(m_mesh_data.count(filename))
+                       errorstream<<"Multiple models with name \""<<filename.c_str()
+                                       <<"\" found; replacing previous model"<<std::endl;
+               m_mesh_data[filename] = data;
+               return true;
+       }
+
+       const char *translate_ext[] = {
+               ".tr", NULL
+       };
+       name = removeStringEnd(filename, translate_ext);
+       if (!name.empty()) {
+               verbosestream << "Client: Loading translation: "
+                               << "\"" << filename << "\"" << std::endl;
+               g_translations->loadTranslation(data);
+               return true;
+       }
+
+       errorstream << "Client: Don't know how to load file \""
+               << filename << "\"" << std::endl;
+       return false;
+}
+
+// Virtual methods from con::PeerHandler
+void Client::peerAdded(con::Peer *peer)
+{
+       infostream << "Client::peerAdded(): peer->id="
+                       << peer->id << std::endl;
+}
+void Client::deletingPeer(con::Peer *peer, bool timeout)
+{
+       infostream << "Client::deletingPeer(): "
+                       "Server Peer is getting deleted "
+                       << "(timeout=" << timeout << ")" << std::endl;
+
+       if (timeout) {
+               m_access_denied = true;
+               m_access_denied_reason = gettext("Connection timed out.");
+       }
+}
+
+/*
+       u16 command
+       u16 number of files requested
+       for each file {
+               u16 length of name
+               string name
+       }
+*/
+void Client::request_media(const std::vector<std::string> &file_requests)
+{
+       std::ostringstream os(std::ios_base::binary);
+       writeU16(os, TOSERVER_REQUEST_MEDIA);
+       size_t file_requests_size = file_requests.size();
+
+       FATAL_ERROR_IF(file_requests_size > 0xFFFF, "Unsupported number of file requests");
+
+       // Packet dynamicly resized
+       NetworkPacket pkt(TOSERVER_REQUEST_MEDIA, 2 + 0);
+
+       pkt << (u16) (file_requests_size & 0xFFFF);
+
+       for (const std::string &file_request : file_requests) {
+               pkt << file_request;
+       }
+
+       Send(&pkt);
+
+       infostream << "Client: Sending media request list to server ("
+                       << file_requests.size() << " files. packet size)" << std::endl;
+}
+
+void Client::initLocalMapSaving(const Address &address,
+               const std::string &hostname,
+               bool is_local_server)
+{
+       if (!g_settings->getBool("enable_local_map_saving") || is_local_server) {
+               return;
+       }
+
+       const std::string world_path = porting::path_user
+               + DIR_DELIM + "worlds"
+               + DIR_DELIM + "server_"
+               + hostname + "_" + std::to_string(address.getPort());
+
+       fs::CreateAllDirs(world_path);
+
+       m_localdb = new MapDatabaseSQLite3(world_path);
+       m_localdb->beginSave();
+       actionstream << "Local map saving started, map will be saved at '" << world_path << "'" << std::endl;
+}
+
+void Client::ReceiveAll()
+{
+       u64 start_ms = porting::getTimeMs();
+       for(;;)
+       {
+               // Limit time even if there would be huge amounts of data to
+               // process
+               if(porting::getTimeMs() > start_ms + 100)
+                       break;
+
+               try {
+                       Receive();
+                       g_profiler->graphAdd("client_received_packets", 1);
+               }
+               catch(con::NoIncomingDataException &e) {
+                       break;
+               }
+               catch(con::InvalidIncomingDataException &e) {
+                       infostream<<"Client::ReceiveAll(): "
+                                       "InvalidIncomingDataException: what()="
+                                       <<e.what()<<std::endl;
+               }
+       }
+}
+
+void Client::Receive()
+{
+       NetworkPacket pkt;
+       m_con->Receive(&pkt);
+       ProcessData(&pkt);
+}
+
+inline void Client::handleCommand(NetworkPacket* pkt)
+{
+       const ToClientCommandHandler& opHandle = toClientCommandTable[pkt->getCommand()];
+       (this->*opHandle.handler)(pkt);
+}
+
+/*
+       sender_peer_id given to this shall be quaranteed to be a valid peer
+*/
+void Client::ProcessData(NetworkPacket *pkt)
+{
+       ToClientCommand command = (ToClientCommand) pkt->getCommand();
+       u32 sender_peer_id = pkt->getPeerId();
+
+       //infostream<<"Client: received command="<<command<<std::endl;
+       m_packetcounter.add((u16)command);
+
+       /*
+               If this check is removed, be sure to change the queue
+               system to know the ids
+       */
+       if(sender_peer_id != PEER_ID_SERVER) {
+               infostream << "Client::ProcessData(): Discarding data not "
+                       "coming from server: peer_id=" << sender_peer_id
+                       << std::endl;
+               return;
+       }
+
+       // Command must be handled into ToClientCommandHandler
+       if (command >= TOCLIENT_NUM_MSG_TYPES) {
+               infostream << "Client: Ignoring unknown command "
+                       << command << std::endl;
+               return;
+       }
+
+       /*
+        * Those packets are handled before m_server_ser_ver is set, it's normal
+        * But we must use the new ToClientConnectionState in the future,
+        * as a byte mask
+        */
+       if(toClientCommandTable[command].state == TOCLIENT_STATE_NOT_CONNECTED) {
+               handleCommand(pkt);
+               return;
+       }
+
+       if(m_server_ser_ver == SER_FMT_VER_INVALID) {
+               infostream << "Client: Server serialization"
+                               " format invalid or not initialized."
+                               " Skipping incoming command=" << command << std::endl;
+               return;
+       }
+
+       /*
+         Handle runtime commands
+       */
+
+       handleCommand(pkt);
+}
+
+void Client::Send(NetworkPacket* pkt)
+{
+       m_con->Send(PEER_ID_SERVER,
+               serverCommandFactoryTable[pkt->getCommand()].channel,
+               pkt,
+               serverCommandFactoryTable[pkt->getCommand()].reliable);
+}
+
+// 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;
+       s32 pitch        = myplayer->getPitch() * 100;
+       s32 yaw          = myplayer->getYaw() * 100;
+       u32 keyPressed   = myplayer->keyPressed;
+       // scaled by 80, so that pi can fit into a u8
+       u8 fov           = clientMap->getCameraFov() * 80;
+       u8 wanted_range  = MYMIN(255,
+                       std::ceil(clientMap->getControl().wanted_range / MAP_BLOCKSIZE));
+
+       v3s32 position(pf.X, pf.Y, pf.Z);
+       v3s32 speed(sf.X, sf.Y, sf.Z);
+
+       /*
+               Format:
+               [0] v3s32 position*100
+               [12] v3s32 speed*100
+               [12+12] s32 pitch*100
+               [12+12+4] s32 yaw*100
+               [12+12+4+4] u32 keyPressed
+               [12+12+4+4+4] u8 fov*80
+               [12+12+4+4+4+1] u8 ceil(wanted_range / MAP_BLOCKSIZE)
+       */
+       *pkt << position << speed << pitch << yaw << keyPressed;
+       *pkt << fov << wanted_range;
+}
+
+void Client::interact(u8 action, const PointedThing& pointed)
+{
+       if(m_state != LC_Ready) {
+               errorstream << "Client::interact() "
+                               "Canceled (not connected)"
+                               << std::endl;
+               return;
+       }
+
+       LocalPlayer *myplayer = m_env.getLocalPlayer();
+       if (myplayer == NULL)
+               return;
+
+       /*
+               [0] u16 command
+               [2] u8 action
+               [3] u16 item
+               [5] u32 length of the next item (plen)
+               [9] serialized PointedThing
+               [9 + plen] player position information
+               actions:
+               0: start digging (from undersurface) or use
+               1: stop digging (all parameters ignored)
+               2: digging completed
+               3: place block or item (to abovesurface)
+               4: use item
+               5: perform secondary action of item
+       */
+
+       NetworkPacket pkt(TOSERVER_INTERACT, 1 + 2 + 0);
+
+       pkt << action;
+       pkt << (u16)getPlayerItem();
+
+       std::ostringstream tmp_os(std::ios::binary);
+       pointed.serialize(tmp_os);
+
+       pkt.putLongString(tmp_os.str());
+
+       writePlayerPos(myplayer, &m_env.getClientMap(), &pkt);
+
+       Send(&pkt);
+}
+
+void Client::deleteAuthData()
+{
+       if (!m_auth_data)
+               return;
+
+       switch (m_chosen_auth_mech) {
+               case AUTH_MECHANISM_FIRST_SRP:
+                       break;
+               case AUTH_MECHANISM_SRP:
+               case AUTH_MECHANISM_LEGACY_PASSWORD:
+                       srp_user_delete((SRPUser *) m_auth_data);
+                       m_auth_data = NULL;
+                       break;
+               case AUTH_MECHANISM_NONE:
+                       break;
+       }
+       m_chosen_auth_mech = AUTH_MECHANISM_NONE;
+}
+
+
+AuthMechanism Client::choseAuthMech(const u32 mechs)
+{
+       if (mechs & AUTH_MECHANISM_SRP)
+               return AUTH_MECHANISM_SRP;
+
+       if (mechs & AUTH_MECHANISM_FIRST_SRP)
+               return AUTH_MECHANISM_FIRST_SRP;
+
+       if (mechs & AUTH_MECHANISM_LEGACY_PASSWORD)
+               return AUTH_MECHANISM_LEGACY_PASSWORD;
+
+       return AUTH_MECHANISM_NONE;
+}
+
+void Client::sendInit(const std::string &playerName)
+{
+       NetworkPacket pkt(TOSERVER_INIT, 1 + 2 + 2 + (1 + playerName.size()));
+
+       // we don't support network compression yet
+       u16 supp_comp_modes = NETPROTO_COMPRESSION_NONE;
+
+       pkt << (u8) SER_FMT_VER_HIGHEST_READ << (u16) supp_comp_modes;
+       pkt << (u16) CLIENT_PROTOCOL_VERSION_MIN << (u16) CLIENT_PROTOCOL_VERSION_MAX;
+       pkt << playerName;
+
+       Send(&pkt);
+}
+
+void Client::promptConfirmRegistration(AuthMechanism chosen_auth_mechanism)
+{
+       m_chosen_auth_mech = chosen_auth_mechanism;
+       m_is_registration_confirmation_state = true;
+}
+
+void Client::confirmRegistration()
+{
+       m_is_registration_confirmation_state = false;
+       startAuth(m_chosen_auth_mech);
+}
+
+void Client::startAuth(AuthMechanism chosen_auth_mechanism)
+{
+       m_chosen_auth_mech = chosen_auth_mechanism;
+
+       switch (chosen_auth_mechanism) {
+               case AUTH_MECHANISM_FIRST_SRP: {
+                       // send srp verifier to server
+                       std::string verifier;
+                       std::string salt;
+                       generate_srp_verifier_and_salt(getPlayerName(), m_password,
+                               &verifier, &salt);
+
+                       NetworkPacket resp_pkt(TOSERVER_FIRST_SRP, 0);
+                       resp_pkt << salt << verifier << (u8)((m_password.empty()) ? 1 : 0);
+
+                       Send(&resp_pkt);
+                       break;
+               }
+               case AUTH_MECHANISM_SRP:
+               case AUTH_MECHANISM_LEGACY_PASSWORD: {
+                       u8 based_on = 1;
+
+                       if (chosen_auth_mechanism == AUTH_MECHANISM_LEGACY_PASSWORD) {
+                               m_password = translate_password(getPlayerName(), m_password);
+                               based_on = 0;
+                       }
+
+                       std::string playername_u = lowercase(getPlayerName());
+                       m_auth_data = srp_user_new(SRP_SHA256, SRP_NG_2048,
+                               getPlayerName().c_str(), playername_u.c_str(),
+                               (const unsigned char *) m_password.c_str(),
+                               m_password.length(), NULL, NULL);
+                       char *bytes_A = 0;
+                       size_t len_A = 0;
+                       SRP_Result res = srp_user_start_authentication(
+                               (struct SRPUser *) m_auth_data, NULL, NULL, 0,
+                               (unsigned char **) &bytes_A, &len_A);
+                       FATAL_ERROR_IF(res != SRP_OK, "Creating local SRP user failed.");
+
+                       NetworkPacket resp_pkt(TOSERVER_SRP_BYTES_A, 0);
+                       resp_pkt << std::string(bytes_A, len_A) << based_on;
+                       Send(&resp_pkt);
+                       break;
+               }
+               case AUTH_MECHANISM_NONE:
+                       break; // not handled in this method
+       }
+}
+
+void Client::sendDeletedBlocks(std::vector<v3s16> &blocks)
+{
+       NetworkPacket pkt(TOSERVER_DELETEDBLOCKS, 1 + sizeof(v3s16) * blocks.size());
+
+       pkt << (u8) blocks.size();
+
+       for (const v3s16 &block : blocks) {
+               pkt << block;
+       }
+
+       Send(&pkt);
+}
+
+void Client::sendGotBlocks(v3s16 block)
+{
+       NetworkPacket pkt(TOSERVER_GOTBLOCKS, 1 + 6);
+       pkt << (u8) 1 << block;
+       Send(&pkt);
+}
+
+void Client::sendRemovedSounds(std::vector<s32> &soundList)
+{
+       size_t server_ids = soundList.size();
+       assert(server_ids <= 0xFFFF);
+
+       NetworkPacket pkt(TOSERVER_REMOVED_SOUNDS, 2 + server_ids * 4);
+
+       pkt << (u16) (server_ids & 0xFFFF);
+
+       for (int sound_id : soundList)
+               pkt << sound_id;
+
+       Send(&pkt);
+}
+
+void Client::sendNodemetaFields(v3s16 p, const std::string &formname,
+               const StringMap &fields)
+{
+       size_t fields_size = fields.size();
+
+       FATAL_ERROR_IF(fields_size > 0xFFFF, "Unsupported number of nodemeta fields");
+
+       NetworkPacket pkt(TOSERVER_NODEMETA_FIELDS, 0);
+
+       pkt << p << formname << (u16) (fields_size & 0xFFFF);
+
+       StringMap::const_iterator it;
+       for (it = fields.begin(); it != fields.end(); ++it) {
+               const std::string &name = it->first;
+               const std::string &value = it->second;
+               pkt << name;
+               pkt.putLongString(value);
+       }
+
+       Send(&pkt);
+}
+
+void Client::sendInventoryFields(const std::string &formname,
+               const StringMap &fields)
+{
+       size_t fields_size = fields.size();
+       FATAL_ERROR_IF(fields_size > 0xFFFF, "Unsupported number of inventory fields");
+
+       NetworkPacket pkt(TOSERVER_INVENTORY_FIELDS, 0);
+       pkt << formname << (u16) (fields_size & 0xFFFF);
+
+       StringMap::const_iterator it;
+       for (it = fields.begin(); it != fields.end(); ++it) {
+               const std::string &name  = it->first;
+               const std::string &value = it->second;
+               pkt << name;
+               pkt.putLongString(value);
+       }
+
+       Send(&pkt);
+}
+
+void Client::sendInventoryAction(InventoryAction *a)
+{
+       std::ostringstream os(std::ios_base::binary);
+
+       a->serialize(os);
+
+       // Make data buffer
+       std::string s = os.str();
+
+       NetworkPacket pkt(TOSERVER_INVENTORY_ACTION, s.size());
+       pkt.putRawString(s.c_str(),s.size());
+
+       Send(&pkt);
+}
+
+bool Client::canSendChatMessage() const
+{
+       u32 now = time(NULL);
+       float time_passed = now - m_last_chat_message_sent;
+
+       float virt_chat_message_allowance = m_chat_message_allowance + time_passed *
+                       (CLIENT_CHAT_MESSAGE_LIMIT_PER_10S / 8.0f);
+
+       if (virt_chat_message_allowance < 1.0f)
+               return false;
+
+       return true;
+}
+
+void Client::sendChatMessage(const std::wstring &message)
+{
+       const s16 max_queue_size = g_settings->getS16("max_out_chat_queue_size");
+       if (canSendChatMessage()) {
+               u32 now = time(NULL);
+               float time_passed = now - m_last_chat_message_sent;
+               m_last_chat_message_sent = time(NULL);
+
+               m_chat_message_allowance += time_passed * (CLIENT_CHAT_MESSAGE_LIMIT_PER_10S / 8.0f);
+               if (m_chat_message_allowance > CLIENT_CHAT_MESSAGE_LIMIT_PER_10S)
+                       m_chat_message_allowance = CLIENT_CHAT_MESSAGE_LIMIT_PER_10S;
+
+               m_chat_message_allowance -= 1.0f;
+
+               NetworkPacket pkt(TOSERVER_CHAT_MESSAGE, 2 + message.size() * sizeof(u16));
+
+               pkt << message;
+
+               Send(&pkt);
+       } else if (m_out_chat_queue.size() < (u16) max_queue_size || max_queue_size == -1) {
+               m_out_chat_queue.push(message);
+       } else {
+               infostream << "Could not queue chat message because maximum out chat queue size ("
+                               << max_queue_size << ") is reached." << std::endl;
+       }
+}
+
+void Client::clearOutChatQueue()
+{
+       m_out_chat_queue = std::queue<std::wstring>();
+}
+
+void Client::sendChangePassword(const std::string &oldpassword,
+        const std::string &newpassword)
+{
+       LocalPlayer *player = m_env.getLocalPlayer();
+       if (player == NULL)
+               return;
+
+       // get into sudo mode and then send new password to server
+       m_password = oldpassword;
+       m_new_password = newpassword;
+       startAuth(choseAuthMech(m_sudo_auth_methods));
+}
+
+
+void Client::sendDamage(u8 damage)
+{
+       NetworkPacket pkt(TOSERVER_DAMAGE, sizeof(u8));
+       pkt << damage;
+       Send(&pkt);
+}
+
+void Client::sendRespawn()
+{
+       NetworkPacket pkt(TOSERVER_RESPAWN, 0);
+       Send(&pkt);
+}
+
+void Client::sendReady()
+{
+       NetworkPacket pkt(TOSERVER_CLIENT_READY,
+                       1 + 1 + 1 + 1 + 2 + sizeof(char) * strlen(g_version_hash));
+
+       pkt << (u8) VERSION_MAJOR << (u8) VERSION_MINOR << (u8) VERSION_PATCH
+               << (u8) 0 << (u16) strlen(g_version_hash);
+
+       pkt.putRawString(g_version_hash, (u16) strlen(g_version_hash));
+       Send(&pkt);
+}
+
+void Client::sendPlayerPos()
+{
+       LocalPlayer *myplayer = m_env.getLocalPlayer();
+       if (!myplayer)
+               return;
+
+       ClientMap &map = m_env.getClientMap();
+
+       u8 camera_fov    = map.getCameraFov();
+       u8 wanted_range  = map.getControl().wanted_range;
+
+       // Save bandwidth by only updating position when something changed
+       if(myplayer->last_position        == myplayer->getPosition() &&
+                       myplayer->last_speed        == myplayer->getSpeed()    &&
+                       myplayer->last_pitch        == myplayer->getPitch()    &&
+                       myplayer->last_yaw          == myplayer->getYaw()      &&
+                       myplayer->last_keyPressed   == myplayer->keyPressed    &&
+                       myplayer->last_camera_fov   == camera_fov              &&
+                       myplayer->last_wanted_range == wanted_range)
+               return;
+
+       myplayer->last_position     = myplayer->getPosition();
+       myplayer->last_speed        = myplayer->getSpeed();
+       myplayer->last_pitch        = myplayer->getPitch();
+       myplayer->last_yaw          = myplayer->getYaw();
+       myplayer->last_keyPressed   = myplayer->keyPressed;
+       myplayer->last_camera_fov   = camera_fov;
+       myplayer->last_wanted_range = wanted_range;
+
+       NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1);
+
+       writePlayerPos(myplayer, &map, &pkt);
+
+       Send(&pkt);
+}
+
+void Client::sendPlayerItem(u16 item)
+{
+       LocalPlayer *myplayer = m_env.getLocalPlayer();
+       if (!myplayer)
+               return;
+
+       NetworkPacket pkt(TOSERVER_PLAYERITEM, 2);
+
+       pkt << item;
+
+       Send(&pkt);
+}
+
+void Client::removeNode(v3s16 p)
+{
+       std::map<v3s16, MapBlock*> modified_blocks;
+
+       try {
+               m_env.getMap().removeNodeAndUpdate(p, modified_blocks);
+       }
+       catch(InvalidPositionException &e) {
+       }
+
+       for (const auto &modified_block : modified_blocks) {
+               addUpdateMeshTaskWithEdge(modified_block.first, false, true);
+       }
+}
+
+/**
+ * Helper function for Client Side Modding
+ * CSM restrictions are applied there, this should not be used for core engine
+ * @param p
+ * @param is_valid_position
+ * @return
+ */
+MapNode Client::getNode(v3s16 p, bool *is_valid_position)
+{
+       if (checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOOKUP_NODES)) {
+               v3s16 ppos = floatToInt(m_env.getLocalPlayer()->getPosition(), BS);
+               if ((u32) ppos.getDistanceFrom(p) > m_csm_restriction_noderange) {
+                       *is_valid_position = false;
+                       return {};
+               }
+       }
+       return m_env.getMap().getNodeNoEx(p, is_valid_position);
+}
+
+void Client::addNode(v3s16 p, MapNode n, bool remove_metadata)
+{
+       //TimeTaker timer1("Client::addNode()");
+
+       std::map<v3s16, MapBlock*> modified_blocks;
+
+       try {
+               //TimeTaker timer3("Client::addNode(): addNodeAndUpdate");
+               m_env.getMap().addNodeAndUpdate(p, n, modified_blocks, remove_metadata);
+       }
+       catch(InvalidPositionException &e) {
+       }
+
+       for (const auto &modified_block : modified_blocks) {
+               addUpdateMeshTaskWithEdge(modified_block.first, false, true);
+       }
+}
+
+void Client::setPlayerControl(PlayerControl &control)
+{
+       LocalPlayer *player = m_env.getLocalPlayer();
+       assert(player);
+       player->control = control;
+}
+
+void Client::selectPlayerItem(u16 item)
+{
+       m_playeritem = item;
+       m_inventory_updated = true;
+       sendPlayerItem(item);
+}
+
+// Returns true if the inventory of the local player has been
+// updated from the server. If it is true, it is set to false.
+bool Client::getLocalInventoryUpdated()
+{
+       bool updated = m_inventory_updated;
+       m_inventory_updated = false;
+       return updated;
+}
+
+// Copies the inventory of the local player to parameter
+void Client::getLocalInventory(Inventory &dst)
+{
+       LocalPlayer *player = m_env.getLocalPlayer();
+       assert(player);
+       dst = player->inventory;
+}
+
+Inventory* Client::getInventory(const InventoryLocation &loc)
+{
+       switch(loc.type){
+       case InventoryLocation::UNDEFINED:
+       {}
+       break;
+       case InventoryLocation::CURRENT_PLAYER:
+       {
+               LocalPlayer *player = m_env.getLocalPlayer();
+               assert(player);
+               return &player->inventory;
+       }
+       break;
+       case InventoryLocation::PLAYER:
+       {
+               // Check if we are working with local player inventory
+               LocalPlayer *player = m_env.getLocalPlayer();
+               if (!player || strcmp(player->getName(), loc.name.c_str()) != 0)
+                       return NULL;
+               return &player->inventory;
+       }
+       break;
+       case InventoryLocation::NODEMETA:
+       {
+               NodeMetadata *meta = m_env.getMap().getNodeMetadata(loc.p);
+               if(!meta)
+                       return NULL;
+               return meta->getInventory();
+       }
+       break;
+       case InventoryLocation::DETACHED:
+       {
+               if (m_detached_inventories.count(loc.name) == 0)
+                       return NULL;
+               return m_detached_inventories[loc.name];
+       }
+       break;
+       default:
+               FATAL_ERROR("Invalid inventory location type.");
+               break;
+       }
+       return NULL;
+}
+
+void Client::inventoryAction(InventoryAction *a)
+{
+       /*
+               Send it to the server
+       */
+       sendInventoryAction(a);
+
+       /*
+               Predict some local inventory changes
+       */
+       a->clientApply(this, this);
+
+       // Remove it
+       delete a;
+}
+
+float Client::getAnimationTime()
+{
+       return m_animation_time;
+}
+
+int Client::getCrackLevel()
+{
+       return m_crack_level;
+}
+
+v3s16 Client::getCrackPos()
+{
+       return m_crack_pos;
+}
+
+void Client::setCrack(int level, v3s16 pos)
+{
+       int old_crack_level = m_crack_level;
+       v3s16 old_crack_pos = m_crack_pos;
+
+       m_crack_level = level;
+       m_crack_pos = pos;
+
+       if(old_crack_level >= 0 && (level < 0 || pos != old_crack_pos))
+       {
+               // remove old crack
+               addUpdateMeshTaskForNode(old_crack_pos, false, true);
+       }
+       if(level >= 0 && (old_crack_level < 0 || pos != old_crack_pos))
+       {
+               // add new crack
+               addUpdateMeshTaskForNode(pos, false, true);
+       }
+}
+
+u16 Client::getHP()
+{
+       LocalPlayer *player = m_env.getLocalPlayer();
+       assert(player);
+       return player->hp;
+}
+
+bool Client::getChatMessage(std::wstring &res)
+{
+       if (m_chat_queue.empty())
+               return false;
+
+       ChatMessage *chatMessage = m_chat_queue.front();
+       m_chat_queue.pop();
+
+       res = L"";
+
+       switch (chatMessage->type) {
+               case CHATMESSAGE_TYPE_RAW:
+               case CHATMESSAGE_TYPE_ANNOUNCE:
+               case CHATMESSAGE_TYPE_SYSTEM:
+                       res = chatMessage->message;
+                       break;
+               case CHATMESSAGE_TYPE_NORMAL: {
+                       if (!chatMessage->sender.empty())
+                               res = L"<" + chatMessage->sender + L"> " + chatMessage->message;
+                       else
+                               res = chatMessage->message;
+                       break;
+               }
+               default:
+                       break;
+       }
+
+       delete chatMessage;
+       return true;
+}
+
+void Client::typeChatMessage(const std::wstring &message)
+{
+       // Discard empty line
+       if (message.empty())
+               return;
+
+       // If message was consumed by script API, don't send it to server
+       if (m_modding_enabled && m_script->on_sending_message(wide_to_utf8(message)))
+               return;
+
+       // Send to others
+       sendChatMessage(message);
+
+       // Show locally
+       if (message[0] != L'/') {
+               // compatibility code
+               if (m_proto_ver < 29) {
+                       LocalPlayer *player = m_env.getLocalPlayer();
+                       assert(player);
+                       std::wstring name = narrow_to_wide(player->getName());
+                       pushToChatQueue(new ChatMessage(CHATMESSAGE_TYPE_NORMAL, message, name));
+               }
+       }
+}
+
+void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server, bool urgent)
+{
+       // Check if the block exists to begin with. In the case when a non-existing
+       // neighbor is automatically added, it may not. In that case we don't want
+       // to tell the mesh update thread about it.
+       MapBlock *b = m_env.getMap().getBlockNoCreateNoEx(p);
+       if (b == NULL)
+               return;
+
+       m_mesh_update_thread.updateBlock(&m_env.getMap(), p, ack_to_server, urgent);
+}
+
+void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server, bool urgent)
+{
+       try{
+               addUpdateMeshTask(blockpos, ack_to_server, urgent);
+       }
+       catch(InvalidPositionException &e){}
+
+       // Leading edge
+       for (int i=0;i<6;i++)
+       {
+               try{
+                       v3s16 p = blockpos + g_6dirs[i];
+                       addUpdateMeshTask(p, false, urgent);
+               }
+               catch(InvalidPositionException &e){}
+       }
+}
+
+void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool urgent)
+{
+       {
+               v3s16 p = nodepos;
+               infostream<<"Client::addUpdateMeshTaskForNode(): "
+                               <<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
+                               <<std::endl;
+       }
+
+       v3s16 blockpos          = getNodeBlockPos(nodepos);
+       v3s16 blockpos_relative = blockpos * MAP_BLOCKSIZE;
+
+       try{
+               addUpdateMeshTask(blockpos, ack_to_server, urgent);
+       }
+       catch(InvalidPositionException &e) {}
+
+       // 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){}
+       }
+}
+
+ClientEvent *Client::getClientEvent()
+{
+       FATAL_ERROR_IF(m_client_event_queue.empty(),
+                       "Cannot getClientEvent, queue is empty.");
+
+       ClientEvent *event = m_client_event_queue.front();
+       m_client_event_queue.pop();
+       return event;
+}
+
+bool Client::connectedToServer()
+{
+       return m_con->Connected();
+}
+
+const Address Client::getServerAddress()
+{
+       return m_con->GetPeerAddress(PEER_ID_SERVER);
+}
+
+float Client::mediaReceiveProgress()
+{
+       if (m_media_downloader)
+               return m_media_downloader->getProgress();
+
+       return 1.0; // downloader only exists when not yet done
+}
+
+typedef struct TextureUpdateArgs {
+       gui::IGUIEnvironment *guienv;
+       u64 last_time_ms;
+       u16 last_percent;
+       const wchar_t* text_base;
+       ITextureSource *tsrc;
+} TextureUpdateArgs;
+
+void texture_update_progress(void *args, u32 progress, u32 max_progress)
+{
+               TextureUpdateArgs* targs = (TextureUpdateArgs*) args;
+               u16 cur_percent = ceil(progress / (double) max_progress * 100.);
+
+               // update the loading menu -- if neccessary
+               bool do_draw = false;
+               u64 time_ms = targs->last_time_ms;
+               if (cur_percent != targs->last_percent) {
+                       targs->last_percent = cur_percent;
+                       time_ms = porting::getTimeMs();
+                       // only draw when the user will notice something:
+                       do_draw = (time_ms - targs->last_time_ms > 100);
+               }
+
+               if (do_draw) {
+                       targs->last_time_ms = time_ms;
+                       std::basic_stringstream<wchar_t> strm;
+                       strm << targs->text_base << " " << targs->last_percent << "%...";
+                       RenderingEngine::draw_load_screen(strm.str(), targs->guienv, targs->tsrc, 0,
+                               72 + (u16) ((18. / 100.) * (double) targs->last_percent), true);
+               }
+}
+
+void Client::afterContentReceived()
+{
+       infostream<<"Client::afterContentReceived() started"<<std::endl;
+       assert(m_itemdef_received); // pre-condition
+       assert(m_nodedef_received); // pre-condition
+       assert(mediaReceived()); // pre-condition
+
+       const wchar_t* text = wgettext("Loading textures...");
+
+       // Clear cached pre-scaled 2D GUI images, as this cache
+       // might have images with the same name but different
+       // content from previous sessions.
+       guiScalingCacheClear();
+
+       // Rebuild inherited images and recreate textures
+       infostream<<"- Rebuilding images and textures"<<std::endl;
+       RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 70);
+       m_tsrc->rebuildImagesAndTextures();
+       delete[] text;
+
+       // Rebuild shaders
+       infostream<<"- Rebuilding shaders"<<std::endl;
+       text = wgettext("Rebuilding shaders...");
+       RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 71);
+       m_shsrc->rebuildShaders();
+       delete[] text;
+
+       // Update node aliases
+       infostream<<"- Updating node aliases"<<std::endl;
+       text = wgettext("Initializing nodes...");
+       RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 72);
+       m_nodedef->updateAliases(m_itemdef);
+       for (const auto &path : getTextureDirs())
+               m_nodedef->applyTextureOverrides(path + DIR_DELIM + "override.txt");
+       m_nodedef->setNodeRegistrationStatus(true);
+       m_nodedef->runNodeResolveCallbacks();
+       delete[] text;
+
+       // Update node textures and assign shaders to each tile
+       infostream<<"- Updating node textures"<<std::endl;
+       TextureUpdateArgs tu_args;
+       tu_args.guienv = guienv;
+       tu_args.last_time_ms = porting::getTimeMs();
+       tu_args.last_percent = 0;
+       tu_args.text_base =  wgettext("Initializing nodes");
+       tu_args.tsrc = m_tsrc;
+       m_nodedef->updateTextures(this, texture_update_progress, &tu_args);
+       delete[] tu_args.text_base;
+
+       // Start mesh update thread after setting up content definitions
+       infostream<<"- Starting mesh update thread"<<std::endl;
+       m_mesh_update_thread.start();
+
+       m_state = LC_Ready;
+       sendReady();
+
+       if (g_settings->getBool("enable_client_modding")) {
+               m_script->on_client_ready(m_env.getLocalPlayer());
+       }
+
+       text = wgettext("Done!");
+       RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 100);
+       infostream<<"Client::afterContentReceived() done"<<std::endl;
+       delete[] text;
+}
+
+// returns the Round Trip Time
+// if the RTT did not become updated within 2 seconds, e.g. before timing out,
+// it returns the expired time instead
+float Client::getRTT()
+{
+       float avg_rtt = m_con->getPeerStat(PEER_ID_SERVER, con::AVG_RTT);
+       float time_from_last_rtt =
+               m_con->getPeerStat(PEER_ID_SERVER, con::TIMEOUT_COUNTER);
+       if (avg_rtt + 2.0f > time_from_last_rtt)
+               return avg_rtt;
+       return time_from_last_rtt;
+}
+
+float Client::getCurRate()
+{
+       return (m_con->getLocalStat(con::CUR_INC_RATE) +
+                       m_con->getLocalStat(con::CUR_DL_RATE));
+}
+
+void Client::makeScreenshot()
+{
+       irr::video::IVideoDriver *driver = RenderingEngine::get_video_driver();
+       irr::video::IImage* const raw_image = driver->createScreenShot();
+
+       if (!raw_image)
+               return;
+
+       time_t t = time(NULL);
+       struct tm *tm = localtime(&t);
+
+       char timetstamp_c[64];
+       strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", tm);
+
+       std::string filename_base = g_settings->get("screenshot_path")
+                       + DIR_DELIM
+                       + std::string("screenshot_")
+                       + std::string(timetstamp_c);
+       std::string filename_ext = "." + g_settings->get("screenshot_format");
+       std::string filename;
+
+       u32 quality = (u32)g_settings->getS32("screenshot_quality");
+       quality = MYMIN(MYMAX(quality, 0), 100) / 100.0 * 255;
+
+       // Try to find a unique filename
+       unsigned serial = 0;
+
+       while (serial < SCREENSHOT_MAX_SERIAL_TRIES) {
+               filename = filename_base + (serial > 0 ? ("_" + itos(serial)) : "") + filename_ext;
+               std::ifstream tmp(filename.c_str());
+               if (!tmp.good())
+                       break;  // File did not apparently exist, we'll go with it
+               serial++;
+       }
+
+       if (serial == SCREENSHOT_MAX_SERIAL_TRIES) {
+               infostream << "Could not find suitable filename for screenshot" << std::endl;
+       } else {
+               irr::video::IImage* const image =
+                               driver->createImage(video::ECF_R8G8B8, raw_image->getDimension());
+
+               if (image) {
+                       raw_image->copyTo(image);
+
+                       std::ostringstream sstr;
+                       if (driver->writeImageToFile(image, filename.c_str(), quality)) {
+                               sstr << "Saved screenshot to '" << filename << "'";
+                       } else {
+                               sstr << "Failed to save screenshot '" << filename << "'";
+                       }
+                       pushToChatQueue(new ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
+                                       narrow_to_wide(sstr.str())));
+                       infostream << sstr.str() << std::endl;
+                       image->drop();
+               }
+       }
+
+       raw_image->drop();
+}
+
+bool Client::shouldShowMinimap() const
+{
+       return !m_minimap_disabled_by_server;
+}
+
+void Client::pushToEventQueue(ClientEvent *event)
+{
+       m_client_event_queue.push(event);
+}
+
+void Client::showMinimap(const bool show)
+{
+       m_game_ui->showMinimap(show);
+}
+
+// IGameDef interface
+// Under envlock
+IItemDefManager* Client::getItemDefManager()
+{
+       return m_itemdef;
+}
+const NodeDefManager* Client::getNodeDefManager()
+{
+       return m_nodedef;
+}
+ICraftDefManager* Client::getCraftDefManager()
+{
+       return NULL;
+       //return m_craftdef;
+}
+ITextureSource* Client::getTextureSource()
+{
+       return m_tsrc;
+}
+IShaderSource* Client::getShaderSource()
+{
+       return m_shsrc;
+}
+
+u16 Client::allocateUnknownNodeId(const std::string &name)
+{
+       errorstream << "Client::allocateUnknownNodeId(): "
+                       << "Client cannot allocate node IDs" << std::endl;
+       FATAL_ERROR("Client allocated unknown node");
+
+       return CONTENT_IGNORE;
+}
+ISoundManager* Client::getSoundManager()
+{
+       return m_sound;
+}
+MtEventManager* Client::getEventManager()
+{
+       return m_event;
+}
+
+ParticleManager* Client::getParticleManager()
+{
+       return &m_particle_manager;
+}
+
+scene::IAnimatedMesh* Client::getMesh(const std::string &filename, bool cache)
+{
+       StringMap::const_iterator it = m_mesh_data.find(filename);
+       if (it == m_mesh_data.end()) {
+               errorstream << "Client::getMesh(): Mesh not found: \"" << filename
+                       << "\"" << std::endl;
+               return NULL;
+       }
+       const std::string &data    = it->second;
+
+       // Create the mesh, remove it from cache and return it
+       // This allows unique vertex colors and other properties for each instance
+       Buffer<char> data_rw(data.c_str(), data.size()); // Const-incorrect Irrlicht
+       io::IReadFile *rfile   = RenderingEngine::get_filesystem()->createMemoryReadFile(
+                       *data_rw, data_rw.getSize(), filename.c_str());
+       FATAL_ERROR_IF(!rfile, "Could not create/open RAM file");
+
+       scene::IAnimatedMesh *mesh = RenderingEngine::get_scene_manager()->getMesh(rfile);
+       rfile->drop();
+       mesh->grab();
+       if (!cache)
+               RenderingEngine::get_mesh_cache()->removeMesh(mesh);
+       return mesh;
+}
+
+const std::string* Client::getModFile(const std::string &filename)
+{
+       StringMap::const_iterator it = m_mod_files.find(filename);
+       if (it == m_mod_files.end()) {
+               errorstream << "Client::getModFile(): File not found: \"" << filename
+                       << "\"" << std::endl;
+               return NULL;
+       }
+       return &it->second;
+}
+
+bool Client::registerModStorage(ModMetadata *storage)
+{
+       if (m_mod_storages.find(storage->getModName()) != m_mod_storages.end()) {
+               errorstream << "Unable to register same mod storage twice. Storage name: "
+                               << storage->getModName() << std::endl;
+               return false;
+       }
+
+       m_mod_storages[storage->getModName()] = storage;
+       return true;
+}
+
+void Client::unregisterModStorage(const std::string &name)
+{
+       std::unordered_map<std::string, ModMetadata *>::const_iterator it =
+               m_mod_storages.find(name);
+       if (it != m_mod_storages.end()) {
+               // Save unconditionaly on unregistration
+               it->second->save(getModStoragePath());
+               m_mod_storages.erase(name);
+       }
+}
+
+std::string Client::getModStoragePath() const
+{
+       return porting::path_user + DIR_DELIM + "client" + DIR_DELIM + "mod_storage";
+}
+
+/*
+ * Mod channels
+ */
+
+bool Client::joinModChannel(const std::string &channel)
+{
+       if (m_modchannel_mgr->channelRegistered(channel))
+               return false;
+
+       NetworkPacket pkt(TOSERVER_MODCHANNEL_JOIN, 2 + channel.size());
+       pkt << channel;
+       Send(&pkt);
+
+       m_modchannel_mgr->joinChannel(channel, 0);
+       return true;
+}
+
+bool Client::leaveModChannel(const std::string &channel)
+{
+       if (!m_modchannel_mgr->channelRegistered(channel))
+               return false;
+
+       NetworkPacket pkt(TOSERVER_MODCHANNEL_LEAVE, 2 + channel.size());
+       pkt << channel;
+       Send(&pkt);
+
+       m_modchannel_mgr->leaveChannel(channel, 0);
+       return true;
+}
+
+bool Client::sendModChannelMessage(const std::string &channel, const std::string &message)
+{
+       if (!m_modchannel_mgr->canWriteOnChannel(channel))
+               return false;
+
+       if (message.size() > STRING_MAX_LEN) {
+               warningstream << "ModChannel message too long, dropping before sending "
+                               << " (" << message.size() << " > " << STRING_MAX_LEN << ", channel: "
+                               << channel << ")" << std::endl;
+               return false;
+       }
+
+       // @TODO: do some client rate limiting
+       NetworkPacket pkt(TOSERVER_MODCHANNEL_MSG, 2 + channel.size() + 2 + message.size());
+       pkt << channel << message;
+       Send(&pkt);
+       return true;
+}
+
+ModChannel* Client::getModChannel(const std::string &channel)
+{
+       return m_modchannel_mgr->getModChannel(channel);
+}
diff --git a/src/client/client.h b/src/client/client.h
new file mode 100644 (file)
index 0000000..68a832d
--- /dev/null
@@ -0,0 +1,608 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "clientenvironment.h"
+#include "irrlichttypes_extrabloated.h"
+#include <ostream>
+#include <map>
+#include <set>
+#include <vector>
+#include <unordered_set>
+#include "clientobject.h"
+#include "gamedef.h"
+#include "inventorymanager.h"
+#include "localplayer.h"
+#include "client/hud.h"
+#include "particles.h"
+#include "mapnode.h"
+#include "tileanimation.h"
+#include "mesh_generator_thread.h"
+#include "network/address.h"
+#include "network/peerhandler.h"
+#include <fstream>
+
+#define CLIENT_CHAT_MESSAGE_LIMIT_PER_10S 10.0f
+
+struct ClientEvent;
+struct MeshMakeData;
+struct ChatMessage;
+class MapBlockMesh;
+class IWritableTextureSource;
+class IWritableShaderSource;
+class IWritableItemDefManager;
+class ISoundManager;
+class NodeDefManager;
+//class IWritableCraftDefManager;
+class ClientMediaDownloader;
+struct MapDrawControl;
+class ModChannelMgr;
+class MtEventManager;
+struct PointedThing;
+class MapDatabase;
+class Minimap;
+struct MinimapMapblock;
+class Camera;
+class NetworkPacket;
+namespace con {
+class Connection;
+}
+
+enum LocalClientState {
+       LC_Created,
+       LC_Init,
+       LC_Ready
+};
+
+/*
+       Packet counter
+*/
+
+class PacketCounter
+{
+public:
+       PacketCounter() = default;
+
+       void add(u16 command)
+       {
+               std::map<u16, u16>::iterator n = m_packets.find(command);
+               if(n == m_packets.end())
+               {
+                       m_packets[command] = 1;
+               }
+               else
+               {
+                       n->second++;
+               }
+       }
+
+       void clear()
+       {
+               for (auto &m_packet : m_packets) {
+                       m_packet.second = 0;
+               }
+       }
+
+       void print(std::ostream &o)
+       {
+               for (const auto &m_packet : m_packets) {
+                       o << "cmd "<< m_packet.first <<" count "<< m_packet.second << std::endl;
+               }
+       }
+
+private:
+       // command, count
+       std::map<u16, u16> m_packets;
+};
+
+class ClientScripting;
+class GameUI;
+
+class Client : public con::PeerHandler, public InventoryManager, public IGameDef
+{
+public:
+       /*
+               NOTE: Nothing is thread-safe here.
+       */
+
+       Client(
+                       const char *playername,
+                       const std::string &password,
+                       const std::string &address_name,
+                       MapDrawControl &control,
+                       IWritableTextureSource *tsrc,
+                       IWritableShaderSource *shsrc,
+                       IWritableItemDefManager *itemdef,
+                       NodeDefManager *nodedef,
+                       ISoundManager *sound,
+                       MtEventManager *event,
+                       bool ipv6,
+                       GameUI *game_ui
+       );
+
+       ~Client();
+       DISABLE_CLASS_COPY(Client);
+
+       // Load local mods into memory
+       void scanModSubfolder(const std::string &mod_name, const std::string &mod_path,
+                               std::string mod_subpath);
+       inline void scanModIntoMemory(const std::string &mod_name, const std::string &mod_path)
+       {
+               scanModSubfolder(mod_name, mod_path, "");
+       }
+
+       /*
+        request all threads managed by client to be stopped
+        */
+       void Stop();
+
+
+       bool isShutdown();
+
+       /*
+               The name of the local player should already be set when
+               calling this, as it is sent in the initialization.
+       */
+       void connect(Address address, bool is_local_server);
+
+       /*
+               Stuff that references the environment is valid only as
+               long as this is not called. (eg. Players)
+               If this throws a PeerNotFoundException, the connection has
+               timed out.
+       */
+       void step(float dtime);
+
+       /*
+        * Command Handlers
+        */
+
+       void handleCommand(NetworkPacket* pkt);
+
+       void handleCommand_Null(NetworkPacket* pkt) {};
+       void handleCommand_Deprecated(NetworkPacket* pkt);
+       void handleCommand_Hello(NetworkPacket* pkt);
+       void handleCommand_AuthAccept(NetworkPacket* pkt);
+       void handleCommand_AcceptSudoMode(NetworkPacket* pkt);
+       void handleCommand_DenySudoMode(NetworkPacket* pkt);
+       void handleCommand_AccessDenied(NetworkPacket* pkt);
+       void handleCommand_RemoveNode(NetworkPacket* pkt);
+       void handleCommand_AddNode(NetworkPacket* pkt);
+       void handleCommand_BlockData(NetworkPacket* pkt);
+       void handleCommand_Inventory(NetworkPacket* pkt);
+       void handleCommand_TimeOfDay(NetworkPacket* pkt);
+       void handleCommand_ChatMessage(NetworkPacket *pkt);
+       void handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt);
+       void handleCommand_ActiveObjectMessages(NetworkPacket* pkt);
+       void handleCommand_Movement(NetworkPacket* pkt);
+       void handleCommand_HP(NetworkPacket* pkt);
+       void handleCommand_Breath(NetworkPacket* pkt);
+       void handleCommand_MovePlayer(NetworkPacket* pkt);
+       void handleCommand_DeathScreen(NetworkPacket* pkt);
+       void handleCommand_AnnounceMedia(NetworkPacket* pkt);
+       void handleCommand_Media(NetworkPacket* pkt);
+       void handleCommand_NodeDef(NetworkPacket* pkt);
+       void handleCommand_ItemDef(NetworkPacket* pkt);
+       void handleCommand_PlaySound(NetworkPacket* pkt);
+       void handleCommand_StopSound(NetworkPacket* pkt);
+       void handleCommand_FadeSound(NetworkPacket *pkt);
+       void handleCommand_Privileges(NetworkPacket* pkt);
+       void handleCommand_InventoryFormSpec(NetworkPacket* pkt);
+       void handleCommand_DetachedInventory(NetworkPacket* pkt);
+       void handleCommand_ShowFormSpec(NetworkPacket* pkt);
+       void handleCommand_SpawnParticle(NetworkPacket* pkt);
+       void handleCommand_AddParticleSpawner(NetworkPacket* pkt);
+       void handleCommand_DeleteParticleSpawner(NetworkPacket* pkt);
+       void handleCommand_HudAdd(NetworkPacket* pkt);
+       void handleCommand_HudRemove(NetworkPacket* pkt);
+       void handleCommand_HudChange(NetworkPacket* pkt);
+       void handleCommand_HudSetFlags(NetworkPacket* pkt);
+       void handleCommand_HudSetParam(NetworkPacket* pkt);
+       void handleCommand_HudSetSky(NetworkPacket* pkt);
+       void handleCommand_CloudParams(NetworkPacket* pkt);
+       void handleCommand_OverrideDayNightRatio(NetworkPacket* pkt);
+       void handleCommand_LocalPlayerAnimations(NetworkPacket* pkt);
+       void handleCommand_EyeOffset(NetworkPacket* pkt);
+       void handleCommand_UpdatePlayerList(NetworkPacket* pkt);
+       void handleCommand_ModChannelMsg(NetworkPacket *pkt);
+       void handleCommand_ModChannelSignal(NetworkPacket *pkt);
+       void handleCommand_SrpBytesSandB(NetworkPacket *pkt);
+       void handleCommand_FormspecPrepend(NetworkPacket *pkt);
+       void handleCommand_CSMRestrictionFlags(NetworkPacket *pkt);
+
+       void ProcessData(NetworkPacket *pkt);
+
+       void Send(NetworkPacket* pkt);
+
+       void interact(u8 action, const PointedThing& pointed);
+
+       void sendNodemetaFields(v3s16 p, const std::string &formname,
+               const StringMap &fields);
+       void sendInventoryFields(const std::string &formname,
+               const StringMap &fields);
+       void sendInventoryAction(InventoryAction *a);
+       void sendChatMessage(const std::wstring &message);
+       void clearOutChatQueue();
+       void sendChangePassword(const std::string &oldpassword,
+               const std::string &newpassword);
+       void sendDamage(u8 damage);
+       void sendRespawn();
+       void sendReady();
+
+       ClientEnvironment& getEnv() { return m_env; }
+       ITextureSource *tsrc() { return getTextureSource(); }
+       ISoundManager *sound() { return getSoundManager(); }
+       static const std::string &getBuiltinLuaPath();
+       static const std::string &getClientModsLuaPath();
+
+       const std::vector<ModSpec> &getMods() const override;
+       const ModSpec* getModSpec(const std::string &modname) const override;
+
+       // Causes urgent mesh updates (unlike Map::add/removeNodeWithEvent)
+       void removeNode(v3s16 p);
+
+       /**
+        * Helper function for Client Side Modding
+        * CSM restrictions are applied there, this should not be used for core engine
+        * @param p
+        * @param is_valid_position
+        * @return
+        */
+       MapNode getNode(v3s16 p, bool *is_valid_position);
+       void addNode(v3s16 p, MapNode n, bool remove_metadata = true);
+
+       void setPlayerControl(PlayerControl &control);
+
+       void selectPlayerItem(u16 item);
+       u16 getPlayerItem() const
+       { return m_playeritem; }
+
+       // Returns true if the inventory of the local player has been
+       // updated from the server. If it is true, it is set to false.
+       bool getLocalInventoryUpdated();
+       // Copies the inventory of the local player to parameter
+       void getLocalInventory(Inventory &dst);
+
+       /* InventoryManager interface */
+       Inventory* getInventory(const InventoryLocation &loc) override;
+       void inventoryAction(InventoryAction *a) override;
+
+       const std::list<std::string> &getConnectedPlayerNames()
+       {
+               return m_env.getPlayerNames();
+       }
+
+       float getAnimationTime();
+
+       int getCrackLevel();
+       v3s16 getCrackPos();
+       void setCrack(int level, v3s16 pos);
+
+       u16 getHP();
+
+       bool checkPrivilege(const std::string &priv) const
+       { return (m_privileges.count(priv) != 0); }
+
+       const std::unordered_set<std::string> &getPrivilegeList() const
+       { return m_privileges; }
+
+       bool getChatMessage(std::wstring &message);
+       void typeChatMessage(const std::wstring& message);
+
+       u64 getMapSeed(){ return m_map_seed; }
+
+       void addUpdateMeshTask(v3s16 blockpos, bool ack_to_server=false, bool urgent=false);
+       // Including blocks at appropriate edges
+       void addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server=false, bool urgent=false);
+       void addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server=false, bool urgent=false);
+
+       void updateCameraOffset(v3s16 camera_offset)
+       { m_mesh_update_thread.m_camera_offset = camera_offset; }
+
+       bool hasClientEvents() const { return !m_client_event_queue.empty(); }
+       // Get event from queue. If queue is empty, it triggers an assertion failure.
+       ClientEvent * getClientEvent();
+
+       bool accessDenied() const { return m_access_denied; }
+
+       bool reconnectRequested() const { return m_access_denied_reconnect; }
+
+       void setFatalError(const std::string &reason)
+       {
+               m_access_denied = true;
+               m_access_denied_reason = reason;
+       }
+
+       // Renaming accessDeniedReason to better name could be good as it's used to
+       // disconnect client when CSM failed.
+       const std::string &accessDeniedReason() const { return m_access_denied_reason; }
+
+       bool itemdefReceived()
+       { return m_itemdef_received; }
+       bool nodedefReceived()
+       { return m_nodedef_received; }
+       bool mediaReceived()
+       { return !m_media_downloader; }
+
+       u8 getProtoVersion()
+       { return m_proto_ver; }
+
+       bool connectedToServer();
+       void confirmRegistration();
+       bool m_is_registration_confirmation_state = false;
+       bool m_simple_singleplayer_mode;
+
+       float mediaReceiveProgress();
+
+       void afterContentReceived();
+
+       float getRTT();
+       float getCurRate();
+
+       Minimap* getMinimap() { return m_minimap; }
+       void setCamera(Camera* camera) { m_camera = camera; }
+
+       Camera* getCamera () { return m_camera; }
+
+       bool shouldShowMinimap() const;
+
+       // IGameDef interface
+       IItemDefManager* getItemDefManager() override;
+       const NodeDefManager* getNodeDefManager() override;
+       ICraftDefManager* getCraftDefManager() override;
+       ITextureSource* getTextureSource();
+       virtual IShaderSource* getShaderSource();
+       u16 allocateUnknownNodeId(const std::string &name) override;
+       virtual ISoundManager* getSoundManager();
+       MtEventManager* getEventManager();
+       virtual ParticleManager* getParticleManager();
+       bool checkLocalPrivilege(const std::string &priv)
+       { return checkPrivilege(priv); }
+       virtual scene::IAnimatedMesh* getMesh(const std::string &filename, bool cache = false);
+       const std::string* getModFile(const std::string &filename);
+
+       std::string getModStoragePath() const override;
+       bool registerModStorage(ModMetadata *meta) override;
+       void unregisterModStorage(const std::string &name) override;
+
+       // The following set of functions is used by ClientMediaDownloader
+       // Insert a media file appropriately into the appropriate manager
+       bool loadMedia(const std::string &data, const std::string &filename);
+       // Send a request for conventional media transfer
+       void request_media(const std::vector<std::string> &file_requests);
+
+       LocalClientState getState() { return m_state; }
+
+       void makeScreenshot();
+
+       inline void pushToChatQueue(ChatMessage *cec)
+       {
+               m_chat_queue.push(cec);
+       }
+
+       ClientScripting *getScript() { return m_script; }
+       const bool moddingEnabled() const { return m_modding_enabled; }
+       const bool modsLoaded() const { return m_mods_loaded; }
+
+       void pushToEventQueue(ClientEvent *event);
+
+       void showMinimap(bool show = true);
+
+       const Address getServerAddress();
+
+       const std::string &getAddressName() const
+       {
+               return m_address_name;
+       }
+
+       inline bool checkCSMRestrictionFlag(CSMRestrictionFlags flag) const
+       {
+               return m_csm_restriction_flags & flag;
+       }
+
+       u32 getCSMNodeRangeLimit() const
+       {
+               return m_csm_restriction_noderange;
+       }
+
+       inline std::unordered_map<u32, u32> &getHUDTranslationMap()
+       {
+               return m_hud_server_to_client;
+       }
+
+       bool joinModChannel(const std::string &channel) override;
+       bool leaveModChannel(const std::string &channel) override;
+       bool sendModChannelMessage(const std::string &channel,
+                       const std::string &message) override;
+       ModChannel *getModChannel(const std::string &channel) override;
+
+       const std::string &getFormspecPrepend() const
+       {
+               return m_env.getLocalPlayer()->formspec_prepend;
+       }
+private:
+       void loadMods();
+       bool checkBuiltinIntegrity();
+
+       // Virtual methods from con::PeerHandler
+       void peerAdded(con::Peer *peer) override;
+       void deletingPeer(con::Peer *peer, bool timeout) override;
+
+       void initLocalMapSaving(const Address &address,
+                       const std::string &hostname,
+                       bool is_local_server);
+
+       void ReceiveAll();
+       void Receive();
+
+       void sendPlayerPos();
+       // Send the item number 'item' as player item to the server
+       void sendPlayerItem(u16 item);
+
+       void deleteAuthData();
+       // helper method shared with clientpackethandler
+       static AuthMechanism choseAuthMech(const u32 mechs);
+
+       void sendInit(const std::string &playerName);
+       void promptConfirmRegistration(AuthMechanism chosen_auth_mechanism);
+       void startAuth(AuthMechanism chosen_auth_mechanism);
+       void sendDeletedBlocks(std::vector<v3s16> &blocks);
+       void sendGotBlocks(v3s16 block);
+       void sendRemovedSounds(std::vector<s32> &soundList);
+
+       // Helper function
+       inline std::string getPlayerName()
+       { return m_env.getLocalPlayer()->getName(); }
+
+       bool canSendChatMessage() const;
+
+       float m_packetcounter_timer = 0.0f;
+       float m_connection_reinit_timer = 0.1f;
+       float m_avg_rtt_timer = 0.0f;
+       float m_playerpos_send_timer = 0.0f;
+       IntervalLimiter m_map_timer_and_unload_interval;
+
+       IWritableTextureSource *m_tsrc;
+       IWritableShaderSource *m_shsrc;
+       IWritableItemDefManager *m_itemdef;
+       NodeDefManager *m_nodedef;
+       ISoundManager *m_sound;
+       MtEventManager *m_event;
+
+
+       MeshUpdateThread m_mesh_update_thread;
+       ClientEnvironment m_env;
+       ParticleManager m_particle_manager;
+       std::unique_ptr<con::Connection> m_con;
+       std::string m_address_name;
+       Camera *m_camera = nullptr;
+       Minimap *m_minimap = nullptr;
+       bool m_minimap_disabled_by_server = false;
+       // Server serialization version
+       u8 m_server_ser_ver;
+
+       // Used version of the protocol with server
+       // Values smaller than 25 only mean they are smaller than 25,
+       // and aren't accurate. We simply just don't know, because
+       // the server didn't send the version back then.
+       // If 0, server init hasn't been received yet.
+       u8 m_proto_ver = 0;
+
+       u16 m_playeritem = 0;
+       bool m_inventory_updated = false;
+       Inventory *m_inventory_from_server = nullptr;
+       float m_inventory_from_server_age = 0.0f;
+       PacketCounter m_packetcounter;
+       // Block mesh animation parameters
+       float m_animation_time = 0.0f;
+       int m_crack_level = -1;
+       v3s16 m_crack_pos;
+       // 0 <= m_daynight_i < DAYNIGHT_CACHE_COUNT
+       //s32 m_daynight_i;
+       //u32 m_daynight_ratio;
+       std::queue<std::wstring> m_out_chat_queue;
+       u32 m_last_chat_message_sent;
+       float m_chat_message_allowance = 5.0f;
+       std::queue<ChatMessage *> m_chat_queue;
+
+       // The authentication methods we can use to enter sudo mode (=change password)
+       u32 m_sudo_auth_methods;
+
+       // The seed returned by the server in TOCLIENT_INIT is stored here
+       u64 m_map_seed = 0;
+
+       // Auth data
+       std::string m_playername;
+       std::string m_password;
+       // If set, this will be sent (and cleared) upon a TOCLIENT_ACCEPT_SUDO_MODE
+       std::string m_new_password;
+       // Usable by auth mechanisms.
+       AuthMechanism m_chosen_auth_mech;
+       void *m_auth_data = nullptr;
+
+
+       bool m_access_denied = false;
+       bool m_access_denied_reconnect = false;
+       std::string m_access_denied_reason = "";
+       std::queue<ClientEvent *> m_client_event_queue;
+       bool m_itemdef_received = false;
+       bool m_nodedef_received = false;
+       bool m_mods_loaded = false;
+       ClientMediaDownloader *m_media_downloader;
+
+       // time_of_day speed approximation for old protocol
+       bool m_time_of_day_set = false;
+       float m_last_time_of_day_f = -1.0f;
+       float m_time_of_day_update_timer = 0.0f;
+
+       // An interval for generally sending object positions and stuff
+       float m_recommended_send_interval = 0.1f;
+
+       // Sounds
+       float m_removed_sounds_check_timer = 0.0f;
+       // Mapping from server sound ids to our sound ids
+       std::unordered_map<s32, int> m_sounds_server_to_client;
+       // And the other way!
+       std::unordered_map<int, s32> m_sounds_client_to_server;
+       // And relations to objects
+       std::unordered_map<int, u16> m_sounds_to_objects;
+
+       // CSM/client IDs to SSM/server IDs Mapping
+       // Map server particle spawner IDs to client IDs
+       std::unordered_map<u32, u32> m_particles_server_to_client;
+       // Map server hud ids to client hud ids
+       std::unordered_map<u32, u32> m_hud_server_to_client;
+
+       // Privileges
+       std::unordered_set<std::string> m_privileges;
+
+       // Detached inventories
+       // key = name
+       std::unordered_map<std::string, Inventory*> m_detached_inventories;
+
+       // Storage for mesh data for creating multiple instances of the same mesh
+       StringMap m_mesh_data;
+
+       StringMap m_mod_files;
+
+       // own state
+       LocalClientState m_state;
+
+       GameUI *m_game_ui;
+
+       // Used for saving server map to disk client-side
+       MapDatabase *m_localdb = nullptr;
+       IntervalLimiter m_localdb_save_interval;
+       u16 m_cache_save_interval;
+
+       ClientScripting *m_script = nullptr;
+       bool m_modding_enabled;
+       std::unordered_map<std::string, ModMetadata *> m_mod_storages;
+       float m_mod_storage_save_timer = 10.0f;
+       std::vector<ModSpec> m_mods;
+
+       bool m_shutdown = false;
+
+       // CSM restrictions byteflag
+       u64 m_csm_restriction_flags = CSMRestrictionFlags::CSM_RF_NONE;
+       u32 m_csm_restriction_noderange = 8;
+
+       std::unique_ptr<ModChannelMgr> m_modchannel_mgr;
+};
diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp
new file mode 100644 (file)
index 0000000..e2f24aa
--- /dev/null
@@ -0,0 +1,540 @@
+/*
+Minetest
+Copyright (C) 2010-2017 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 "util/serialize.h"
+#include "util/pointedthing.h"
+#include "client.h"
+#include "clientenvironment.h"
+#include "clientsimpleobject.h"
+#include "clientmap.h"
+#include "scripting_client.h"
+#include "mapblock_mesh.h"
+#include "event.h"
+#include "collision.h"
+#include "nodedef.h"
+#include "profiler.h"
+#include "raycast.h"
+#include "voxelalgorithms.h"
+#include "settings.h"
+#include "content_cao.h"
+#include <algorithm>
+#include "client/renderingengine.h"
+
+/*
+       ClientEnvironment
+*/
+
+ClientEnvironment::ClientEnvironment(ClientMap *map,
+       ITextureSource *texturesource, Client *client):
+       Environment(client),
+       m_map(map),
+       m_texturesource(texturesource),
+       m_client(client)
+{
+       char zero = 0;
+       memset(attachement_parent_ids, zero, sizeof(attachement_parent_ids));
+}
+
+ClientEnvironment::~ClientEnvironment()
+{
+       // delete active objects
+       for (auto &active_object : m_active_objects) {
+               delete active_object.second;
+       }
+
+       for (auto &simple_object : m_simple_objects) {
+               delete simple_object;
+       }
+
+       // Drop/delete map
+       m_map->drop();
+
+       delete m_local_player;
+}
+
+Map & ClientEnvironment::getMap()
+{
+       return *m_map;
+}
+
+ClientMap & ClientEnvironment::getClientMap()
+{
+       return *m_map;
+}
+
+void ClientEnvironment::setLocalPlayer(LocalPlayer *player)
+{
+       /*
+               It is a failure if already is a local player
+       */
+       FATAL_ERROR_IF(m_local_player != NULL,
+               "Local player already allocated");
+
+       m_local_player = player;
+}
+
+void ClientEnvironment::step(float dtime)
+{
+       /* Step time of day */
+       stepTimeOfDay(dtime);
+
+       // Get some settings
+       bool fly_allowed = m_client->checkLocalPrivilege("fly");
+       bool free_move = fly_allowed && g_settings->getBool("free_move");
+
+       // Get local player
+       LocalPlayer *lplayer = getLocalPlayer();
+       assert(lplayer);
+       // collision info queue
+       std::vector<CollisionInfo> player_collisions;
+
+       /*
+               Get the speed the player is going
+       */
+       bool is_climbing = lplayer->is_climbing;
+
+       f32 player_speed = lplayer->getSpeed().getLength();
+
+       /*
+               Maximum position increment
+       */
+       //f32 position_max_increment = 0.05*BS;
+       f32 position_max_increment = 0.1*BS;
+
+       // Maximum time increment (for collision detection etc)
+       // time = distance / speed
+       f32 dtime_max_increment = 1;
+       if(player_speed > 0.001)
+               dtime_max_increment = position_max_increment / player_speed;
+
+       // Maximum time increment is 10ms or lower
+       if(dtime_max_increment > 0.01)
+               dtime_max_increment = 0.01;
+
+       // Don't allow overly huge dtime
+       if(dtime > 0.5)
+               dtime = 0.5;
+
+       f32 dtime_downcount = dtime;
+
+       /*
+               Stuff that has a maximum time increment
+       */
+
+       u32 loopcount = 0;
+       do
+       {
+               loopcount++;
+
+               f32 dtime_part;
+               if(dtime_downcount > dtime_max_increment)
+               {
+                       dtime_part = dtime_max_increment;
+                       dtime_downcount -= dtime_part;
+               }
+               else
+               {
+                       dtime_part = dtime_downcount;
+                       /*
+                               Setting this to 0 (no -=dtime_part) disables an infinite loop
+                               when dtime_part is so small that dtime_downcount -= dtime_part
+                               does nothing
+                       */
+                       dtime_downcount = 0;
+               }
+
+               /*
+                       Handle local player
+               */
+
+               {
+                       // Apply physics
+                       if (!free_move && !is_climbing) {
+                               // Gravity
+                               v3f speed = lplayer->getSpeed();
+                               if (!lplayer->in_liquid)
+                                       speed.Y -= lplayer->movement_gravity *
+                                               lplayer->physics_override_gravity * dtime_part * 2.0f;
+
+                               // Liquid floating / sinking
+                               if (lplayer->in_liquid && !lplayer->swimming_vertical)
+                                       speed.Y -= lplayer->movement_liquid_sink * dtime_part * 2.0f;
+
+                               // Liquid resistance
+                               if (lplayer->in_liquid_stable || lplayer->in_liquid) {
+                                       // How much the node's viscosity blocks movement, ranges
+                                       // between 0 and 1. Should match the scale at which viscosity
+                                       // increase affects other liquid attributes.
+                                       static const f32 viscosity_factor = 0.3f;
+
+                                       v3f d_wanted = -speed / lplayer->movement_liquid_fluidity;
+                                       f32 dl = d_wanted.getLength();
+                                       if (dl > lplayer->movement_liquid_fluidity_smooth)
+                                               dl = lplayer->movement_liquid_fluidity_smooth;
+
+                                       dl *= (lplayer->liquid_viscosity * viscosity_factor) +
+                                               (1 - viscosity_factor);
+                                       v3f d = d_wanted.normalize() * (dl * dtime_part * 100.0f);
+                                       speed += d;
+                               }
+
+                               lplayer->setSpeed(speed);
+                       }
+
+                       /*
+                               Move the lplayer.
+                               This also does collision detection.
+                       */
+                       lplayer->move(dtime_part, this, position_max_increment,
+                               &player_collisions);
+               }
+       } while (dtime_downcount > 0.001);
+
+       bool player_immortal = lplayer->getCAO() && lplayer->getCAO()->isImmortal();
+
+       for (const CollisionInfo &info : player_collisions) {
+               v3f speed_diff = info.new_speed - info.old_speed;;
+               // Handle only fall damage
+               // (because otherwise walking against something in fast_move kills you)
+               if (speed_diff.Y < 0 || info.old_speed.Y >= 0)
+                       continue;
+               // Get rid of other components
+               speed_diff.X = 0;
+               speed_diff.Z = 0;
+               f32 pre_factor = 1; // 1 hp per node/s
+               f32 tolerance = BS*14; // 5 without damage
+               f32 post_factor = 1; // 1 hp per node/s
+               if (info.type == COLLISION_NODE) {
+                       const ContentFeatures &f = m_client->ndef()->
+                               get(m_map->getNodeNoEx(info.node_p));
+                       // Determine fall damage multiplier
+                       int addp = itemgroup_get(f.groups, "fall_damage_add_percent");
+                       pre_factor = 1.0f + (float)addp / 100.0f;
+               }
+               float speed = pre_factor * speed_diff.getLength();
+               if (speed > tolerance && !player_immortal) {
+                       f32 damage_f = (speed - tolerance) / BS * post_factor;
+                       u8 damage = (u8)MYMIN(damage_f + 0.5, 255);
+                       if (damage != 0) {
+                               damageLocalPlayer(damage, true);
+                               m_client->getEventManager()->put(
+                                       new SimpleTriggerEvent(MtEvent::PLAYER_FALLING_DAMAGE));
+                       }
+               }
+       }
+
+       if (m_client->modsLoaded())
+               m_script->environment_step(dtime);
+
+       // Update lighting on local player (used for wield item)
+       u32 day_night_ratio = getDayNightRatio();
+       {
+               // Get node at head
+
+               // On InvalidPositionException, use this as default
+               // (day: LIGHT_SUN, night: 0)
+               MapNode node_at_lplayer(CONTENT_AIR, 0x0f, 0);
+
+               v3s16 p = lplayer->getLightPosition();
+               node_at_lplayer = m_map->getNodeNoEx(p);
+
+               u16 light = getInteriorLight(node_at_lplayer, 0, m_client->ndef());
+               final_color_blend(&lplayer->light_color, light, day_night_ratio);
+       }
+
+       /*
+               Step active objects and update lighting of them
+       */
+
+       g_profiler->avg("CEnv: num of objects", m_active_objects.size());
+       bool update_lighting = m_active_object_light_update_interval.step(dtime, 0.21);
+       for (auto &ao_it : m_active_objects) {
+               ClientActiveObject* obj = ao_it.second;
+               // Step object
+               obj->step(dtime, this);
+
+               if (update_lighting) {
+                       // Update lighting
+                       u8 light = 0;
+                       bool pos_ok;
+
+                       // Get node at head
+                       v3s16 p = obj->getLightPosition();
+                       MapNode n = m_map->getNodeNoEx(p, &pos_ok);
+                       if (pos_ok)
+                               light = n.getLightBlend(day_night_ratio, m_client->ndef());
+                       else
+                               light = blend_light(day_night_ratio, LIGHT_SUN, 0);
+
+                       obj->updateLight(light);
+               }
+       }
+
+       /*
+               Step and handle simple objects
+       */
+       g_profiler->avg("CEnv: num of simple objects", m_simple_objects.size());
+       for (auto i = m_simple_objects.begin(); i != m_simple_objects.end();) {
+               auto cur = i;
+               ClientSimpleObject *simple = *cur;
+
+               simple->step(dtime);
+               if(simple->m_to_be_removed) {
+                       delete simple;
+                       i = m_simple_objects.erase(cur);
+               }
+               else {
+                       ++i;
+               }
+       }
+}
+
+void ClientEnvironment::addSimpleObject(ClientSimpleObject *simple)
+{
+       m_simple_objects.push_back(simple);
+}
+
+GenericCAO* ClientEnvironment::getGenericCAO(u16 id)
+{
+       ClientActiveObject *obj = getActiveObject(id);
+       if (obj && obj->getType() == ACTIVEOBJECT_TYPE_GENERIC)
+               return (GenericCAO*) obj;
+
+       return NULL;
+}
+
+ClientActiveObject* ClientEnvironment::getActiveObject(u16 id)
+{
+       auto n = m_active_objects.find(id);
+       if (n == m_active_objects.end())
+               return NULL;
+       return n->second;
+}
+
+bool isFreeClientActiveObjectId(const u16 id,
+       ClientActiveObjectMap &objects)
+{
+       return id != 0 && objects.find(id) == objects.end();
+
+}
+
+u16 getFreeClientActiveObjectId(ClientActiveObjectMap &objects)
+{
+       //try to reuse id's as late as possible
+       static u16 last_used_id = 0;
+       u16 startid = last_used_id;
+       for(;;) {
+               last_used_id ++;
+               if (isFreeClientActiveObjectId(last_used_id, objects))
+                       return last_used_id;
+
+               if (last_used_id == startid)
+                       return 0;
+       }
+}
+
+u16 ClientEnvironment::addActiveObject(ClientActiveObject *object)
+{
+       assert(object); // Pre-condition
+       if(object->getId() == 0)
+       {
+               u16 new_id = getFreeClientActiveObjectId(m_active_objects);
+               if(new_id == 0)
+               {
+                       infostream<<"ClientEnvironment::addActiveObject(): "
+                               <<"no free ids available"<<std::endl;
+                       delete object;
+                       return 0;
+               }
+               object->setId(new_id);
+       }
+       if (!isFreeClientActiveObjectId(object->getId(), m_active_objects)) {
+               infostream<<"ClientEnvironment::addActiveObject(): "
+                       <<"id is not free ("<<object->getId()<<")"<<std::endl;
+               delete object;
+               return 0;
+       }
+       infostream<<"ClientEnvironment::addActiveObject(): "
+               <<"added (id="<<object->getId()<<")"<<std::endl;
+       m_active_objects[object->getId()] = object;
+       object->addToScene(m_texturesource);
+       { // Update lighting immediately
+               u8 light = 0;
+               bool pos_ok;
+
+               // Get node at head
+               v3s16 p = object->getLightPosition();
+               MapNode n = m_map->getNodeNoEx(p, &pos_ok);
+               if (pos_ok)
+                       light = n.getLightBlend(getDayNightRatio(), m_client->ndef());
+               else
+                       light = blend_light(getDayNightRatio(), LIGHT_SUN, 0);
+
+               object->updateLight(light);
+       }
+       return object->getId();
+}
+
+void ClientEnvironment::addActiveObject(u16 id, u8 type,
+       const std::string &init_data)
+{
+       ClientActiveObject* obj =
+               ClientActiveObject::create((ActiveObjectType) type, m_client, this);
+       if(obj == NULL)
+       {
+               infostream<<"ClientEnvironment::addActiveObject(): "
+                       <<"id="<<id<<" type="<<type<<": Couldn't create object"
+                       <<std::endl;
+               return;
+       }
+
+       obj->setId(id);
+
+       try
+       {
+               obj->initialize(init_data);
+       }
+       catch(SerializationError &e)
+       {
+               errorstream<<"ClientEnvironment::addActiveObject():"
+                       <<" id="<<id<<" type="<<type
+                       <<": SerializationError in initialize(): "
+                       <<e.what()
+                       <<": init_data="<<serializeJsonString(init_data)
+                       <<std::endl;
+       }
+
+       addActiveObject(obj);
+}
+
+void ClientEnvironment::removeActiveObject(u16 id)
+{
+       verbosestream<<"ClientEnvironment::removeActiveObject(): "
+               <<"id="<<id<<std::endl;
+       ClientActiveObject* obj = getActiveObject(id);
+       if (obj == NULL) {
+               infostream<<"ClientEnvironment::removeActiveObject(): "
+                       <<"id="<<id<<" not found"<<std::endl;
+               return;
+       }
+       obj->removeFromScene(true);
+       delete obj;
+       m_active_objects.erase(id);
+}
+
+void ClientEnvironment::processActiveObjectMessage(u16 id, const std::string &data)
+{
+       ClientActiveObject *obj = getActiveObject(id);
+       if (obj == NULL) {
+               infostream << "ClientEnvironment::processActiveObjectMessage():"
+                       << " got message for id=" << id << ", which doesn't exist."
+                       << std::endl;
+               return;
+       }
+
+       try {
+               obj->processMessage(data);
+       } catch (SerializationError &e) {
+               errorstream<<"ClientEnvironment::processActiveObjectMessage():"
+                       << " id=" << id << " type=" << obj->getType()
+                       << " SerializationError in processMessage(): " << e.what()
+                       << std::endl;
+       }
+}
+
+/*
+       Callbacks for activeobjects
+*/
+
+void ClientEnvironment::damageLocalPlayer(u8 damage, bool handle_hp)
+{
+       LocalPlayer *lplayer = getLocalPlayer();
+       assert(lplayer);
+
+       if (handle_hp) {
+               if (lplayer->hp > damage)
+                       lplayer->hp -= damage;
+               else
+                       lplayer->hp = 0;
+       }
+
+       ClientEnvEvent event;
+       event.type = CEE_PLAYER_DAMAGE;
+       event.player_damage.amount = damage;
+       event.player_damage.send_to_server = handle_hp;
+       m_client_event_queue.push(event);
+}
+
+/*
+       Client likes to call these
+*/
+
+void ClientEnvironment::getActiveObjects(v3f origin, f32 max_d,
+       std::vector<DistanceSortedActiveObject> &dest)
+{
+       for (auto &ao_it : m_active_objects) {
+               ClientActiveObject* obj = ao_it.second;
+
+               f32 d = (obj->getPosition() - origin).getLength();
+
+               if (d > max_d)
+                       continue;
+
+               dest.emplace_back(obj, d);
+       }
+}
+
+ClientEnvEvent ClientEnvironment::getClientEnvEvent()
+{
+       FATAL_ERROR_IF(m_client_event_queue.empty(),
+                       "ClientEnvironment::getClientEnvEvent(): queue is empty");
+
+       ClientEnvEvent event = m_client_event_queue.front();
+       m_client_event_queue.pop();
+       return event;
+}
+
+void ClientEnvironment::getSelectedActiveObjects(
+       const core::line3d<f32> &shootline_on_map,
+       std::vector<PointedThing> &objects)
+{
+       std::vector<DistanceSortedActiveObject> allObjects;
+       getActiveObjects(shootline_on_map.start,
+               shootline_on_map.getLength() + 10.0f, allObjects);
+       const v3f line_vector = shootline_on_map.getVector();
+
+       for (const auto &allObject : allObjects) {
+               ClientActiveObject *obj = allObject.obj;
+               aabb3f selection_box;
+               if (!obj->getSelectionBox(&selection_box))
+                       continue;
+
+               const v3f &pos = obj->getPosition();
+               aabb3f offsetted_box(selection_box.MinEdge + pos,
+                       selection_box.MaxEdge + pos);
+
+               v3f current_intersection;
+               v3s16 current_normal;
+               if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
+                               &current_intersection, &current_normal)) {
+                       objects.emplace_back((s16) obj->getId(), current_intersection, current_normal,
+                               (current_intersection - shootline_on_map.start).getLengthSQ());
+               }
+       }
+}
diff --git a/src/client/clientenvironment.h b/src/client/clientenvironment.h
new file mode 100644 (file)
index 0000000..606070e
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+Minetest
+Copyright (C) 2010-2017 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "environment.h"
+#include <ISceneManager.h>
+#include "clientobject.h"
+#include "util/numeric.h"
+
+class ClientSimpleObject;
+class ClientMap;
+class ClientScripting;
+class ClientActiveObject;
+class GenericCAO;
+class LocalPlayer;
+
+/*
+       The client-side environment.
+
+       This is not thread-safe.
+       Must be called from main (irrlicht) thread (uses the SceneManager)
+       Client uses an environment mutex.
+*/
+
+enum ClientEnvEventType
+{
+       CEE_NONE,
+       CEE_PLAYER_DAMAGE
+};
+
+struct ClientEnvEvent
+{
+       ClientEnvEventType type;
+       union {
+               //struct{
+               //} none;
+               struct{
+                       u8 amount;
+                       bool send_to_server;
+               } player_damage;
+       };
+};
+
+typedef std::unordered_map<u16, ClientActiveObject*> ClientActiveObjectMap;
+class ClientEnvironment : public Environment
+{
+public:
+       ClientEnvironment(ClientMap *map, ITextureSource *texturesource, Client *client);
+       ~ClientEnvironment();
+
+       Map & getMap();
+       ClientMap & getClientMap();
+
+       Client *getGameDef() { return m_client; }
+       void setScript(ClientScripting *script) { m_script = script; }
+
+       void step(f32 dtime);
+
+       virtual void setLocalPlayer(LocalPlayer *player);
+       LocalPlayer *getLocalPlayer() const { return m_local_player; }
+
+       /*
+               ClientSimpleObjects
+       */
+
+       void addSimpleObject(ClientSimpleObject *simple);
+
+       /*
+               ActiveObjects
+       */
+
+       GenericCAO* getGenericCAO(u16 id);
+       ClientActiveObject* getActiveObject(u16 id);
+
+       /*
+               Adds an active object to the environment.
+               Environment handles deletion of object.
+               Object may be deleted by environment immediately.
+               If id of object is 0, assigns a free id to it.
+               Returns the id of the object.
+               Returns 0 if not added and thus deleted.
+       */
+       u16 addActiveObject(ClientActiveObject *object);
+
+       void addActiveObject(u16 id, u8 type, const std::string &init_data);
+       void removeActiveObject(u16 id);
+
+       void processActiveObjectMessage(u16 id, const std::string &data);
+
+       /*
+               Callbacks for activeobjects
+       */
+
+       void damageLocalPlayer(u8 damage, bool handle_hp=true);
+
+       /*
+               Client likes to call these
+       */
+
+       // Get all nearby objects
+       void getActiveObjects(v3f origin, f32 max_d,
+               std::vector<DistanceSortedActiveObject> &dest);
+
+       bool hasClientEnvEvents() const { return !m_client_event_queue.empty(); }
+
+       // Get event from queue. If queue is empty, it triggers an assertion failure.
+       ClientEnvEvent getClientEnvEvent();
+
+       virtual void getSelectedActiveObjects(
+               const core::line3d<f32> &shootline_on_map,
+               std::vector<PointedThing> &objects
+       );
+
+       u16 attachement_parent_ids[USHRT_MAX + 1];
+
+       const std::list<std::string> &getPlayerNames() { return m_player_names; }
+       void addPlayerName(const std::string &name) { m_player_names.push_back(name); }
+       void removePlayerName(const std::string &name) { m_player_names.remove(name); }
+       void updateCameraOffset(const v3s16 &camera_offset)
+       { m_camera_offset = camera_offset; }
+       v3s16 getCameraOffset() const { return m_camera_offset; }
+private:
+       ClientMap *m_map;
+       LocalPlayer *m_local_player = nullptr;
+       ITextureSource *m_texturesource;
+       Client *m_client;
+       ClientScripting *m_script = nullptr;
+       ClientActiveObjectMap m_active_objects;
+       std::vector<ClientSimpleObject*> m_simple_objects;
+       std::queue<ClientEnvEvent> m_client_event_queue;
+       IntervalLimiter m_active_object_light_update_interval;
+       std::list<std::string> m_player_names;
+       v3s16 m_camera_offset;
+};
diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp
new file mode 100644 (file)
index 0000000..969c555
--- /dev/null
@@ -0,0 +1,671 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 "clientmap.h"
+#include "client.h"
+#include "mapblock_mesh.h"
+#include <IMaterialRenderer.h>
+#include <matrix4.h>
+#include "mapsector.h"
+#include "mapblock.h"
+#include "profiler.h"
+#include "settings.h"
+#include "camera.h"               // CameraModes
+#include "util/basic_macros.h"
+#include <algorithm>
+#include "client/renderingengine.h"
+
+ClientMap::ClientMap(
+               Client *client,
+               MapDrawControl &control,
+               s32 id
+):
+       Map(dout_client, client),
+       scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(),
+               RenderingEngine::get_scene_manager(), id),
+       m_client(client),
+       m_control(control)
+{
+       m_box = aabb3f(-BS*1000000,-BS*1000000,-BS*1000000,
+                       BS*1000000,BS*1000000,BS*1000000);
+
+       /* TODO: Add a callback function so these can be updated when a setting
+        *       changes.  At this point in time it doesn't matter (e.g. /set
+        *       is documented to change server settings only)
+        *
+        * 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.
+        */
+       m_cache_trilinear_filter  = g_settings->getBool("trilinear_filter");
+       m_cache_bilinear_filter   = g_settings->getBool("bilinear_filter");
+       m_cache_anistropic_filter = g_settings->getBool("anisotropic_filter");
+
+}
+
+MapSector * ClientMap::emergeSector(v2s16 p2d)
+{
+       // Check that it doesn't exist already
+       try {
+               return getSectorNoGenerate(p2d);
+       } catch(InvalidPositionException &e) {
+       }
+
+       // Create a sector
+       MapSector *sector = new MapSector(this, p2d, m_gamedef);
+       m_sectors[p2d] = sector;
+
+       return sector;
+}
+
+void ClientMap::OnRegisterSceneNode()
+{
+       if(IsVisible)
+       {
+               SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID);
+               SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT);
+       }
+
+       ISceneNode::OnRegisterSceneNode();
+}
+
+void ClientMap::getBlocksInViewRange(v3s16 cam_pos_nodes,
+               v3s16 *p_blocks_min, v3s16 *p_blocks_max)
+{
+       v3s16 box_nodes_d = m_control.wanted_range * v3s16(1, 1, 1);
+       // Define p_nodes_min/max as v3s32 because 'cam_pos_nodes -/+ box_nodes_d'
+       // can exceed the range of v3s16 when a large view range is used near the
+       // world edges.
+       v3s32 p_nodes_min(
+               cam_pos_nodes.X - box_nodes_d.X,
+               cam_pos_nodes.Y - box_nodes_d.Y,
+               cam_pos_nodes.Z - box_nodes_d.Z);
+       v3s32 p_nodes_max(
+               cam_pos_nodes.X + box_nodes_d.X,
+               cam_pos_nodes.Y + box_nodes_d.Y,
+               cam_pos_nodes.Z + box_nodes_d.Z);
+       // Take a fair amount as we will be dropping more out later
+       // Umm... these additions are a bit strange but they are needed.
+       *p_blocks_min = v3s16(
+                       p_nodes_min.X / MAP_BLOCKSIZE - 3,
+                       p_nodes_min.Y / MAP_BLOCKSIZE - 3,
+                       p_nodes_min.Z / MAP_BLOCKSIZE - 3);
+       *p_blocks_max = v3s16(
+                       p_nodes_max.X / MAP_BLOCKSIZE + 1,
+                       p_nodes_max.Y / MAP_BLOCKSIZE + 1,
+                       p_nodes_max.Z / MAP_BLOCKSIZE + 1);
+}
+
+void ClientMap::updateDrawList()
+{
+       ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG);
+       g_profiler->add("CM::updateDrawList() count", 1);
+
+       for (auto &i : m_drawlist) {
+               MapBlock *block = i.second;
+               block->refDrop();
+       }
+       m_drawlist.clear();
+
+       v3f camera_position = m_camera_position;
+       v3f camera_direction = m_camera_direction;
+       f32 camera_fov = m_camera_fov;
+
+       // Use a higher fov to accomodate faster camera movements.
+       // Blocks are cropped better when they are drawn.
+       // Or maybe they aren't? Well whatever.
+       camera_fov *= 1.2;
+
+       v3s16 cam_pos_nodes = floatToInt(camera_position, BS);
+       v3s16 p_blocks_min;
+       v3s16 p_blocks_max;
+       getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max);
+
+       // Number of blocks in rendering range
+       u32 blocks_in_range = 0;
+       // Number of blocks occlusion culled
+       u32 blocks_occlusion_culled = 0;
+       // Number of blocks in rendering range but don't have a mesh
+       u32 blocks_in_range_without_mesh = 0;
+       // Blocks that had mesh that would have been drawn according to
+       // rendering range (if max blocks limit didn't kick in)
+       u32 blocks_would_have_drawn = 0;
+       // Blocks that were drawn and had a mesh
+       u32 blocks_drawn = 0;
+       // Blocks which had a corresponding meshbuffer for this pass
+       //u32 blocks_had_pass_meshbuf = 0;
+       // Blocks from which stuff was actually drawn
+       //u32 blocks_without_stuff = 0;
+       // Distance to farthest drawn block
+       float farthest_drawn = 0;
+
+       // No occlusion culling when free_move is on and camera is
+       // inside ground
+       bool occlusion_culling_enabled = true;
+       if (g_settings->getBool("free_move")) {
+               MapNode n = getNodeNoEx(cam_pos_nodes);
+               if (n.getContent() == CONTENT_IGNORE ||
+                               m_nodedef->get(n).solidness == 2)
+                       occlusion_culling_enabled = false;
+       }
+
+       for (const auto &sector_it : m_sectors) {
+               MapSector *sector = sector_it.second;
+               v2s16 sp = sector->getPos();
+
+               if (!m_control.range_all) {
+                       if (sp.X < p_blocks_min.X || sp.X > p_blocks_max.X ||
+                                       sp.Y < p_blocks_min.Z || sp.Y > p_blocks_max.Z)
+                               continue;
+               }
+
+               MapBlockVect sectorblocks;
+               sector->getBlocks(sectorblocks);
+
+               /*
+                       Loop through blocks in sector
+               */
+
+               u32 sector_blocks_drawn = 0;
+
+               for (auto block : sectorblocks) {
+                       /*
+                       Compare block position to camera position, skip
+                       if not seen on display
+               */
+
+                       if (block->mesh)
+                               block->mesh->updateCameraOffset(m_camera_offset);
+
+                       float range = 100000 * BS;
+                       if (!m_control.range_all)
+                               range = m_control.wanted_range * BS;
+
+                       float d = 0.0;
+                       if (!isBlockInSight(block->getPos(), camera_position,
+                                       camera_direction, camera_fov, range, &d))
+                               continue;
+
+                       blocks_in_range++;
+
+                       /*
+                               Ignore if mesh doesn't exist
+                       */
+                       if (!block->mesh) {
+                               blocks_in_range_without_mesh++;
+                               continue;
+                       }
+
+                       /*
+                               Occlusion culling
+                       */
+                       if (occlusion_culling_enabled && isBlockOccluded(block, cam_pos_nodes)) {
+                               blocks_occlusion_culled++;
+                               continue;
+                       }
+
+                       // This block is in range. Reset usage timer.
+                       block->resetUsageTimer();
+
+                       // Limit block count in case of a sudden increase
+                       blocks_would_have_drawn++;
+                       if (blocks_drawn >= m_control.wanted_max_blocks &&
+                                       !m_control.range_all &&
+                                       d > m_control.wanted_range * BS)
+                               continue;
+
+                       // Add to set
+                       block->refGrab();
+                       m_drawlist[block->getPos()] = block;
+
+                       sector_blocks_drawn++;
+                       blocks_drawn++;
+                       if (d / BS > farthest_drawn)
+                               farthest_drawn = d / BS;
+
+               } // foreach sectorblocks
+
+               if (sector_blocks_drawn != 0)
+                       m_last_drawn_sectors.insert(sp);
+       }
+
+       g_profiler->avg("CM: blocks in range", blocks_in_range);
+       g_profiler->avg("CM: blocks occlusion culled", blocks_occlusion_culled);
+       if (blocks_in_range != 0)
+               g_profiler->avg("CM: blocks in range without mesh (frac)",
+                               (float)blocks_in_range_without_mesh / blocks_in_range);
+       g_profiler->avg("CM: blocks drawn", blocks_drawn);
+       g_profiler->avg("CM: farthest drawn", farthest_drawn);
+       g_profiler->avg("CM: wanted max blocks", m_control.wanted_max_blocks);
+}
+
+struct MeshBufList
+{
+       video::SMaterial m;
+       std::vector<scene::IMeshBuffer*> bufs;
+};
+
+struct MeshBufListList
+{
+       /*!
+        * Stores the mesh buffers of the world.
+        * The array index is the material's layer.
+        * The vector part groups vertices by material.
+        */
+       std::vector<MeshBufList> lists[MAX_TILE_LAYERS];
+
+       void clear()
+       {
+               for (auto &list : lists)
+                       list.clear();
+       }
+
+       void add(scene::IMeshBuffer *buf, u8 layer)
+       {
+               // Append to the correct layer
+               std::vector<MeshBufList> &list = lists[layer];
+               const video::SMaterial &m = buf->getMaterial();
+               for (MeshBufList &l : list) {
+                       // comparing a full material is quite expensive so we don't do it if
+                       // not even first texture is equal
+                       if (l.m.TextureLayer[0].Texture != m.TextureLayer[0].Texture)
+                               continue;
+
+                       if (l.m == m) {
+                               l.bufs.push_back(buf);
+                               return;
+                       }
+               }
+               MeshBufList l;
+               l.m = m;
+               l.bufs.push_back(buf);
+               list.push_back(l);
+       }
+};
+
+void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
+{
+       bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
+
+       std::string prefix;
+       if (pass == scene::ESNRP_SOLID)
+               prefix = "CM: solid: ";
+       else
+               prefix = "CM: transparent: ";
+
+       /*
+               This is called two times per frame, reset on the non-transparent one
+       */
+       if (pass == scene::ESNRP_SOLID)
+               m_last_drawn_sectors.clear();
+
+       /*
+               Get time for measuring timeout.
+
+               Measuring time is very useful for long delays when the
+               machine is swapping a lot.
+       */
+       std::time_t time1 = time(0);
+
+       /*
+               Get animation parameters
+       */
+       float animation_time = m_client->getAnimationTime();
+       int crack = m_client->getCrackLevel();
+       u32 daynight_ratio = m_client->getEnv().getDayNightRatio();
+
+       v3f camera_position = m_camera_position;
+       v3f camera_direction = m_camera_direction;
+       f32 camera_fov = m_camera_fov;
+
+       /*
+               Get all blocks and draw all visible ones
+       */
+
+       u32 vertex_count = 0;
+       u32 meshbuffer_count = 0;
+
+       // For limiting number of mesh animations per frame
+       u32 mesh_animate_count = 0;
+       u32 mesh_animate_count_far = 0;
+
+       // Blocks that were drawn and had a mesh
+       u32 blocks_drawn = 0;
+       // Blocks which had a corresponding meshbuffer for this pass
+       u32 blocks_had_pass_meshbuf = 0;
+       // Blocks from which stuff was actually drawn
+       u32 blocks_without_stuff = 0;
+
+       /*
+               Draw the selected MapBlocks
+       */
+
+       {
+       ScopeProfiler sp(g_profiler, prefix + "drawing blocks", SPT_AVG);
+
+       MeshBufListList drawbufs;
+
+       for (auto &i : m_drawlist) {
+               MapBlock *block = i.second;
+
+               // If the mesh of the block happened to get deleted, ignore it
+               if (!block->mesh)
+                       continue;
+
+               float d = 0.0;
+               if (!isBlockInSight(block->getPos(), camera_position,
+                               camera_direction, camera_fov, 100000 * BS, &d))
+                       continue;
+
+               // Mesh animation
+               if (pass == scene::ESNRP_SOLID) {
+                       //MutexAutoLock lock(block->mesh_mutex);
+                       MapBlockMesh *mapBlockMesh = block->mesh;
+                       assert(mapBlockMesh);
+                       // Pretty random but this should work somewhat nicely
+                       bool faraway = d >= BS * 50;
+                       //bool faraway = d >= m_control.wanted_range * BS;
+                       if (mapBlockMesh->isAnimationForced() || !faraway ||
+                                       mesh_animate_count_far < (m_control.range_all ? 200 : 50)) {
+                               bool animated = mapBlockMesh->animate(faraway, animation_time,
+                                       crack, daynight_ratio);
+                               if (animated)
+                                       mesh_animate_count++;
+                               if (animated && faraway)
+                                       mesh_animate_count_far++;
+                       } else {
+                               mapBlockMesh->decreaseAnimationForceTimer();
+                       }
+               }
+
+               /*
+                       Get the meshbuffers of the block
+               */
+               {
+                       //MutexAutoLock lock(block->mesh_mutex);
+
+                       MapBlockMesh *mapBlockMesh = block->mesh;
+                       assert(mapBlockMesh);
+
+                       for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
+                               scene::IMesh *mesh = mapBlockMesh->getMesh(layer);
+                               assert(mesh);
+
+                               u32 c = mesh->getMeshBufferCount();
+                               for (u32 i = 0; i < c; i++) {
+                                       scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
+
+                                       video::SMaterial& material = buf->getMaterial();
+                                       video::IMaterialRenderer* rnd =
+                                               driver->getMaterialRenderer(material.MaterialType);
+                                       bool transparent = (rnd && rnd->isTransparent());
+                                       if (transparent == is_transparent_pass) {
+                                               if (buf->getVertexCount() == 0)
+                                                       errorstream << "Block [" << analyze_block(block)
+                                                               << "] contains an empty meshbuf" << std::endl;
+
+                                               material.setFlag(video::EMF_TRILINEAR_FILTER,
+                                                       m_cache_trilinear_filter);
+                                               material.setFlag(video::EMF_BILINEAR_FILTER,
+                                                       m_cache_bilinear_filter);
+                                               material.setFlag(video::EMF_ANISOTROPIC_FILTER,
+                                                       m_cache_anistropic_filter);
+                                               material.setFlag(video::EMF_WIREFRAME,
+                                                       m_control.show_wireframe);
+
+                                               drawbufs.add(buf, layer);
+                                       }
+                               }
+                       }
+               }
+       }
+
+       // Render all layers in order
+       for (auto &lists : drawbufs.lists) {
+               int timecheck_counter = 0;
+               for (MeshBufList &list : lists) {
+                       timecheck_counter++;
+                       if (timecheck_counter > 50) {
+                               timecheck_counter = 0;
+                               std::time_t time2 = time(0);
+                               if (time2 > time1 + 4) {
+                                       infostream << "ClientMap::renderMap(): "
+                                               "Rendering takes ages, returning."
+                                               << std::endl;
+                                       return;
+                               }
+                       }
+
+                       driver->setMaterial(list.m);
+
+                       for (scene::IMeshBuffer *buf : list.bufs) {
+                               driver->drawMeshBuffer(buf);
+                               vertex_count += buf->getVertexCount();
+                               meshbuffer_count++;
+                       }
+               }
+       }
+       } // ScopeProfiler
+
+       // Log only on solid pass because values are the same
+       if (pass == scene::ESNRP_SOLID) {
+               g_profiler->avg("CM: animated meshes", mesh_animate_count);
+               g_profiler->avg("CM: animated meshes (far)", mesh_animate_count_far);
+       }
+
+       g_profiler->avg(prefix + "vertices drawn", vertex_count);
+       if (blocks_had_pass_meshbuf != 0)
+               g_profiler->avg(prefix + "meshbuffers per block",
+                       (float)meshbuffer_count / (float)blocks_had_pass_meshbuf);
+       if (blocks_drawn != 0)
+               g_profiler->avg(prefix + "empty blocks (frac)",
+                       (float)blocks_without_stuff / blocks_drawn);
+}
+
+static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step,
+       float step_multiplier, float start_distance, float end_distance,
+       const NodeDefManager *ndef, u32 daylight_factor, float sunlight_min_d,
+       int *result, bool *sunlight_seen)
+{
+       int brightness_sum = 0;
+       int brightness_count = 0;
+       float distance = start_distance;
+       dir.normalize();
+       v3f pf = p0;
+       pf += dir * distance;
+       int noncount = 0;
+       bool nonlight_seen = false;
+       bool allow_allowing_non_sunlight_propagates = false;
+       bool allow_non_sunlight_propagates = false;
+       // Check content nearly at camera position
+       {
+               v3s16 p = floatToInt(p0 /*+ dir * 3*BS*/, BS);
+               MapNode n = map->getNodeNoEx(p);
+               if(ndef->get(n).param_type == CPT_LIGHT &&
+                               !ndef->get(n).sunlight_propagates)
+                       allow_allowing_non_sunlight_propagates = true;
+       }
+       // If would start at CONTENT_IGNORE, start closer
+       {
+               v3s16 p = floatToInt(pf, BS);
+               MapNode n = map->getNodeNoEx(p);
+               if(n.getContent() == CONTENT_IGNORE){
+                       float newd = 2*BS;
+                       pf = p0 + dir * 2*newd;
+                       distance = newd;
+                       sunlight_min_d = 0;
+               }
+       }
+       for (int i=0; distance < end_distance; i++) {
+               pf += dir * step;
+               distance += step;
+               step *= step_multiplier;
+
+               v3s16 p = floatToInt(pf, BS);
+               MapNode n = map->getNodeNoEx(p);
+               if (allow_allowing_non_sunlight_propagates && i == 0 &&
+                               ndef->get(n).param_type == CPT_LIGHT &&
+                               !ndef->get(n).sunlight_propagates) {
+                       allow_non_sunlight_propagates = true;
+               }
+
+               if (ndef->get(n).param_type != CPT_LIGHT ||
+                               (!ndef->get(n).sunlight_propagates &&
+                                       !allow_non_sunlight_propagates)){
+                       nonlight_seen = true;
+                       noncount++;
+                       if(noncount >= 4)
+                               break;
+                       continue;
+               }
+
+               if (distance >= sunlight_min_d && !*sunlight_seen && !nonlight_seen)
+                       if (n.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN)
+                               *sunlight_seen = true;
+               noncount = 0;
+               brightness_sum += decode_light(n.getLightBlend(daylight_factor, ndef));
+               brightness_count++;
+       }
+       *result = 0;
+       if(brightness_count == 0)
+               return false;
+       *result = brightness_sum / brightness_count;
+       /*std::cerr<<"Sampled "<<brightness_count<<" points; result="
+                       <<(*result)<<std::endl;*/
+       return true;
+}
+
+int ClientMap::getBackgroundBrightness(float max_d, u32 daylight_factor,
+               int oldvalue, bool *sunlight_seen_result)
+{
+       static v3f z_directions[50] = {
+               v3f(-100, 0, 0)
+       };
+       static f32 z_offsets[sizeof(z_directions)/sizeof(*z_directions)] = {
+               -1000,
+       };
+
+       if(z_directions[0].X < -99){
+               for(u32 i=0; i<sizeof(z_directions)/sizeof(*z_directions); i++){
+                       // Assumes FOV of 72 and 16/9 aspect ratio
+                       z_directions[i] = v3f(
+                               0.02 * myrand_range(-100, 100),
+                               1.0,
+                               0.01 * myrand_range(-100, 100)
+                       ).normalize();
+                       z_offsets[i] = 0.01 * myrand_range(0,100);
+               }
+       }
+
+       int sunlight_seen_count = 0;
+       float sunlight_min_d = max_d*0.8;
+       if(sunlight_min_d > 35*BS)
+               sunlight_min_d = 35*BS;
+       std::vector<int> values;
+       for(u32 i=0; i<sizeof(z_directions)/sizeof(*z_directions); i++){
+               v3f z_dir = z_directions[i];
+               core::CMatrix4<f32> a;
+               a.buildRotateFromTo(v3f(0,1,0), z_dir);
+               v3f dir = m_camera_direction;
+               a.rotateVect(dir);
+               int br = 0;
+               float step = BS*1.5;
+               if(max_d > 35*BS)
+                       step = max_d / 35 * 1.5;
+               float off = step * z_offsets[i];
+               bool sunlight_seen_now = false;
+               bool ok = getVisibleBrightness(this, m_camera_position, dir,
+                               step, 1.0, max_d*0.6+off, max_d, m_nodedef, daylight_factor,
+                               sunlight_min_d,
+                               &br, &sunlight_seen_now);
+               if(sunlight_seen_now)
+                       sunlight_seen_count++;
+               if(!ok)
+                       continue;
+               values.push_back(br);
+               // Don't try too much if being in the sun is clear
+               if(sunlight_seen_count >= 20)
+                       break;
+       }
+       int brightness_sum = 0;
+       int brightness_count = 0;
+       std::sort(values.begin(), values.end());
+       u32 num_values_to_use = values.size();
+       if(num_values_to_use >= 10)
+               num_values_to_use -= num_values_to_use/2;
+       else if(num_values_to_use >= 7)
+               num_values_to_use -= num_values_to_use/3;
+       u32 first_value_i = (values.size() - num_values_to_use) / 2;
+
+       for (u32 i=first_value_i; i < first_value_i + num_values_to_use; i++) {
+               brightness_sum += values[i];
+               brightness_count++;
+       }
+
+       int ret = 0;
+       if(brightness_count == 0){
+               MapNode n = getNodeNoEx(floatToInt(m_camera_position, BS));
+               if(m_nodedef->get(n).param_type == CPT_LIGHT){
+                       ret = decode_light(n.getLightBlend(daylight_factor, m_nodedef));
+               } else {
+                       ret = oldvalue;
+               }
+       } else {
+               ret = brightness_sum / brightness_count;
+       }
+
+       *sunlight_seen_result = (sunlight_seen_count > 0);
+       return ret;
+}
+
+void ClientMap::renderPostFx(CameraMode cam_mode)
+{
+       // Sadly ISceneManager has no "post effects" render pass, in that case we
+       // could just register for that and handle it in renderMap().
+
+       MapNode n = getNodeNoEx(floatToInt(m_camera_position, BS));
+
+       // - If the player is in a solid node, make everything black.
+       // - If the player is in liquid, draw a semi-transparent overlay.
+       // - Do not if player is in third person mode
+       const ContentFeatures& features = m_nodedef->get(n);
+       video::SColor post_effect_color = features.post_effect_color;
+       if(features.solidness == 2 && !(g_settings->getBool("noclip") &&
+                       m_client->checkLocalPrivilege("noclip")) &&
+                       cam_mode == CAMERA_MODE_FIRST)
+       {
+               post_effect_color = video::SColor(255, 0, 0, 0);
+       }
+       if (post_effect_color.getAlpha() != 0)
+       {
+               // Draw a full-screen rectangle
+               video::IVideoDriver* driver = SceneManager->getVideoDriver();
+               v2u32 ss = driver->getScreenSize();
+               core::rect<s32> rect(0,0, ss.X, ss.Y);
+               driver->draw2DRectangle(post_effect_color, rect);
+       }
+}
+
+void ClientMap::PrintInfo(std::ostream &out)
+{
+       out<<"ClientMap: ";
+}
+
+
diff --git a/src/client/clientmap.h b/src/client/clientmap.h
new file mode 100644 (file)
index 0000000..8402bb0
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "map.h"
+#include "camera.h"
+#include <set>
+#include <map>
+
+struct MapDrawControl
+{
+       // Overrides limits by drawing everything
+       bool range_all = false;
+       // Wanted drawing range
+       float wanted_range = 0.0f;
+       // Maximum number of blocks to draw
+       u32 wanted_max_blocks = 0;
+       // show a wire frame for debugging
+       bool show_wireframe = false;
+};
+
+class Client;
+class ITextureSource;
+
+/*
+       ClientMap
+
+       This is the only map class that is able to render itself on screen.
+*/
+
+class ClientMap : public Map, public scene::ISceneNode
+{
+public:
+       ClientMap(
+                       Client *client,
+                       MapDrawControl &control,
+                       s32 id
+       );
+
+       virtual ~ClientMap() = default;
+
+       s32 mapType() const
+       {
+               return MAPTYPE_CLIENT;
+       }
+
+       void drop()
+       {
+               ISceneNode::drop();
+       }
+
+       void updateCamera(const v3f &pos, const v3f &dir, f32 fov, const v3s16 &offset)
+       {
+               m_camera_position = pos;
+               m_camera_direction = dir;
+               m_camera_fov = fov;
+               m_camera_offset = offset;
+       }
+
+       /*
+               Forcefully get a sector from somewhere
+       */
+       MapSector * emergeSector(v2s16 p);
+
+       //void deSerializeSector(v2s16 p2d, std::istream &is);
+
+       /*
+               ISceneNode methods
+       */
+
+       virtual void OnRegisterSceneNode();
+
+       virtual void render()
+       {
+               video::IVideoDriver* driver = SceneManager->getVideoDriver();
+               driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
+               renderMap(driver, SceneManager->getSceneNodeRenderPass());
+       }
+
+       virtual const aabb3f &getBoundingBox() const
+       {
+               return m_box;
+       }
+
+       void getBlocksInViewRange(v3s16 cam_pos_nodes,
+               v3s16 *p_blocks_min, v3s16 *p_blocks_max);
+       void updateDrawList();
+       void renderMap(video::IVideoDriver* driver, s32 pass);
+
+       int getBackgroundBrightness(float max_d, u32 daylight_factor,
+                       int oldvalue, bool *sunlight_seen_result);
+
+       void renderPostFx(CameraMode cam_mode);
+
+       // For debug printing
+       virtual void PrintInfo(std::ostream &out);
+
+       const MapDrawControl & getControl() const { return m_control; }
+       f32 getCameraFov() const { return m_camera_fov; }
+private:
+       Client *m_client;
+
+       aabb3f m_box = aabb3f(-BS * 1000000, -BS * 1000000, -BS * 1000000,
+               BS * 1000000, BS * 1000000, BS * 1000000);
+
+       MapDrawControl &m_control;
+
+       v3f m_camera_position = v3f(0,0,0);
+       v3f m_camera_direction = v3f(0,0,1);
+       f32 m_camera_fov = M_PI;
+       v3s16 m_camera_offset;
+
+       std::map<v3s16, MapBlock*> m_drawlist;
+
+       std::set<v2s16> m_last_drawn_sectors;
+
+       bool m_cache_trilinear_filter;
+       bool m_cache_bilinear_filter;
+       bool m_cache_anistropic_filter;
+};
diff --git a/src/client/clientmedia.cpp b/src/client/clientmedia.cpp
new file mode 100644 (file)
index 0000000..97931ee
--- /dev/null
@@ -0,0 +1,639 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 "clientmedia.h"
+#include "httpfetch.h"
+#include "client.h"
+#include "filecache.h"
+#include "filesys.h"
+#include "log.h"
+#include "porting.h"
+#include "settings.h"
+#include "util/hex.h"
+#include "util/serialize.h"
+#include "util/sha1.h"
+#include "util/string.h"
+
+static std::string getMediaCacheDir()
+{
+       return porting::path_cache + DIR_DELIM + "media";
+}
+
+/*
+       ClientMediaDownloader
+*/
+
+ClientMediaDownloader::ClientMediaDownloader():
+       m_media_cache(getMediaCacheDir()),
+       m_httpfetch_caller(HTTPFETCH_DISCARD)
+{
+}
+
+ClientMediaDownloader::~ClientMediaDownloader()
+{
+       if (m_httpfetch_caller != HTTPFETCH_DISCARD)
+               httpfetch_caller_free(m_httpfetch_caller);
+
+       for (auto &file_it : m_files)
+               delete file_it.second;
+
+       for (auto &remote : m_remotes)
+               delete remote;
+}
+
+void ClientMediaDownloader::addFile(const std::string &name, const std::string &sha1)
+{
+       assert(!m_initial_step_done); // pre-condition
+
+       // if name was already announced, ignore the new announcement
+       if (m_files.count(name) != 0) {
+               errorstream << "Client: ignoring duplicate media announcement "
+                               << "sent by server: \"" << name << "\""
+                               << std::endl;
+               return;
+       }
+
+       // if name is empty or contains illegal characters, ignore the file
+       if (name.empty() || !string_allowed(name, TEXTURENAME_ALLOWED_CHARS)) {
+               errorstream << "Client: ignoring illegal file name "
+                               << "sent by server: \"" << name << "\""
+                               << std::endl;
+               return;
+       }
+
+       // length of sha1 must be exactly 20 (160 bits), else ignore the file
+       if (sha1.size() != 20) {
+               errorstream << "Client: ignoring illegal SHA1 sent by server: "
+                               << hex_encode(sha1) << " \"" << name << "\""
+                               << std::endl;
+               return;
+       }
+
+       FileStatus *filestatus = new FileStatus();
+       filestatus->received = false;
+       filestatus->sha1 = sha1;
+       filestatus->current_remote = -1;
+       m_files.insert(std::make_pair(name, filestatus));
+}
+
+void ClientMediaDownloader::addRemoteServer(const std::string &baseurl)
+{
+       assert(!m_initial_step_done);   // pre-condition
+
+       #ifdef USE_CURL
+
+       if (g_settings->getBool("enable_remote_media_server")) {
+               infostream << "Client: Adding remote server \""
+                       << baseurl << "\" for media download" << std::endl;
+
+               RemoteServerStatus *remote = new RemoteServerStatus();
+               remote->baseurl = baseurl;
+               remote->active_count = 0;
+               remote->request_by_filename = false;
+               m_remotes.push_back(remote);
+       }
+
+       #else
+
+       infostream << "Client: Ignoring remote server \""
+               << baseurl << "\" because cURL support is not compiled in"
+               << std::endl;
+
+       #endif
+}
+
+void ClientMediaDownloader::step(Client *client)
+{
+       if (!m_initial_step_done) {
+               initialStep(client);
+               m_initial_step_done = true;
+       }
+
+       // Remote media: check for completion of fetches
+       if (m_httpfetch_active) {
+               bool fetched_something = false;
+               HTTPFetchResult fetch_result;
+
+               while (httpfetch_async_get(m_httpfetch_caller, fetch_result)) {
+                       m_httpfetch_active--;
+                       fetched_something = true;
+
+                       // Is this a hashset (index.mth) or a media file?
+                       if (fetch_result.request_id < m_remotes.size())
+                               remoteHashSetReceived(fetch_result);
+                       else
+                               remoteMediaReceived(fetch_result, client);
+               }
+
+               if (fetched_something)
+                       startRemoteMediaTransfers();
+
+               // Did all remote transfers end and no new ones can be started?
+               // If so, request still missing files from the minetest server
+               // (Or report that we have all files.)
+               if (m_httpfetch_active == 0) {
+                       if (m_uncached_received_count < m_uncached_count) {
+                               infostream << "Client: Failed to remote-fetch "
+                                       << (m_uncached_count-m_uncached_received_count)
+                                       << " files. Requesting them"
+                                       << " the usual way." << std::endl;
+                       }
+                       startConventionalTransfers(client);
+               }
+       }
+}
+
+void ClientMediaDownloader::initialStep(Client *client)
+{
+       // Check media cache
+       m_uncached_count = m_files.size();
+       for (auto &file_it : m_files) {
+               std::string name = file_it.first;
+               FileStatus *filestatus = file_it.second;
+               const std::string &sha1 = filestatus->sha1;
+
+               std::ostringstream tmp_os(std::ios_base::binary);
+               bool found_in_cache = m_media_cache.load(hex_encode(sha1), tmp_os);
+
+               // If found in cache, try to load it from there
+               if (found_in_cache) {
+                       bool success = checkAndLoad(name, sha1,
+                                       tmp_os.str(), true, client);
+                       if (success) {
+                               filestatus->received = true;
+                               m_uncached_count--;
+                       }
+               }
+       }
+
+       assert(m_uncached_received_count == 0);
+
+       // Create the media cache dir if we are likely to write to it
+       if (m_uncached_count != 0) {
+               bool did = fs::CreateAllDirs(getMediaCacheDir());
+               if (!did) {
+                       errorstream << "Client: "
+                               << "Could not create media cache directory: "
+                               << getMediaCacheDir()
+                               << std::endl;
+               }
+       }
+
+       // If we found all files in the cache, report this fact to the server.
+       // If the server reported no remote servers, immediately start
+       // conventional transfers. Note: if cURL support is not compiled in,
+       // m_remotes is always empty, so "!USE_CURL" is redundant but may
+       // reduce the size of the compiled code
+       if (!USE_CURL || m_uncached_count == 0 || m_remotes.empty()) {
+               startConventionalTransfers(client);
+       }
+       else {
+               // Otherwise start off by requesting each server's sha1 set
+
+               // This is the first time we use httpfetch, so alloc a caller ID
+               m_httpfetch_caller = httpfetch_caller_alloc();
+               m_httpfetch_timeout = g_settings->getS32("curl_timeout");
+
+               // Set the active fetch limit to curl_parallel_limit or 84,
+               // whichever is greater. This gives us some leeway so that
+               // inefficiencies in communicating with the httpfetch thread
+               // don't slow down fetches too much. (We still want some limit
+               // so that when the first remote server returns its hash set,
+               // not all files are requested from that server immediately.)
+               // One such inefficiency is that ClientMediaDownloader::step()
+               // is only called a couple times per second, while httpfetch
+               // might return responses much faster than that.
+               // Note that httpfetch strictly enforces curl_parallel_limit
+               // but at no inter-thread communication cost. This however
+               // doesn't help with the aforementioned inefficiencies.
+               // The signifance of 84 is that it is 2*6*9 in base 13.
+               m_httpfetch_active_limit = g_settings->getS32("curl_parallel_limit");
+               m_httpfetch_active_limit = MYMAX(m_httpfetch_active_limit, 84);
+
+               // Write a list of hashes that we need. This will be POSTed
+               // to the server using Content-Type: application/octet-stream
+               std::string required_hash_set = serializeRequiredHashSet();
+
+               // minor fixme: this loop ignores m_httpfetch_active_limit
+
+               // another minor fixme, unlikely to matter in normal usage:
+               // these index.mth fetches do (however) count against
+               // m_httpfetch_active_limit when starting actual media file
+               // requests, so if there are lots of remote servers that are
+               // not responding, those will stall new media file transfers.
+
+               for (u32 i = 0; i < m_remotes.size(); ++i) {
+                       assert(m_httpfetch_next_id == i);
+
+                       RemoteServerStatus *remote = m_remotes[i];
+                       actionstream << "Client: Contacting remote server \""
+                               << remote->baseurl << "\"" << std::endl;
+
+                       HTTPFetchRequest fetch_request;
+                       fetch_request.url =
+                               remote->baseurl + MTHASHSET_FILE_NAME;
+                       fetch_request.caller = m_httpfetch_caller;
+                       fetch_request.request_id = m_httpfetch_next_id; // == i
+                       fetch_request.timeout = m_httpfetch_timeout;
+                       fetch_request.connect_timeout = m_httpfetch_timeout;
+                       fetch_request.post_data = required_hash_set;
+                       fetch_request.extra_headers.emplace_back(
+                               "Content-Type: application/octet-stream");
+                       httpfetch_async(fetch_request);
+
+                       m_httpfetch_active++;
+                       m_httpfetch_next_id++;
+                       m_outstanding_hash_sets++;
+               }
+       }
+}
+
+void ClientMediaDownloader::remoteHashSetReceived(
+               const HTTPFetchResult &fetch_result)
+{
+       u32 remote_id = fetch_result.request_id;
+       assert(remote_id < m_remotes.size());
+       RemoteServerStatus *remote = m_remotes[remote_id];
+
+       m_outstanding_hash_sets--;
+
+       if (fetch_result.succeeded) {
+               try {
+                       // Server sent a list of file hashes that are
+                       // available on it, try to parse the list
+
+                       std::set<std::string> sha1_set;
+                       deSerializeHashSet(fetch_result.data, sha1_set);
+
+                       // Parsing succeeded: For every file that is
+                       // available on this server, add this server
+                       // to the available_remotes array
+
+                       for(std::map<std::string, FileStatus*>::iterator
+                                       it = m_files.upper_bound(m_name_bound);
+                                       it != m_files.end(); ++it) {
+                               FileStatus *f = it->second;
+                               if (!f->received && sha1_set.count(f->sha1))
+                                       f->available_remotes.push_back(remote_id);
+                       }
+               }
+               catch (SerializationError &e) {
+                       infostream << "Client: Remote server \""
+                               << remote->baseurl << "\" sent invalid hash set: "
+                               << e.what() << std::endl;
+               }
+       }
+
+       // For compatibility: If index.mth is not found, assume that the
+       // server contains files named like the original files (not their sha1)
+
+       // Do NOT check for any particular response code (e.g. 404) here,
+       // because different servers respond differently
+
+       if (!fetch_result.succeeded && !fetch_result.timeout) {
+               infostream << "Client: Enabling compatibility mode for remote "
+                       << "server \"" << remote->baseurl << "\"" << std::endl;
+               remote->request_by_filename = true;
+
+               // Assume every file is available on this server
+
+               for(std::map<std::string, FileStatus*>::iterator
+                               it = m_files.upper_bound(m_name_bound);
+                               it != m_files.end(); ++it) {
+                       FileStatus *f = it->second;
+                       if (!f->received)
+                               f->available_remotes.push_back(remote_id);
+               }
+       }
+}
+
+void ClientMediaDownloader::remoteMediaReceived(
+               const HTTPFetchResult &fetch_result,
+               Client *client)
+{
+       // Some remote server sent us a file.
+       // -> decrement number of active fetches
+       // -> mark file as received if fetch succeeded
+       // -> try to load media
+
+       std::string name;
+       {
+               std::unordered_map<unsigned long, std::string>::iterator it =
+                       m_remote_file_transfers.find(fetch_result.request_id);
+               assert(it != m_remote_file_transfers.end());
+               name = it->second;
+               m_remote_file_transfers.erase(it);
+       }
+
+       sanity_check(m_files.count(name) != 0);
+
+       FileStatus *filestatus = m_files[name];
+       sanity_check(!filestatus->received);
+       sanity_check(filestatus->current_remote >= 0);
+
+       RemoteServerStatus *remote = m_remotes[filestatus->current_remote];
+
+       filestatus->current_remote = -1;
+       remote->active_count--;
+
+       // If fetch succeeded, try to load media file
+
+       if (fetch_result.succeeded) {
+               bool success = checkAndLoad(name, filestatus->sha1,
+                               fetch_result.data, false, client);
+               if (success) {
+                       filestatus->received = true;
+                       assert(m_uncached_received_count < m_uncached_count);
+                       m_uncached_received_count++;
+               }
+       }
+}
+
+s32 ClientMediaDownloader::selectRemoteServer(FileStatus *filestatus)
+{
+       // Pre-conditions
+       assert(filestatus != NULL);
+       assert(!filestatus->received);
+       assert(filestatus->current_remote < 0);
+
+       if (filestatus->available_remotes.empty())
+               return -1;
+
+       // Of all servers that claim to provide the file (and haven't
+       // been unsuccessfully tried before), find the one with the
+       // smallest number of currently active transfers
+
+       s32 best = 0;
+       s32 best_remote_id = filestatus->available_remotes[best];
+       s32 best_active_count = m_remotes[best_remote_id]->active_count;
+
+       for (u32 i = 1; i < filestatus->available_remotes.size(); ++i) {
+               s32 remote_id = filestatus->available_remotes[i];
+               s32 active_count = m_remotes[remote_id]->active_count;
+               if (active_count < best_active_count) {
+                       best = i;
+                       best_remote_id = remote_id;
+                       best_active_count = active_count;
+               }
+       }
+
+       filestatus->available_remotes.erase(
+                       filestatus->available_remotes.begin() + best);
+
+       return best_remote_id;
+
+}
+
+void ClientMediaDownloader::startRemoteMediaTransfers()
+{
+       bool changing_name_bound = true;
+
+       for (std::map<std::string, FileStatus*>::iterator
+                       files_iter = m_files.upper_bound(m_name_bound);
+                       files_iter != m_files.end(); ++files_iter) {
+
+               // Abort if active fetch limit is exceeded
+               if (m_httpfetch_active >= m_httpfetch_active_limit)
+                       break;
+
+               const std::string &name = files_iter->first;
+               FileStatus *filestatus = files_iter->second;
+
+               if (!filestatus->received && filestatus->current_remote < 0) {
+                       // File has not been received yet and is not currently
+                       // being transferred. Choose a server for it.
+                       s32 remote_id = selectRemoteServer(filestatus);
+                       if (remote_id >= 0) {
+                               // Found a server, so start fetching
+                               RemoteServerStatus *remote =
+                                       m_remotes[remote_id];
+
+                               std::string url = remote->baseurl +
+                                       (remote->request_by_filename ? name :
+                                       hex_encode(filestatus->sha1));
+                               verbosestream << "Client: "
+                                       << "Requesting remote media file "
+                                       << "\"" << name << "\" "
+                                       << "\"" << url << "\"" << std::endl;
+
+                               HTTPFetchRequest fetch_request;
+                               fetch_request.url = url;
+                               fetch_request.caller = m_httpfetch_caller;
+                               fetch_request.request_id = m_httpfetch_next_id;
+                               fetch_request.timeout = 0; // no data timeout!
+                               fetch_request.connect_timeout =
+                                       m_httpfetch_timeout;
+                               httpfetch_async(fetch_request);
+
+                               m_remote_file_transfers.insert(std::make_pair(
+                                                       m_httpfetch_next_id,
+                                                       name));
+
+                               filestatus->current_remote = remote_id;
+                               remote->active_count++;
+                               m_httpfetch_active++;
+                               m_httpfetch_next_id++;
+                       }
+               }
+
+               if (filestatus->received ||
+                               (filestatus->current_remote < 0 &&
+                                !m_outstanding_hash_sets)) {
+                       // If we arrive here, we conclusively know that we
+                       // won't fetch this file from a remote server in the
+                       // future. So update the name bound if possible.
+                       if (changing_name_bound)
+                               m_name_bound = name;
+               }
+               else
+                       changing_name_bound = false;
+       }
+
+}
+
+void ClientMediaDownloader::startConventionalTransfers(Client *client)
+{
+       assert(m_httpfetch_active == 0);        // pre-condition
+
+       if (m_uncached_received_count != m_uncached_count) {
+               // Some media files have not been received yet, use the
+               // conventional slow method (minetest protocol) to get them
+               std::vector<std::string> file_requests;
+               for (auto &file : m_files) {
+                       if (!file.second->received)
+                               file_requests.push_back(file.first);
+               }
+               assert((s32) file_requests.size() ==
+                               m_uncached_count - m_uncached_received_count);
+               client->request_media(file_requests);
+       }
+}
+
+void ClientMediaDownloader::conventionalTransferDone(
+               const std::string &name,
+               const std::string &data,
+               Client *client)
+{
+       // Check that file was announced
+       std::map<std::string, FileStatus*>::iterator
+               file_iter = m_files.find(name);
+       if (file_iter == m_files.end()) {
+               errorstream << "Client: server sent media file that was"
+                       << "not announced, ignoring it: \"" << name << "\""
+                       << std::endl;
+               return;
+       }
+       FileStatus *filestatus = file_iter->second;
+       assert(filestatus != NULL);
+
+       // Check that file hasn't already been received
+       if (filestatus->received) {
+               errorstream << "Client: server sent media file that we already"
+                       << "received, ignoring it: \"" << name << "\""
+                       << std::endl;
+               return;
+       }
+
+       // Mark file as received, regardless of whether loading it works and
+       // whether the checksum matches (because at this point there is no
+       // other server that could send a replacement)
+       filestatus->received = true;
+       assert(m_uncached_received_count < m_uncached_count);
+       m_uncached_received_count++;
+
+       // Check that received file matches announced checksum
+       // If so, load it
+       checkAndLoad(name, filestatus->sha1, data, false, client);
+}
+
+bool ClientMediaDownloader::checkAndLoad(
+               const std::string &name, const std::string &sha1,
+               const std::string &data, bool is_from_cache, Client *client)
+{
+       const char *cached_or_received = is_from_cache ? "cached" : "received";
+       const char *cached_or_received_uc = is_from_cache ? "Cached" : "Received";
+       std::string sha1_hex = hex_encode(sha1);
+
+       // Compute actual checksum of data
+       std::string data_sha1;
+       {
+               SHA1 data_sha1_calculator;
+               data_sha1_calculator.addBytes(data.c_str(), data.size());
+               unsigned char *data_tmpdigest = data_sha1_calculator.getDigest();
+               data_sha1.assign((char*) data_tmpdigest, 20);
+               free(data_tmpdigest);
+       }
+
+       // Check that received file matches announced checksum
+       if (data_sha1 != sha1) {
+               std::string data_sha1_hex = hex_encode(data_sha1);
+               infostream << "Client: "
+                       << cached_or_received_uc << " media file "
+                       << sha1_hex << " \"" << name << "\" "
+                       << "mismatches actual checksum " << data_sha1_hex
+                       << std::endl;
+               return false;
+       }
+
+       // Checksum is ok, try loading the file
+       bool success = client->loadMedia(data, name);
+       if (!success) {
+               infostream << "Client: "
+                       << "Failed to load " << cached_or_received << " media: "
+                       << sha1_hex << " \"" << name << "\""
+                       << std::endl;
+               return false;
+       }
+
+       verbosestream << "Client: "
+               << "Loaded " << cached_or_received << " media: "
+               << sha1_hex << " \"" << name << "\""
+               << std::endl;
+
+       // Update cache (unless we just loaded the file from the cache)
+       if (!is_from_cache)
+               m_media_cache.update(sha1_hex, data);
+
+       return true;
+}
+
+
+/*
+       Minetest Hashset File Format
+
+       All values are stored in big-endian byte order.
+       [u32] signature: 'MTHS'
+       [u16] version: 1
+       For each hash in set:
+               [u8*20] SHA1 hash
+
+       Version changes:
+       1 - Initial version
+*/
+
+std::string ClientMediaDownloader::serializeRequiredHashSet()
+{
+       std::ostringstream os(std::ios::binary);
+
+       writeU32(os, MTHASHSET_FILE_SIGNATURE); // signature
+       writeU16(os, 1);                        // version
+
+       // Write list of hashes of files that have not been
+       // received (found in cache) yet
+       for (std::map<std::string, FileStatus*>::iterator
+                       it = m_files.begin();
+                       it != m_files.end(); ++it) {
+               if (!it->second->received) {
+                       FATAL_ERROR_IF(it->second->sha1.size() != 20, "Invalid SHA1 size");
+                       os << it->second->sha1;
+               }
+       }
+
+       return os.str();
+}
+
+void ClientMediaDownloader::deSerializeHashSet(const std::string &data,
+               std::set<std::string> &result)
+{
+       if (data.size() < 6 || data.size() % 20 != 6) {
+               throw SerializationError(
+                               "ClientMediaDownloader::deSerializeHashSet: "
+                               "invalid hash set file size");
+       }
+
+       const u8 *data_cstr = (const u8*) data.c_str();
+
+       u32 signature = readU32(&data_cstr[0]);
+       if (signature != MTHASHSET_FILE_SIGNATURE) {
+               throw SerializationError(
+                               "ClientMediaDownloader::deSerializeHashSet: "
+                               "invalid hash set file signature");
+       }
+
+       u16 version = readU16(&data_cstr[4]);
+       if (version != 1) {
+               throw SerializationError(
+                               "ClientMediaDownloader::deSerializeHashSet: "
+                               "unsupported hash set file version");
+       }
+
+       for (u32 pos = 6; pos < data.size(); pos += 20) {
+               result.insert(data.substr(pos, 20));
+       }
+}
diff --git a/src/client/clientmedia.h b/src/client/clientmedia.h
new file mode 100644 (file)
index 0000000..b08b83e
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "irrlichttypes.h"
+#include "filecache.h"
+#include <ostream>
+#include <map>
+#include <set>
+#include <vector>
+#include <unordered_map>
+
+class Client;
+struct HTTPFetchResult;
+
+#define MTHASHSET_FILE_SIGNATURE 0x4d544853 // 'MTHS'
+#define MTHASHSET_FILE_NAME "index.mth"
+
+class ClientMediaDownloader
+{
+public:
+       ClientMediaDownloader();
+       ~ClientMediaDownloader();
+
+       float getProgress() const {
+               if (m_uncached_count >= 1)
+                       return 1.0f * m_uncached_received_count /
+                               m_uncached_count;
+
+               return 0.0f;
+       }
+
+       bool isStarted() const {
+               return m_initial_step_done;
+       }
+
+       // If this returns true, the downloader is done and can be deleted
+       bool isDone() const {
+               return m_initial_step_done &&
+                       m_uncached_received_count == m_uncached_count;
+       }
+
+       // Add a file to the list of required file (but don't fetch it yet)
+       void addFile(const std::string &name, const std::string &sha1);
+
+       // Add a remote server to the list; ignored if not built with cURL
+       void addRemoteServer(const std::string &baseurl);
+
+       // Steps the media downloader:
+       // - May load media into client by calling client->loadMedia()
+       // - May check media cache for files
+       // - May add files to media cache
+       // - May start remote transfers by calling httpfetch_async
+       // - May check for completion of current remote transfers
+       // - May start conventional transfers by calling client->request_media()
+       // - May inform server that all media has been loaded
+       //   by calling client->received_media()
+       // After step has been called once, don't call addFile/addRemoteServer.
+       void step(Client *client);
+
+       // Must be called for each file received through TOCLIENT_MEDIA
+       void conventionalTransferDone(
+                       const std::string &name,
+                       const std::string &data,
+                       Client *client);
+
+private:
+       struct FileStatus {
+               bool received;
+               std::string sha1;
+               s32 current_remote;
+               std::vector<s32> available_remotes;
+       };
+
+       struct RemoteServerStatus {
+               std::string baseurl;
+               s32 active_count;
+               bool request_by_filename;
+       };
+
+       void initialStep(Client *client);
+       void remoteHashSetReceived(const HTTPFetchResult &fetch_result);
+       void remoteMediaReceived(const HTTPFetchResult &fetch_result,
+                       Client *client);
+       s32 selectRemoteServer(FileStatus *filestatus);
+       void startRemoteMediaTransfers();
+       void startConventionalTransfers(Client *client);
+
+       bool checkAndLoad(const std::string &name, const std::string &sha1,
+                       const std::string &data, bool is_from_cache,
+                       Client *client);
+
+       std::string serializeRequiredHashSet();
+       static void deSerializeHashSet(const std::string &data,
+                       std::set<std::string> &result);
+
+       // Maps filename to file status
+       std::map<std::string, FileStatus*> m_files;
+
+       // Array of remote media servers
+       std::vector<RemoteServerStatus*> m_remotes;
+
+       // Filesystem-based media cache
+       FileCache m_media_cache;
+
+       // Has an attempt been made to load media files from the file cache?
+       // Have hash sets been requested from remote servers?
+       bool m_initial_step_done = false;
+
+       // Total number of media files to load
+       s32 m_uncached_count = 0;
+
+       // Number of media files that have been received
+       s32 m_uncached_received_count = 0;
+
+       // Status of remote transfers
+       unsigned long m_httpfetch_caller;
+       unsigned long m_httpfetch_next_id = 0;
+       long m_httpfetch_timeout = 0;
+       s32 m_httpfetch_active = 0;
+       s32 m_httpfetch_active_limit = 0;
+       s32 m_outstanding_hash_sets = 0;
+       std::unordered_map<unsigned long, std::string> m_remote_file_transfers;
+
+       // All files up to this name have either been received from a
+       // remote server or failed on all remote servers, so those files
+       // don't need to be looked at again
+       // (use m_files.upper_bound(m_name_bound) to get an iterator)
+       std::string m_name_bound = "";
+
+};
diff --git a/src/client/clientobject.cpp b/src/client/clientobject.cpp
new file mode 100644 (file)
index 0000000..f4b6920
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 "clientobject.h"
+#include "debug.h"
+#include "porting.h"
+
+/*
+       ClientActiveObject
+*/
+
+ClientActiveObject::ClientActiveObject(u16 id, Client *client,
+               ClientEnvironment *env):
+       ActiveObject(id),
+       m_client(client),
+       m_env(env)
+{
+}
+
+ClientActiveObject::~ClientActiveObject()
+{
+       removeFromScene(true);
+}
+
+ClientActiveObject* ClientActiveObject::create(ActiveObjectType type,
+               Client *client, ClientEnvironment *env)
+{
+       // Find factory function
+       auto n = m_types.find(type);
+       if (n == m_types.end()) {
+               // If factory is not found, just return.
+               warningstream << "ClientActiveObject: No factory for type="
+                               << (int)type << std::endl;
+               return NULL;
+       }
+
+       Factory f = n->second;
+       ClientActiveObject *object = (*f)(client, env);
+       return object;
+}
+
+void ClientActiveObject::registerType(u16 type, Factory f)
+{
+       auto n = m_types.find(type);
+       if (n != m_types.end())
+               return;
+       m_types[type] = f;
+}
+
+
diff --git a/src/client/clientobject.h b/src/client/clientobject.h
new file mode 100644 (file)
index 0000000..9377d1e
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "activeobject.h"
+#include <unordered_map>
+
+class ClientEnvironment;
+class ITextureSource;
+class Client;
+class IGameDef;
+class LocalPlayer;
+struct ItemStack;
+class WieldMeshSceneNode;
+
+class ClientActiveObject : public ActiveObject
+{
+public:
+       ClientActiveObject(u16 id, Client *client, ClientEnvironment *env);
+       virtual ~ClientActiveObject();
+
+       virtual void addToScene(ITextureSource *tsrc) {};
+       virtual void removeFromScene(bool permanent) {}
+       // 0 <= light_at_pos <= LIGHT_SUN
+       virtual void updateLight(u8 light_at_pos){}
+       virtual void updateLightNoCheck(u8 light_at_pos){}
+       virtual v3s16 getLightPosition(){return v3s16(0,0,0);}
+       virtual bool getCollisionBox(aabb3f *toset) const { return false; }
+       virtual bool getSelectionBox(aabb3f *toset) const { return false; }
+       virtual bool collideWithObjects() const { return false; }
+       virtual v3f getPosition(){ return v3f(0,0,0); }
+       virtual float getYaw() const { return 0; }
+       virtual scene::ISceneNode *getSceneNode() { return NULL; }
+       virtual scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() { return NULL; }
+       virtual bool isLocalPlayer() const {return false;}
+       virtual ClientActiveObject *getParent() const { return nullptr; };
+       virtual void setAttachments() {}
+       virtual bool doShowSelectionBox(){return true;}
+
+       // Step object in time
+       virtual void step(float dtime, ClientEnvironment *env){}
+
+       // Process a message sent by the server side object
+       virtual void processMessage(const std::string &data){}
+
+       virtual std::string infoText() {return "";}
+       virtual std::string debugInfoText() {return "";}
+
+       /*
+               This takes the return value of
+               ServerActiveObject::getClientInitializationData
+       */
+       virtual void initialize(const std::string &data){}
+
+       // Create a certain type of ClientActiveObject
+       static ClientActiveObject* create(ActiveObjectType type, Client *client,
+                       ClientEnvironment *env);
+
+       // If returns true, punch will not be sent to the server
+       virtual bool directReportPunch(v3f dir, const ItemStack *punchitem=NULL,
+                       float time_from_last_punch=1000000)
+       { return false; }
+
+protected:
+       // Used for creating objects based on type
+       typedef ClientActiveObject* (*Factory)(Client *client, ClientEnvironment *env);
+       static void registerType(u16 type, Factory f);
+       Client *m_client;
+       ClientEnvironment *m_env;
+private:
+       // Used for creating objects based on type
+       static std::unordered_map<u16, Factory> m_types;
+};
+
+struct DistanceSortedActiveObject
+{
+       ClientActiveObject *obj;
+       f32 d;
+
+       DistanceSortedActiveObject(ClientActiveObject *a_obj, f32 a_d)
+       {
+               obj = a_obj;
+               d = a_d;
+       }
+
+       bool operator < (const DistanceSortedActiveObject &other) const
+       {
+               return d < other.d;
+       }
+};
diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp
new file mode 100644 (file)
index 0000000..13051f3
--- /dev/null
@@ -0,0 +1,386 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 "client/renderingengine.h"
+#include "clouds.h"
+#include "noise.h"
+#include "constants.h"
+#include "debug.h"
+#include "profiler.h"
+#include "settings.h"
+#include <cmath>
+
+
+// Menu clouds are created later
+class Clouds;
+Clouds *g_menuclouds = NULL;
+irr::scene::ISceneManager *g_menucloudsmgr = NULL;
+
+// Constant for now
+static constexpr const float cloud_size = BS * 64.0f;
+
+static void cloud_3d_setting_changed(const std::string &settingname, void *data)
+{
+       ((Clouds *)data)->readSettings();
+}
+
+Clouds::Clouds(scene::ISceneManager* mgr,
+               s32 id,
+               u32 seed
+):
+       scene::ISceneNode(mgr->getRootSceneNode(), mgr, id),
+       m_seed(seed)
+{
+       m_material.setFlag(video::EMF_LIGHTING, false);
+       //m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
+       m_material.setFlag(video::EMF_BACK_FACE_CULLING, true);
+       m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
+       m_material.setFlag(video::EMF_FOG_ENABLE, true);
+       m_material.setFlag(video::EMF_ANTI_ALIASING, true);
+       //m_material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
+       m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+
+       m_params.height        = 120;
+       m_params.density       = 0.4f;
+       m_params.thickness     = 16.0f;
+       m_params.color_bright  = video::SColor(229, 240, 240, 255);
+       m_params.color_ambient = video::SColor(255, 0, 0, 0);
+       m_params.speed         = v2f(0.0f, -2.0f);
+
+       readSettings();
+       g_settings->registerChangedCallback("enable_3d_clouds",
+               &cloud_3d_setting_changed, this);
+
+       updateBox();
+}
+
+Clouds::~Clouds()
+{
+       g_settings->deregisterChangedCallback("enable_3d_clouds",
+               &cloud_3d_setting_changed, this);
+}
+
+void Clouds::OnRegisterSceneNode()
+{
+       if(IsVisible)
+       {
+               SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT);
+               //SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID);
+       }
+
+       ISceneNode::OnRegisterSceneNode();
+}
+
+void Clouds::render()
+{
+
+       if (m_params.density <= 0.0f)
+               return; // no need to do anything
+
+       video::IVideoDriver* driver = SceneManager->getVideoDriver();
+
+       if(SceneManager->getSceneNodeRenderPass() != scene::ESNRP_TRANSPARENT)
+       //if(SceneManager->getSceneNodeRenderPass() != scene::ESNRP_SOLID)
+               return;
+
+       ScopeProfiler sp(g_profiler, "Rendering of clouds, avg", SPT_AVG);
+
+       int num_faces_to_draw = m_enable_3d ? 6 : 1;
+
+       m_material.setFlag(video::EMF_BACK_FACE_CULLING, m_enable_3d);
+
+       driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
+       driver->setMaterial(m_material);
+
+       /*
+               Clouds move from Z+ towards Z-
+       */
+
+       const float cloud_full_radius = cloud_size * m_cloud_radius_i;
+
+       v2f camera_pos_2d(m_camera_pos.X, m_camera_pos.Z);
+       // Position of cloud noise origin from the camera
+       v2f cloud_origin_from_camera_f = m_origin - camera_pos_2d;
+       // The center point of drawing in the noise
+       v2f center_of_drawing_in_noise_f = -cloud_origin_from_camera_f;
+       // The integer center point of drawing in the noise
+       v2s16 center_of_drawing_in_noise_i(
+               std::floor(center_of_drawing_in_noise_f.X / cloud_size),
+               std::floor(center_of_drawing_in_noise_f.Y / cloud_size)
+       );
+
+       // The world position of the integer center point of drawing in the noise
+       v2f world_center_of_drawing_in_noise_f = v2f(
+               center_of_drawing_in_noise_i.X * cloud_size,
+               center_of_drawing_in_noise_i.Y * cloud_size
+       ) + m_origin;
+
+       /*video::SColor c_top(128,b*240,b*240,b*255);
+       video::SColor c_side_1(128,b*230,b*230,b*255);
+       video::SColor c_side_2(128,b*220,b*220,b*245);
+       video::SColor c_bottom(128,b*205,b*205,b*230);*/
+       video::SColorf c_top_f(m_color);
+       video::SColorf c_side_1_f(m_color);
+       video::SColorf c_side_2_f(m_color);
+       video::SColorf c_bottom_f(m_color);
+       c_side_1_f.r *= 0.95;
+       c_side_1_f.g *= 0.95;
+       c_side_1_f.b *= 0.95;
+       c_side_2_f.r *= 0.90;
+       c_side_2_f.g *= 0.90;
+       c_side_2_f.b *= 0.90;
+       c_bottom_f.r *= 0.80;
+       c_bottom_f.g *= 0.80;
+       c_bottom_f.b *= 0.80;
+       video::SColor c_top = c_top_f.toSColor();
+       video::SColor c_side_1 = c_side_1_f.toSColor();
+       video::SColor c_side_2 = c_side_2_f.toSColor();
+       video::SColor c_bottom = c_bottom_f.toSColor();
+
+       // Get fog parameters for setting them back later
+       video::SColor fog_color(0,0,0,0);
+       video::E_FOG_TYPE fog_type = video::EFT_FOG_LINEAR;
+       f32 fog_start = 0;
+       f32 fog_end = 0;
+       f32 fog_density = 0;
+       bool fog_pixelfog = false;
+       bool fog_rangefog = false;
+       driver->getFog(fog_color, fog_type, fog_start, fog_end, fog_density,
+                       fog_pixelfog, fog_rangefog);
+
+       // Set our own fog
+       driver->setFog(fog_color, fog_type, cloud_full_radius * 0.5,
+                       cloud_full_radius*1.2, fog_density, fog_pixelfog, fog_rangefog);
+
+       // Read noise
+
+       bool *grid = new bool[m_cloud_radius_i * 2 * m_cloud_radius_i * 2];
+
+
+       for(s16 zi = -m_cloud_radius_i; zi < m_cloud_radius_i; zi++) {
+               u32 si = (zi + m_cloud_radius_i) * m_cloud_radius_i * 2 + m_cloud_radius_i;
+
+               for (s16 xi = -m_cloud_radius_i; xi < m_cloud_radius_i; xi++) {
+                       u32 i = si + xi;
+
+                       grid[i] = gridFilled(
+                               xi + center_of_drawing_in_noise_i.X,
+                               zi + center_of_drawing_in_noise_i.Y
+                       );
+               }
+       }
+
+#define GETINDEX(x, z, radius) (((z)+(radius))*(radius)*2 + (x)+(radius))
+#define INAREA(x, z, radius) \
+       ((x) >= -(radius) && (x) < (radius) && (z) >= -(radius) && (z) < (radius))
+
+       for (s16 zi0= -m_cloud_radius_i; zi0 < m_cloud_radius_i; zi0++)
+       for (s16 xi0= -m_cloud_radius_i; xi0 < m_cloud_radius_i; xi0++)
+       {
+               s16 zi = zi0;
+               s16 xi = xi0;
+               // Draw from front to back (needed for transparency)
+               /*if(zi <= 0)
+                       zi = -m_cloud_radius_i - zi;
+               if(xi <= 0)
+                       xi = -m_cloud_radius_i - xi;*/
+               // Draw from back to front
+               if(zi >= 0)
+                       zi = m_cloud_radius_i - zi - 1;
+               if(xi >= 0)
+                       xi = m_cloud_radius_i - xi - 1;
+
+               u32 i = GETINDEX(xi, zi, m_cloud_radius_i);
+
+               if (!grid[i])
+                       continue;
+
+               v2f p0 = v2f(xi,zi)*cloud_size + world_center_of_drawing_in_noise_f;
+
+               video::S3DVertex v[4] = {
+                       video::S3DVertex(0,0,0, 0,0,0, c_top, 0, 1),
+                       video::S3DVertex(0,0,0, 0,0,0, c_top, 1, 1),
+                       video::S3DVertex(0,0,0, 0,0,0, c_top, 1, 0),
+                       video::S3DVertex(0,0,0, 0,0,0, c_top, 0, 0)
+               };
+
+               /*if(zi <= 0 && xi <= 0){
+                       v[0].Color.setBlue(255);
+                       v[1].Color.setBlue(255);
+                       v[2].Color.setBlue(255);
+                       v[3].Color.setBlue(255);
+               }*/
+
+               f32 rx = cloud_size / 2.0f;
+               // if clouds are flat, the top layer should be at the given height
+               f32 ry = m_enable_3d ? m_params.thickness * BS : 0.0f;
+               f32 rz = cloud_size / 2;
+
+               for(int i=0; i<num_faces_to_draw; i++)
+               {
+                       switch(i)
+                       {
+                       case 0: // top
+                               for (video::S3DVertex &vertex : v) {
+                                       vertex.Normal.set(0,1,0);
+                               }
+                               v[0].Pos.set(-rx, ry,-rz);
+                               v[1].Pos.set(-rx, ry, rz);
+                               v[2].Pos.set( rx, ry, rz);
+                               v[3].Pos.set( rx, ry,-rz);
+                               break;
+                       case 1: // back
+                               if (INAREA(xi, zi - 1, m_cloud_radius_i)) {
+                                       u32 j = GETINDEX(xi, zi - 1, m_cloud_radius_i);
+                                       if(grid[j])
+                                               continue;
+                               }
+                               for (video::S3DVertex &vertex : v) {
+                                       vertex.Color = c_side_1;
+                                       vertex.Normal.set(0,0,-1);
+                               }
+                               v[0].Pos.set(-rx, ry,-rz);
+                               v[1].Pos.set( rx, ry,-rz);
+                               v[2].Pos.set( rx,  0,-rz);
+                               v[3].Pos.set(-rx,  0,-rz);
+                               break;
+                       case 2: //right
+                               if (INAREA(xi + 1, zi, m_cloud_radius_i)) {
+                                       u32 j = GETINDEX(xi+1, zi, m_cloud_radius_i);
+                                       if(grid[j])
+                                               continue;
+                               }
+                               for (video::S3DVertex &vertex : v) {
+                                       vertex.Color = c_side_2;
+                                       vertex.Normal.set(1,0,0);
+                               }
+                               v[0].Pos.set( rx, ry,-rz);
+                               v[1].Pos.set( rx, ry, rz);
+                               v[2].Pos.set( rx,  0, rz);
+                               v[3].Pos.set( rx,  0,-rz);
+                               break;
+                       case 3: // front
+                               if (INAREA(xi, zi + 1, m_cloud_radius_i)) {
+                                       u32 j = GETINDEX(xi, zi + 1, m_cloud_radius_i);
+                                       if(grid[j])
+                                               continue;
+                               }
+                               for (video::S3DVertex &vertex : v) {
+                                       vertex.Color = c_side_1;
+                                       vertex.Normal.set(0,0,-1);
+                               }
+                               v[0].Pos.set( rx, ry, rz);
+                               v[1].Pos.set(-rx, ry, rz);
+                               v[2].Pos.set(-rx,  0, rz);
+                               v[3].Pos.set( rx,  0, rz);
+                               break;
+                       case 4: // left
+                               if (INAREA(xi-1, zi, m_cloud_radius_i)) {
+                                       u32 j = GETINDEX(xi-1, zi, m_cloud_radius_i);
+                                       if(grid[j])
+                                               continue;
+                               }
+                               for (video::S3DVertex &vertex : v) {
+                                       vertex.Color = c_side_2;
+                                       vertex.Normal.set(-1,0,0);
+                               }
+                               v[0].Pos.set(-rx, ry, rz);
+                               v[1].Pos.set(-rx, ry,-rz);
+                               v[2].Pos.set(-rx,  0,-rz);
+                               v[3].Pos.set(-rx,  0, rz);
+                               break;
+                       case 5: // bottom
+                               for (video::S3DVertex &vertex : v) {
+                                       vertex.Color = c_bottom;
+                                       vertex.Normal.set(0,-1,0);
+                               }
+                               v[0].Pos.set( rx,  0, rz);
+                               v[1].Pos.set(-rx,  0, rz);
+                               v[2].Pos.set(-rx,  0,-rz);
+                               v[3].Pos.set( rx,  0,-rz);
+                               break;
+                       }
+
+                       v3f pos(p0.X, m_params.height * BS, p0.Y);
+                       pos -= intToFloat(m_camera_offset, BS);
+
+                       for (video::S3DVertex &vertex : v)
+                               vertex.Pos += pos;
+                       u16 indices[] = {0,1,2,2,3,0};
+                       driver->drawVertexPrimitiveList(v, 4, indices, 2,
+                                       video::EVT_STANDARD, scene::EPT_TRIANGLES, video::EIT_16BIT);
+               }
+       }
+
+       delete[] grid;
+
+       // Restore fog settings
+       driver->setFog(fog_color, fog_type, fog_start, fog_end, fog_density,
+                       fog_pixelfog, fog_rangefog);
+}
+
+void Clouds::step(float dtime)
+{
+       m_origin = m_origin + dtime * BS * m_params.speed;
+}
+
+void Clouds::update(const v3f &camera_p, const video::SColorf &color_diffuse)
+{
+       m_camera_pos = camera_p;
+       m_color.r = MYMIN(MYMAX(color_diffuse.r * m_params.color_bright.getRed(),
+                       m_params.color_ambient.getRed()), 255) / 255.0f;
+       m_color.g = MYMIN(MYMAX(color_diffuse.g * m_params.color_bright.getGreen(),
+                       m_params.color_ambient.getGreen()), 255) / 255.0f;
+       m_color.b = MYMIN(MYMAX(color_diffuse.b * m_params.color_bright.getBlue(),
+                       m_params.color_ambient.getBlue()), 255) / 255.0f;
+       m_color.a = m_params.color_bright.getAlpha() / 255.0f;
+
+       // is the camera inside the cloud mesh?
+       m_camera_inside_cloud = false; // default
+       if (m_enable_3d) {
+               float camera_height = camera_p.Y;
+               if (camera_height >= m_box.MinEdge.Y &&
+                               camera_height <= m_box.MaxEdge.Y) {
+                       v2f camera_in_noise;
+                       camera_in_noise.X = floor((camera_p.X - m_origin.X) / cloud_size + 0.5);
+                       camera_in_noise.Y = floor((camera_p.Z - m_origin.Y) / cloud_size + 0.5);
+                       bool filled = gridFilled(camera_in_noise.X, camera_in_noise.Y);
+                       m_camera_inside_cloud = filled;
+               }
+       }
+}
+
+void Clouds::readSettings()
+{
+       m_cloud_radius_i = g_settings->getU16("cloud_radius");
+       m_enable_3d = g_settings->getBool("enable_3d_clouds");
+}
+
+bool Clouds::gridFilled(int x, int y) const
+{
+       float cloud_size_noise = cloud_size / (BS * 200.f);
+       float noise = noise2d_perlin(
+                       (float)x * cloud_size_noise,
+                       (float)y * cloud_size_noise,
+                       m_seed, 3, 0.5);
+       // normalize to 0..1 (given 3 octaves)
+       static constexpr const float noise_bound = 1.0f + 0.5f + 0.25f;
+       float density = noise / noise_bound * 0.5f + 0.5f;
+       return (density < m_params.density);
+}
diff --git a/src/client/clouds.h b/src/client/clouds.h
new file mode 100644 (file)
index 0000000..a4d810f
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include <iostream>
+#include "constants.h"
+#include "cloudparams.h"
+
+// Menu clouds
+class Clouds;
+extern Clouds *g_menuclouds;
+
+// Scene manager used for menu clouds
+namespace irr{namespace scene{class ISceneManager;}}
+extern irr::scene::ISceneManager *g_menucloudsmgr;
+
+class Clouds : public scene::ISceneNode
+{
+public:
+       Clouds(scene::ISceneManager* mgr,
+                       s32 id,
+                       u32 seed
+       );
+
+       ~Clouds();
+
+       /*
+               ISceneNode methods
+       */
+
+       virtual void OnRegisterSceneNode();
+
+       virtual void render();
+
+       virtual const aabb3f &getBoundingBox() const
+       {
+               return m_box;
+       }
+
+       virtual u32 getMaterialCount() const
+       {
+               return 1;
+       }
+
+       virtual video::SMaterial& getMaterial(u32 i)
+       {
+               return m_material;
+       }
+
+       /*
+               Other stuff
+       */
+
+       void step(float dtime);
+
+       void update(const v3f &camera_p, const video::SColorf &color);
+
+       void updateCameraOffset(const v3s16 &camera_offset)
+       {
+               m_camera_offset = camera_offset;
+               updateBox();
+       }
+
+       void readSettings();
+
+       void setDensity(float density)
+       {
+               m_params.density = density;
+               // currently does not need bounding
+       }
+
+       void setColorBright(const video::SColor &color_bright)
+       {
+               m_params.color_bright = color_bright;
+       }
+
+       void setColorAmbient(const video::SColor &color_ambient)
+       {
+               m_params.color_ambient = color_ambient;
+       }
+
+       void setHeight(float height)
+       {
+               m_params.height = height; // add bounding when necessary
+               updateBox();
+       }
+
+       void setSpeed(v2f speed)
+       {
+               m_params.speed = speed;
+       }
+
+       void setThickness(float thickness)
+       {
+               m_params.thickness = thickness;
+               updateBox();
+       }
+
+       bool isCameraInsideCloud() const { return m_camera_inside_cloud; }
+
+       const video::SColor getColor() const { return m_color.toSColor(); }
+
+private:
+       void updateBox()
+       {
+               float height_bs    = m_params.height    * BS;
+               float thickness_bs = m_params.thickness * BS;
+               m_box = aabb3f(-BS * 1000000.0f, height_bs - BS * m_camera_offset.Y, -BS * 1000000.0f,
+                               BS * 1000000.0f, height_bs + thickness_bs - BS * m_camera_offset.Y, BS * 1000000.0f);
+       }
+
+       bool gridFilled(int x, int y) const;
+
+       video::SMaterial m_material;
+       aabb3f m_box;
+       u16 m_cloud_radius_i;
+       bool m_enable_3d;
+       u32 m_seed;
+       v3f m_camera_pos;
+       v2f m_origin;
+       v3s16 m_camera_offset;
+       video::SColorf m_color = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f);
+       CloudParams m_params;
+       bool m_camera_inside_cloud = false;
+
+};
diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp
new file mode 100644 (file)
index 0000000..db59ae5
--- /dev/null
@@ -0,0 +1,1611 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 <ICameraSceneNode.h>
+#include <ITextSceneNode.h>
+#include <IBillboardSceneNode.h>
+#include <IMeshManipulator.h>
+#include <IAnimatedMeshSceneNode.h>
+#include "content_cao.h"
+#include "util/numeric.h" // For IntervalLimiter
+#include "util/serialize.h"
+#include "util/basic_macros.h"
+#include "client/sound.h"
+#include "client/tile.h"
+#include "environment.h"
+#include "collision.h"
+#include "settings.h"
+#include "serialization.h" // For decompressZlib
+#include "clientobject.h"
+#include "mesh.h"
+#include "itemdef.h"
+#include "tool.h"
+#include "content_cso.h"
+#include "sound.h"
+#include "nodedef.h"
+#include "localplayer.h"
+#include "map.h"
+#include "camera.h" // CameraModes
+#include "client.h"
+#include "wieldmesh.h"
+#include <algorithm>
+#include <cmath>
+#include "client/renderingengine.h"
+
+class Settings;
+struct ToolCapabilities;
+
+std::unordered_map<u16, ClientActiveObject::Factory> ClientActiveObject::m_types;
+
+template<typename T>
+void SmoothTranslator<T>::init(T current)
+{
+       val_old = current;
+       val_current = current;
+       val_target = current;
+       anim_time = 0;
+       anim_time_counter = 0;
+       aim_is_end = true;
+}
+
+template<typename T>
+void SmoothTranslator<T>::update(T new_target, bool is_end_position, float update_interval)
+{
+       aim_is_end = is_end_position;
+       val_old = val_current;
+       val_target = new_target;
+       if (update_interval > 0) {
+               anim_time = update_interval;
+       } else {
+               if (anim_time < 0.001 || anim_time > 1.0)
+                       anim_time = anim_time_counter;
+               else
+                       anim_time = anim_time * 0.9 + anim_time_counter * 0.1;
+       }
+       anim_time_counter = 0;
+}
+
+template<typename T>
+void SmoothTranslator<T>::translate(f32 dtime)
+{
+       anim_time_counter = anim_time_counter + dtime;
+       T val_diff = val_target - val_old;
+       f32 moveratio = 1.0;
+       if (anim_time > 0.001)
+               moveratio = anim_time_counter / anim_time;
+       f32 move_end = aim_is_end ? 1.0 : 1.5;
+
+       // Move a bit less than should, to avoid oscillation
+       moveratio = std::min(moveratio * 0.8f, move_end);
+       val_current = val_old + val_diff * moveratio;
+}
+
+void SmoothTranslatorWrapped::translate(f32 dtime)
+{
+       anim_time_counter = anim_time_counter + dtime;
+       f32 val_diff = std::abs(val_target - val_old);
+       if (val_diff > 180.f)
+               val_diff = 360.f - val_diff;
+
+       f32 moveratio = 1.0;
+       if (anim_time > 0.001)
+               moveratio = anim_time_counter / anim_time;
+       f32 move_end = aim_is_end ? 1.0 : 1.5;
+
+       // Move a bit less than should, to avoid oscillation
+       moveratio = std::min(moveratio * 0.8f, move_end);
+       wrappedApproachShortest(val_current, val_target,
+               val_diff * moveratio, 360.f);
+}
+
+void SmoothTranslatorWrappedv3f::translate(f32 dtime)
+{
+       anim_time_counter = anim_time_counter + dtime;
+
+       v3f val_diff_v3f;
+       val_diff_v3f.X = std::abs(val_target.X - val_old.X);
+       val_diff_v3f.Y = std::abs(val_target.Y - val_old.Y);
+       val_diff_v3f.Z = std::abs(val_target.Z - val_old.Z);
+
+       if (val_diff_v3f.X > 180.f)
+               val_diff_v3f.X = 360.f - val_diff_v3f.X;
+
+       if (val_diff_v3f.Y > 180.f)
+               val_diff_v3f.Y = 360.f - val_diff_v3f.Y;
+
+       if (val_diff_v3f.Z > 180.f)
+               val_diff_v3f.Z = 360.f - val_diff_v3f.Z;
+
+       f32 moveratio = 1.0;
+       if (anim_time > 0.001)
+               moveratio = anim_time_counter / anim_time;
+       f32 move_end = aim_is_end ? 1.0 : 1.5;
+
+       // Move a bit less than should, to avoid oscillation
+       moveratio = std::min(moveratio * 0.8f, move_end);
+       wrappedApproachShortest(val_current.X, val_target.X,
+               val_diff_v3f.X * moveratio, 360.f);
+
+       wrappedApproachShortest(val_current.Y, val_target.Y,
+               val_diff_v3f.Y * moveratio, 360.f);
+
+       wrappedApproachShortest(val_current.Z, val_target.Z,
+               val_diff_v3f.Z * moveratio, 360.f);
+}
+
+/*
+       Other stuff
+*/
+
+static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill,
+               float txs, float tys, int col, int row)
+{
+       video::SMaterial& material = bill->getMaterial(0);
+       core::matrix4& matrix = material.getTextureMatrix(0);
+       matrix.setTextureTranslate(txs*col, tys*row);
+       matrix.setTextureScale(txs, tys);
+}
+
+/*
+       TestCAO
+*/
+
+class TestCAO : public ClientActiveObject
+{
+public:
+       TestCAO(Client *client, ClientEnvironment *env);
+       virtual ~TestCAO() = default;
+
+       ActiveObjectType getType() const
+       {
+               return ACTIVEOBJECT_TYPE_TEST;
+       }
+
+       static ClientActiveObject* create(Client *client, ClientEnvironment *env);
+
+       void addToScene(ITextureSource *tsrc);
+       void removeFromScene(bool permanent);
+       void updateLight(u8 light_at_pos);
+       v3s16 getLightPosition();
+       void updateNodePos();
+
+       void step(float dtime, ClientEnvironment *env);
+
+       void processMessage(const std::string &data);
+
+       bool getCollisionBox(aabb3f *toset) const { return false; }
+private:
+       scene::IMeshSceneNode *m_node;
+       v3f m_position;
+};
+
+// Prototype
+TestCAO proto_TestCAO(NULL, NULL);
+
+TestCAO::TestCAO(Client *client, ClientEnvironment *env):
+       ClientActiveObject(0, client, env),
+       m_node(NULL),
+       m_position(v3f(0,10*BS,0))
+{
+       ClientActiveObject::registerType(getType(), create);
+}
+
+ClientActiveObject* TestCAO::create(Client *client, ClientEnvironment *env)
+{
+       return new TestCAO(client, env);
+}
+
+void TestCAO::addToScene(ITextureSource *tsrc)
+{
+       if(m_node != NULL)
+               return;
+
+       //video::IVideoDriver* driver = smgr->getVideoDriver();
+
+       scene::SMesh *mesh = new scene::SMesh();
+       scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+       video::SColor c(255,255,255,255);
+       video::S3DVertex vertices[4] =
+       {
+               video::S3DVertex(-BS/2,-BS/4,0, 0,0,0, c, 0,1),
+               video::S3DVertex(BS/2,-BS/4,0, 0,0,0, c, 1,1),
+               video::S3DVertex(BS/2,BS/4,0, 0,0,0, c, 1,0),
+               video::S3DVertex(-BS/2,BS/4,0, 0,0,0, c, 0,0),
+       };
+       u16 indices[] = {0,1,2,2,3,0};
+       buf->append(vertices, 4, indices, 6);
+       // Set material
+       buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+       buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
+       buf->getMaterial().setTexture(0, tsrc->getTextureForMesh("rat.png"));
+       buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+       buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true);
+       buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+       // Add to mesh
+       mesh->addMeshBuffer(buf);
+       buf->drop();
+       m_node = RenderingEngine::get_scene_manager()->addMeshSceneNode(mesh, NULL);
+       mesh->drop();
+       updateNodePos();
+}
+
+void TestCAO::removeFromScene(bool permanent)
+{
+       if (!m_node)
+               return;
+
+       m_node->remove();
+       m_node = NULL;
+}
+
+void TestCAO::updateLight(u8 light_at_pos)
+{
+}
+
+v3s16 TestCAO::getLightPosition()
+{
+       return floatToInt(m_position, BS);
+}
+
+void TestCAO::updateNodePos()
+{
+       if (!m_node)
+               return;
+
+       m_node->setPosition(m_position);
+       //m_node->setRotation(v3f(0, 45, 0));
+}
+
+void TestCAO::step(float dtime, ClientEnvironment *env)
+{
+       if(m_node)
+       {
+               v3f rot = m_node->getRotation();
+               //infostream<<"dtime="<<dtime<<", rot.Y="<<rot.Y<<std::endl;
+               rot.Y += dtime * 180;
+               m_node->setRotation(rot);
+       }
+}
+
+void TestCAO::processMessage(const std::string &data)
+{
+       infostream<<"TestCAO: Got data: "<<data<<std::endl;
+       std::istringstream is(data, std::ios::binary);
+       u16 cmd;
+       is>>cmd;
+       if(cmd == 0)
+       {
+               v3f newpos;
+               is>>newpos.X;
+               is>>newpos.Y;
+               is>>newpos.Z;
+               m_position = newpos;
+               updateNodePos();
+       }
+}
+
+/*
+       GenericCAO
+*/
+
+#include "genericobject.h"
+
+GenericCAO::GenericCAO(Client *client, ClientEnvironment *env):
+               ClientActiveObject(0, client, env)
+{
+       if (client == NULL) {
+               ClientActiveObject::registerType(getType(), create);
+       } else {
+               m_client = client;
+       }
+}
+
+bool GenericCAO::getCollisionBox(aabb3f *toset) const
+{
+       if (m_prop.physical)
+       {
+               //update collision box
+               toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
+               toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
+
+               toset->MinEdge += m_position;
+               toset->MaxEdge += m_position;
+
+               return true;
+       }
+
+       return false;
+}
+
+bool GenericCAO::collideWithObjects() const
+{
+       return m_prop.collideWithObjects;
+}
+
+void GenericCAO::initialize(const std::string &data)
+{
+       infostream<<"GenericCAO: Got init data"<<std::endl;
+       processInitData(data);
+
+       if (m_is_player) {
+               // Check if it's the current player
+               LocalPlayer *player = m_env->getLocalPlayer();
+               if (player && strcmp(player->getName(), m_name.c_str()) == 0) {
+                       m_is_local_player = true;
+                       m_is_visible = false;
+                       player->setCAO(this);
+               }
+       }
+}
+
+void GenericCAO::processInitData(const std::string &data)
+{
+       std::istringstream is(data, std::ios::binary);
+       int num_messages = 0;
+       // version
+       u8 version = readU8(is);
+       // check version
+       if (version == 1) { // In PROTOCOL_VERSION 14
+               m_name = deSerializeString(is);
+               m_is_player = readU8(is);
+               m_id = readU16(is);
+               m_position = readV3F1000(is);
+               m_rotation = readV3F1000(is);
+               m_hp = readS16(is);
+               num_messages = readU8(is);
+       } else {
+               errorstream<<"GenericCAO: Unsupported init data version"
+                               <<std::endl;
+               return;
+       }
+
+       for (int i = 0; i < num_messages; i++) {
+               std::string message = deSerializeLongString(is);
+               processMessage(message);
+       }
+
+       m_rotation = wrapDegrees_0_360_v3f(m_rotation);
+       pos_translator.init(m_position);
+       rot_translator.init(m_rotation);
+       updateNodePos();
+}
+
+GenericCAO::~GenericCAO()
+{
+       removeFromScene(true);
+}
+
+bool GenericCAO::getSelectionBox(aabb3f *toset) const
+{
+       if (!m_prop.is_visible || !m_is_visible || m_is_local_player
+                       || !m_prop.pointable) {
+               return false;
+       }
+       *toset = m_selection_box;
+       return true;
+}
+
+v3f GenericCAO::getPosition()
+{
+       if (getParent() != NULL) {
+               scene::ISceneNode *node = getSceneNode();
+               if (node)
+                       return node->getAbsolutePosition();
+
+               return m_position;
+       }
+       return pos_translator.val_current;
+}
+
+const bool GenericCAO::isImmortal()
+{
+       return itemgroup_get(getGroups(), "immortal");
+}
+
+scene::ISceneNode* GenericCAO::getSceneNode()
+{
+       if (m_meshnode) {
+               return m_meshnode;
+       }
+
+       if (m_animated_meshnode) {
+               return m_animated_meshnode;
+       }
+
+       if (m_wield_meshnode) {
+               return m_wield_meshnode;
+       }
+
+       if (m_spritenode) {
+               return m_spritenode;
+       }
+       return NULL;
+}
+
+scene::IAnimatedMeshSceneNode* GenericCAO::getAnimatedMeshSceneNode()
+{
+       return m_animated_meshnode;
+}
+
+void GenericCAO::setChildrenVisible(bool toset)
+{
+       for (u16 cao_id : m_children) {
+               GenericCAO *obj = m_env->getGenericCAO(cao_id);
+               if (obj) {
+                       obj->setVisible(toset);
+               }
+       }
+}
+
+void GenericCAO::setAttachments()
+{
+       updateAttachments();
+}
+
+ClientActiveObject* GenericCAO::getParent() const
+{
+       ClientActiveObject *obj = NULL;
+
+       u16 attached_id = m_env->attachement_parent_ids[getId()];
+
+       if ((attached_id != 0) &&
+                       (attached_id != getId())) {
+               obj = m_env->getActiveObject(attached_id);
+       }
+       return obj;
+}
+
+void GenericCAO::removeFromScene(bool permanent)
+{
+       // Should be true when removing the object permanently and false when refreshing (eg: updating visuals)
+       if((m_env != NULL) && (permanent))
+       {
+               for (u16 ci : m_children) {
+                       if (m_env->attachement_parent_ids[ci] == getId()) {
+                               m_env->attachement_parent_ids[ci] = 0;
+                       }
+               }
+               m_children.clear();
+
+               m_env->attachement_parent_ids[getId()] = 0;
+
+               LocalPlayer* player = m_env->getLocalPlayer();
+               if (this == player->parent) {
+                       player->parent = NULL;
+                       player->isAttached = false;
+               }
+       }
+
+       if (m_meshnode) {
+               m_meshnode->remove();
+               m_meshnode->drop();
+               m_meshnode = NULL;
+       } else if (m_animated_meshnode) {
+               m_animated_meshnode->remove();
+               m_animated_meshnode->drop();
+               m_animated_meshnode = NULL;
+       } else if (m_wield_meshnode) {
+               m_wield_meshnode->remove();
+               m_wield_meshnode->drop();
+               m_wield_meshnode = NULL;
+       } else if (m_spritenode) {
+               m_spritenode->remove();
+               m_spritenode->drop();
+               m_spritenode = NULL;
+       }
+
+       if (m_nametag) {
+               m_client->getCamera()->removeNametag(m_nametag);
+               m_nametag = NULL;
+       }
+}
+
+void GenericCAO::addToScene(ITextureSource *tsrc)
+{
+       m_smgr = RenderingEngine::get_scene_manager();
+
+       if (getSceneNode() != NULL) {
+               return;
+       }
+
+       m_visuals_expired = false;
+
+       if (!m_prop.is_visible) {
+               return;
+       }
+
+       video::E_MATERIAL_TYPE material_type = (m_prop.use_texture_alpha) ?
+               video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
+
+       if (m_prop.visual == "sprite") {
+               infostream<<"GenericCAO::addToScene(): single_sprite"<<std::endl;
+               m_spritenode = RenderingEngine::get_scene_manager()->addBillboardSceneNode(
+                               NULL, v2f(1, 1), v3f(0,0,0), -1);
+               m_spritenode->grab();
+               m_spritenode->setMaterialTexture(0,
+                               tsrc->getTextureForMesh("unknown_node.png"));
+               m_spritenode->setMaterialFlag(video::EMF_LIGHTING, false);
+               m_spritenode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
+               m_spritenode->setMaterialType(material_type);
+               m_spritenode->setMaterialFlag(video::EMF_FOG_ENABLE, true);
+               u8 li = m_last_light;
+               m_spritenode->setColor(video::SColor(255,li,li,li));
+               m_spritenode->setSize(m_prop.visual_size*BS);
+               {
+                       const float txs = 1.0 / 1;
+                       const float tys = 1.0 / 1;
+                       setBillboardTextureMatrix(m_spritenode,
+                                       txs, tys, 0, 0);
+               }
+       } else if (m_prop.visual == "upright_sprite") {
+               scene::SMesh *mesh = new scene::SMesh();
+               double dx = BS * m_prop.visual_size.X / 2;
+               double dy = BS * m_prop.visual_size.Y / 2;
+               u8 li = m_last_light;
+               video::SColor c(255, li, li, li);
+
+               { // Front
+                       scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+                       video::S3DVertex vertices[4] = {
+                               video::S3DVertex(-dx, -dy, 0, 0,0,0, c, 1,1),
+                               video::S3DVertex( dx, -dy, 0, 0,0,0, c, 0,1),
+                               video::S3DVertex( dx,  dy, 0, 0,0,0, c, 0,0),
+                               video::S3DVertex(-dx,  dy, 0, 0,0,0, c, 1,0),
+                       };
+                       if (m_is_player) {
+                               // Move minimal Y position to 0 (feet position)
+                               for (video::S3DVertex &vertex : vertices)
+                                       vertex.Pos.Y += dy;
+                       }
+                       u16 indices[] = {0,1,2,2,3,0};
+                       buf->append(vertices, 4, indices, 6);
+                       // Set material
+                       buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+                       buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+                       buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true);
+                       buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+                       // Add to mesh
+                       mesh->addMeshBuffer(buf);
+                       buf->drop();
+               }
+               { // Back
+                       scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+                       video::S3DVertex vertices[4] = {
+                               video::S3DVertex( dx,-dy, 0, 0,0,0, c, 1,1),
+                               video::S3DVertex(-dx,-dy, 0, 0,0,0, c, 0,1),
+                               video::S3DVertex(-dx, dy, 0, 0,0,0, c, 0,0),
+                               video::S3DVertex( dx, dy, 0, 0,0,0, c, 1,0),
+                       };
+                       if (m_is_player) {
+                               // Move minimal Y position to 0 (feet position)
+                               for (video::S3DVertex &vertex : vertices)
+                                       vertex.Pos.Y += dy;
+                       }
+                       u16 indices[] = {0,1,2,2,3,0};
+                       buf->append(vertices, 4, indices, 6);
+                       // Set material
+                       buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+                       buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+                       buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true);
+                       buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
+                       // Add to mesh
+                       mesh->addMeshBuffer(buf);
+                       buf->drop();
+               }
+               m_meshnode = RenderingEngine::get_scene_manager()->addMeshSceneNode(mesh, NULL);
+               m_meshnode->grab();
+               mesh->drop();
+               // Set it to use the materials of the meshbuffers directly.
+               // This is needed for changing the texture in the future
+               m_meshnode->setReadOnlyMaterials(true);
+       }
+       else if(m_prop.visual == "cube") {
+               infostream<<"GenericCAO::addToScene(): cube"<<std::endl;
+               scene::IMesh *mesh = createCubeMesh(v3f(BS,BS,BS));
+               m_meshnode = RenderingEngine::get_scene_manager()->addMeshSceneNode(mesh, NULL);
+               m_meshnode->grab();
+               mesh->drop();
+
+               m_meshnode->setScale(v3f(m_prop.visual_size.X,
+                               m_prop.visual_size.Y,
+                               m_prop.visual_size.X));
+               u8 li = m_last_light;
+               setMeshColor(m_meshnode->getMesh(), video::SColor(255,li,li,li));
+
+               m_meshnode->setMaterialFlag(video::EMF_LIGHTING, false);
+               m_meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
+               m_meshnode->setMaterialType(material_type);
+               m_meshnode->setMaterialFlag(video::EMF_FOG_ENABLE, true);
+       }
+       else if(m_prop.visual == "mesh") {
+               infostream<<"GenericCAO::addToScene(): mesh"<<std::endl;
+               scene::IAnimatedMesh *mesh = m_client->getMesh(m_prop.mesh, true);
+               if(mesh)
+               {
+                       m_animated_meshnode = RenderingEngine::get_scene_manager()->
+                               addAnimatedMeshSceneNode(mesh, NULL);
+                       m_animated_meshnode->grab();
+                       mesh->drop(); // The scene node took hold of it
+                       m_animated_meshnode->animateJoints(); // Needed for some animations
+                       m_animated_meshnode->setScale(v3f(m_prop.visual_size.X,
+                                       m_prop.visual_size.Y,
+                                       m_prop.visual_size.X));
+                       u8 li = m_last_light;
+
+                       // set vertex colors to ensure alpha is set
+                       setMeshColor(m_animated_meshnode->getMesh(), video::SColor(255,li,li,li));
+
+                       setAnimatedMeshColor(m_animated_meshnode, video::SColor(255,li,li,li));
+
+                       m_animated_meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
+                       m_animated_meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
+                       m_animated_meshnode->setMaterialType(material_type);
+                       m_animated_meshnode->setMaterialFlag(video::EMF_FOG_ENABLE, true);
+                       m_animated_meshnode->setMaterialFlag(video::EMF_BACK_FACE_CULLING,
+                               m_prop.backface_culling);
+               }
+               else
+                       errorstream<<"GenericCAO::addToScene(): Could not load mesh "<<m_prop.mesh<<std::endl;
+       } else if (m_prop.visual == "wielditem") {
+               ItemStack item;
+               infostream << "GenericCAO::addToScene(): wielditem" << std::endl;
+               if (m_prop.wield_item.empty()) {
+                       // Old format, only textures are specified.
+                       infostream << "textures: " << m_prop.textures.size() << std::endl;
+                       if (!m_prop.textures.empty()) {
+                               infostream << "textures[0]: " << m_prop.textures[0]
+                                       << std::endl;
+                               IItemDefManager *idef = m_client->idef();
+                               item = ItemStack(m_prop.textures[0], 1, 0, idef);
+                       }
+               } else {
+                       infostream << "serialized form: " << m_prop.wield_item << std::endl;
+                       item.deSerialize(m_prop.wield_item, m_client->idef());
+               }
+               m_wield_meshnode = new WieldMeshSceneNode(
+                       RenderingEngine::get_scene_manager(), -1);
+               m_wield_meshnode->setItem(item, m_client);
+
+               m_wield_meshnode->setScale(
+                       v3f(m_prop.visual_size.X / 2, m_prop.visual_size.Y / 2,
+                               m_prop.visual_size.X / 2));
+               u8 li = m_last_light;
+               m_wield_meshnode->setColor(video::SColor(255, li, li, li));
+       } else {
+               infostream<<"GenericCAO::addToScene(): \""<<m_prop.visual
+                               <<"\" not supported"<<std::endl;
+       }
+
+       /* don't update while punch texture modifier is active */
+       if (m_reset_textures_timer < 0)
+               updateTextures(m_current_texture_modifier);
+
+       scene::ISceneNode *node = getSceneNode();
+       if (node && !m_prop.nametag.empty() && !m_is_local_player) {
+               // Add nametag
+               v3f pos;
+               pos.Y = m_prop.selectionbox.MaxEdge.Y + 0.3f;
+               m_nametag = m_client->getCamera()->addNametag(node,
+                       m_prop.nametag, m_prop.nametag_color,
+                       pos);
+       }
+
+       updateNodePos();
+       updateAnimation();
+       updateBonePosition();
+       updateAttachments();
+}
+
+void GenericCAO::updateLight(u8 light_at_pos)
+{
+       // Don't update light of attached one
+       if (getParent() != NULL) {
+               return;
+       }
+
+       updateLightNoCheck(light_at_pos);
+
+       // Update light of all children
+       for (u16 i : m_children) {
+               ClientActiveObject *obj = m_env->getActiveObject(i);
+               if (obj) {
+                       obj->updateLightNoCheck(light_at_pos);
+               }
+       }
+}
+
+void GenericCAO::updateLightNoCheck(u8 light_at_pos)
+{
+       if (m_glow < 0)
+               return;
+
+       u8 li = decode_light(light_at_pos + m_glow);
+       if (li != m_last_light) {
+               m_last_light = li;
+               video::SColor color(255,li,li,li);
+               if (m_meshnode) {
+                       setMeshColor(m_meshnode->getMesh(), color);
+               } else if (m_animated_meshnode) {
+                       setAnimatedMeshColor(m_animated_meshnode, color);
+               } else if (m_wield_meshnode) {
+                       m_wield_meshnode->setColor(color);
+               } else if (m_spritenode) {
+                       m_spritenode->setColor(color);
+               }
+       }
+}
+
+v3s16 GenericCAO::getLightPosition()
+{
+       if (m_is_player)
+               return floatToInt(m_position + v3f(0, 0.5 * BS, 0), BS);
+
+       return floatToInt(m_position, BS);
+}
+
+void GenericCAO::updateNodePos()
+{
+       if (getParent() != NULL)
+               return;
+
+       scene::ISceneNode *node = getSceneNode();
+
+       if (node) {
+               v3s16 camera_offset = m_env->getCameraOffset();
+               node->setPosition(pos_translator.val_current - intToFloat(camera_offset, BS));
+               if (node != m_spritenode) { // rotate if not a sprite
+                       v3f rot = m_is_local_player ? -m_rotation : -rot_translator.val_current;
+                       node->setRotation(rot);
+               }
+       }
+}
+
+void GenericCAO::step(float dtime, ClientEnvironment *env)
+{
+       // Handel model of local player instantly to prevent lags
+       if (m_is_local_player) {
+               LocalPlayer *player = m_env->getLocalPlayer();
+               if (m_is_visible) {
+                       int old_anim = player->last_animation;
+                       float old_anim_speed = player->last_animation_speed;
+                       m_position = player->getPosition();
+                       m_rotation.Y = wrapDegrees_0_360(player->getYaw());
+                       m_velocity = v3f(0,0,0);
+                       m_acceleration = v3f(0,0,0);
+                       pos_translator.val_current = m_position;
+                       rot_translator.val_current = m_rotation;
+                       const PlayerControl &controls = player->getPlayerControl();
+
+                       bool walking = false;
+                       if (controls.up || controls.down || controls.left || controls.right ||
+                                       controls.forw_move_joystick_axis != 0.f ||
+                                       controls.sidew_move_joystick_axis != 0.f)
+                               walking = true;
+
+                       f32 new_speed = player->local_animation_speed;
+                       v2s32 new_anim = v2s32(0,0);
+                       bool allow_update = false;
+
+                       // increase speed if using fast or flying fast
+                       if((g_settings->getBool("fast_move") &&
+                                       m_client->checkLocalPrivilege("fast")) &&
+                                       (controls.aux1 ||
+                                       (!player->touching_ground &&
+                                       g_settings->getBool("free_move") &&
+                                       m_client->checkLocalPrivilege("fly"))))
+                                       new_speed *= 1.5;
+                       // slowdown speed if sneeking
+                       if (controls.sneak && walking)
+                               new_speed /= 2;
+
+                       if (walking && (controls.LMB || controls.RMB)) {
+                               new_anim = player->local_animations[3];
+                               player->last_animation = WD_ANIM;
+                       } else if(walking) {
+                               new_anim = player->local_animations[1];
+                               player->last_animation = WALK_ANIM;
+                       } else if(controls.LMB || controls.RMB) {
+                               new_anim = player->local_animations[2];
+                               player->last_animation = DIG_ANIM;
+                       }
+
+                       // Apply animations if input detected and not attached
+                       // or set idle animation
+                       if ((new_anim.X + new_anim.Y) > 0 && !player->isAttached) {
+                               allow_update = true;
+                               m_animation_range = new_anim;
+                               m_animation_speed = new_speed;
+                               player->last_animation_speed = m_animation_speed;
+                       } else {
+                               player->last_animation = NO_ANIM;
+
+                               if (old_anim != NO_ANIM) {
+                                       m_animation_range = player->local_animations[0];
+                                       updateAnimation();
+                               }
+                       }
+
+                       // Update local player animations
+                       if ((player->last_animation != old_anim ||
+                               m_animation_speed != old_anim_speed) &&
+                               player->last_animation != NO_ANIM && allow_update)
+                                       updateAnimation();
+
+               }
+       }
+
+       if (m_visuals_expired && m_smgr) {
+               m_visuals_expired = false;
+
+               // Attachments, part 1: All attached objects must be unparented first,
+               // or Irrlicht causes a segmentation fault
+               for (auto ci = m_children.begin(); ci != m_children.end();) {
+                       if (m_env->attachement_parent_ids[*ci] != getId()) {
+                               ci = m_children.erase(ci);
+                               continue;
+                       }
+                       ClientActiveObject *obj = m_env->getActiveObject(*ci);
+                       if (obj) {
+                               scene::ISceneNode *child_node = obj->getSceneNode();
+                               if (child_node)
+                                       child_node->setParent(m_smgr->getRootSceneNode());
+                       }
+                       ++ci;
+               }
+
+               removeFromScene(false);
+               addToScene(m_client->tsrc());
+
+               // Attachments, part 2: Now that the parent has been refreshed, put its attachments back
+               for (u16 cao_id : m_children) {
+                       // Get the object of the child
+                       ClientActiveObject *obj = m_env->getActiveObject(cao_id);
+                       if (obj)
+                               obj->setAttachments();
+               }
+       }
+
+       // Make sure m_is_visible is always applied
+       scene::ISceneNode *node = getSceneNode();
+       if (node)
+               node->setVisible(m_is_visible);
+
+       if(getParent() != NULL) // Attachments should be glued to their parent by Irrlicht
+       {
+               // Set these for later
+               m_position = getPosition();
+               m_velocity = v3f(0,0,0);
+               m_acceleration = v3f(0,0,0);
+               pos_translator.val_current = m_position;
+
+               if(m_is_local_player) // Update local player attachment position
+               {
+                       LocalPlayer *player = m_env->getLocalPlayer();
+                       player->overridePosition = getParent()->getPosition();
+                       m_env->getLocalPlayer()->parent = getParent();
+               }
+       } else {
+               rot_translator.translate(dtime);
+               v3f lastpos = pos_translator.val_current;
+
+               if(m_prop.physical)
+               {
+                       aabb3f box = m_prop.collisionbox;
+                       box.MinEdge *= BS;
+                       box.MaxEdge *= BS;
+                       collisionMoveResult moveresult;
+                       f32 pos_max_d = BS*0.125; // Distance per iteration
+                       v3f p_pos = m_position;
+                       v3f p_velocity = m_velocity;
+                       moveresult = collisionMoveSimple(env,env->getGameDef(),
+                                       pos_max_d, box, m_prop.stepheight, dtime,
+                                       &p_pos, &p_velocity, m_acceleration,
+                                       this, m_prop.collideWithObjects);
+                       // Apply results
+                       m_position = p_pos;
+                       m_velocity = p_velocity;
+
+                       bool is_end_position = moveresult.collides;
+                       pos_translator.update(m_position, is_end_position, dtime);
+                       pos_translator.translate(dtime);
+                       updateNodePos();
+               } else {
+                       m_position += dtime * m_velocity + 0.5 * dtime * dtime * m_acceleration;
+                       m_velocity += dtime * m_acceleration;
+                       pos_translator.update(m_position, pos_translator.aim_is_end,
+                                       pos_translator.anim_time);
+                       pos_translator.translate(dtime);
+                       updateNodePos();
+               }
+
+               float moved = lastpos.getDistanceFrom(pos_translator.val_current);
+               m_step_distance_counter += moved;
+               if (m_step_distance_counter > 1.5f * BS) {
+                       m_step_distance_counter = 0.0f;
+                       if (!m_is_local_player && m_prop.makes_footstep_sound) {
+                               const NodeDefManager *ndef = m_client->ndef();
+                               v3s16 p = floatToInt(getPosition() +
+                                       v3f(0.0f, (m_prop.collisionbox.MinEdge.Y - 0.5f) * BS, 0.0f), BS);
+                               MapNode n = m_env->getMap().getNodeNoEx(p);
+                               SimpleSoundSpec spec = ndef->get(n).sound_footstep;
+                               // Reduce footstep gain, as non-local-player footsteps are
+                               // somehow louder.
+                               spec.gain *= 0.6f;
+                               m_client->sound()->playSoundAt(spec, false, getPosition());
+                       }
+               }
+       }
+
+       m_anim_timer += dtime;
+       if(m_anim_timer >= m_anim_framelength)
+       {
+               m_anim_timer -= m_anim_framelength;
+               m_anim_frame++;
+               if(m_anim_frame >= m_anim_num_frames)
+                       m_anim_frame = 0;
+       }
+
+       updateTexturePos();
+
+       if(m_reset_textures_timer >= 0)
+       {
+               m_reset_textures_timer -= dtime;
+               if(m_reset_textures_timer <= 0) {
+                       m_reset_textures_timer = -1;
+                       updateTextures(m_previous_texture_modifier);
+               }
+       }
+       if (!getParent() && std::fabs(m_prop.automatic_rotate) > 0.001) {
+               m_rotation.Y += dtime * m_prop.automatic_rotate * 180 / M_PI;
+               rot_translator.val_current = m_rotation;
+               updateNodePos();
+       }
+
+       if (!getParent() && m_prop.automatic_face_movement_dir &&
+                       (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)) {
+
+               float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI
+                               + m_prop.automatic_face_movement_dir_offset;
+               float max_rotation_delta =
+                               dtime * m_prop.automatic_face_movement_max_rotation_per_sec;
+
+               wrappedApproachShortest(m_rotation.Y, target_yaw, max_rotation_delta, 360.f);
+               rot_translator.val_current = m_rotation;
+
+               updateNodePos();
+       }
+}
+
+void GenericCAO::updateTexturePos()
+{
+       if(m_spritenode)
+       {
+               scene::ICameraSceneNode* camera =
+                               m_spritenode->getSceneManager()->getActiveCamera();
+               if(!camera)
+                       return;
+               v3f cam_to_entity = m_spritenode->getAbsolutePosition()
+                               - camera->getAbsolutePosition();
+               cam_to_entity.normalize();
+
+               int row = m_tx_basepos.Y;
+               int col = m_tx_basepos.X;
+
+               if (m_tx_select_horiz_by_yawpitch) {
+                       if (cam_to_entity.Y > 0.75)
+                               col += 5;
+                       else if (cam_to_entity.Y < -0.75)
+                               col += 4;
+                       else {
+                               float mob_dir =
+                                               atan2(cam_to_entity.Z, cam_to_entity.X) / M_PI * 180.;
+                               float dir = mob_dir - m_rotation.Y;
+                               dir = wrapDegrees_180(dir);
+                               if (std::fabs(wrapDegrees_180(dir - 0)) <= 45.1f)
+                                       col += 2;
+                               else if(std::fabs(wrapDegrees_180(dir - 90)) <= 45.1f)
+                                       col += 3;
+                               else if(std::fabs(wrapDegrees_180(dir - 180)) <= 45.1f)
+                                       col += 0;
+                               else if(std::fabs(wrapDegrees_180(dir + 90)) <= 45.1f)
+                                       col += 1;
+                               else
+                                       col += 4;
+                       }
+               }
+
+               // Animation goes downwards
+               row += m_anim_frame;
+
+               float txs = m_tx_size.X;
+               float tys = m_tx_size.Y;
+               setBillboardTextureMatrix(m_spritenode, txs, tys, col, row);
+       }
+}
+
+void GenericCAO::updateTextures(std::string mod)
+{
+       ITextureSource *tsrc = m_client->tsrc();
+
+       bool use_trilinear_filter = g_settings->getBool("trilinear_filter");
+       bool use_bilinear_filter = g_settings->getBool("bilinear_filter");
+       bool use_anisotropic_filter = g_settings->getBool("anisotropic_filter");
+
+       m_previous_texture_modifier = m_current_texture_modifier;
+       m_current_texture_modifier = mod;
+       m_glow = m_prop.glow;
+
+       video::E_MATERIAL_TYPE material_type = (m_prop.use_texture_alpha) ?
+               video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
+
+       if (m_spritenode) {
+               if (m_prop.visual == "sprite") {
+                       std::string texturestring = "unknown_node.png";
+                       if (!m_prop.textures.empty())
+                               texturestring = m_prop.textures[0];
+                       texturestring += mod;
+                       m_spritenode->getMaterial(0).MaterialType = material_type;
+                       m_spritenode->setMaterialTexture(0,
+                                       tsrc->getTextureForMesh(texturestring));
+
+                       // This allows setting per-material colors. However, until a real lighting
+                       // system is added, the code below will have no effect. Once MineTest
+                       // has directional lighting, it should work automatically.
+                       if (!m_prop.colors.empty()) {
+                               m_spritenode->getMaterial(0).AmbientColor = m_prop.colors[0];
+                               m_spritenode->getMaterial(0).DiffuseColor = m_prop.colors[0];
+                               m_spritenode->getMaterial(0).SpecularColor = m_prop.colors[0];
+                       }
+
+                       m_spritenode->getMaterial(0).setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
+                       m_spritenode->getMaterial(0).setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
+                       m_spritenode->getMaterial(0).setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
+               }
+       }
+
+       if (m_animated_meshnode) {
+               if (m_prop.visual == "mesh") {
+                       for (u32 i = 0; i < m_prop.textures.size() &&
+                                       i < m_animated_meshnode->getMaterialCount(); ++i) {
+                               std::string texturestring = m_prop.textures[i];
+                               if (texturestring.empty())
+                                       continue; // Empty texture string means don't modify that material
+                               texturestring += mod;
+                               video::ITexture* texture = tsrc->getTextureForMesh(texturestring);
+                               if (!texture) {
+                                       errorstream<<"GenericCAO::updateTextures(): Could not load texture "<<texturestring<<std::endl;
+                                       continue;
+                               }
+
+                               // Set material flags and texture
+                               video::SMaterial& material = m_animated_meshnode->getMaterial(i);
+                               material.MaterialType = material_type;
+                               material.TextureLayer[0].Texture = texture;
+                               material.setFlag(video::EMF_LIGHTING, true);
+                               material.setFlag(video::EMF_BILINEAR_FILTER, false);
+                               material.setFlag(video::EMF_BACK_FACE_CULLING, m_prop.backface_culling);
+
+                               // don't filter low-res textures, makes them look blurry
+                               // player models have a res of 64
+                               const core::dimension2d<u32> &size = texture->getOriginalSize();
+                               const u32 res = std::min(size.Height, size.Width);
+                               use_trilinear_filter &= res > 64;
+                               use_bilinear_filter &= res > 64;
+
+                               m_animated_meshnode->getMaterial(i)
+                                               .setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
+                               m_animated_meshnode->getMaterial(i)
+                                               .setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
+                               m_animated_meshnode->getMaterial(i)
+                                               .setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
+                       }
+                       for (u32 i = 0; i < m_prop.colors.size() &&
+                       i < m_animated_meshnode->getMaterialCount(); ++i)
+                       {
+                               // This allows setting per-material colors. However, until a real lighting
+                               // system is added, the code below will have no effect. Once MineTest
+                               // has directional lighting, it should work automatically.
+                               m_animated_meshnode->getMaterial(i).AmbientColor = m_prop.colors[i];
+                               m_animated_meshnode->getMaterial(i).DiffuseColor = m_prop.colors[i];
+                               m_animated_meshnode->getMaterial(i).SpecularColor = m_prop.colors[i];
+                       }
+               }
+       }
+       if(m_meshnode)
+       {
+               if(m_prop.visual == "cube")
+               {
+                       for (u32 i = 0; i < 6; ++i)
+                       {
+                               std::string texturestring = "unknown_node.png";
+                               if(m_prop.textures.size() > i)
+                                       texturestring = m_prop.textures[i];
+                               texturestring += mod;
+
+
+                               // Set material flags and texture
+                               video::SMaterial& material = m_meshnode->getMaterial(i);
+                               material.MaterialType = material_type;
+                               material.setFlag(video::EMF_LIGHTING, false);
+                               material.setFlag(video::EMF_BILINEAR_FILTER, false);
+                               material.setTexture(0,
+                                               tsrc->getTextureForMesh(texturestring));
+                               material.getTextureMatrix(0).makeIdentity();
+
+                               // This allows setting per-material colors. However, until a real lighting
+                               // system is added, the code below will have no effect. Once MineTest
+                               // has directional lighting, it should work automatically.
+                               if(m_prop.colors.size() > i)
+                               {
+                                       m_meshnode->getMaterial(i).AmbientColor = m_prop.colors[i];
+                                       m_meshnode->getMaterial(i).DiffuseColor = m_prop.colors[i];
+                                       m_meshnode->getMaterial(i).SpecularColor = m_prop.colors[i];
+                               }
+
+                               m_meshnode->getMaterial(i).setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
+                               m_meshnode->getMaterial(i).setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
+                               m_meshnode->getMaterial(i).setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
+                       }
+               } else if (m_prop.visual == "upright_sprite") {
+                       scene::IMesh *mesh = m_meshnode->getMesh();
+                       {
+                               std::string tname = "unknown_object.png";
+                               if (!m_prop.textures.empty())
+                                       tname = m_prop.textures[0];
+                               tname += mod;
+                               scene::IMeshBuffer *buf = mesh->getMeshBuffer(0);
+                               buf->getMaterial().setTexture(0,
+                                               tsrc->getTextureForMesh(tname));
+
+                               // This allows setting per-material colors. However, until a real lighting
+                               // system is added, the code below will have no effect. Once MineTest
+                               // has directional lighting, it should work automatically.
+                               if(!m_prop.colors.empty()) {
+                                       buf->getMaterial().AmbientColor = m_prop.colors[0];
+                                       buf->getMaterial().DiffuseColor = m_prop.colors[0];
+                                       buf->getMaterial().SpecularColor = m_prop.colors[0];
+                               }
+
+                               buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
+                               buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
+                               buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
+                       }
+                       {
+                               std::string tname = "unknown_object.png";
+                               if (m_prop.textures.size() >= 2)
+                                       tname = m_prop.textures[1];
+                               else if (!m_prop.textures.empty())
+                                       tname = m_prop.textures[0];
+                               tname += mod;
+                               scene::IMeshBuffer *buf = mesh->getMeshBuffer(1);
+                               buf->getMaterial().setTexture(0,
+                                               tsrc->getTextureForMesh(tname));
+
+                               // This allows setting per-material colors. However, until a real lighting
+                               // system is added, the code below will have no effect. Once MineTest
+                               // has directional lighting, it should work automatically.
+                               if (m_prop.colors.size() >= 2) {
+                                       buf->getMaterial().AmbientColor = m_prop.colors[1];
+                                       buf->getMaterial().DiffuseColor = m_prop.colors[1];
+                                       buf->getMaterial().SpecularColor = m_prop.colors[1];
+                                       setMeshColor(mesh, m_prop.colors[1]);
+                               } else if (!m_prop.colors.empty()) {
+                                       buf->getMaterial().AmbientColor = m_prop.colors[0];
+                                       buf->getMaterial().DiffuseColor = m_prop.colors[0];
+                                       buf->getMaterial().SpecularColor = m_prop.colors[0];
+                                       setMeshColor(mesh, m_prop.colors[0]);
+                               }
+
+                               buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
+                               buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
+                               buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
+                       }
+               }
+       }
+}
+
+void GenericCAO::updateAnimation()
+{
+       if (!m_animated_meshnode)
+               return;
+
+       if (m_animated_meshnode->getStartFrame() != m_animation_range.X ||
+               m_animated_meshnode->getEndFrame() != m_animation_range.Y)
+                       m_animated_meshnode->setFrameLoop(m_animation_range.X, m_animation_range.Y);
+       if (m_animated_meshnode->getAnimationSpeed() != m_animation_speed)
+               m_animated_meshnode->setAnimationSpeed(m_animation_speed);
+       m_animated_meshnode->setTransitionTime(m_animation_blend);
+// Requires Irrlicht 1.8 or greater
+#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR > 1
+       if (m_animated_meshnode->getLoopMode() != m_animation_loop)
+               m_animated_meshnode->setLoopMode(m_animation_loop);
+#endif
+}
+
+void GenericCAO::updateAnimationSpeed()
+{
+       if (!m_animated_meshnode)
+               return;
+
+       m_animated_meshnode->setAnimationSpeed(m_animation_speed);
+}
+
+void GenericCAO::updateBonePosition()
+{
+       if (m_bone_position.empty() || !m_animated_meshnode)
+               return;
+
+       m_animated_meshnode->setJointMode(irr::scene::EJUOR_CONTROL); // To write positions to the mesh on render
+       for(std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
+                       ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
+               std::string bone_name = (*ii).first;
+               v3f bone_pos = (*ii).second.X;
+               v3f bone_rot = (*ii).second.Y;
+               irr::scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str());
+               if(bone)
+               {
+                       bone->setPosition(bone_pos);
+                       bone->setRotation(bone_rot);
+               }
+       }
+}
+
+void GenericCAO::updateAttachments()
+{
+
+       if (!getParent()) { // Detach or don't attach
+               scene::ISceneNode *node = getSceneNode();
+               if (node) {
+                       v3f old_position = node->getAbsolutePosition();
+                       v3f old_rotation = node->getRotation();
+                       node->setParent(m_smgr->getRootSceneNode());
+                       node->setPosition(old_position);
+                       node->setRotation(old_rotation);
+                       node->updateAbsolutePosition();
+               }
+               if (m_is_local_player) {
+                       LocalPlayer *player = m_env->getLocalPlayer();
+                       player->isAttached = false;
+               }
+       }
+       else // Attach
+       {
+               scene::ISceneNode *my_node = getSceneNode();
+
+               scene::ISceneNode *parent_node = getParent()->getSceneNode();
+               scene::IAnimatedMeshSceneNode *parent_animated_mesh_node =
+                               getParent()->getAnimatedMeshSceneNode();
+               if (parent_animated_mesh_node && !m_attachment_bone.empty()) {
+                       parent_node = parent_animated_mesh_node->getJointNode(m_attachment_bone.c_str());
+               }
+
+               if (my_node && parent_node) {
+                       my_node->setParent(parent_node);
+                       my_node->setPosition(m_attachment_position);
+                       my_node->setRotation(m_attachment_rotation);
+                       my_node->updateAbsolutePosition();
+               }
+               if (m_is_local_player) {
+                       LocalPlayer *player = m_env->getLocalPlayer();
+                       player->isAttached = true;
+               }
+       }
+}
+
+void GenericCAO::processMessage(const std::string &data)
+{
+       //infostream<<"GenericCAO: Got message"<<std::endl;
+       std::istringstream is(data, std::ios::binary);
+       // command
+       u8 cmd = readU8(is);
+       if (cmd == GENERIC_CMD_SET_PROPERTIES) {
+               m_prop = gob_read_set_properties(is);
+
+               m_selection_box = m_prop.selectionbox;
+               m_selection_box.MinEdge *= BS;
+               m_selection_box.MaxEdge *= BS;
+
+               m_tx_size.X = 1.0 / m_prop.spritediv.X;
+               m_tx_size.Y = 1.0 / m_prop.spritediv.Y;
+
+               if(!m_initial_tx_basepos_set){
+                       m_initial_tx_basepos_set = true;
+                       m_tx_basepos = m_prop.initial_sprite_basepos;
+               }
+               if (m_is_local_player) {
+                       LocalPlayer *player = m_env->getLocalPlayer();
+                       player->makes_footstep_sound = m_prop.makes_footstep_sound;
+                       aabb3f collision_box = m_prop.collisionbox;
+                       collision_box.MinEdge *= BS;
+                       collision_box.MaxEdge *= BS;
+                       player->setCollisionbox(collision_box);
+                       player->setEyeHeight(m_prop.eye_height);
+                       player->setZoomFOV(m_prop.zoom_fov);
+               }
+
+               if ((m_is_player && !m_is_local_player) && m_prop.nametag.empty())
+                       m_prop.nametag = m_name;
+
+               expireVisuals();
+       } else if (cmd == GENERIC_CMD_UPDATE_POSITION) {
+               // Not sent by the server if this object is an attachment.
+               // We might however get here if the server notices the object being detached before the client.
+               m_position = readV3F1000(is);
+               m_velocity = readV3F1000(is);
+               m_acceleration = readV3F1000(is);
+
+               if (std::fabs(m_prop.automatic_rotate) < 0.001f)
+                       m_rotation = readV3F1000(is);
+               else
+                       readV3F1000(is);
+
+               m_rotation = wrapDegrees_0_360_v3f(m_rotation);
+               bool do_interpolate = readU8(is);
+               bool is_end_position = readU8(is);
+               float update_interval = readF1000(is);
+
+               // Place us a bit higher if we're physical, to not sink into
+               // the ground due to sucky collision detection...
+               if(m_prop.physical)
+                       m_position += v3f(0,0.002,0);
+
+               if(getParent() != NULL) // Just in case
+                       return;
+
+               if(do_interpolate)
+               {
+                       if(!m_prop.physical)
+                               pos_translator.update(m_position, is_end_position, update_interval);
+               } else {
+                       pos_translator.init(m_position);
+               }
+               rot_translator.update(m_rotation, false, update_interval);
+               updateNodePos();
+       } else if (cmd == GENERIC_CMD_SET_TEXTURE_MOD) {
+               std::string mod = deSerializeString(is);
+
+               // immediatly reset a engine issued texture modifier if a mod sends a different one
+               if (m_reset_textures_timer > 0) {
+                       m_reset_textures_timer = -1;
+                       updateTextures(m_previous_texture_modifier);
+               }
+               updateTextures(mod);
+       } else if (cmd == GENERIC_CMD_SET_SPRITE) {
+               v2s16 p = readV2S16(is);
+               int num_frames = readU16(is);
+               float framelength = readF1000(is);
+               bool select_horiz_by_yawpitch = readU8(is);
+
+               m_tx_basepos = p;
+               m_anim_num_frames = num_frames;
+               m_anim_framelength = framelength;
+               m_tx_select_horiz_by_yawpitch = select_horiz_by_yawpitch;
+
+               updateTexturePos();
+       } else if (cmd == GENERIC_CMD_SET_PHYSICS_OVERRIDE) {
+               float override_speed = readF1000(is);
+               float override_jump = readF1000(is);
+               float override_gravity = readF1000(is);
+               // these are sent inverted so we get true when the server sends nothing
+               bool sneak = !readU8(is);
+               bool sneak_glitch = !readU8(is);
+               bool new_move = !readU8(is);
+
+
+               if(m_is_local_player)
+               {
+                       LocalPlayer *player = m_env->getLocalPlayer();
+                       player->physics_override_speed = override_speed;
+                       player->physics_override_jump = override_jump;
+                       player->physics_override_gravity = override_gravity;
+                       player->physics_override_sneak = sneak;
+                       player->physics_override_sneak_glitch = sneak_glitch;
+                       player->physics_override_new_move = new_move;
+               }
+       } else if (cmd == GENERIC_CMD_SET_ANIMATION) {
+               // TODO: change frames send as v2s32 value
+               v2f range = readV2F1000(is);
+               if (!m_is_local_player) {
+                       m_animation_range = v2s32((s32)range.X, (s32)range.Y);
+                       m_animation_speed = readF1000(is);
+                       m_animation_blend = readF1000(is);
+                       // these are sent inverted so we get true when the server sends nothing
+                       m_animation_loop = !readU8(is);
+                       updateAnimation();
+               } else {
+                       LocalPlayer *player = m_env->getLocalPlayer();
+                       if(player->last_animation == NO_ANIM)
+                       {
+                               m_animation_range = v2s32((s32)range.X, (s32)range.Y);
+                               m_animation_speed = readF1000(is);
+                               m_animation_blend = readF1000(is);
+                               // these are sent inverted so we get true when the server sends nothing
+                               m_animation_loop = !readU8(is);
+                       }
+                       // update animation only if local animations present
+                       // and received animation is unknown (except idle animation)
+                       bool is_known = false;
+                       for (int i = 1;i<4;i++)
+                       {
+                               if(m_animation_range.Y == player->local_animations[i].Y)
+                                       is_known = true;
+                       }
+                       if(!is_known ||
+                                       (player->local_animations[1].Y + player->local_animations[2].Y < 1))
+                       {
+                                       updateAnimation();
+                       }
+               }
+       } else if (cmd == GENERIC_CMD_SET_ANIMATION_SPEED) {
+               m_animation_speed = readF1000(is);
+               updateAnimationSpeed();
+       } else if (cmd == GENERIC_CMD_SET_BONE_POSITION) {
+               std::string bone = deSerializeString(is);
+               v3f position = readV3F1000(is);
+               v3f rotation = readV3F1000(is);
+               m_bone_position[bone] = core::vector2d<v3f>(position, rotation);
+
+               updateBonePosition();
+       } else if (cmd == GENERIC_CMD_ATTACH_TO) {
+               u16 parent_id = readS16(is);
+               u16 &old_parent_id = m_env->attachement_parent_ids[getId()];
+               if (parent_id != old_parent_id) {
+                       if (GenericCAO *old_parent = m_env->getGenericCAO(old_parent_id)) {
+                               old_parent->m_children.erase(std::remove(
+                                       m_children.begin(), m_children.end(),
+                                       getId()), m_children.end());
+                       }
+                       if (GenericCAO *new_parent = m_env->getGenericCAO(parent_id))
+                               new_parent->m_children.push_back(getId());
+
+                       old_parent_id = parent_id;
+               }
+
+               m_attachment_bone = deSerializeString(is);
+               m_attachment_position = readV3F1000(is);
+               m_attachment_rotation = readV3F1000(is);
+
+               // localplayer itself can't be attached to localplayer
+               if (!m_is_local_player) {
+                       m_attached_to_local = getParent() != NULL && getParent()->isLocalPlayer();
+                       // Objects attached to the local player should be hidden by default
+                       m_is_visible = !m_attached_to_local;
+               }
+
+               updateAttachments();
+       } else if (cmd == GENERIC_CMD_PUNCHED) {
+               /*s16 damage =*/ readS16(is);
+               s16 result_hp = readS16(is);
+
+               // Use this instead of the send damage to not interfere with prediction
+               s16 damage = m_hp - result_hp;
+
+               m_hp = result_hp;
+
+               if (damage > 0)
+               {
+                       if (m_hp <= 0)
+                       {
+                               // TODO: Execute defined fast response
+                               // As there is no definition, make a smoke puff
+                               ClientSimpleObject *simple = createSmokePuff(
+                                               m_smgr, m_env, m_position,
+                                               m_prop.visual_size * BS);
+                               m_env->addSimpleObject(simple);
+                       } else if (m_reset_textures_timer < 0) {
+                               // TODO: Execute defined fast response
+                               // Flashing shall suffice as there is no definition
+                               m_reset_textures_timer = 0.05;
+                               if(damage >= 2)
+                                       m_reset_textures_timer += 0.05 * damage;
+                               updateTextures(m_current_texture_modifier + "^[brighten");
+                       }
+               }
+       } else if (cmd == GENERIC_CMD_UPDATE_ARMOR_GROUPS) {
+               m_armor_groups.clear();
+               int armor_groups_size = readU16(is);
+               for(int i=0; i<armor_groups_size; i++)
+               {
+                       std::string name = deSerializeString(is);
+                       int rating = readS16(is);
+                       m_armor_groups[name] = rating;
+               }
+       } else if (cmd == GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) {
+               // Deprecated, for backwards compatibility only.
+               readU8(is); // version
+               m_prop.nametag_color = readARGB8(is);
+               if (m_nametag != NULL) {
+                       m_nametag->nametag_color = m_prop.nametag_color;
+                       v3f pos;
+                       pos.Y = m_prop.collisionbox.MaxEdge.Y + 0.3f;
+                       m_nametag->nametag_pos = pos;
+               }
+       } else if (cmd == GENERIC_CMD_SPAWN_INFANT) {
+               u16 child_id = readU16(is);
+               u8 type = readU8(is);
+
+               if (GenericCAO *childobj = m_env->getGenericCAO(child_id)) {
+                       childobj->processInitData(deSerializeLongString(is));
+               } else {
+                       m_env->addActiveObject(child_id, type, deSerializeLongString(is));
+               }
+       } else {
+               warningstream << FUNCTION_NAME
+                       << ": unknown command or outdated client \""
+                       << +cmd << "\"" << std::endl;
+       }
+}
+
+/* \pre punchitem != NULL
+ */
+bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem,
+               float time_from_last_punch)
+{
+       assert(punchitem);      // pre-condition
+       const ToolCapabilities *toolcap =
+                       &punchitem->getToolCapabilities(m_client->idef());
+       PunchDamageResult result = getPunchDamage(
+                       m_armor_groups,
+                       toolcap,
+                       punchitem,
+                       time_from_last_punch);
+
+       if(result.did_punch && result.damage != 0)
+       {
+               if(result.damage < m_hp)
+               {
+                       m_hp -= result.damage;
+               } else {
+                       m_hp = 0;
+                       // TODO: Execute defined fast response
+                       // As there is no definition, make a smoke puff
+                       ClientSimpleObject *simple = createSmokePuff(
+                                       m_smgr, m_env, m_position,
+                                       m_prop.visual_size * BS);
+                       m_env->addSimpleObject(simple);
+               }
+               // TODO: Execute defined fast response
+               // Flashing shall suffice as there is no definition
+               if (m_reset_textures_timer < 0) {
+                       m_reset_textures_timer = 0.05;
+                       if (result.damage >= 2)
+                               m_reset_textures_timer += 0.05 * result.damage;
+                       updateTextures(m_current_texture_modifier + "^[brighten");
+               }
+       }
+
+       return false;
+}
+
+std::string GenericCAO::debugInfoText()
+{
+       std::ostringstream os(std::ios::binary);
+       os<<"GenericCAO hp="<<m_hp<<"\n";
+       os<<"armor={";
+       for(ItemGroupList::const_iterator i = m_armor_groups.begin();
+                       i != m_armor_groups.end(); ++i)
+       {
+               os<<i->first<<"="<<i->second<<", ";
+       }
+       os<<"}";
+       return os.str();
+}
+
+// Prototype
+GenericCAO proto_GenericCAO(NULL, NULL);
diff --git a/src/client/content_cao.h b/src/client/content_cao.h
new file mode 100644 (file)
index 0000000..9893213
--- /dev/null
@@ -0,0 +1,236 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include <map>
+#include "irrlichttypes_extrabloated.h"
+#include "clientobject.h"
+#include "object_properties.h"
+#include "itemgroup.h"
+#include "constants.h"
+
+class Camera;
+class Client;
+struct Nametag;
+
+/*
+       SmoothTranslator
+*/
+
+template<typename T>
+struct SmoothTranslator
+{
+       T val_old;
+       T val_current;
+       T val_target;
+       f32 anim_time = 0;
+       f32 anim_time_counter = 0;
+       bool aim_is_end = true;
+
+       SmoothTranslator() = default;
+
+       void init(T current);
+
+       void update(T new_target, bool is_end_position = false,
+               float update_interval = -1);
+
+       void translate(f32 dtime);
+};
+
+struct SmoothTranslatorWrapped : SmoothTranslator<f32>
+{
+       void translate(f32 dtime);
+};
+
+struct SmoothTranslatorWrappedv3f : SmoothTranslator<v3f>
+{
+       void translate(f32 dtime);
+};
+
+class GenericCAO : public ClientActiveObject
+{
+private:
+       // Only set at initialization
+       std::string m_name = "";
+       bool m_is_player = false;
+       bool m_is_local_player = false;
+       // Property-ish things
+       ObjectProperties m_prop;
+       //
+       scene::ISceneManager *m_smgr = nullptr;
+       Client *m_client = nullptr;
+       aabb3f m_selection_box = aabb3f(-BS/3.,-BS/3.,-BS/3., BS/3.,BS/3.,BS/3.);
+       scene::IMeshSceneNode *m_meshnode = nullptr;
+       scene::IAnimatedMeshSceneNode *m_animated_meshnode = nullptr;
+       WieldMeshSceneNode *m_wield_meshnode = nullptr;
+       scene::IBillboardSceneNode *m_spritenode = nullptr;
+       Nametag *m_nametag = nullptr;
+       v3f m_position = v3f(0.0f, 10.0f * BS, 0);
+       v3f m_velocity;
+       v3f m_acceleration;
+       v3f m_rotation;
+       s16 m_hp = 1;
+       SmoothTranslator<v3f> pos_translator;
+       SmoothTranslatorWrappedv3f rot_translator;
+       // Spritesheet/animation stuff
+       v2f m_tx_size = v2f(1,1);
+       v2s16 m_tx_basepos;
+       bool m_initial_tx_basepos_set = false;
+       bool m_tx_select_horiz_by_yawpitch = false;
+       v2s32 m_animation_range;
+       float m_animation_speed = 15.0f;
+       float m_animation_blend = 0.0f;
+       bool m_animation_loop = true;
+       // stores position and rotation for each bone name
+       std::unordered_map<std::string, core::vector2d<v3f>> m_bone_position;
+       std::string m_attachment_bone = "";
+       v3f m_attachment_position;
+       v3f m_attachment_rotation;
+       bool m_attached_to_local = false;
+       int m_anim_frame = 0;
+       int m_anim_num_frames = 1;
+       float m_anim_framelength = 0.2f;
+       float m_anim_timer = 0.0f;
+       ItemGroupList m_armor_groups;
+       float m_reset_textures_timer = -1.0f;
+       // stores texture modifier before punch update
+       std::string m_previous_texture_modifier = "";
+       // last applied texture modifier
+       std::string m_current_texture_modifier = "";
+       bool m_visuals_expired = false;
+       float m_step_distance_counter = 0.0f;
+       u8 m_last_light = 255;
+       bool m_is_visible = false;
+       s8 m_glow = 0;
+
+       std::vector<u16> m_children;
+
+public:
+       GenericCAO(Client *client, ClientEnvironment *env);
+
+       ~GenericCAO();
+
+       static ClientActiveObject* create(Client *client, ClientEnvironment *env)
+       {
+               return new GenericCAO(client, env);
+       }
+
+       inline ActiveObjectType getType() const
+       {
+               return ACTIVEOBJECT_TYPE_GENERIC;
+       }
+       inline const ItemGroupList &getGroups() const
+       {
+               return m_armor_groups;
+       }
+       void initialize(const std::string &data);
+
+       void processInitData(const std::string &data);
+
+       bool getCollisionBox(aabb3f *toset) const;
+
+       bool collideWithObjects() const;
+
+       virtual bool getSelectionBox(aabb3f *toset) const;
+
+       v3f getPosition();
+
+       inline const v3f &getRotation()
+       {
+               return m_rotation;
+       }
+
+       const bool isImmortal();
+
+       scene::ISceneNode *getSceneNode();
+
+       scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode();
+
+       inline f32 getStepHeight() const
+       {
+               return m_prop.stepheight;
+       }
+
+       inline bool isLocalPlayer() const
+       {
+               return m_is_local_player;
+       }
+
+       inline bool isVisible() const
+       {
+               return m_is_visible;
+       }
+
+       inline void setVisible(bool toset)
+       {
+               m_is_visible = toset;
+       }
+
+       void setChildrenVisible(bool toset);
+
+       ClientActiveObject *getParent() const;
+
+       void setAttachments();
+
+       void removeFromScene(bool permanent);
+
+       void addToScene(ITextureSource *tsrc);
+
+       inline void expireVisuals()
+       {
+               m_visuals_expired = true;
+       }
+
+       void updateLight(u8 light_at_pos);
+
+       void updateLightNoCheck(u8 light_at_pos);
+
+       v3s16 getLightPosition();
+
+       void updateNodePos();
+
+       void step(float dtime, ClientEnvironment *env);
+
+       void updateTexturePos();
+
+       // std::string copy is mandatory as mod can be a class member and there is a swap
+       // on those class members... do NOT pass by reference
+       void updateTextures(std::string mod);
+
+       void updateAnimation();
+
+       void updateAnimationSpeed();
+
+       void updateBonePosition();
+
+       void updateAttachments();
+
+       void processMessage(const std::string &data);
+
+       bool directReportPunch(v3f dir, const ItemStack *punchitem=NULL,
+                       float time_from_last_punch=1000000);
+
+       std::string debugInfoText();
+
+       std::string infoText()
+       {
+               return m_prop.infotext;
+       }
+};
diff --git a/src/client/content_cso.cpp b/src/client/content_cso.cpp
new file mode 100644 (file)
index 0000000..04c503f
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 "content_cso.h"
+#include <IBillboardSceneNode.h>
+#include "client/tile.h"
+#include "clientenvironment.h"
+#include "client.h"
+#include "map.h"
+
+class SmokePuffCSO: public ClientSimpleObject
+{
+       float m_age = 0.0f;
+       scene::IBillboardSceneNode *m_spritenode = nullptr;
+public:
+       SmokePuffCSO(scene::ISceneManager *smgr,
+                       ClientEnvironment *env, const v3f &pos, const v2f &size)
+       {
+               infostream<<"SmokePuffCSO: constructing"<<std::endl;
+               m_spritenode = smgr->addBillboardSceneNode(
+                               NULL, v2f(1,1), pos, -1);
+               m_spritenode->setMaterialTexture(0,
+                               env->getGameDef()->tsrc()->getTextureForMesh("smoke_puff.png"));
+               m_spritenode->setMaterialFlag(video::EMF_LIGHTING, false);
+               m_spritenode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
+               //m_spritenode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF);
+               m_spritenode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL);
+               m_spritenode->setMaterialFlag(video::EMF_FOG_ENABLE, true);
+               m_spritenode->setColor(video::SColor(255,0,0,0));
+               m_spritenode->setVisible(true);
+               m_spritenode->setSize(size);
+               /* Update brightness */
+               u8 light;
+               bool pos_ok;
+               MapNode n = env->getMap().getNodeNoEx(floatToInt(pos, BS), &pos_ok);
+               light = pos_ok ? decode_light(n.getLightBlend(env->getDayNightRatio(),
+                                                       env->getGameDef()->ndef()))
+                              : 64;
+               video::SColor color(255,light,light,light);
+               m_spritenode->setColor(color);
+       }
+       virtual ~SmokePuffCSO()
+       {
+               infostream<<"SmokePuffCSO: destructing"<<std::endl;
+               m_spritenode->remove();
+       }
+       void step(float dtime)
+       {
+               m_age += dtime;
+               if(m_age > 1.0){
+                       m_to_be_removed = true;
+               }
+       }
+};
+
+ClientSimpleObject* createSmokePuff(scene::ISceneManager *smgr,
+               ClientEnvironment *env, v3f pos, v2f size)
+{
+       return new SmokePuffCSO(smgr, env, pos, size);
+}
+
diff --git a/src/client/content_cso.h b/src/client/content_cso.h
new file mode 100644 (file)
index 0000000..cc92131
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "clientsimpleobject.h"
+
+ClientSimpleObject* createSmokePuff(scene::ISceneManager *smgr,
+               ClientEnvironment *env, v3f pos, v2f size);
diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp
new file mode 100644 (file)
index 0000000..4a0df61
--- /dev/null
@@ -0,0 +1,1430 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 "content_mapblock.h"
+#include "util/numeric.h"
+#include "util/directiontables.h"
+#include "mapblock_mesh.h"
+#include "settings.h"
+#include "nodedef.h"
+#include "client/tile.h"
+#include "mesh.h"
+#include <IMeshManipulator.h>
+#include "client/meshgen/collector.h"
+#include "client/renderingengine.h"
+#include "client.h"
+#include "noise.h"
+
+// Distance of light extrapolation (for oversized nodes)
+// After this distance, it gives up and considers light level constant
+#define SMOOTH_LIGHTING_OVERSIZE 1.0
+
+// Node edge count (for glasslike-framed)
+#define FRAMED_EDGE_COUNT 12
+
+// Node neighbor count, including edge-connected, but not vertex-connected
+// (for glasslike-framed)
+// Corresponding offsets are listed in g_27dirs
+#define FRAMED_NEIGHBOR_COUNT 18
+
+static const v3s16 light_dirs[8] = {
+       v3s16(-1, -1, -1),
+       v3s16(-1, -1,  1),
+       v3s16(-1,  1, -1),
+       v3s16(-1,  1,  1),
+       v3s16( 1, -1, -1),
+       v3s16( 1, -1,  1),
+       v3s16( 1,  1, -1),
+       v3s16( 1,  1,  1),
+};
+
+// Standard index set to make a quad on 4 vertices
+static constexpr u16 quad_indices[] = {0, 1, 2, 2, 3, 0};
+
+const std::string MapblockMeshGenerator::raillike_groupname = "connect_to_raillike";
+
+MapblockMeshGenerator::MapblockMeshGenerator(MeshMakeData *input, MeshCollector *output)
+{
+       data      = input;
+       collector = output;
+
+       nodedef   = data->m_client->ndef();
+       meshmanip = RenderingEngine::get_scene_manager()->getMeshManipulator();
+
+       enable_mesh_cache = g_settings->getBool("enable_mesh_cache") &&
+               !data->m_smooth_lighting; // Mesh cache is not supported with smooth lighting
+
+       blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE;
+}
+
+void MapblockMeshGenerator::useTile(int index, u8 set_flags, u8 reset_flags, bool special)
+{
+       if (special)
+               getSpecialTile(index, &tile, p == data->m_crack_pos_relative);
+       else
+               getTile(index, &tile);
+       if (!data->m_smooth_lighting)
+               color = encode_light(light, f->light_source);
+
+       for (auto &layer : tile.layers) {
+               layer.material_flags |= set_flags;
+               layer.material_flags &= ~reset_flags;
+       }
+}
+
+// Returns a tile, ready for use, non-rotated.
+void MapblockMeshGenerator::getTile(int index, TileSpec *tile)
+{
+       getNodeTileN(n, p, index, data, *tile);
+}
+
+// Returns a tile, ready for use, rotated according to the node facedir.
+void MapblockMeshGenerator::getTile(v3s16 direction, TileSpec *tile)
+{
+       getNodeTile(n, p, direction, data, *tile);
+}
+
+// Returns a special tile, ready for use, non-rotated.
+void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile, bool apply_crack)
+{
+       *tile = f->special_tiles[index];
+       TileLayer *top_layer = nullptr;
+
+       for (auto &layernum : tile->layers) {
+               TileLayer *layer = &layernum;
+               if (layer->texture_id == 0)
+                       continue;
+               top_layer = layer;
+               if (!layer->has_color)
+                       n.getColor(*f, &layer->color);
+       }
+
+       if (apply_crack)
+               top_layer->material_flags |= MATERIAL_FLAG_CRACK;
+}
+
+void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal,
+       float vertical_tiling)
+{
+       const v2f tcoords[4] = {v2f(0.0, 0.0), v2f(1.0, 0.0),
+               v2f(1.0, vertical_tiling), v2f(0.0, vertical_tiling)};
+       video::S3DVertex vertices[4];
+       bool shade_face = !f->light_source && (normal != v3s16(0, 0, 0));
+       v3f normal2(normal.X, normal.Y, normal.Z);
+       for (int j = 0; j < 4; j++) {
+               vertices[j].Pos = coords[j] + origin;
+               vertices[j].Normal = normal2;
+               if (data->m_smooth_lighting)
+                       vertices[j].Color = blendLightColor(coords[j]);
+               else
+                       vertices[j].Color = color;
+               if (shade_face)
+                       applyFacesShading(vertices[j].Color, normal2);
+               vertices[j].TCoords = tcoords[j];
+       }
+       collector->append(tile, vertices, 4, quad_indices, 6);
+}
+
+// Create a cuboid.
+//  tiles     - the tiles (materials) to use (for all 6 faces)
+//  tilecount - number of entries in tiles, 1<=tilecount<=6
+//  lights    - vertex light levels. The order is the same as in light_dirs.
+//              NULL may be passed if smooth lighting is disabled.
+//  txc       - texture coordinates - this is a list of texture coordinates
+//              for the opposite corners of each face - therefore, there
+//              should be (2+2)*6=24 values in the list. The order of
+//              the faces in the list is up-down-right-left-back-front
+//              (compatible with ContentFeatures).
+void MapblockMeshGenerator::drawCuboid(const aabb3f &box,
+       TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc)
+{
+       assert(tilecount >= 1 && tilecount <= 6); // pre-condition
+
+       v3f min = box.MinEdge;
+       v3f max = box.MaxEdge;
+
+       video::SColor colors[6];
+       if (!data->m_smooth_lighting) {
+               for (int face = 0; face != 6; ++face) {
+                       colors[face] = encode_light(light, f->light_source);
+               }
+               if (!f->light_source) {
+                       applyFacesShading(colors[0], v3f(0, 1, 0));
+                       applyFacesShading(colors[1], v3f(0, -1, 0));
+                       applyFacesShading(colors[2], v3f(1, 0, 0));
+                       applyFacesShading(colors[3], v3f(-1, 0, 0));
+                       applyFacesShading(colors[4], v3f(0, 0, 1));
+                       applyFacesShading(colors[5], v3f(0, 0, -1));
+               }
+       }
+
+       video::S3DVertex vertices[24] = {
+               // top
+               video::S3DVertex(min.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[0], txc[1]),
+               video::S3DVertex(max.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[2], txc[1]),
+               video::S3DVertex(max.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[2], txc[3]),
+               video::S3DVertex(min.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[0], txc[3]),
+               // bottom
+               video::S3DVertex(min.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[4], txc[5]),
+               video::S3DVertex(max.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[6], txc[5]),
+               video::S3DVertex(max.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[6], txc[7]),
+               video::S3DVertex(min.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[4], txc[7]),
+               // right
+               video::S3DVertex(max.X, max.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[9]),
+               video::S3DVertex(max.X, max.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[9]),
+               video::S3DVertex(max.X, min.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[11]),
+               video::S3DVertex(max.X, min.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[11]),
+               // left
+               video::S3DVertex(min.X, max.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[13]),
+               video::S3DVertex(min.X, max.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[13]),
+               video::S3DVertex(min.X, min.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[15]),
+               video::S3DVertex(min.X, min.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[15]),
+               // back
+               video::S3DVertex(max.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[17]),
+               video::S3DVertex(min.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[17]),
+               video::S3DVertex(min.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[19]),
+               video::S3DVertex(max.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[19]),
+               // front
+               video::S3DVertex(min.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[21]),
+               video::S3DVertex(max.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[21]),
+               video::S3DVertex(max.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[23]),
+               video::S3DVertex(min.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[23]),
+       };
+
+       static const u8 light_indices[24] = {
+               3, 7, 6, 2,
+               0, 4, 5, 1,
+               6, 7, 5, 4,
+               3, 2, 0, 1,
+               7, 3, 1, 5,
+               2, 6, 4, 0
+       };
+
+       for (int face = 0; face < 6; face++) {
+               int tileindex = MYMIN(face, tilecount - 1);
+               const TileSpec &tile = tiles[tileindex];
+               for (int j = 0; j < 4; j++) {
+                       video::S3DVertex &vertex = vertices[face * 4 + j];
+                       v2f &tcoords = vertex.TCoords;
+                       switch (tile.rotation) {
+                       case 0:
+                               break;
+                       case 1: // R90
+                               tcoords.rotateBy(90, irr::core::vector2df(0, 0));
+                               break;
+                       case 2: // R180
+                               tcoords.rotateBy(180, irr::core::vector2df(0, 0));
+                               break;
+                       case 3: // R270
+                               tcoords.rotateBy(270, irr::core::vector2df(0, 0));
+                               break;
+                       case 4: // FXR90
+                               tcoords.X = 1.0 - tcoords.X;
+                               tcoords.rotateBy(90, irr::core::vector2df(0, 0));
+                               break;
+                       case 5: // FXR270
+                               tcoords.X = 1.0 - tcoords.X;
+                               tcoords.rotateBy(270, irr::core::vector2df(0, 0));
+                               break;
+                       case 6: // FYR90
+                               tcoords.Y = 1.0 - tcoords.Y;
+                               tcoords.rotateBy(90, irr::core::vector2df(0, 0));
+                               break;
+                       case 7: // FYR270
+                               tcoords.Y = 1.0 - tcoords.Y;
+                               tcoords.rotateBy(270, irr::core::vector2df(0, 0));
+                               break;
+                       case 8: // FX
+                               tcoords.X = 1.0 - tcoords.X;
+                               break;
+                       case 9: // FY
+                               tcoords.Y = 1.0 - tcoords.Y;
+                               break;
+                       default:
+                               break;
+                       }
+               }
+       }
+
+       if (data->m_smooth_lighting) {
+               for (int j = 0; j < 24; ++j) {
+                       video::S3DVertex &vertex = vertices[j];
+                       vertex.Color = encode_light(
+                               lights[light_indices[j]].getPair(MYMAX(0.0f, vertex.Normal.Y)),
+                               f->light_source);
+                       if (!f->light_source)
+                               applyFacesShading(vertex.Color, vertex.Normal);
+               }
+       }
+
+       // Add to mesh collector
+       for (int k = 0; k < 6; ++k) {
+               int tileindex = MYMIN(k, tilecount - 1);
+               collector->append(tiles[tileindex], vertices + 4 * k, 4, quad_indices, 6);
+       }
+}
+
+// Gets the base lighting values for a node
+void MapblockMeshGenerator::getSmoothLightFrame()
+{
+       for (int k = 0; k < 8; ++k)
+               frame.sunlight[k] = false;
+       for (int k = 0; k < 8; ++k) {
+               LightPair light(getSmoothLightTransparent(blockpos_nodes + p, light_dirs[k], data));
+               frame.lightsDay[k] = light.lightDay;
+               frame.lightsNight[k] = light.lightNight;
+               // If there is direct sunlight and no ambient occlusion at some corner,
+               // mark the vertical edge (top and bottom corners) containing it.
+               if (light.lightDay == 255) {
+                       frame.sunlight[k] = true;
+                       frame.sunlight[k ^ 2] = true;
+               }
+       }
+}
+
+// Calculates vertex light level
+//  vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so)
+LightInfo MapblockMeshGenerator::blendLight(const v3f &vertex_pos)
+{
+       // Light levels at (logical) node corners are known. Here,
+       // trilinear interpolation is used to calculate light level
+       // at a given point in the node.
+       f32 x = core::clamp(vertex_pos.X / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
+       f32 y = core::clamp(vertex_pos.Y / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
+       f32 z = core::clamp(vertex_pos.Z / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
+       f32 lightDay = 0.0; // daylight
+       f32 lightNight = 0.0;
+       f32 lightBoosted = 0.0; // daylight + direct sunlight, if any
+       for (int k = 0; k < 8; ++k) {
+               f32 dx = (k & 4) ? x : 1 - x;
+               f32 dy = (k & 2) ? y : 1 - y;
+               f32 dz = (k & 1) ? z : 1 - z;
+               // Use direct sunlight (255), if any; use daylight otherwise.
+               f32 light_boosted = frame.sunlight[k] ? 255 : frame.lightsDay[k];
+               lightDay += dx * dy * dz * frame.lightsDay[k];
+               lightNight += dx * dy * dz * frame.lightsNight[k];
+               lightBoosted += dx * dy * dz * light_boosted;
+       }
+       return LightInfo{lightDay, lightNight, lightBoosted};
+}
+
+// Calculates vertex color to be used in mapblock mesh
+//  vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so)
+//  tile_color - node's tile color
+video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos)
+{
+       LightInfo light = blendLight(vertex_pos);
+       return encode_light(light.getPair(), f->light_source);
+}
+
+video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos,
+       const v3f &vertex_normal)
+{
+       LightInfo light = blendLight(vertex_pos);
+       video::SColor color = encode_light(light.getPair(MYMAX(0.0f, vertex_normal.Y)), f->light_source);
+       if (!f->light_source)
+               applyFacesShading(color, vertex_normal);
+       return color;
+}
+
+void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 *coords)
+{
+       f32 tx1 = (box.MinEdge.X / BS) + 0.5;
+       f32 ty1 = (box.MinEdge.Y / BS) + 0.5;
+       f32 tz1 = (box.MinEdge.Z / BS) + 0.5;
+       f32 tx2 = (box.MaxEdge.X / BS) + 0.5;
+       f32 ty2 = (box.MaxEdge.Y / BS) + 0.5;
+       f32 tz2 = (box.MaxEdge.Z / BS) + 0.5;
+       f32 txc[24] = {
+                   tx1, 1 - tz2,     tx2, 1 - tz1, // up
+                   tx1,     tz1,     tx2,     tz2, // down
+                   tz1, 1 - ty2,     tz2, 1 - ty1, // right
+               1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1, // left
+               1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1, // back
+                   tx1, 1 - ty2,     tx2, 1 - ty1, // front
+       };
+       for (int i = 0; i != 24; ++i)
+               coords[i] = txc[i];
+}
+
+void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
+       TileSpec *tiles, int tile_count)
+{
+       f32 texture_coord_buf[24];
+       f32 dx1 = box.MinEdge.X;
+       f32 dy1 = box.MinEdge.Y;
+       f32 dz1 = box.MinEdge.Z;
+       f32 dx2 = box.MaxEdge.X;
+       f32 dy2 = box.MaxEdge.Y;
+       f32 dz2 = box.MaxEdge.Z;
+       box.MinEdge += origin;
+       box.MaxEdge += origin;
+       if (!txc) {
+               generateCuboidTextureCoords(box, texture_coord_buf);
+               txc = texture_coord_buf;
+       }
+       if (!tiles) {
+               tiles = &tile;
+               tile_count = 1;
+       }
+       if (data->m_smooth_lighting) {
+               LightInfo lights[8];
+               for (int j = 0; j < 8; ++j) {
+                       v3f d;
+                       d.X = (j & 4) ? dx2 : dx1;
+                       d.Y = (j & 2) ? dy2 : dy1;
+                       d.Z = (j & 1) ? dz2 : dz1;
+                       lights[j] = blendLight(d);
+               }
+               drawCuboid(box, tiles, tile_count, lights, txc);
+       } else {
+               drawCuboid(box, tiles, tile_count, nullptr, txc);
+       }
+}
+
+void MapblockMeshGenerator::prepareLiquidNodeDrawing()
+{
+       getSpecialTile(0, &tile_liquid_top);
+       getSpecialTile(1, &tile_liquid);
+
+       MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y + 1, p.Z));
+       MapNode nbottom = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y - 1, p.Z));
+       c_flowing = nodedef->getId(f->liquid_alternative_flowing);
+       c_source = nodedef->getId(f->liquid_alternative_source);
+       top_is_same_liquid = (ntop.getContent() == c_flowing) || (ntop.getContent() == c_source);
+       draw_liquid_bottom = (nbottom.getContent() != c_flowing) && (nbottom.getContent() != c_source);
+       if (draw_liquid_bottom) {
+               const ContentFeatures &f2 = nodedef->get(nbottom.getContent());
+               if (f2.solidness > 1)
+                       draw_liquid_bottom = false;
+       }
+
+       if (data->m_smooth_lighting)
+               return; // don't need to pre-compute anything in this case
+
+       if (f->light_source != 0) {
+               // If this liquid emits light and doesn't contain light, draw
+               // it at what it emits, for an increased effect
+               u8 e = decode_light(f->light_source);
+               light = LightPair(std::max(e, light.lightDay), std::max(e, light.lightNight));
+       } else if (nodedef->get(ntop).param_type == CPT_LIGHT) {
+               // Otherwise, use the light of the node on top if possible
+               light = LightPair(getInteriorLight(ntop, 0, nodedef));
+       }
+
+       color_liquid_top = encode_light(light, f->light_source);
+       color = encode_light(light, f->light_source);
+}
+
+void MapblockMeshGenerator::getLiquidNeighborhood()
+{
+       u8 range = rangelim(nodedef->get(c_flowing).liquid_range, 1, 8);
+
+       for (int w = -1; w <= 1; w++)
+       for (int u = -1; u <= 1; u++) {
+               NeighborData &neighbor = liquid_neighbors[w + 1][u + 1];
+               v3s16 p2 = p + v3s16(u, 0, w);
+               MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
+               neighbor.content = n2.getContent();
+               neighbor.level = -0.5 * BS;
+               neighbor.is_same_liquid = false;
+               neighbor.top_is_same_liquid = false;
+
+               if (neighbor.content == CONTENT_IGNORE)
+                       continue;
+
+               if (neighbor.content == c_source) {
+                       neighbor.is_same_liquid = true;
+                       neighbor.level = 0.5 * BS;
+               } else if (neighbor.content == c_flowing) {
+                       neighbor.is_same_liquid = true;
+                       u8 liquid_level = (n2.param2 & LIQUID_LEVEL_MASK);
+                       if (liquid_level <= LIQUID_LEVEL_MAX + 1 - range)
+                               liquid_level = 0;
+                       else
+                               liquid_level -= (LIQUID_LEVEL_MAX + 1 - range);
+                       neighbor.level = (-0.5 + (liquid_level + 0.5) / range) * BS;
+               }
+
+               // Check node above neighbor.
+               // NOTE: This doesn't get executed if neighbor
+               //       doesn't exist
+               p2.Y++;
+               n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
+               if (n2.getContent() == c_source || n2.getContent() == c_flowing)
+                       neighbor.top_is_same_liquid = true;
+       }
+}
+
+void MapblockMeshGenerator::calculateCornerLevels()
+{
+       for (int k = 0; k < 2; k++)
+       for (int i = 0; i < 2; i++)
+               corner_levels[k][i] = getCornerLevel(i, k);
+}
+
+f32 MapblockMeshGenerator::getCornerLevel(int i, int k)
+{
+       float sum = 0;
+       int count = 0;
+       int air_count = 0;
+       for (int dk = 0; dk < 2; dk++)
+       for (int di = 0; di < 2; di++) {
+               NeighborData &neighbor_data = liquid_neighbors[k + dk][i + di];
+               content_t content = neighbor_data.content;
+
+               // If top is liquid, draw starting from top of node
+               if (neighbor_data.top_is_same_liquid)
+                       return 0.5 * BS;
+
+               // Source always has the full height
+               if (content == c_source)
+                       return 0.5 * BS;
+
+               // Flowing liquid has level information
+               if (content == c_flowing) {
+                       sum += neighbor_data.level;
+                       count++;
+               } else if (content == CONTENT_AIR) {
+                       air_count++;
+                       if (air_count >= 2)
+                               return -0.5 * BS + 0.2;
+               }
+       }
+       if (count > 0)
+               return sum / count;
+       return 0;
+}
+
+void MapblockMeshGenerator::drawLiquidSides()
+{
+       struct LiquidFaceDesc {
+               v3s16 dir; // XZ
+               v3s16 p[2]; // XZ only; 1 means +, 0 means -
+       };
+       struct UV {
+               int u, v;
+       };
+       static const LiquidFaceDesc base_faces[4] = {
+               {v3s16( 1, 0,  0), {v3s16(1, 0, 1), v3s16(1, 0, 0)}},
+               {v3s16(-1, 0,  0), {v3s16(0, 0, 0), v3s16(0, 0, 1)}},
+               {v3s16( 0, 0,  1), {v3s16(0, 0, 1), v3s16(1, 0, 1)}},
+               {v3s16( 0, 0, -1), {v3s16(1, 0, 0), v3s16(0, 0, 0)}},
+       };
+       static const UV base_vertices[4] = {
+               {0, 1},
+               {1, 1},
+               {1, 0},
+               {0, 0}
+       };
+
+       for (const auto &face : base_faces) {
+               const NeighborData &neighbor = liquid_neighbors[face.dir.Z + 1][face.dir.X + 1];
+
+               // No face between nodes of the same liquid, unless there is node
+               // at the top to which it should be connected. Again, unless the face
+               // there would be inside the liquid
+               if (neighbor.is_same_liquid) {
+                       if (!top_is_same_liquid)
+                               continue;
+                       if (neighbor.top_is_same_liquid)
+                               continue;
+               }
+
+               const ContentFeatures &neighbor_features = nodedef->get(neighbor.content);
+               // Don't draw face if neighbor is blocking the view
+               if (neighbor_features.solidness == 2)
+                       continue;
+
+               video::S3DVertex vertices[4];
+               for (int j = 0; j < 4; j++) {
+                       const UV &vertex = base_vertices[j];
+                       const v3s16 &base = face.p[vertex.u];
+                       v3f pos;
+                       pos.X = (base.X - 0.5) * BS;
+                       pos.Z = (base.Z - 0.5) * BS;
+                       if (vertex.v)
+                               pos.Y = neighbor.is_same_liquid ? corner_levels[base.Z][base.X] : -0.5 * BS;
+                       else
+                               pos.Y =     !top_is_same_liquid ? corner_levels[base.Z][base.X] :  0.5 * BS;
+                       if (data->m_smooth_lighting)
+                               color = blendLightColor(pos);
+                       pos += origin;
+                       vertices[j] = video::S3DVertex(pos.X, pos.Y, pos.Z, 0, 0, 0, color, vertex.u, vertex.v);
+               };
+               collector->append(tile_liquid, vertices, 4, quad_indices, 6);
+       }
+}
+
+void MapblockMeshGenerator::drawLiquidTop()
+{
+       // To get backface culling right, the vertices need to go
+       // clockwise around the front of the face. And we happened to
+       // calculate corner levels in exact reverse order.
+       static const int corner_resolve[4][2] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}};
+
+       video::S3DVertex vertices[4] = {
+               video::S3DVertex(-BS / 2, 0,  BS / 2, 0, 0, 0, color_liquid_top, 0, 1),
+               video::S3DVertex( BS / 2, 0,  BS / 2, 0, 0, 0, color_liquid_top, 1, 1),
+               video::S3DVertex( BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0),
+               video::S3DVertex(-BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0),
+       };
+
+       for (int i = 0; i < 4; i++) {
+               int u = corner_resolve[i][0];
+               int w = corner_resolve[i][1];
+               vertices[i].Pos.Y += corner_levels[w][u];
+               if (data->m_smooth_lighting)
+                       vertices[i].Color = blendLightColor(vertices[i].Pos);
+               vertices[i].Pos += origin;
+       }
+
+       // Default downwards-flowing texture animation goes from
+       // -Z towards +Z, thus the direction is +Z.
+       // Rotate texture to make animation go in flow direction
+       // Positive if liquid moves towards +Z
+       f32 dz = (corner_levels[0][0] + corner_levels[0][1]) -
+                (corner_levels[1][0] + corner_levels[1][1]);
+       // Positive if liquid moves towards +X
+       f32 dx = (corner_levels[0][0] + corner_levels[1][0]) -
+                (corner_levels[0][1] + corner_levels[1][1]);
+       f32 tcoord_angle = atan2(dz, dx) * core::RADTODEG;
+       v2f tcoord_center(0.5, 0.5);
+       v2f tcoord_translate(blockpos_nodes.Z + p.Z, blockpos_nodes.X + p.X);
+       tcoord_translate.rotateBy(tcoord_angle);
+       tcoord_translate.X -= floor(tcoord_translate.X);
+       tcoord_translate.Y -= floor(tcoord_translate.Y);
+
+       for (video::S3DVertex &vertex : vertices) {
+               vertex.TCoords.rotateBy(tcoord_angle, tcoord_center);
+               vertex.TCoords += tcoord_translate;
+       }
+
+       std::swap(vertices[0].TCoords, vertices[2].TCoords);
+
+       collector->append(tile_liquid_top, vertices, 4, quad_indices, 6);
+}
+
+void MapblockMeshGenerator::drawLiquidBottom()
+{
+       video::S3DVertex vertices[4] = {
+               video::S3DVertex(-BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0),
+               video::S3DVertex( BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0),
+               video::S3DVertex( BS / 2, -BS / 2,  BS / 2, 0, 0, 0, color_liquid_top, 1, 1),
+               video::S3DVertex(-BS / 2, -BS / 2,  BS / 2, 0, 0, 0, color_liquid_top, 0, 1),
+       };
+
+       for (int i = 0; i < 4; i++) {
+               if (data->m_smooth_lighting)
+                       vertices[i].Color = blendLightColor(vertices[i].Pos);
+               vertices[i].Pos += origin;
+       }
+
+       collector->append(tile_liquid_top, vertices, 4, quad_indices, 6);
+}
+
+void MapblockMeshGenerator::drawLiquidNode()
+{
+       prepareLiquidNodeDrawing();
+       getLiquidNeighborhood();
+       calculateCornerLevels();
+       drawLiquidSides();
+       if (!top_is_same_liquid)
+               drawLiquidTop();
+       if (draw_liquid_bottom)
+               drawLiquidBottom();
+}
+
+void MapblockMeshGenerator::drawGlasslikeNode()
+{
+       useTile(0, 0, 0);
+
+       for (int face = 0; face < 6; face++) {
+               // Check this neighbor
+               v3s16 dir = g_6dirs[face];
+               v3s16 neighbor_pos = blockpos_nodes + p + dir;
+               MapNode neighbor = data->m_vmanip.getNodeNoExNoEmerge(neighbor_pos);
+               // Don't make face if neighbor is of same type
+               if (neighbor.getContent() == n.getContent())
+                       continue;
+               // Face at Z-
+               v3f vertices[4] = {
+                       v3f(-BS / 2,  BS / 2, -BS / 2),
+                       v3f( BS / 2,  BS / 2, -BS / 2),
+                       v3f( BS / 2, -BS / 2, -BS / 2),
+                       v3f(-BS / 2, -BS / 2, -BS / 2),
+               };
+
+               for (v3f &vertex : vertices) {
+                       switch (face) {
+                               case D6D_ZP:
+                                       vertex.rotateXZBy(180); break;
+                               case D6D_YP:
+                                       vertex.rotateYZBy( 90); break;
+                               case D6D_XP:
+                                       vertex.rotateXZBy( 90); break;
+                               case D6D_ZN:
+                                       vertex.rotateXZBy(  0); break;
+                               case D6D_YN:
+                                       vertex.rotateYZBy(-90); break;
+                               case D6D_XN:
+                                       vertex.rotateXZBy(-90); break;
+                       }
+               }
+               drawQuad(vertices, dir);
+       }
+}
+
+void MapblockMeshGenerator::drawGlasslikeFramedNode()
+{
+       TileSpec tiles[6];
+       for (int face = 0; face < 6; face++)
+               getTile(g_6dirs[face], &tiles[face]);
+
+       if (!data->m_smooth_lighting)
+               color = encode_light(light, f->light_source);
+
+       TileSpec glass_tiles[6];
+       for (auto &glass_tile : glass_tiles)
+               glass_tile = tiles[4];
+
+       u8 param2 = n.getParam2();
+       bool H_merge = !(param2 & 128);
+       bool V_merge = !(param2 & 64);
+       param2 &= 63;
+
+       static const float a = BS / 2.0f;
+       static const float g = a - 0.03f;
+       static const float b = 0.876f * (BS / 2.0f);
+
+       static const aabb3f frame_edges[FRAMED_EDGE_COUNT] = {
+               aabb3f( b,  b, -a,  a,  a,  a), // y+
+               aabb3f(-a,  b, -a, -b,  a,  a), // y+
+               aabb3f( b, -a, -a,  a, -b,  a), // y-
+               aabb3f(-a, -a, -a, -b, -b,  a), // y-
+               aabb3f( b, -a,  b,  a,  a,  a), // x+
+               aabb3f( b, -a, -a,  a,  a, -b), // x+
+               aabb3f(-a, -a,  b, -b,  a,  a), // x-
+               aabb3f(-a, -a, -a, -b,  a, -b), // x-
+               aabb3f(-a,  b,  b,  a,  a,  a), // z+
+               aabb3f(-a, -a,  b,  a, -b,  a), // z+
+               aabb3f(-a, -a, -a,  a, -b, -b), // z-
+               aabb3f(-a,  b, -a,  a,  a, -b), // z-
+       };
+
+       // tables of neighbour (connect if same type and merge allowed),
+       // checked with g_26dirs
+
+       // 1 = connect, 0 = face visible
+       bool nb[FRAMED_NEIGHBOR_COUNT] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+
+       // 1 = check
+       static const bool check_nb_vertical [FRAMED_NEIGHBOR_COUNT] =
+               {0,1,0,0,1,0, 0,0,0,0, 0,0,0,0, 0,0,0,0};
+       static const bool check_nb_horizontal [FRAMED_NEIGHBOR_COUNT] =
+               {1,0,1,1,0,1, 0,0,0,0, 1,1,1,1, 0,0,0,0};
+       static const bool check_nb_all [FRAMED_NEIGHBOR_COUNT] =
+               {1,1,1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1};
+       const bool *check_nb = check_nb_all;
+
+       // neighbours checks for frames visibility
+       if (H_merge || V_merge) {
+               if (!H_merge)
+                       check_nb = check_nb_vertical; // vertical-only merge
+               if (!V_merge)
+                       check_nb = check_nb_horizontal; // horizontal-only merge
+               content_t current = n.getContent();
+               for (int i = 0; i < FRAMED_NEIGHBOR_COUNT; i++) {
+                       if (!check_nb[i])
+                               continue;
+                       v3s16 n2p = blockpos_nodes + p + g_26dirs[i];
+                       MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
+                       content_t n2c = n2.getContent();
+                       if (n2c == current)
+                               nb[i] = 1;
+               }
+       }
+
+       // edge visibility
+
+       static const u8 nb_triplet[FRAMED_EDGE_COUNT][3] = {
+               {1, 2,  7}, {1, 5,  6}, {4, 2, 15}, {4, 5, 14},
+               {2, 0, 11}, {2, 3, 13}, {5, 0, 10}, {5, 3, 12},
+               {0, 1,  8}, {0, 4, 16}, {3, 4, 17}, {3, 1,  9},
+       };
+
+       tile = tiles[1];
+       for (int edge = 0; edge < FRAMED_EDGE_COUNT; edge++) {
+               bool edge_invisible;
+               if (nb[nb_triplet[edge][2]])
+                       edge_invisible = nb[nb_triplet[edge][0]] & nb[nb_triplet[edge][1]];
+               else
+                       edge_invisible = nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]];
+               if (edge_invisible)
+                       continue;
+               drawAutoLightedCuboid(frame_edges[edge]);
+       }
+
+       for (int face = 0; face < 6; face++) {
+               if (nb[face])
+                       continue;
+
+               tile = glass_tiles[face];
+               // Face at Z-
+               v3f vertices[4] = {
+                       v3f(-a,  a, -g),
+                       v3f( a,  a, -g),
+                       v3f( a, -a, -g),
+                       v3f(-a, -a, -g),
+               };
+
+               for (v3f &vertex : vertices) {
+                       switch (face) {
+                               case D6D_ZP:
+                                       vertex.rotateXZBy(180); break;
+                               case D6D_YP:
+                                       vertex.rotateYZBy( 90); break;
+                               case D6D_XP:
+                                       vertex.rotateXZBy( 90); break;
+                               case D6D_ZN:
+                                       vertex.rotateXZBy(  0); break;
+                               case D6D_YN:
+                                       vertex.rotateYZBy(-90); break;
+                               case D6D_XN:
+                                       vertex.rotateXZBy(-90); break;
+                       }
+               }
+               v3s16 dir = g_6dirs[face];
+               drawQuad(vertices, dir);
+       }
+
+       // Optionally render internal liquid level defined by param2
+       // Liquid is textured with 1 tile defined in nodedef 'special_tiles'
+       if (param2 > 0 && f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL &&
+                       f->special_tiles[0].layers[0].texture) {
+               // Internal liquid level has param2 range 0 .. 63,
+               // convert it to -0.5 .. 0.5
+               float vlev = (param2 / 63.0f) * 2.0f - 1.0f;
+               getSpecialTile(0, &tile);
+               drawAutoLightedCuboid(aabb3f(-(nb[5] ? g : b),
+                                            -(nb[4] ? g : b),
+                                            -(nb[3] ? g : b),
+                                             (nb[2] ? g : b),
+                                             (nb[1] ? g : b) * vlev,
+                                             (nb[0] ? g : b)));
+       }
+}
+
+void MapblockMeshGenerator::drawAllfacesNode()
+{
+       static const aabb3f box(-BS / 2, -BS / 2, -BS / 2, BS / 2, BS / 2, BS / 2);
+       useTile(0, 0, 0);
+       drawAutoLightedCuboid(box);
+}
+
+void MapblockMeshGenerator::drawTorchlikeNode()
+{
+       u8 wall = n.getWallMounted(nodedef);
+       u8 tileindex = 0;
+       switch (wall) {
+               case DWM_YP: tileindex = 1; break; // ceiling
+               case DWM_YN: tileindex = 0; break; // floor
+               default:     tileindex = 2; // side (or invalid—should we care?)
+       }
+       useTile(tileindex, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
+
+       float size = BS / 2 * f->visual_scale;
+       v3f vertices[4] = {
+               v3f(-size,  size, 0),
+               v3f( size,  size, 0),
+               v3f( size, -size, 0),
+               v3f(-size, -size, 0),
+       };
+
+       for (v3f &vertex : vertices) {
+               switch (wall) {
+                       case DWM_YP:
+                               vertex.rotateXZBy(-45); break;
+                       case DWM_YN:
+                               vertex.rotateXZBy( 45); break;
+                       case DWM_XP:
+                               vertex.rotateXZBy(  0); break;
+                       case DWM_XN:
+                               vertex.rotateXZBy(180); break;
+                       case DWM_ZP:
+                               vertex.rotateXZBy( 90); break;
+                       case DWM_ZN:
+                               vertex.rotateXZBy(-90); break;
+               }
+       }
+       drawQuad(vertices);
+}
+
+void MapblockMeshGenerator::drawSignlikeNode()
+{
+       u8 wall = n.getWallMounted(nodedef);
+       useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
+       static const float offset = BS / 16;
+       float size = BS / 2 * f->visual_scale;
+       // Wall at X+ of node
+       v3f vertices[4] = {
+               v3f(BS / 2 - offset,  size,  size),
+               v3f(BS / 2 - offset,  size, -size),
+               v3f(BS / 2 - offset, -size, -size),
+               v3f(BS / 2 - offset, -size,  size),
+       };
+
+       for (v3f &vertex : vertices) {
+               switch (wall) {
+                       case DWM_YP:
+                               vertex.rotateXYBy( 90); break;
+                       case DWM_YN:
+                               vertex.rotateXYBy(-90); break;
+                       case DWM_XP:
+                               vertex.rotateXZBy(  0); break;
+                       case DWM_XN:
+                               vertex.rotateXZBy(180); break;
+                       case DWM_ZP:
+                               vertex.rotateXZBy( 90); break;
+                       case DWM_ZN:
+                               vertex.rotateXZBy(-90); break;
+               }
+       }
+       drawQuad(vertices);
+}
+
+void MapblockMeshGenerator::drawPlantlikeQuad(float rotation, float quad_offset,
+       bool offset_top_only)
+{
+       v3f vertices[4] = {
+               v3f(-scale, -BS / 2 + 2.0 * scale * plant_height, 0),
+               v3f( scale, -BS / 2 + 2.0 * scale * plant_height, 0),
+               v3f( scale, -BS / 2, 0),
+               v3f(-scale, -BS / 2, 0),
+       };
+       if (random_offset_Y) {
+               PseudoRandom yrng(face_num++ | p.X << 16 | p.Z << 8 | p.Y << 24);
+               offset.Y = -BS * ((yrng.next() % 16 / 16.0) * 0.125);
+       }
+       int offset_count = offset_top_only ? 2 : 4;
+       for (int i = 0; i < offset_count; i++)
+               vertices[i].Z += quad_offset;
+
+       for (v3f &vertex : vertices) {
+               vertex.rotateXZBy(rotation + rotate_degree);
+               vertex += offset;
+       }
+       drawQuad(vertices, v3s16(0, 0, 0), plant_height);
+}
+
+void MapblockMeshGenerator::drawPlantlike()
+{
+       draw_style = PLANT_STYLE_CROSS;
+       scale = BS / 2 * f->visual_scale;
+       offset = v3f(0, 0, 0);
+       rotate_degree = 0;
+       random_offset_Y = false;
+       face_num = 0;
+       plant_height = 1.0;
+
+       switch (f->param_type_2) {
+       case CPT2_MESHOPTIONS:
+               draw_style = PlantlikeStyle(n.param2 & MO_MASK_STYLE);
+               if (n.param2 & MO_BIT_SCALE_SQRT2)
+                       scale *= 1.41421;
+               if (n.param2 & MO_BIT_RANDOM_OFFSET) {
+                       PseudoRandom rng(p.X << 8 | p.Z | p.Y << 16);
+                       offset.X = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145);
+                       offset.Z = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145);
+               }
+               if (n.param2 & MO_BIT_RANDOM_OFFSET_Y)
+                       random_offset_Y = true;
+               break;
+
+       case CPT2_DEGROTATE:
+               rotate_degree = n.param2 * 2;
+               break;
+
+       case CPT2_LEVELED:
+               plant_height = n.param2 / 16.0;
+               break;
+
+       default:
+               break;
+       }
+
+       switch (draw_style) {
+       case PLANT_STYLE_CROSS:
+               drawPlantlikeQuad(46);
+               drawPlantlikeQuad(-44);
+               break;
+
+       case PLANT_STYLE_CROSS2:
+               drawPlantlikeQuad(91);
+               drawPlantlikeQuad(1);
+               break;
+
+       case PLANT_STYLE_STAR:
+               drawPlantlikeQuad(121);
+               drawPlantlikeQuad(241);
+               drawPlantlikeQuad(1);
+               break;
+
+       case PLANT_STYLE_HASH:
+               drawPlantlikeQuad(  1, BS / 4);
+               drawPlantlikeQuad( 91, BS / 4);
+               drawPlantlikeQuad(181, BS / 4);
+               drawPlantlikeQuad(271, BS / 4);
+               break;
+
+       case PLANT_STYLE_HASH2:
+               drawPlantlikeQuad(  1, -BS / 2, true);
+               drawPlantlikeQuad( 91, -BS / 2, true);
+               drawPlantlikeQuad(181, -BS / 2, true);
+               drawPlantlikeQuad(271, -BS / 2, true);
+               break;
+       }
+}
+
+void MapblockMeshGenerator::drawPlantlikeNode()
+{
+       useTile();
+       drawPlantlike();
+}
+
+void MapblockMeshGenerator::drawPlantlikeRootedNode()
+{
+       useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, 0, true);
+       origin += v3f(0.0, BS, 0.0);
+       p.Y++;
+       if (data->m_smooth_lighting) {
+               getSmoothLightFrame();
+       } else {
+               MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
+               light = LightPair(getInteriorLight(ntop, 1, nodedef));
+       }
+       drawPlantlike();
+       p.Y--;
+}
+
+void MapblockMeshGenerator::drawFirelikeQuad(float rotation, float opening_angle,
+       float offset_h, float offset_v)
+{
+       v3f vertices[4] = {
+               v3f(-scale, -BS / 2 + scale * 2, 0),
+               v3f( scale, -BS / 2 + scale * 2, 0),
+               v3f( scale, -BS / 2, 0),
+               v3f(-scale, -BS / 2, 0),
+       };
+
+       for (v3f &vertex : vertices) {
+               vertex.rotateYZBy(opening_angle);
+               vertex.Z += offset_h;
+               vertex.rotateXZBy(rotation);
+               vertex.Y += offset_v;
+       }
+       drawQuad(vertices);
+}
+
+void MapblockMeshGenerator::drawFirelikeNode()
+{
+       useTile();
+       scale = BS / 2 * f->visual_scale;
+
+       // Check for adjacent nodes
+       bool neighbors = false;
+       bool neighbor[6] = {0, 0, 0, 0, 0, 0};
+       content_t current = n.getContent();
+       for (int i = 0; i < 6; i++) {
+               v3s16 n2p = blockpos_nodes + p + g_6dirs[i];
+               MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
+               content_t n2c = n2.getContent();
+               if (n2c != CONTENT_IGNORE && n2c != CONTENT_AIR && n2c != current) {
+                       neighbor[i] = true;
+                       neighbors = true;
+               }
+       }
+       bool drawBasicFire = neighbor[D6D_YN] || !neighbors;
+       bool drawBottomFire = neighbor[D6D_YP];
+
+       if (drawBasicFire || neighbor[D6D_ZP])
+               drawFirelikeQuad(0, -10, 0.4 * BS);
+       else if (drawBottomFire)
+               drawFirelikeQuad(0, 70, 0.47 * BS, 0.484 * BS);
+
+       if (drawBasicFire || neighbor[D6D_XN])
+               drawFirelikeQuad(90, -10, 0.4 * BS);
+       else if (drawBottomFire)
+               drawFirelikeQuad(90, 70, 0.47 * BS, 0.484 * BS);
+
+       if (drawBasicFire || neighbor[D6D_ZN])
+               drawFirelikeQuad(180, -10, 0.4 * BS);
+       else if (drawBottomFire)
+               drawFirelikeQuad(180, 70, 0.47 * BS, 0.484 * BS);
+
+       if (drawBasicFire || neighbor[D6D_XP])
+               drawFirelikeQuad(270, -10, 0.4 * BS);
+       else if (drawBottomFire)
+               drawFirelikeQuad(270, 70, 0.47 * BS, 0.484 * BS);
+
+       if (drawBasicFire) {
+               drawFirelikeQuad(45, 0, 0.0);
+               drawFirelikeQuad(-45, 0, 0.0);
+       }
+}
+
+void MapblockMeshGenerator::drawFencelikeNode()
+{
+       useTile(0, 0, 0);
+       TileSpec tile_nocrack = tile;
+
+       for (auto &layer : tile_nocrack.layers)
+               layer.material_flags &= ~MATERIAL_FLAG_CRACK;
+
+       // Put wood the right way around in the posts
+       TileSpec tile_rot = tile;
+       tile_rot.rotation = 1;
+
+       static const f32 post_rad = BS / 8;
+       static const f32 bar_rad  = BS / 16;
+       static const f32 bar_len  = BS / 2 - post_rad;
+
+       // The post - always present
+       static const aabb3f post(-post_rad, -BS / 2, -post_rad,
+                                 post_rad,  BS / 2,  post_rad);
+       static const f32 postuv[24] = {
+               0.375, 0.375, 0.625, 0.625,
+               0.375, 0.375, 0.625, 0.625,
+               0.000, 0.000, 0.250, 1.000,
+               0.250, 0.000, 0.500, 1.000,
+               0.500, 0.000, 0.750, 1.000,
+               0.750, 0.000, 1.000, 1.000,
+       };
+       tile = tile_rot;
+       drawAutoLightedCuboid(post, postuv);
+
+       tile = tile_nocrack;
+
+       // Now a section of fence, +X, if there's a post there
+       v3s16 p2 = p;
+       p2.X++;
+       MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
+       const ContentFeatures *f2 = &nodedef->get(n2);
+       if (f2->drawtype == NDT_FENCELIKE) {
+               static const aabb3f bar_x1(BS / 2 - bar_len,  BS / 4 - bar_rad, -bar_rad,
+                                          BS / 2 + bar_len,  BS / 4 + bar_rad,  bar_rad);
+               static const aabb3f bar_x2(BS / 2 - bar_len, -BS / 4 - bar_rad, -bar_rad,
+                                          BS / 2 + bar_len, -BS / 4 + bar_rad,  bar_rad);
+               static const f32 xrailuv[24] = {
+                       0.000, 0.125, 1.000, 0.250,
+                       0.000, 0.250, 1.000, 0.375,
+                       0.375, 0.375, 0.500, 0.500,
+                       0.625, 0.625, 0.750, 0.750,
+                       0.000, 0.500, 1.000, 0.625,
+                       0.000, 0.875, 1.000, 1.000,
+               };
+               drawAutoLightedCuboid(bar_x1, xrailuv);
+               drawAutoLightedCuboid(bar_x2, xrailuv);
+       }
+
+       // Now a section of fence, +Z, if there's a post there
+       p2 = p;
+       p2.Z++;
+       n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
+       f2 = &nodedef->get(n2);
+       if (f2->drawtype == NDT_FENCELIKE) {
+               static const aabb3f bar_z1(-bar_rad,  BS / 4 - bar_rad, BS / 2 - bar_len,
+                                           bar_rad,  BS / 4 + bar_rad, BS / 2 + bar_len);
+               static const aabb3f bar_z2(-bar_rad, -BS / 4 - bar_rad, BS / 2 - bar_len,
+                                           bar_rad, -BS / 4 + bar_rad, BS / 2 + bar_len);
+               static const f32 zrailuv[24] = {
+                       0.1875, 0.0625, 0.3125, 0.3125, // cannot rotate; stretch
+                       0.2500, 0.0625, 0.3750, 0.3125, // for wood texture instead
+                       0.0000, 0.5625, 1.0000, 0.6875,
+                       0.0000, 0.3750, 1.0000, 0.5000,
+                       0.3750, 0.3750, 0.5000, 0.5000,
+                       0.6250, 0.6250, 0.7500, 0.7500,
+               };
+               drawAutoLightedCuboid(bar_z1, zrailuv);
+               drawAutoLightedCuboid(bar_z2, zrailuv);
+       }
+}
+
+bool MapblockMeshGenerator::isSameRail(v3s16 dir)
+{
+       MapNode node2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p + dir);
+       if (node2.getContent() == n.getContent())
+               return true;
+       const ContentFeatures &def2 = nodedef->get(node2);
+       return ((def2.drawtype == NDT_RAILLIKE) &&
+               (def2.getGroup(raillike_groupname) == raillike_group));
+}
+
+void MapblockMeshGenerator::drawRaillikeNode()
+{
+       static const v3s16 direction[4] = {
+               v3s16( 0, 0,  1),
+               v3s16( 0, 0, -1),
+               v3s16(-1, 0,  0),
+               v3s16( 1, 0,  0),
+       };
+       static const int slope_angle[4] = {0, 180, 90, -90};
+
+       enum RailTile {
+               straight,
+               curved,
+               junction,
+               cross,
+       };
+       struct RailDesc {
+               int tile_index;
+               int angle;
+       };
+       static const RailDesc rail_kinds[16] = {
+                                  // +x -x -z +z
+                                  //-------------
+               {straight,   0}, //  .  .  .  .
+               {straight,   0}, //  .  .  . +Z
+               {straight,   0}, //  .  . -Z  .
+               {straight,   0}, //  .  . -Z +Z
+               {straight,  90}, //  . -X  .  .
+               {  curved, 180}, //  . -X  . +Z
+               {  curved, 270}, //  . -X -Z  .
+               {junction, 180}, //  . -X -Z +Z
+               {straight,  90}, // +X  .  .  .
+               {  curved,  90}, // +X  .  . +Z
+               {  curved,   0}, // +X  . -Z  .
+               {junction,   0}, // +X  . -Z +Z
+               {straight,  90}, // +X -X  .  .
+               {junction,  90}, // +X -X  . +Z
+               {junction, 270}, // +X -X -Z  .
+               {   cross,   0}, // +X -X -Z +Z
+       };
+
+       raillike_group = nodedef->get(n).getGroup(raillike_groupname);
+
+       int code = 0;
+       int angle;
+       int tile_index;
+       bool sloped = false;
+       for (int dir = 0; dir < 4; dir++) {
+               bool rail_above = isSameRail(direction[dir] + v3s16(0, 1, 0));
+               if (rail_above) {
+                       sloped = true;
+                       angle = slope_angle[dir];
+               }
+               if (rail_above ||
+                               isSameRail(direction[dir]) ||
+                               isSameRail(direction[dir] + v3s16(0, -1, 0)))
+                       code |= 1 << dir;
+       }
+
+       if (sloped) {
+               tile_index = straight;
+       } else {
+               tile_index = rail_kinds[code].tile_index;
+               angle = rail_kinds[code].angle;
+       }
+
+       useTile(tile_index, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
+
+       static const float offset = BS / 64;
+       static const float size   = BS / 2;
+       float y2 = sloped ? size : -size;
+       v3f vertices[4] = {
+               v3f(-size,    y2 + offset,  size),
+               v3f( size,    y2 + offset,  size),
+               v3f( size, -size + offset, -size),
+               v3f(-size, -size + offset, -size),
+       };
+       if (angle)
+               for (v3f &vertex : vertices)
+                       vertex.rotateXZBy(angle);
+       drawQuad(vertices);
+}
+
+void MapblockMeshGenerator::drawNodeboxNode()
+{
+       static const v3s16 tile_dirs[6] = {
+               v3s16(0, 1, 0),
+               v3s16(0, -1, 0),
+               v3s16(1, 0, 0),
+               v3s16(-1, 0, 0),
+               v3s16(0, 0, 1),
+               v3s16(0, 0, -1)
+       };
+
+       // we have this order for some reason...
+       static const v3s16 connection_dirs[6] = {
+               v3s16( 0,  1,  0), // top
+               v3s16( 0, -1,  0), // bottom
+               v3s16( 0,  0, -1), // front
+               v3s16(-1,  0,  0), // left
+               v3s16( 0,  0,  1), // back
+               v3s16( 1,  0,  0), // right
+       };
+
+       TileSpec tiles[6];
+       for (int face = 0; face < 6; face++) {
+               // Handles facedir rotation for textures
+               getTile(tile_dirs[face], &tiles[face]);
+       }
+
+       // locate possible neighboring nodes to connect to
+       int neighbors_set = 0;
+       if (f->node_box.type == NODEBOX_CONNECTED) {
+               for (int dir = 0; dir != 6; dir++) {
+                       int flag = 1 << dir;
+                       v3s16 p2 = blockpos_nodes + p + connection_dirs[dir];
+                       MapNode n2 = data->m_vmanip.getNodeNoEx(p2);
+                       if (nodedef->nodeboxConnects(n, n2, flag))
+                               neighbors_set |= flag;
+               }
+       }
+
+       std::vector<aabb3f> boxes;
+       n.getNodeBoxes(nodedef, &boxes, neighbors_set);
+       for (const auto &box : boxes)
+               drawAutoLightedCuboid(box, nullptr, tiles, 6);
+}
+
+void MapblockMeshGenerator::drawMeshNode()
+{
+       u8 facedir = 0;
+       scene::IMesh* mesh;
+       bool private_mesh; // as a grab/drop pair is not thread-safe
+
+       if (f->param_type_2 == CPT2_FACEDIR ||
+                       f->param_type_2 == CPT2_COLORED_FACEDIR) {
+               facedir = n.getFaceDir(nodedef);
+       } else if (f->param_type_2 == CPT2_WALLMOUNTED ||
+                       f->param_type_2 == CPT2_COLORED_WALLMOUNTED) {
+               // Convert wallmounted to 6dfacedir.
+               // When cache enabled, it is already converted.
+               facedir = n.getWallMounted(nodedef);
+               if (!enable_mesh_cache)
+                       facedir = wallmounted_to_facedir[facedir];
+       }
+
+       if (!data->m_smooth_lighting && f->mesh_ptr[facedir]) {
+               // use cached meshes
+               private_mesh = false;
+               mesh = f->mesh_ptr[facedir];
+       } else if (f->mesh_ptr[0]) {
+               // no cache, clone and rotate mesh
+               private_mesh = true;
+               mesh = cloneMesh(f->mesh_ptr[0]);
+               rotateMeshBy6dFacedir(mesh, facedir);
+               recalculateBoundingBox(mesh);
+               meshmanip->recalculateNormals(mesh, true, false);
+       } else
+               return;
+
+       int mesh_buffer_count = mesh->getMeshBufferCount();
+       for (int j = 0; j < mesh_buffer_count; j++) {
+               useTile(j);
+               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+               video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices();
+               int vertex_count = buf->getVertexCount();
+
+               if (data->m_smooth_lighting) {
+                       // Mesh is always private here. So the lighting is applied to each
+                       // vertex right here.
+                       for (int k = 0; k < vertex_count; k++) {
+                               video::S3DVertex &vertex = vertices[k];
+                               vertex.Color = blendLightColor(vertex.Pos, vertex.Normal);
+                               vertex.Pos += origin;
+                       }
+                       collector->append(tile, vertices, vertex_count,
+                               buf->getIndices(), buf->getIndexCount());
+               } else {
+                       // Don't modify the mesh, it may not be private here.
+                       // Instead, let the collector process colors, etc.
+                       collector->append(tile, vertices, vertex_count,
+                               buf->getIndices(), buf->getIndexCount(), origin,
+                               color, f->light_source);
+               }
+       }
+       if (private_mesh)
+               mesh->drop();
+}
+
+// also called when the drawtype is known but should have been pre-converted
+void MapblockMeshGenerator::errorUnknownDrawtype()
+{
+       infostream << "Got drawtype " << f->drawtype << std::endl;
+       FATAL_ERROR("Unknown drawtype");
+}
+
+void MapblockMeshGenerator::drawNode()
+{
+       // skip some drawtypes early
+       switch (f->drawtype) {
+               case NDT_NORMAL:   // Drawn by MapBlockMesh
+               case NDT_AIRLIKE:  // Not drawn at all
+               case NDT_LIQUID:   // Drawn by MapBlockMesh
+                       return;
+               default:
+                       break;
+       }
+       origin = intToFloat(p, BS);
+       if (data->m_smooth_lighting)
+               getSmoothLightFrame();
+       else
+               light = LightPair(getInteriorLight(n, 1, nodedef));
+       switch (f->drawtype) {
+               case NDT_FLOWINGLIQUID:     drawLiquidNode(); break;
+               case NDT_GLASSLIKE:         drawGlasslikeNode(); break;
+               case NDT_GLASSLIKE_FRAMED:  drawGlasslikeFramedNode(); break;
+               case NDT_ALLFACES:          drawAllfacesNode(); break;
+               case NDT_TORCHLIKE:         drawTorchlikeNode(); break;
+               case NDT_SIGNLIKE:          drawSignlikeNode(); break;
+               case NDT_PLANTLIKE:         drawPlantlikeNode(); break;
+               case NDT_PLANTLIKE_ROOTED:  drawPlantlikeRootedNode(); break;
+               case NDT_FIRELIKE:          drawFirelikeNode(); break;
+               case NDT_FENCELIKE:         drawFencelikeNode(); break;
+               case NDT_RAILLIKE:          drawRaillikeNode(); break;
+               case NDT_NODEBOX:           drawNodeboxNode(); break;
+               case NDT_MESH:              drawMeshNode(); break;
+               default:                    errorUnknownDrawtype(); break;
+       }
+}
+
+/*
+       TODO: Fix alpha blending for special nodes
+       Currently only the last element rendered is blended correct
+*/
+void MapblockMeshGenerator::generate()
+{
+       for (p.Z = 0; p.Z < MAP_BLOCKSIZE; p.Z++)
+       for (p.Y = 0; p.Y < MAP_BLOCKSIZE; p.Y++)
+       for (p.X = 0; p.X < MAP_BLOCKSIZE; p.X++) {
+               n = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
+               f = &nodedef->get(n);
+               drawNode();
+       }
+}
+
+void MapblockMeshGenerator::renderSingle(content_t node)
+{
+       p = {0, 0, 0};
+       n = MapNode(node, 0xff, 0x00);
+       f = &nodedef->get(n);
+       drawNode();
+}
diff --git a/src/client/content_mapblock.h b/src/client/content_mapblock.h
new file mode 100644 (file)
index 0000000..97947cd
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "nodedef.h"
+#include <IMeshManipulator.h>
+
+struct MeshMakeData;
+struct MeshCollector;
+
+struct LightPair {
+       u8 lightDay;
+       u8 lightNight;
+
+       LightPair() = default;
+       explicit LightPair(u16 value) : lightDay(value & 0xff), lightNight(value >> 8) {}
+       LightPair(u8 valueA, u8 valueB) : lightDay(valueA), lightNight(valueB) {}
+       LightPair(float valueA, float valueB) :
+               lightDay(core::clamp(core::round32(valueA), 0, 255)),
+               lightNight(core::clamp(core::round32(valueB), 0, 255)) {}
+       operator u16() const { return lightDay | lightNight << 8; }
+};
+
+struct LightInfo {
+       float light_day;
+       float light_night;
+       float light_boosted;
+
+       LightPair getPair(float sunlight_boost = 0.0) const
+       {
+               return LightPair(
+                       (1 - sunlight_boost) * light_day
+                       + sunlight_boost * light_boosted,
+                       light_night);
+       }
+};
+
+struct LightFrame {
+       f32 lightsDay[8];
+       f32 lightsNight[8];
+       bool sunlight[8];
+};
+
+class MapblockMeshGenerator
+{
+public:
+       MeshMakeData *data;
+       MeshCollector *collector;
+
+       const NodeDefManager *nodedef;
+       scene::IMeshManipulator *meshmanip;
+
+// options
+       bool enable_mesh_cache;
+
+// current node
+       v3s16 blockpos_nodes;
+       v3s16 p;
+       v3f origin;
+       MapNode n;
+       const ContentFeatures *f;
+       LightPair light;
+       LightFrame frame;
+       video::SColor color;
+       TileSpec tile;
+       float scale;
+
+// lighting
+       void getSmoothLightFrame();
+       LightInfo blendLight(const v3f &vertex_pos);
+       video::SColor blendLightColor(const v3f &vertex_pos);
+       video::SColor blendLightColor(const v3f &vertex_pos, const v3f &vertex_normal);
+
+       void useTile(int index = 0, u8 set_flags = MATERIAL_FLAG_CRACK_OVERLAY,
+               u8 reset_flags = 0, bool special = false);
+       void getTile(int index, TileSpec *tile);
+       void getTile(v3s16 direction, TileSpec *tile);
+       void getSpecialTile(int index, TileSpec *tile, bool apply_crack = false);
+
+// face drawing
+       void drawQuad(v3f *vertices, const v3s16 &normal = v3s16(0, 0, 0),
+               float vertical_tiling = 1.0);
+
+// cuboid drawing!
+       void drawCuboid(const aabb3f &box, TileSpec *tiles, int tilecount,
+               const LightInfo *lights , const f32 *txc);
+       void generateCuboidTextureCoords(aabb3f const &box, f32 *coords);
+       void drawAutoLightedCuboid(aabb3f box, const f32 *txc = NULL,
+               TileSpec *tiles = NULL, int tile_count = 0);
+
+// liquid-specific
+       bool top_is_same_liquid;
+       bool draw_liquid_bottom;
+       TileSpec tile_liquid;
+       TileSpec tile_liquid_top;
+       content_t c_flowing;
+       content_t c_source;
+       video::SColor color_liquid_top;
+       struct NeighborData {
+               f32 level;
+               content_t content;
+               bool is_same_liquid;
+               bool top_is_same_liquid;
+       };
+       NeighborData liquid_neighbors[3][3];
+       f32 corner_levels[2][2];
+
+       void prepareLiquidNodeDrawing();
+       void getLiquidNeighborhood();
+       void calculateCornerLevels();
+       f32 getCornerLevel(int i, int k);
+       void drawLiquidSides();
+       void drawLiquidTop();
+       void drawLiquidBottom();
+
+// raillike-specific
+       // name of the group that enables connecting to raillike nodes of different kind
+       static const std::string raillike_groupname;
+       int raillike_group;
+       bool isSameRail(v3s16 dir);
+
+// plantlike-specific
+       PlantlikeStyle draw_style;
+       v3f offset;
+       int rotate_degree;
+       bool random_offset_Y;
+       int face_num;
+       float plant_height;
+
+       void drawPlantlikeQuad(float rotation, float quad_offset = 0,
+               bool offset_top_only = false);
+       void drawPlantlike();
+
+// firelike-specific
+       void drawFirelikeQuad(float rotation, float opening_angle,
+               float offset_h, float offset_v = 0.0);
+
+// drawtypes
+       void drawLiquidNode();
+       void drawGlasslikeNode();
+       void drawGlasslikeFramedNode();
+       void drawAllfacesNode();
+       void drawTorchlikeNode();
+       void drawSignlikeNode();
+       void drawPlantlikeNode();
+       void drawPlantlikeRootedNode();
+       void drawFirelikeNode();
+       void drawFencelikeNode();
+       void drawRaillikeNode();
+       void drawNodeboxNode();
+       void drawMeshNode();
+
+// common
+       void errorUnknownDrawtype();
+       void drawNode();
+
+public:
+       MapblockMeshGenerator(MeshMakeData *input, MeshCollector *output);
+       void generate();
+       void renderSingle(content_t node);
+};
diff --git a/src/client/filecache.cpp b/src/client/filecache.cpp
new file mode 100644 (file)
index 0000000..3d1b302
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013 Jonathan Neuschäfer <j.neuschaefer@gmx.net>
+
+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 "filecache.h"
+
+#include "network/networkprotocol.h"
+#include "log.h"
+#include "filesys.h"
+#include <string>
+#include <iostream>
+#include <fstream>
+#include <cstdlib>
+
+bool FileCache::loadByPath(const std::string &path, std::ostream &os)
+{
+       std::ifstream fis(path.c_str(), std::ios_base::binary);
+
+       if(!fis.good()){
+               verbosestream<<"FileCache: File not found in cache: "
+                               <<path<<std::endl;
+               return false;
+       }
+
+       bool bad = false;
+       for(;;){
+               char buf[1024];
+               fis.read(buf, 1024);
+               std::streamsize len = fis.gcount();
+               os.write(buf, len);
+               if(fis.eof())
+                       break;
+               if(!fis.good()){
+                       bad = true;
+                       break;
+               }
+       }
+       if(bad){
+               errorstream<<"FileCache: Failed to read file from cache: \""
+                               <<path<<"\""<<std::endl;
+       }
+
+       return !bad;
+}
+
+bool FileCache::updateByPath(const std::string &path, const std::string &data)
+{
+       std::ofstream file(path.c_str(), std::ios_base::binary |
+                       std::ios_base::trunc);
+
+       if(!file.good())
+       {
+               errorstream<<"FileCache: Can't write to file at "
+                               <<path<<std::endl;
+               return false;
+       }
+
+       file.write(data.c_str(), data.length());
+       file.close();
+
+       return !file.fail();
+}
+
+bool FileCache::update(const std::string &name, const std::string &data)
+{
+       std::string path = m_dir + DIR_DELIM + name;
+       return updateByPath(path, data);
+}
+bool FileCache::load(const std::string &name, std::ostream &os)
+{
+       std::string path = m_dir + DIR_DELIM + name;
+       return loadByPath(path, os);
+}
diff --git a/src/client/filecache.h b/src/client/filecache.h
new file mode 100644 (file)
index 0000000..96e4c8b
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013 Jonathan Neuschäfer <j.neuschaefer@gmx.net>
+
+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.
+*/
+
+#pragma once
+
+#include <iostream>
+#include <string>
+
+class FileCache
+{
+public:
+       /*
+               'dir' is the file cache directory to use.
+       */
+       FileCache(const std::string &dir) : m_dir(dir) {}
+
+       bool update(const std::string &name, const std::string &data);
+       bool load(const std::string &name, std::ostream &os);
+
+private:
+       std::string m_dir;
+
+       bool loadByPath(const std::string &path, std::ostream &os);
+       bool updateByPath(const std::string &path, const std::string &data);
+};
diff --git a/src/client/fontengine.cpp b/src/client/fontengine.cpp
new file mode 100644 (file)
index 0000000..dc98fb1
--- /dev/null
@@ -0,0 +1,505 @@
+/*
+Minetest
+Copyright (C) 2010-2014 sapier <sapier at gmx dot net>
+
+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 "fontengine.h"
+#include <cmath>
+#include "client/renderingengine.h"
+#include "config.h"
+#include "porting.h"
+#include "filesys.h"
+
+#if USE_FREETYPE
+#include "gettext.h"
+#include "irrlicht_changes/CGUITTFont.h"
+#endif
+
+/** maximum size distance for getting a "similar" font size */
+#define MAX_FONT_SIZE_OFFSET 10
+
+/** reference to access font engine, has to be initialized by main */
+FontEngine* g_fontengine = NULL;
+
+/** callback to be used on change of font size setting */
+static void font_setting_changed(const std::string &name, void *userdata)
+{
+       g_fontengine->readSettings();
+}
+
+/******************************************************************************/
+FontEngine::FontEngine(Settings* main_settings, gui::IGUIEnvironment* env) :
+       m_settings(main_settings),
+       m_env(env)
+{
+
+       for (u32 &i : m_default_size) {
+               i = (FontMode) FONT_SIZE_UNSPECIFIED;
+       }
+
+       assert(m_settings != NULL); // pre-condition
+       assert(m_env != NULL); // pre-condition
+       assert(m_env->getSkin() != NULL); // pre-condition
+
+       m_currentMode = FM_Simple;
+
+#if USE_FREETYPE
+       if (g_settings->getBool("freetype")) {
+               m_default_size[FM_Standard] = m_settings->getU16("font_size");
+               m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size");
+               m_default_size[FM_Mono]     = m_settings->getU16("mono_font_size");
+
+               if (is_yes(gettext("needs_fallback_font"))) {
+                       m_currentMode = FM_Fallback;
+               }
+               else {
+                       m_currentMode = FM_Standard;
+               }
+       }
+
+       // having freetype but not using it is quite a strange case so we need to do
+       // special handling for it
+       if (m_currentMode == FM_Simple) {
+               std::stringstream fontsize;
+               fontsize << DEFAULT_FONT_SIZE;
+               m_settings->setDefault("font_size", fontsize.str());
+               m_settings->setDefault("mono_font_size", fontsize.str());
+       }
+#endif
+
+       m_default_size[FM_Simple]       = m_settings->getU16("font_size");
+       m_default_size[FM_SimpleMono]   = m_settings->getU16("mono_font_size");
+
+       updateSkin();
+
+       if (m_currentMode == FM_Standard) {
+               m_settings->registerChangedCallback("font_size", font_setting_changed, NULL);
+               m_settings->registerChangedCallback("font_path", font_setting_changed, NULL);
+               m_settings->registerChangedCallback("font_shadow", font_setting_changed, NULL);
+               m_settings->registerChangedCallback("font_shadow_alpha", font_setting_changed, NULL);
+       }
+       else if (m_currentMode == FM_Fallback) {
+               m_settings->registerChangedCallback("fallback_font_size", font_setting_changed, NULL);
+               m_settings->registerChangedCallback("fallback_font_path", font_setting_changed, NULL);
+               m_settings->registerChangedCallback("fallback_font_shadow", font_setting_changed, NULL);
+               m_settings->registerChangedCallback("fallback_font_shadow_alpha", font_setting_changed, NULL);
+       }
+
+       m_settings->registerChangedCallback("mono_font_path", font_setting_changed, NULL);
+       m_settings->registerChangedCallback("mono_font_size", font_setting_changed, NULL);
+       m_settings->registerChangedCallback("screen_dpi", font_setting_changed, NULL);
+       m_settings->registerChangedCallback("gui_scaling", font_setting_changed, NULL);
+}
+
+/******************************************************************************/
+FontEngine::~FontEngine()
+{
+       cleanCache();
+}
+
+/******************************************************************************/
+void FontEngine::cleanCache()
+{
+       for (auto &font_cache_it : m_font_cache) {
+
+               for (auto &font_it : font_cache_it) {
+                       font_it.second->drop();
+                       font_it.second = NULL;
+               }
+               font_cache_it.clear();
+       }
+}
+
+/******************************************************************************/
+irr::gui::IGUIFont* FontEngine::getFont(unsigned int font_size, FontMode mode)
+{
+       if (mode == FM_Unspecified) {
+               mode = m_currentMode;
+       }
+       else if ((mode == FM_Mono) && (m_currentMode == FM_Simple)) {
+               mode = FM_SimpleMono;
+       }
+
+       if (font_size == FONT_SIZE_UNSPECIFIED) {
+               font_size = m_default_size[mode];
+       }
+
+       if ((font_size == m_lastSize) && (mode == m_lastMode)) {
+               return m_lastFont;
+       }
+
+       if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) {
+               initFont(font_size, mode);
+       }
+
+       if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) {
+               return NULL;
+       }
+
+       m_lastSize = font_size;
+       m_lastMode = mode;
+       m_lastFont = m_font_cache[mode][font_size];
+
+       return m_font_cache[mode][font_size];
+}
+
+/******************************************************************************/
+unsigned int FontEngine::getTextHeight(unsigned int font_size, FontMode mode)
+{
+       irr::gui::IGUIFont* font = getFont(font_size, mode);
+
+       // use current skin font as fallback
+       if (font == NULL) {
+               font = m_env->getSkin()->getFont();
+       }
+       FATAL_ERROR_IF(font == NULL, "Could not get skin font");
+
+       return font->getDimension(L"Some unimportant example String").Height;
+}
+
+/******************************************************************************/
+unsigned int FontEngine::getTextWidth(const std::wstring& text,
+               unsigned int font_size, FontMode mode)
+{
+       irr::gui::IGUIFont* font = getFont(font_size, mode);
+
+       // use current skin font as fallback
+       if (font == NULL) {
+               font = m_env->getSkin()->getFont();
+       }
+       FATAL_ERROR_IF(font == NULL, "Could not get font");
+
+       return font->getDimension(text.c_str()).Width;
+}
+
+
+/** get line height for a specific font (including empty room between lines) */
+unsigned int FontEngine::getLineHeight(unsigned int font_size, FontMode mode)
+{
+       irr::gui::IGUIFont* font = getFont(font_size, mode);
+
+       // use current skin font as fallback
+       if (font == NULL) {
+               font = m_env->getSkin()->getFont();
+       }
+       FATAL_ERROR_IF(font == NULL, "Could not get font");
+
+       return font->getDimension(L"Some unimportant example String").Height
+                       + font->getKerningHeight();
+}
+
+/******************************************************************************/
+unsigned int FontEngine::getDefaultFontSize()
+{
+       return m_default_size[m_currentMode];
+}
+
+/******************************************************************************/
+void FontEngine::readSettings()
+{
+#if USE_FREETYPE
+       if (g_settings->getBool("freetype")) {
+               m_default_size[FM_Standard] = m_settings->getU16("font_size");
+               m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size");
+               m_default_size[FM_Mono]     = m_settings->getU16("mono_font_size");
+
+               if (is_yes(gettext("needs_fallback_font"))) {
+                       m_currentMode = FM_Fallback;
+               }
+               else {
+                       m_currentMode = FM_Standard;
+               }
+       }
+#endif
+       m_default_size[FM_Simple]       = m_settings->getU16("font_size");
+       m_default_size[FM_SimpleMono]   = m_settings->getU16("mono_font_size");
+
+       cleanCache();
+       updateFontCache();
+       updateSkin();
+}
+
+/******************************************************************************/
+void FontEngine::updateSkin()
+{
+       gui::IGUIFont *font = getFont();
+
+       if (font)
+               m_env->getSkin()->setFont(font);
+       else
+               errorstream << "FontEngine: Default font file: " <<
+                               "\n\t\"" << m_settings->get("font_path") << "\"" <<
+                               "\n\trequired for current screen configuration was not found" <<
+                               " or was invalid file format." <<
+                               "\n\tUsing irrlicht default font." << std::endl;
+
+       // If we did fail to create a font our own make irrlicht find a default one
+       font = m_env->getSkin()->getFont();
+       FATAL_ERROR_IF(font == NULL, "Could not create/get font");
+
+       u32 text_height = font->getDimension(L"Hello, world!").Height;
+       infostream << "text_height=" << text_height << std::endl;
+}
+
+/******************************************************************************/
+void FontEngine::updateFontCache()
+{
+       /* the only font to be initialized is default one,
+        * all others are re-initialized on demand */
+       initFont(m_default_size[m_currentMode], m_currentMode);
+
+       /* reset font quick access */
+       m_lastMode = FM_Unspecified;
+       m_lastSize = 0;
+       m_lastFont = NULL;
+}
+
+/******************************************************************************/
+void FontEngine::initFont(unsigned int basesize, FontMode mode)
+{
+
+       std::string font_config_prefix;
+
+       if (mode == FM_Unspecified) {
+               mode = m_currentMode;
+       }
+
+       switch (mode) {
+
+               case FM_Standard:
+                       font_config_prefix = "";
+                       break;
+
+               case FM_Fallback:
+                       font_config_prefix = "fallback_";
+                       break;
+
+               case FM_Mono:
+                       font_config_prefix = "mono_";
+                       if (m_currentMode == FM_Simple)
+                               mode = FM_SimpleMono;
+                       break;
+
+               case FM_Simple: /* Fallthrough */
+               case FM_SimpleMono: /* Fallthrough */
+               default:
+                       font_config_prefix = "";
+
+       }
+
+       if (m_font_cache[mode].find(basesize) != m_font_cache[mode].end())
+               return;
+
+       if ((mode == FM_Simple) || (mode == FM_SimpleMono)) {
+               initSimpleFont(basesize, mode);
+               return;
+       }
+#if USE_FREETYPE
+       else {
+               if (!is_yes(m_settings->get("freetype"))) {
+                       return;
+               }
+               u32 size = std::floor(RenderingEngine::getDisplayDensity() *
+                               m_settings->getFloat("gui_scaling") * basesize);
+               u32 font_shadow       = 0;
+               u32 font_shadow_alpha = 0;
+
+               try {
+                       font_shadow =
+                                       g_settings->getU16(font_config_prefix + "font_shadow");
+               } catch (SettingNotFoundException&) {}
+               try {
+                       font_shadow_alpha =
+                                       g_settings->getU16(font_config_prefix + "font_shadow_alpha");
+               } catch (SettingNotFoundException&) {}
+
+               std::string font_path = g_settings->get(font_config_prefix + "font_path");
+
+               irr::gui::IGUIFont* font = gui::CGUITTFont::createTTFont(m_env,
+                               font_path.c_str(), size, true, true, font_shadow,
+                               font_shadow_alpha);
+
+               if (font) {
+                       m_font_cache[mode][basesize] = font;
+                       return;
+               }
+
+               if (font_config_prefix == "mono_") {
+                       const std::string &mono_font_path = m_settings->getDefault("mono_font_path");
+
+                       if (font_path != mono_font_path) {
+                               // try original mono font
+                               errorstream << "FontEngine: failed to load custom mono "
+                                               "font: " << font_path << ", trying to fall back to "
+                                               "original mono font" << std::endl;
+
+                               font = gui::CGUITTFont::createTTFont(m_env,
+                                       mono_font_path.c_str(), size, true, true,
+                                       font_shadow, font_shadow_alpha);
+
+                               if (font) {
+                                       m_font_cache[mode][basesize] = font;
+                                       return;
+                               }
+                       }
+               } else {
+                       // try fallback font
+                       errorstream << "FontEngine: failed to load: " << font_path <<
+                                       ", trying to fall back to fallback font" << std::endl;
+
+                       font_path = g_settings->get(font_config_prefix + "fallback_font_path");
+
+                       font = gui::CGUITTFont::createTTFont(m_env,
+                               font_path.c_str(), size, true, true, font_shadow,
+                               font_shadow_alpha);
+
+                       if (font) {
+                               m_font_cache[mode][basesize] = font;
+                               return;
+                       }
+
+                       const std::string &fallback_font_path = m_settings->getDefault("fallback_font_path");
+
+                       if (font_path != fallback_font_path) {
+                               // try original fallback font
+                               errorstream << "FontEngine: failed to load custom fallback "
+                                               "font: " << font_path << ", trying to fall back to "
+                                               "original fallback font" << std::endl;
+
+                               font = gui::CGUITTFont::createTTFont(m_env,
+                                       fallback_font_path.c_str(), size, true, true,
+                                       font_shadow, font_shadow_alpha);
+
+                               if (font) {
+                                       m_font_cache[mode][basesize] = font;
+                                       return;
+                               }
+                       }
+               }
+
+               // give up
+               errorstream << "FontEngine: failed to load freetype font: "
+                               << font_path << std::endl;
+               errorstream << "minetest can not continue without a valid font. "
+                               "Please correct the 'font_path' setting or install the font "
+                               "file in the proper location" << std::endl;
+               abort();
+       }
+#endif
+}
+
+/** initialize a font without freetype */
+void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode)
+{
+       assert(mode == FM_Simple || mode == FM_SimpleMono); // pre-condition
+
+       std::string font_path;
+       if (mode == FM_Simple) {
+               font_path = m_settings->get("font_path");
+       } else {
+               font_path = m_settings->get("mono_font_path");
+       }
+       std::string basename = font_path;
+       std::string ending = font_path.substr(font_path.length() -4);
+
+       if (ending == ".ttf") {
+               errorstream << "FontEngine: Not trying to open \"" << font_path
+                               << "\" which seems to be a truetype font." << std::endl;
+               return;
+       }
+
+       if ((ending == ".xml") || (ending == ".png")) {
+               basename = font_path.substr(0,font_path.length()-4);
+       }
+
+       if (basesize == FONT_SIZE_UNSPECIFIED)
+               basesize = DEFAULT_FONT_SIZE;
+
+       u32 size = std::floor(
+                       RenderingEngine::getDisplayDensity() *
+                       m_settings->getFloat("gui_scaling") *
+                       basesize);
+
+       irr::gui::IGUIFont* font = NULL;
+
+       for(unsigned int offset = 0; offset < MAX_FONT_SIZE_OFFSET; offset++) {
+
+               // try opening positive offset
+               std::stringstream fontsize_plus_png;
+               fontsize_plus_png << basename << "_" << (size + offset) << ".png";
+
+               if (fs::PathExists(fontsize_plus_png.str())) {
+                       font = m_env->getFont(fontsize_plus_png.str().c_str());
+
+                       if (font) {
+                               verbosestream << "FontEngine: found font: " << fontsize_plus_png.str() << std::endl;
+                               break;
+                       }
+               }
+
+               std::stringstream fontsize_plus_xml;
+               fontsize_plus_xml << basename << "_" << (size + offset) << ".xml";
+
+               if (fs::PathExists(fontsize_plus_xml.str())) {
+                       font = m_env->getFont(fontsize_plus_xml.str().c_str());
+
+                       if (font) {
+                               verbosestream << "FontEngine: found font: " << fontsize_plus_xml.str() << std::endl;
+                               break;
+                       }
+               }
+
+               // try negative offset
+               std::stringstream fontsize_minus_png;
+               fontsize_minus_png << basename << "_" << (size - offset) << ".png";
+
+               if (fs::PathExists(fontsize_minus_png.str())) {
+                       font = m_env->getFont(fontsize_minus_png.str().c_str());
+
+                       if (font) {
+                               verbosestream << "FontEngine: found font: " << fontsize_minus_png.str() << std::endl;
+                               break;
+                       }
+               }
+
+               std::stringstream fontsize_minus_xml;
+               fontsize_minus_xml << basename << "_" << (size - offset) << ".xml";
+
+               if (fs::PathExists(fontsize_minus_xml.str())) {
+                       font = m_env->getFont(fontsize_minus_xml.str().c_str());
+
+                       if (font) {
+                               verbosestream << "FontEngine: found font: " << fontsize_minus_xml.str() << std::endl;
+                               break;
+                       }
+               }
+       }
+
+       // try name direct
+       if (font == NULL) {
+               if (fs::PathExists(font_path)) {
+                       font = m_env->getFont(font_path.c_str());
+                       if (font)
+                               verbosestream << "FontEngine: found font: " << font_path << std::endl;
+               }
+       }
+
+       if (font) {
+               font->grab();
+               m_font_cache[mode][basesize] = font;
+       }
+}
diff --git a/src/client/fontengine.h b/src/client/fontengine.h
new file mode 100644 (file)
index 0000000..a75618f
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+Minetest
+Copyright (C) 2010-2014 sapier <sapier at gmx dot net>
+
+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.
+*/
+
+#pragma once
+
+#include <map>
+#include <vector>
+#include "util/basic_macros.h"
+#include <IGUIFont.h>
+#include <IGUISkin.h>
+#include <IGUIEnvironment.h>
+#include "settings.h"
+
+#define FONT_SIZE_UNSPECIFIED 0xFFFFFFFF
+
+enum FontMode {
+       FM_Standard = 0,
+       FM_Mono,
+       FM_Fallback,
+       FM_Simple,
+       FM_SimpleMono,
+       FM_MaxMode,
+       FM_Unspecified
+};
+
+class FontEngine
+{
+public:
+
+       FontEngine(Settings* main_settings, gui::IGUIEnvironment* env);
+
+       ~FontEngine();
+
+       /** get Font */
+       irr::gui::IGUIFont* getFont(unsigned int font_size=FONT_SIZE_UNSPECIFIED,
+                       FontMode mode=FM_Unspecified);
+
+       /** get text height for a specific font */
+       unsigned int getTextHeight(unsigned int font_size=FONT_SIZE_UNSPECIFIED,
+                       FontMode mode=FM_Unspecified);
+
+       /** get text width if a text for a specific font */
+       unsigned int getTextWidth(const std::string& text,
+                       unsigned int font_size=FONT_SIZE_UNSPECIFIED,
+                       FontMode mode=FM_Unspecified)
+       {
+               return getTextWidth(utf8_to_wide(text));
+       }
+
+       /** get text width if a text for a specific font */
+       unsigned int getTextWidth(const std::wstring& text,
+                       unsigned int font_size=FONT_SIZE_UNSPECIFIED,
+                       FontMode mode=FM_Unspecified);
+
+       /** get line height for a specific font (including empty room between lines) */
+       unsigned int getLineHeight(unsigned int font_size=FONT_SIZE_UNSPECIFIED,
+                       FontMode mode=FM_Unspecified);
+
+       /** get default font size */
+       unsigned int getDefaultFontSize();
+
+       /** initialize font engine */
+       void initialize(Settings* main_settings, gui::IGUIEnvironment* env);
+
+       /** update internal parameters from settings */
+       void readSettings();
+
+private:
+       /** update content of font cache in case of a setting change made it invalid */
+       void updateFontCache();
+
+       /** initialize a new font */
+       void initFont(unsigned int basesize, FontMode mode=FM_Unspecified);
+
+       /** initialize a font without freetype */
+       void initSimpleFont(unsigned int basesize, FontMode mode);
+
+       /** update current minetest skin with font changes */
+       void updateSkin();
+
+       /** clean cache */
+       void cleanCache();
+
+       /** pointer to settings for registering callbacks or reading config */
+       Settings* m_settings = nullptr;
+
+       /** pointer to irrlicht gui environment */
+       gui::IGUIEnvironment* m_env = nullptr;
+
+       /** internal storage for caching fonts of different size */
+       std::map<unsigned int, irr::gui::IGUIFont*> m_font_cache[FM_MaxMode];
+
+       /** default font size to use */
+       unsigned int m_default_size[FM_MaxMode];
+
+       /** current font engine mode */
+       FontMode m_currentMode = FM_Standard;
+
+       /** font mode of last request */
+       FontMode m_lastMode;
+
+       /** size of last request */
+       unsigned int m_lastSize = 0;
+
+       /** last font returned */
+       irr::gui::IGUIFont* m_lastFont = nullptr;
+
+       DISABLE_CLASS_COPY(FontEngine);
+};
+
+/** interface to access main font engine*/
+extern FontEngine* g_fontengine;
diff --git a/src/client/game.cpp b/src/client/game.cpp
new file mode 100644 (file)
index 0000000..6cf6723
--- /dev/null
@@ -0,0 +1,4218 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 "game.h"
+
+#include <iomanip>
+#include <cmath>
+#include "client/renderingengine.h"
+#include "camera.h"
+#include "client.h"
+#include "client/clientevent.h"
+#include "client/gameui.h"
+#include "client/inputhandler.h"
+#include "client/tile.h"     // For TextureSource
+#include "client/keys.h"
+#include "client/joystick_controller.h"
+#include "clientmap.h"
+#include "clouds.h"
+#include "config.h"
+#include "content_cao.h"
+#include "client/event_manager.h"
+#include "fontengine.h"
+#include "itemdef.h"
+#include "log.h"
+#include "filesys.h"
+#include "gettext.h"
+#include "gui/guiChatConsole.h"
+#include "gui/guiConfirmRegistration.h"
+#include "gui/guiFormSpecMenu.h"
+#include "gui/guiKeyChangeMenu.h"
+#include "gui/guiPasswordChange.h"
+#include "gui/guiVolumeChange.h"
+#include "gui/mainmenumanager.h"
+#include "gui/profilergraph.h"
+#include "mapblock.h"
+#include "minimap.h"
+#include "nodedef.h"         // Needed for determining pointing to nodes
+#include "nodemetadata.h"
+#include "particles.h"
+#include "porting.h"
+#include "profiler.h"
+#include "quicktune_shortcutter.h"
+#include "raycast.h"
+#include "server.h"
+#include "settings.h"
+#include "shader.h"
+#include "sky.h"
+#include "translation.h"
+#include "util/basic_macros.h"
+#include "util/directiontables.h"
+#include "util/pointedthing.h"
+#include "irrlicht_changes/static_text.h"
+#include "version.h"
+#include "script/scripting_client.h"
+
+#if USE_SOUND
+       #include "client/sound_openal.h"
+#else
+       #include "client/sound.h"
+#endif
+/*
+       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;
+                       }
+
+                       if (fields.find("quit") != fields.end()) {
+                               return;
+                       }
+
+                       if (fields.find("btn_continue") != fields.end()) {
+                               return;
+                       }
+               }
+
+               if (m_formname == "MT_DEATH_SCREEN") {
+                       assert(m_client != 0);
+                       m_client->sendRespawn();
+                       return;
+               }
+
+               if (m_client && m_client->moddingEnabled())
+                       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;
+
+       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)
+       {
+       }
+
+       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);
+               }
+       }
+
+       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;
+       }
+
+       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;
+       }
+};
+
+// 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);
+       }
+};
+
+
+// before 1.8 there isn't a "integer interface", only float
+#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
+typedef f32 SamplerLayer_t;
+#else
+typedef s32 SamplerLayer_t;
+#endif
+
+
+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, 3> m_eye_position_pixel;
+       CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
+       CachedPixelShaderSetting<float, 3> m_minimap_yaw;
+       CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
+       CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture;
+       CachedPixelShaderSetting<SamplerLayer_t> m_texture_flags;
+       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_eye_position_pixel("eyePosition"),
+               m_eye_position_vertex("eyePosition"),
+               m_minimap_yaw("yawVec"),
+               m_base_texture("baseTexture"),
+               m_normal_texture("normalTexture"),
+               m_texture_flags("textureFlags"),
+               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);
+       }
+
+       virtual void onSetConstants(video::IMaterialRendererServices *services,
+                       bool is_highlevel)
+       {
+               if (!is_highlevel)
+                       return;
+
+               // 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);
+
+               u32 animation_timer = porting::getTimeMs() % 100000;
+               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();
+#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
+               eye_position_array[0] = epos.X;
+               eye_position_array[1] = epos.Y;
+               eye_position_array[2] = epos.Z;
+#else
+               epos.getAs3Values(eye_position_array);
+#endif
+               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();
+#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
+                       minimap_yaw_array[0] = minimap_yaw.X;
+                       minimap_yaw_array[1] = minimap_yaw.Y;
+                       minimap_yaw_array[2] = minimap_yaw.Z;
+#else
+                       minimap_yaw.getAs3Values(minimap_yaw_array);
+#endif
+                       m_minimap_yaw.set(minimap_yaw_array, services);
+               }
+
+               SamplerLayer_t base_tex = 0,
+                               normal_tex = 1,
+                               flags_tex = 2;
+               m_base_texture.set(&base_tex, services);
+               m_normal_texture.set(&normal_tex, services);
+               m_texture_flags.set(&flags_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()
+       {
+               GameGlobalShaderConstantSetter *scs = new GameGlobalShaderConstantSetter(
+                               m_sky, m_force_fog_off, m_fog_range, m_client);
+               if (!m_sky)
+                       created_nosky.push_back(scs);
+               return scs;
+       }
+};
+
+#ifdef __ANDROID__
+#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 {
+       u32 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 ldown_for_dig;
+       bool dig_instantly;
+       bool digging_blocked;
+       bool left_punch;
+       bool update_wielded_item_trigger;
+       bool reset_jump_timer;
+       float nodig_delay_timer;
+       float dig_time;
+       float dig_time_complete;
+       float repeat_rightclick_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 *);
+};
+
+/****************************************************************************
+ THE GAME
+ ****************************************************************************/
+
+/* This is not intended to be a public class. If a public class becomes
+ * desirable then it may be better to create another 'wrapper' class that
+ * hides most of the stuff in this class (nothing in this class is required
+ * by any other file) but exposes the public methods/data only.
+ */
+class Game {
+public:
+       Game();
+       ~Game();
+
+       bool startup(bool *kill,
+                       bool random_input,
+                       InputHandler *input,
+                       const std::string &map_dir,
+                       const std::string &playername,
+                       const std::string &password,
+                       // If address is "", local server is used and address is updated
+                       std::string *address,
+                       u16 port,
+                       std::string &error_message,
+                       bool *reconnect,
+                       ChatBackend *chat_backend,
+                       const SubgameSpec &gamespec,    // Used for local game
+                       bool simple_singleplayer_mode);
+
+       void run();
+       void shutdown();
+
+protected:
+
+       void extendedResourceCleanup();
+
+       // Basic initialisation
+       bool init(const std::string &map_dir, std::string *address,
+                       u16 port,
+                       const SubgameSpec &gamespec);
+       bool initSound();
+       bool createSingleplayerServer(const std::string &map_dir,
+                       const SubgameSpec &gamespec, u16 port, std::string *address);
+
+       // Client creation
+       bool createClient(const std::string &playername,
+                       const std::string &password, std::string *address, u16 port);
+       bool initGui();
+
+       // Client connection
+       bool connectToServer(const std::string &playername,
+                       const std::string &password, std::string *address, u16 port,
+                       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 addProfilerGraphs(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
+       void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
+
+       // Input related
+       void processUserInput(f32 dtime);
+       void processKeyInput();
+       void processItemSelection(u16 *new_playeritem);
+
+       void dropSelectedItem(bool single_item = false);
+       void openInventory();
+       void openConsole(float scale, const wchar_t *line=NULL);
+       void toggleFreeMove();
+       void toggleFreeMoveAlt();
+       void toggleFast();
+       void toggleNoClip();
+       void toggleCinematic();
+       void toggleAutoforward();
+
+       void toggleMinimap(bool shift_pressed);
+       void toggleFog();
+       void toggleDebug();
+       void toggleUpdateCamera();
+
+       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 updateCamera(u32 busy_time, f32 dtime);
+       void updateSound(f32 dtime);
+       void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug);
+       /*!
+        * 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 ItemDefinition &playeritem_def, const ItemStack &playeritem,
+               const ToolCapabilities &playeritem_toolcap, 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 ToolCapabilities &playeritem_toolcap, f32 dtime);
+       void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
+                       const CameraOrientation &cam);
+       void updateProfilerGraphs(ProfilerGraph *graph);
+
+       // Misc
+       void limitFps(FpsControl *fps_timings, f32 *dtime);
+
+       void showOverlayMessage(const char *msg, float dtime, int percent,
+                       bool draw_clouds = true);
+
+       static void settingChangedCallback(const std::string &setting_name, void *data);
+       void readSettings();
+
+       inline bool isKeyDown(GameKeyType k)
+       {
+               return input->isKeyDown(k);
+       }
+       inline bool wasKeyDown(GameKeyType k)
+       {
+               return input->wasKeyDown(k);
+       }
+
+#ifdef __ANDROID__
+       void handleAndroidChatInput();
+#endif
+
+private:
+       struct Flags {
+               bool force_fog_off = false;
+               bool disable_camera_update = false;
+       };
+
+       void showDeathFormspec();
+       void showPauseMenu();
+
+       // 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_OverrideDayNigthRatio(ClientEvent *event,
+               CameraOrientation *cam);
+       void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
+
+       void updateChat(f32 dtime, const v2u32 &screensize);
+
+       bool nodePlacementPrediction(const ItemDefinition &playeritem_def,
+               const ItemStack &playeritem, const v3s16 &nodepos, const v3s16 &neighbourpos);
+       static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
+
+       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;
+
+       GUIFormSpecMenu *current_formspec = nullptr;
+       //default: "". If other than "", empty show_formspec packets will only close the formspec when the formname matches
+       std::string cur_formname;
+
+       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()
+       MapDrawControl *draw_control = nullptr;
+       Camera *camera = nullptr;
+       Clouds *clouds = nullptr;                         // Free using ->Drop()
+       Sky *sky = nullptr;                         // Free using ->Drop()
+       Inventory *local_inventory = nullptr;
+       Hud *hud = nullptr;
+       Minimap *mapper = nullptr;
+
+       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;
+       video::IVideoDriver *driver;
+       scene::ISceneManager *smgr;
+       bool *kill;
+       std::string *error_message;
+       bool *reconnect_requested;
+       scene::ISceneNode *skybox;
+
+       bool random_input;
+       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_right_click_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__
+       bool m_cache_hold_aux1;
+       bool m_android_chat_open;
+#endif
+};
+
+Game::Game() :
+       m_game_ui(new GameUI())
+{
+       g_settings->registerChangedCallback("doubletap_jump",
+               &settingChangedCallback, this);
+       g_settings->registerChangedCallback("enable_clouds",
+               &settingChangedCallback, this);
+       g_settings->registerChangedCallback("doubletap_joysticks",
+               &settingChangedCallback, this);
+       g_settings->registerChangedCallback("enable_particles",
+               &settingChangedCallback, this);
+       g_settings->registerChangedCallback("enable_fog",
+               &settingChangedCallback, this);
+       g_settings->registerChangedCallback("mouse_sensitivity",
+               &settingChangedCallback, this);
+       g_settings->registerChangedCallback("joystick_frustum_sensitivity",
+               &settingChangedCallback, this);
+       g_settings->registerChangedCallback("repeat_rightclick_time",
+               &settingChangedCallback, this);
+       g_settings->registerChangedCallback("noclip",
+               &settingChangedCallback, this);
+       g_settings->registerChangedCallback("free_move",
+               &settingChangedCallback, this);
+       g_settings->registerChangedCallback("cinematic",
+               &settingChangedCallback, this);
+       g_settings->registerChangedCallback("cinematic_camera_smoothing",
+               &settingChangedCallback, this);
+       g_settings->registerChangedCallback("camera_smoothing",
+               &settingChangedCallback, this);
+
+       readSettings();
+
+#ifdef __ANDROID__
+       m_cache_hold_aux1 = false;      // This is initialised properly later
+#endif
+
+}
+
+
+
+/****************************************************************************
+ MinetestApp Public
+ ****************************************************************************/
+
+Game::~Game()
+{
+       delete client;
+       delete soundmaker;
+       if (!sound_is_dummy)
+               delete sound;
+
+       delete server; // deleted first to stop all server threads
+
+       delete hud;
+       delete local_inventory;
+       delete camera;
+       delete quicktune;
+       delete eventmgr;
+       delete texture_src;
+       delete shader_src;
+       delete nodedef_manager;
+       delete itemdef_manager;
+       delete draw_control;
+
+       extendedResourceCleanup();
+
+       g_settings->deregisterChangedCallback("doubletap_jump",
+               &settingChangedCallback, this);
+       g_settings->deregisterChangedCallback("enable_clouds",
+               &settingChangedCallback, this);
+       g_settings->deregisterChangedCallback("enable_particles",
+               &settingChangedCallback, this);
+       g_settings->deregisterChangedCallback("enable_fog",
+               &settingChangedCallback, this);
+       g_settings->deregisterChangedCallback("mouse_sensitivity",
+               &settingChangedCallback, this);
+       g_settings->deregisterChangedCallback("repeat_rightclick_time",
+               &settingChangedCallback, this);
+       g_settings->deregisterChangedCallback("noclip",
+               &settingChangedCallback, this);
+       g_settings->deregisterChangedCallback("free_move",
+               &settingChangedCallback, this);
+       g_settings->deregisterChangedCallback("cinematic",
+               &settingChangedCallback, this);
+       g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
+               &settingChangedCallback, this);
+       g_settings->deregisterChangedCallback("camera_smoothing",
+               &settingChangedCallback, this);
+}
+
+bool Game::startup(bool *kill,
+               bool random_input,
+               InputHandler *input,
+               const std::string &map_dir,
+               const std::string &playername,
+               const std::string &password,
+               std::string *address,     // can change if simple_singleplayer_mode
+               u16 port,
+               std::string &error_message,
+               bool *reconnect,
+               ChatBackend *chat_backend,
+               const SubgameSpec &gamespec,
+               bool simple_singleplayer_mode)
+{
+       // "cache"
+       this->device              = RenderingEngine::get_raw_device();
+       this->kill                = kill;
+       this->error_message       = &error_message;
+       this->reconnect_requested = reconnect;
+       this->random_input        = random_input;
+       this->input               = input;
+       this->chat_backend        = chat_backend;
+       this->simple_singleplayer_mode = simple_singleplayer_mode;
+
+       input->keycache.populate();
+
+       driver = device->getVideoDriver();
+       smgr = RenderingEngine::get_scene_manager();
+
+       RenderingEngine::get_scene_manager()->getParameters()->
+               setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
+
+       // Reinit runData
+       runData = GameRunData();
+       runData.time_from_last_punch = 10.0;
+       runData.update_wielded_item_trigger = true;
+
+       m_game_ui->initFlags();
+
+       m_invert_mouse = g_settings->getBool("invert_mouse");
+       m_first_loop_after_window_activation = true;
+
+       g_translations->clear();
+
+       if (!init(map_dir, address, port, gamespec))
+               return false;
+
+       if (!createClient(playername, password, address, port))
+               return false;
+
+       RenderingEngine::initialize(client, hud);
+
+       return true;
+}
+
+
+void Game::run()
+{
+       ProfilerGraph graph;
+       RunStats stats              = { 0 };
+       CameraOrientation cam_view_target  = { 0 };
+       CameraOrientation cam_view  = { 0 };
+       FpsControl draw_times       = { 0 };
+       f32 dtime; // in seconds
+
+       /* Clear the profiler */
+       Profiler::GraphValues dummyvalues;
+       g_profiler->graphGet(dummyvalues);
+
+       draw_times.last_time = RenderingEngine::get_timer_time();
+
+       set_light_table(g_settings->getFloat("display_gamma"));
+
+#ifdef __ANDROID__
+       m_cache_hold_aux1 = g_settings->getBool("fast_move")
+                       && client->checkPrivilege("fast");
+#endif
+
+       irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
+               g_settings->getU16("screen_h"));
+
+       while (RenderingEngine::run()
+                       && !(*kill || g_gamecallback->shutdown_requested
+                       || (server && server->isShutdownRequested()))) {
+
+               const irr::core::dimension2d<u32> &current_screen_size =
+                       RenderingEngine::get_video_driver()->getScreenSize();
+               // Verify if window size has changed and save it if it's the case
+               // Ensure evaluating settings->getBool after verifying screensize
+               // First condition is cheaper
+               if (previous_screen_size != current_screen_size &&
+                               current_screen_size != irr::core::dimension2d<u32>(0,0) &&
+                               g_settings->getBool("autosave_screensize")) {
+                       g_settings->setU16("screen_w", current_screen_size.Width);
+                       g_settings->setU16("screen_h", current_screen_size.Height);
+                       previous_screen_size = current_screen_size;
+               }
+
+               /* Must be called immediately after a device->run() call because it
+                * uses device->getTimer()->getTime()
+                */
+               limitFps(&draw_times, &dtime);
+
+               updateStats(&stats, draw_times, dtime);
+               updateInteractTimers(dtime);
+
+               if (!checkConnection())
+                       break;
+               if (!handleCallbacks())
+                       break;
+
+               processQueues();
+
+               m_game_ui->clearInfoText();
+               hud->resizeHotbar();
+
+               updateProfilers(stats, draw_times, dtime);
+               processUserInput(dtime);
+               // Update camera before player movement to avoid camera lag of one frame
+               updateCameraDirection(&cam_view_target, dtime);
+               cam_view.camera_yaw += (cam_view_target.camera_yaw -
+                               cam_view.camera_yaw) * m_cache_cam_smoothing;
+               cam_view.camera_pitch += (cam_view_target.camera_pitch -
+                               cam_view.camera_pitch) * m_cache_cam_smoothing;
+               updatePlayerControl(cam_view);
+               step(&dtime);
+               processClientEvents(&cam_view_target);
+               updateCamera(draw_times.busy_time, dtime);
+               updateSound(dtime);
+               processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
+                       m_game_ui->m_flags.show_debug);
+               updateFrame(&graph, &stats, dtime, cam_view);
+               updateProfilerGraphs(&graph);
+
+               // Update if minimap has been disabled by the server
+               m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
+
+               if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
+                       showPauseMenu();
+               }
+       }
+}
+
+
+void Game::shutdown()
+{
+       RenderingEngine::finalize();
+#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
+       if (g_settings->get("3d_mode") == "pageflip") {
+               driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
+       }
+#endif
+       if (current_formspec)
+               current_formspec->quitMenu();
+
+       showOverlayMessage("Shutting down...", 0, 0, false);
+
+       if (clouds)
+               clouds->drop();
+
+       if (gui_chat_console)
+               gui_chat_console->drop();
+
+       if (sky)
+               sky->drop();
+
+       /* cleanup menus */
+       while (g_menumgr.menuCount() > 0) {
+               g_menumgr.m_stack.front()->setVisible(false);
+               g_menumgr.deletingMenu(g_menumgr.m_stack.front());
+       }
+
+       if (current_formspec) {
+               current_formspec->drop();
+               current_formspec = NULL;
+       }
+
+       chat_backend->addMessage(L"", L"# Disconnected.");
+       chat_backend->addMessage(L"", L"");
+
+       if (client) {
+               client->Stop();
+               while (!client->isShutdown()) {
+                       assert(texture_src != NULL);
+                       assert(shader_src != NULL);
+                       texture_src->processQueue();
+                       shader_src->processQueue();
+                       sleep_ms(100);
+               }
+       }
+}
+
+
+/****************************************************************************/
+/****************************************************************************
+ Startup
+ ****************************************************************************/
+/****************************************************************************/
+
+bool Game::init(
+               const std::string &map_dir,
+               std::string *address,
+               u16 port,
+               const SubgameSpec &gamespec)
+{
+       texture_src = createTextureSource();
+
+       showOverlayMessage("Loading...", 0, 0);
+
+       shader_src = createShaderSource();
+
+       itemdef_manager = createItemDefManager();
+       nodedef_manager = createNodeDefManager();
+
+       eventmgr = new EventManager();
+       quicktune = new QuicktuneShortcutter();
+
+       if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
+                       && eventmgr && quicktune))
+               return false;
+
+       if (!initSound())
+               return false;
+
+       // Create a server if not connecting to an existing one
+       if (address->empty()) {
+               if (!createSingleplayerServer(map_dir, gamespec, port, address))
+                       return false;
+       }
+
+       return true;
+}
+
+bool Game::initSound()
+{
+#if USE_SOUND
+       if (g_settings->getBool("enable_sound")) {
+               infostream << "Attempting to use OpenAL audio" << std::endl;
+               sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
+               if (!sound)
+                       infostream << "Failed to initialize OpenAL audio" << std::endl;
+       } else
+               infostream << "Sound disabled." << std::endl;
+#endif
+
+       if (!sound) {
+               infostream << "Using dummy audio." << std::endl;
+               sound = &dummySoundManager;
+               sound_is_dummy = true;
+       }
+
+       soundmaker = new SoundMaker(sound, nodedef_manager);
+       if (!soundmaker)
+               return false;
+
+       soundmaker->registerReceiver(eventmgr);
+
+       return true;
+}
+
+bool Game::createSingleplayerServer(const std::string &map_dir,
+               const SubgameSpec &gamespec, u16 port, std::string *address)
+{
+       showOverlayMessage("Creating server...", 0, 5);
+
+       std::string bind_str = g_settings->get("bind_address");
+       Address bind_addr(0, 0, 0, 0, port);
+
+       if (g_settings->getBool("ipv6_server")) {
+               bind_addr.setAddress((IPv6AddressBytes *) NULL);
+       }
+
+       try {
+               bind_addr.Resolve(bind_str.c_str());
+       } catch (ResolveError &e) {
+               infostream << "Resolving bind address \"" << bind_str
+                          << "\" failed: " << e.what()
+                          << " -- Listening on all addresses." << std::endl;
+       }
+
+       if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
+               *error_message = "Unable to listen on " +
+                               bind_addr.serializeString() +
+                               " because IPv6 is disabled";
+               errorstream << *error_message << std::endl;
+               return false;
+       }
+
+       server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr, false);
+       server->init();
+       server->start();
+
+       return true;
+}
+
+bool Game::createClient(const std::string &playername,
+               const std::string &password, std::string *address, u16 port)
+{
+       showOverlayMessage("Creating client...", 0, 10);
+
+       draw_control = new MapDrawControl;
+       if (!draw_control)
+               return false;
+
+       bool could_connect, connect_aborted;
+#ifdef HAVE_TOUCHSCREENGUI
+       if (g_touchscreengui) {
+               g_touchscreengui->init(texture_src);
+               g_touchscreengui->hide();
+       }
+#endif
+       if (!connectToServer(playername, password, address, port,
+                       &could_connect, &connect_aborted))
+               return false;
+
+       if (!could_connect) {
+               if (error_message->empty() && !connect_aborted) {
+                       // Should not happen if error messages are set properly
+                       *error_message = "Connection failed for unknown reason";
+                       errorstream << *error_message << std::endl;
+               }
+               return false;
+       }
+
+       if (!getServerContent(&connect_aborted)) {
+               if (error_message->empty() && !connect_aborted) {
+                       // Should not happen if error messages are set properly
+                       *error_message = "Connection failed for unknown reason";
+                       errorstream << *error_message << std::endl;
+               }
+               return false;
+       }
+
+       GameGlobalShaderConstantSetterFactory *scsf = new GameGlobalShaderConstantSetterFactory(
+                       &m_flags.force_fog_off, &runData.fog_range, client);
+       shader_src->addShaderConstantSetterFactory(scsf);
+
+       // Update cached textures, meshes and materials
+       client->afterContentReceived();
+
+       /* Camera
+        */
+       camera = new Camera(*draw_control, client);
+       if (!camera || !camera->successfullyCreated(*error_message))
+               return false;
+       client->setCamera(camera);
+
+       /* Clouds
+        */
+       if (m_cache_enable_clouds) {
+               clouds = new Clouds(smgr, -1, time(0));
+               if (!clouds) {
+                       *error_message = "Memory allocation error (clouds)";
+                       errorstream << *error_message << std::endl;
+                       return false;
+               }
+       }
+
+       /* Skybox
+        */
+       sky = new Sky(-1, texture_src);
+       scsf->setSky(sky);
+       skybox = NULL;  // This is used/set later on in the main run loop
+
+       local_inventory = new Inventory(itemdef_manager);
+
+       if (!(sky && local_inventory)) {
+               *error_message = "Memory allocation error (sky or local inventory)";
+               errorstream << *error_message << std::endl;
+               return false;
+       }
+
+       /* Pre-calculated values
+        */
+       video::ITexture *t = texture_src->getTexture("crack_anylength.png");
+       if (t) {
+               v2u32 size = t->getOriginalSize();
+               crack_animation_length = size.Y / size.X;
+       } else {
+               crack_animation_length = 5;
+       }
+
+       if (!initGui())
+               return false;
+
+       /* Set window caption
+        */
+       std::wstring str = utf8_to_wide(PROJECT_NAME_C);
+       str += L" ";
+       str += utf8_to_wide(g_version_hash);
+       str += L" [";
+       str += driver->getName();
+       str += L"]";
+       device->setWindowCaption(str.c_str());
+
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
+       player->hurt_tilt_timer = 0;
+       player->hurt_tilt_strength = 0;
+
+       hud = new Hud(guienv, client, player, local_inventory);
+
+       if (!hud) {
+               *error_message = "Memory error: could not create HUD";
+               errorstream << *error_message << std::endl;
+               return false;
+       }
+
+       mapper = client->getMinimap();
+       if (mapper)
+               mapper->setMinimapMode(MINIMAP_MODE_OFF);
+
+       return true;
+}
+
+bool Game::initGui()
+{
+       m_game_ui->init();
+
+       // Remove stale "recent" chat messages from previous connections
+       chat_backend->clearRecentChat();
+
+       // Make sure the size of the recent messages buffer is right
+       chat_backend->applySettings();
+
+       // Chat backend and console
+       gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
+                       -1, chat_backend, client, &g_menumgr);
+       if (!gui_chat_console) {
+               *error_message = "Could not allocate memory for chat console";
+               errorstream << *error_message << std::endl;
+               return false;
+       }
+
+#ifdef HAVE_TOUCHSCREENGUI
+
+       if (g_touchscreengui)
+               g_touchscreengui->show();
+
+#endif
+
+       return true;
+}
+
+bool Game::connectToServer(const std::string &playername,
+               const std::string &password, std::string *address, u16 port,
+               bool *connect_ok, bool *connection_aborted)
+{
+       *connect_ok = false;    // Let's not be overly optimistic
+       *connection_aborted = false;
+       bool local_server_mode = false;
+
+       showOverlayMessage("Resolving address...", 0, 15);
+
+       Address connect_address(0, 0, 0, 0, port);
+
+       try {
+               connect_address.Resolve(address->c_str());
+
+               if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
+                       //connect_address.Resolve("localhost");
+                       if (connect_address.isIPv6()) {
+                               IPv6AddressBytes addr_bytes;
+                               addr_bytes.bytes[15] = 1;
+                               connect_address.setAddress(&addr_bytes);
+                       } else {
+                               connect_address.setAddress(127, 0, 0, 1);
+                       }
+                       local_server_mode = true;
+               }
+       } catch (ResolveError &e) {
+               *error_message = std::string("Couldn't resolve address: ") + e.what();
+               errorstream << *error_message << std::endl;
+               return false;
+       }
+
+       if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
+               *error_message = "Unable to connect to " +
+                               connect_address.serializeString() +
+                               " because IPv6 is disabled";
+               errorstream << *error_message << std::endl;
+               return false;
+       }
+
+       client = new Client(playername.c_str(), password, *address,
+                       *draw_control, texture_src, shader_src,
+                       itemdef_manager, nodedef_manager, sound, eventmgr,
+                       connect_address.isIPv6(), m_game_ui.get());
+
+       if (!client)
+               return false;
+
+       client->m_simple_singleplayer_mode = simple_singleplayer_mode;
+
+       infostream << "Connecting to server at ";
+       connect_address.print(&infostream);
+       infostream << std::endl;
+
+       client->connect(connect_address,
+               simple_singleplayer_mode || local_server_mode);
+
+       /*
+               Wait for server to accept connection
+       */
+
+       try {
+               input->clear();
+
+               FpsControl fps_control = { 0 };
+               f32 dtime;
+               f32 wait_time = 0; // in seconds
+
+               fps_control.last_time = RenderingEngine::get_timer_time();
+
+               while (RenderingEngine::run()) {
+
+                       limitFps(&fps_control, &dtime);
+
+                       // Update client and server
+                       client->step(dtime);
+
+                       if (server != NULL)
+                               server->step(dtime);
+
+                       // End condition
+                       if (client->getState() == LC_Init) {
+                               *connect_ok = true;
+                               break;
+                       }
+
+                       // Break conditions
+                       if (*connection_aborted)
+                               break;
+
+                       if (client->accessDenied()) {
+                               *error_message = "Access denied. Reason: "
+                                               + client->accessDeniedReason();
+                               *reconnect_requested = client->reconnectRequested();
+                               errorstream << *error_message << std::endl;
+                               break;
+                       }
+
+                       if (input->cancelPressed()) {
+                               *connection_aborted = true;
+                               infostream << "Connect aborted [Escape]" << std::endl;
+                               break;
+                       }
+
+                       if (client->m_is_registration_confirmation_state) {
+                               if (registration_confirmation_shown) {
+                                       // Keep drawing the GUI
+                                       RenderingEngine::draw_menu_scene(guienv, dtime, true);
+                               } else {
+                                       registration_confirmation_shown = true;
+                                       (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
+                                                  &g_menumgr, client, playername, password, *address, connection_aborted))->drop();
+                               }
+                       } else {
+                               wait_time += dtime;
+                               // Only time out if we aren't waiting for the server we started
+                               if (!address->empty() && wait_time > 10) {
+                                       *error_message = "Connection timed out.";
+                                       errorstream << *error_message << std::endl;
+                                       break;
+                               }
+
+                               // Update status
+                               showOverlayMessage("Connecting to server...", dtime, 20);
+                       }
+               }
+       } catch (con::PeerNotFoundException &e) {
+               // TODO: Should something be done here? At least an info/error
+               // message?
+               return false;
+       }
+
+       return true;
+}
+
+bool Game::getServerContent(bool *aborted)
+{
+       input->clear();
+
+       FpsControl fps_control = { 0 };
+       f32 dtime; // in seconds
+
+       fps_control.last_time = RenderingEngine::get_timer_time();
+
+       while (RenderingEngine::run()) {
+
+               limitFps(&fps_control, &dtime);
+
+               // Update client and server
+               client->step(dtime);
+
+               if (server != NULL)
+                       server->step(dtime);
+
+               // End condition
+               if (client->mediaReceived() && client->itemdefReceived() &&
+                               client->nodedefReceived()) {
+                       break;
+               }
+
+               // Error conditions
+               if (!checkConnection())
+                       return false;
+
+               if (client->getState() < LC_Init) {
+                       *error_message = "Client disconnected";
+                       errorstream << *error_message << std::endl;
+                       return false;
+               }
+
+               if (input->cancelPressed()) {
+                       *aborted = true;
+                       infostream << "Connect aborted [Escape]" << std::endl;
+                       return false;
+               }
+
+               // Display status
+               int progress = 25;
+
+               if (!client->itemdefReceived()) {
+                       const wchar_t *text = wgettext("Item definitions...");
+                       progress = 25;
+                       RenderingEngine::draw_load_screen(text, guienv, texture_src,
+                               dtime, progress);
+                       delete[] text;
+               } else if (!client->nodedefReceived()) {
+                       const wchar_t *text = wgettext("Node definitions...");
+                       progress = 30;
+                       RenderingEngine::draw_load_screen(text, guienv, texture_src,
+                               dtime, progress);
+                       delete[] text;
+               } else {
+                       std::stringstream message;
+                       std::fixed(message);
+                       message.precision(0);
+                       message << gettext("Media...") << " " << (client->mediaReceiveProgress()*100) << "%";
+                       message.precision(2);
+
+                       if ((USE_CURL == 0) ||
+                                       (!g_settings->getBool("enable_remote_media_server"))) {
+                               float cur = client->getCurRate();
+                               std::string cur_unit = gettext("KiB/s");
+
+                               if (cur > 900) {
+                                       cur /= 1024.0;
+                                       cur_unit = gettext("MiB/s");
+                               }
+
+                               message << " (" << cur << ' ' << cur_unit << ")";
+                       }
+
+                       progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
+                       RenderingEngine::draw_load_screen(utf8_to_wide(message.str()), guienv,
+                               texture_src, dtime, progress);
+               }
+       }
+
+       return true;
+}
+
+
+/****************************************************************************/
+/****************************************************************************
+ Run
+ ****************************************************************************/
+/****************************************************************************/
+
+inline void Game::updateInteractTimers(f32 dtime)
+{
+       if (runData.nodig_delay_timer >= 0)
+               runData.nodig_delay_timer -= dtime;
+
+       if (runData.object_hit_delay_timer >= 0)
+               runData.object_hit_delay_timer -= dtime;
+
+       runData.time_from_last_punch += dtime;
+}
+
+
+/* returns false if game should exit, otherwise true
+ */
+inline bool Game::checkConnection()
+{
+       if (client->accessDenied()) {
+               *error_message = "Access denied. Reason: "
+                               + client->accessDeniedReason();
+               *reconnect_requested = client->reconnectRequested();
+               errorstream << *error_message << std::endl;
+               return false;
+       }
+
+       return true;
+}
+
+
+/* returns false if game should exit, otherwise true
+ */
+inline bool Game::handleCallbacks()
+{
+       if (g_gamecallback->disconnect_requested) {
+               g_gamecallback->disconnect_requested = false;
+               return false;
+       }
+
+       if (g_gamecallback->changepassword_requested) {
+               (new GUIPasswordChange(guienv, guiroot, -1,
+                                      &g_menumgr, client))->drop();
+               g_gamecallback->changepassword_requested = false;
+       }
+
+       if (g_gamecallback->changevolume_requested) {
+               (new GUIVolumeChange(guienv, guiroot, -1,
+                                    &g_menumgr))->drop();
+               g_gamecallback->changevolume_requested = false;
+       }
+
+       if (g_gamecallback->keyconfig_requested) {
+               (new GUIKeyChangeMenu(guienv, guiroot, -1,
+                                     &g_menumgr))->drop();
+               g_gamecallback->keyconfig_requested = false;
+       }
+
+       if (g_gamecallback->keyconfig_changed) {
+               input->keycache.populate(); // update the cache with new settings
+               g_gamecallback->keyconfig_changed = false;
+       }
+
+       return true;
+}
+
+
+void Game::processQueues()
+{
+       texture_src->processQueue();
+       itemdef_manager->processQueue(client);
+       shader_src->processQueue();
+}
+
+
+void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime)
+{
+       float profiler_print_interval =
+                       g_settings->getFloat("profiler_print_interval");
+       bool print_to_log = true;
+
+       if (profiler_print_interval == 0) {
+               print_to_log = false;
+               profiler_print_interval = 5;
+       }
+
+       if (profiler_interval.step(dtime, profiler_print_interval)) {
+               if (print_to_log) {
+                       infostream << "Profiler:" << std::endl;
+                       g_profiler->print(infostream);
+               }
+
+               m_game_ui->updateProfiler();
+               g_profiler->clear();
+       }
+
+       addProfilerGraphs(stats, draw_times, dtime);
+}
+
+
+void Game::addProfilerGraphs(const RunStats &stats,
+               const FpsControl &draw_times, f32 dtime)
+{
+       g_profiler->graphAdd("mainloop_other",
+                       draw_times.busy_time / 1000.0f - stats.drawtime / 1000.0f);
+
+       if (draw_times.sleep_time != 0)
+               g_profiler->graphAdd("mainloop_sleep", draw_times.sleep_time / 1000.0f);
+       g_profiler->graphAdd("mainloop_dtime", dtime);
+
+       g_profiler->add("Elapsed time", dtime);
+       g_profiler->avg("FPS", 1. / dtime);
+}
+
+
+void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
+               f32 dtime)
+{
+
+       f32 jitter;
+       Jitter *jp;
+
+       /* Time average and jitter calculation
+        */
+       jp = &stats->dtime_jitter;
+       jp->avg = jp->avg * 0.96 + dtime * 0.04;
+
+       jitter = dtime - jp->avg;
+
+       if (jitter > jp->max)
+               jp->max = jitter;
+
+       jp->counter += dtime;
+
+       if (jp->counter > 0.0) {
+               jp->counter -= 3.0;
+               jp->max_sample = jp->max;
+               jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
+               jp->max = 0.0;
+       }
+
+       /* Busytime average and jitter calculation
+        */
+       jp = &stats->busy_time_jitter;
+       jp->avg = jp->avg + draw_times.busy_time * 0.02;
+
+       jitter = draw_times.busy_time - jp->avg;
+
+       if (jitter > jp->max)
+               jp->max = jitter;
+       if (jitter < jp->min)
+               jp->min = jitter;
+
+       jp->counter += dtime;
+
+       if (jp->counter > 0.0) {
+               jp->counter -= 3.0;
+               jp->max_sample = jp->max;
+               jp->min_sample = jp->min;
+               jp->max = 0.0;
+               jp->min = 0.0;
+       }
+}
+
+
+
+/****************************************************************************
+ Input handling
+ ****************************************************************************/
+
+void Game::processUserInput(f32 dtime)
+{
+       // Reset input if window not active or some menu is active
+       if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
+               input->clear();
+#ifdef HAVE_TOUCHSCREENGUI
+               g_touchscreengui->hide();
+#endif
+       }
+#ifdef HAVE_TOUCHSCREENGUI
+       else if (g_touchscreengui) {
+               /* on touchscreengui step may generate own input events which ain't
+                * what we want in case we just did clear them */
+               g_touchscreengui->step(dtime);
+       }
+#endif
+
+       if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
+               gui_chat_console->closeConsoleAtOnce();
+       }
+
+       // Input handler step() (used by the random input generator)
+       input->step(dtime);
+
+#ifdef __ANDROID__
+       if (current_formspec != NULL)
+               current_formspec->getAndroidUIInput();
+       else
+               handleAndroidChatInput();
+#endif
+
+       // Increase timer for double tap of "keymap_jump"
+       if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
+               runData.jump_timer += dtime;
+
+       processKeyInput();
+       processItemSelection(&runData.new_playeritem);
+}
+
+
+void Game::processKeyInput()
+{
+       if (wasKeyDown(KeyType::DROP)) {
+               dropSelectedItem(isKeyDown(KeyType::SNEAK));
+       } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
+               toggleAutoforward();
+       } else if (wasKeyDown(KeyType::BACKWARD)) {
+               if (g_settings->getBool("continuous_forward"))
+                       toggleAutoforward();
+       } else if (wasKeyDown(KeyType::INVENTORY)) {
+               openInventory();
+       } else if (input->cancelPressed()) {
+               if (!gui_chat_console->isOpenInhibited()) {
+                       showPauseMenu();
+               }
+       } else if (wasKeyDown(KeyType::CHAT)) {
+               openConsole(0.2, L"");
+       } else if (wasKeyDown(KeyType::CMD)) {
+               openConsole(0.2, L"/");
+       } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
+               if (client->moddingEnabled())
+                       openConsole(0.2, L".");
+               else
+                       m_game_ui->showStatusText(wgettext("CSM is disabled"));
+       } else if (wasKeyDown(KeyType::CONSOLE)) {
+               openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
+       } else if (wasKeyDown(KeyType::FREEMOVE)) {
+               toggleFreeMove();
+       } else if (wasKeyDown(KeyType::JUMP)) {
+               toggleFreeMoveAlt();
+       } else if (wasKeyDown(KeyType::FASTMOVE)) {
+               toggleFast();
+       } else if (wasKeyDown(KeyType::NOCLIP)) {
+               toggleNoClip();
+       } else if (wasKeyDown(KeyType::MUTE)) {
+               bool new_mute_sound = !g_settings->getBool("mute_sound");
+               g_settings->setBool("mute_sound", new_mute_sound);
+               if (new_mute_sound)
+                       m_game_ui->showTranslatedStatusText("Sound muted");
+               else
+                       m_game_ui->showTranslatedStatusText("Sound unmuted");
+       } else if (wasKeyDown(KeyType::INC_VOLUME)) {
+               float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
+               wchar_t buf[100];
+               g_settings->setFloat("sound_volume", new_volume);
+               const wchar_t *str = wgettext("Volume changed to %d%%");
+               swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
+               delete[] str;
+               m_game_ui->showStatusText(buf);
+       } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
+               float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
+               wchar_t buf[100];
+               g_settings->setFloat("sound_volume", new_volume);
+               const wchar_t *str = wgettext("Volume changed to %d%%");
+               swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
+               delete[] str;
+               m_game_ui->showStatusText(buf);
+       } else if (wasKeyDown(KeyType::CINEMATIC)) {
+               toggleCinematic();
+       } else if (wasKeyDown(KeyType::SCREENSHOT)) {
+               client->makeScreenshot();
+       } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
+               m_game_ui->toggleHud();
+       } else if (wasKeyDown(KeyType::MINIMAP)) {
+               toggleMinimap(isKeyDown(KeyType::SNEAK));
+       } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
+               m_game_ui->toggleChat();
+       } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
+               toggleFog();
+       } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
+               toggleUpdateCamera();
+       } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
+               toggleDebug();
+       } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
+               m_game_ui->toggleProfiler();
+       } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
+               increaseViewRange();
+       } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
+               decreaseViewRange();
+       } else if (wasKeyDown(KeyType::RANGESELECT)) {
+               toggleFullViewRange();
+       } else if (wasKeyDown(KeyType::ZOOM)) {
+               checkZoomEnabled();
+       } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
+               quicktune->next();
+       } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
+               quicktune->prev();
+       } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
+               quicktune->inc();
+       } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
+               quicktune->dec();
+       }
+
+       if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
+               runData.reset_jump_timer = false;
+               runData.jump_timer = 0.0f;
+       }
+
+       if (quicktune->hasMessage()) {
+               m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
+       }
+}
+
+void Game::processItemSelection(u16 *new_playeritem)
+{
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
+
+       /* Item selection using mouse wheel
+        */
+       *new_playeritem = client->getPlayerItem();
+
+       s32 wheel = input->getMouseWheel();
+       u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
+                   player->hud_hotbar_itemcount - 1);
+
+       s32 dir = wheel;
+
+       if (input->joystick.wasKeyDown(KeyType::SCROLL_DOWN) ||
+                       wasKeyDown(KeyType::HOTBAR_NEXT)) {
+               dir = -1;
+       }
+
+       if (input->joystick.wasKeyDown(KeyType::SCROLL_UP) ||
+                       wasKeyDown(KeyType::HOTBAR_PREV)) {
+               dir = 1;
+       }
+
+       if (dir < 0)
+               *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
+       else if (dir > 0)
+               *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
+       // else dir == 0
+
+       /* Item selection using hotbar slot keys
+        */
+       for (u16 i = 0; i < 23; i++) {
+               if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
+                       if (i < PLAYER_INVENTORY_SIZE && i < player->hud_hotbar_itemcount) {
+                               *new_playeritem = i;
+                               infostream << "Selected item: " << new_playeritem << std::endl;
+                       }
+                       break;
+               }
+       }
+}
+
+
+void Game::dropSelectedItem(bool single_item)
+{
+       IDropAction *a = new IDropAction();
+       a->count = single_item ? 1 : 0;
+       a->from_inv.setCurrentPlayer();
+       a->from_list = "main";
+       a->from_i = client->getPlayerItem();
+       client->inventoryAction(a);
+}
+
+
+void Game::openInventory()
+{
+       /*
+        * Don't permit to open inventory is CAO or player doesn't exists.
+        * This prevent showing an empty inventory at player load
+        */
+
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
+       if (!player || !player->getCAO())
+               return;
+
+       infostream << "the_game: " << "Launching inventory" << std::endl;
+
+       PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
+
+       InventoryLocation inventoryloc;
+       inventoryloc.setCurrentPlayer();
+
+       if (!client->moddingEnabled()
+                       || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
+               TextDest *txt_dst = new TextDestPlayerInventory(client);
+               GUIFormSpecMenu::create(current_formspec, client, &input->joystick, fs_src,
+                       txt_dst, client->getFormspecPrepend());
+               cur_formname = "";
+               current_formspec->setFormSpec(fs_src->getForm(), inventoryloc);
+       }
+}
+
+
+void Game::openConsole(float scale, const wchar_t *line)
+{
+       assert(scale > 0.0f && scale <= 1.0f);
+
+#ifdef __ANDROID__
+       porting::showInputDialog(gettext("ok"), "", "", 2);
+       m_android_chat_open = true;
+#else
+       if (gui_chat_console->isOpenInhibited())
+               return;
+       gui_chat_console->openConsole(scale);
+       if (line) {
+               gui_chat_console->setCloseOnEnter(true);
+               gui_chat_console->replaceAndAddToHistory(line);
+       }
+#endif
+}
+
+#ifdef __ANDROID__
+void Game::handleAndroidChatInput()
+{
+       if (m_android_chat_open && porting::getInputDialogState() == 0) {
+               std::string text = porting::getInputDialogValue();
+               client->typeChatMessage(utf8_to_wide(text));
+       }
+}
+#endif
+
+
+void Game::toggleFreeMove()
+{
+       bool free_move = !g_settings->getBool("free_move");
+       g_settings->set("free_move", bool_to_cstr(free_move));
+
+       if (free_move) {
+               if (client->checkPrivilege("fly")) {
+                       m_game_ui->showTranslatedStatusText("Fly mode enabled");
+               } else {
+                       m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
+               }
+       } else {
+               m_game_ui->showTranslatedStatusText("Fly mode disabled");
+       }
+}
+
+void Game::toggleFreeMoveAlt()
+{
+       if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
+               toggleFreeMove();
+
+       runData.reset_jump_timer = true;
+}
+
+
+void Game::toggleFast()
+{
+       bool fast_move = !g_settings->getBool("fast_move");
+       bool has_fast_privs = client->checkPrivilege("fast");
+       g_settings->set("fast_move", bool_to_cstr(fast_move));
+
+       if (fast_move) {
+               if (has_fast_privs) {
+                       m_game_ui->showTranslatedStatusText("Fast mode enabled");
+               } else {
+                       m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
+               }
+       } else {
+               m_game_ui->showTranslatedStatusText("Fast mode disabled");
+       }
+
+#ifdef __ANDROID__
+       m_cache_hold_aux1 = fast_move && has_fast_privs;
+#endif
+}
+
+
+void Game::toggleNoClip()
+{
+       bool noclip = !g_settings->getBool("noclip");
+       g_settings->set("noclip", bool_to_cstr(noclip));
+
+       if (noclip) {
+               if (client->checkPrivilege("noclip")) {
+                       m_game_ui->showTranslatedStatusText("Noclip mode enabled");
+               } else {
+                       m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
+               }
+       } else {
+               m_game_ui->showTranslatedStatusText("Noclip mode disabled");
+       }
+}
+
+void Game::toggleCinematic()
+{
+       bool cinematic = !g_settings->getBool("cinematic");
+       g_settings->set("cinematic", bool_to_cstr(cinematic));
+
+       if (cinematic)
+               m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
+       else
+               m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
+}
+
+// Autoforward by toggling continuous forward.
+void Game::toggleAutoforward()
+{
+       bool autorun_enabled = !g_settings->getBool("continuous_forward");
+       g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
+
+       if (autorun_enabled)
+               m_game_ui->showTranslatedStatusText("Automatic forwards enabled");
+       else
+               m_game_ui->showTranslatedStatusText("Automatic forwards disabled");
+}
+
+void Game::toggleMinimap(bool shift_pressed)
+{
+       if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
+               return;
+
+       if (shift_pressed) {
+               mapper->toggleMinimapShape();
+               return;
+       }
+
+       u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
+
+       MinimapMode mode = MINIMAP_MODE_OFF;
+       if (hud_flags & HUD_FLAG_MINIMAP_VISIBLE) {
+               mode = mapper->getMinimapMode();
+               mode = (MinimapMode)((int)mode + 1);
+               // If radar is disabled and in, or switching to, radar mode
+               if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE) && mode > 3)
+                       mode = MINIMAP_MODE_OFF;
+       }
+
+       m_game_ui->m_flags.show_minimap = true;
+       switch (mode) {
+               case MINIMAP_MODE_SURFACEx1:
+                       m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x1");
+                       break;
+               case MINIMAP_MODE_SURFACEx2:
+                       m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x2");
+                       break;
+               case MINIMAP_MODE_SURFACEx4:
+                       m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x4");
+                       break;
+               case MINIMAP_MODE_RADARx1:
+                       m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x1");
+                       break;
+               case MINIMAP_MODE_RADARx2:
+                       m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x2");
+                       break;
+               case MINIMAP_MODE_RADARx4:
+                       m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x4");
+                       break;
+               default:
+                       mode = MINIMAP_MODE_OFF;
+                       m_game_ui->m_flags.show_minimap = false;
+                       if (hud_flags & HUD_FLAG_MINIMAP_VISIBLE)
+                               m_game_ui->showTranslatedStatusText("Minimap hidden");
+                       else
+                               m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
+       }
+
+       mapper->setMinimapMode(mode);
+}
+
+void Game::toggleFog()
+{
+       bool fog_enabled = g_settings->getBool("enable_fog");
+       g_settings->setBool("enable_fog", !fog_enabled);
+       if (fog_enabled)
+               m_game_ui->showTranslatedStatusText("Fog disabled");
+       else
+               m_game_ui->showTranslatedStatusText("Fog enabled");
+}
+
+
+void Game::toggleDebug()
+{
+       // Initial / 4x toggle: Chat only
+       // 1x toggle: Debug text with chat
+       // 2x toggle: Debug text with profiler graph
+       // 3x toggle: Debug text and wireframe
+       if (!m_game_ui->m_flags.show_debug) {
+               m_game_ui->m_flags.show_debug = true;
+               m_game_ui->m_flags.show_profiler_graph = false;
+               draw_control->show_wireframe = false;
+               m_game_ui->showTranslatedStatusText("Debug info shown");
+       } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
+               m_game_ui->m_flags.show_profiler_graph = true;
+               m_game_ui->showTranslatedStatusText("Profiler graph shown");
+       } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
+               m_game_ui->m_flags.show_profiler_graph = false;
+               draw_control->show_wireframe = true;
+               m_game_ui->showTranslatedStatusText("Wireframe shown");
+       } else {
+               m_game_ui->m_flags.show_debug = false;
+               m_game_ui->m_flags.show_profiler_graph = false;
+               draw_control->show_wireframe = false;
+               if (client->checkPrivilege("debug")) {
+                       m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
+               } else {
+                       m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
+               }
+       }
+}
+
+
+void Game::toggleUpdateCamera()
+{
+       m_flags.disable_camera_update = !m_flags.disable_camera_update;
+       if (m_flags.disable_camera_update)
+               m_game_ui->showTranslatedStatusText("Camera update disabled");
+       else
+               m_game_ui->showTranslatedStatusText("Camera update enabled");
+}
+
+
+void Game::increaseViewRange()
+{
+       s16 range = g_settings->getS16("viewing_range");
+       s16 range_new = range + 10;
+
+       wchar_t buf[255];
+       const wchar_t *str;
+       if (range_new > 4000) {
+               range_new = 4000;
+               str = wgettext("Viewing range is at maximum: %d");
+               swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
+               delete[] str;
+               m_game_ui->showStatusText(buf);
+
+       } else {
+               str = wgettext("Viewing range changed to %d");
+               swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
+               delete[] str;
+               m_game_ui->showStatusText(buf);
+       }
+       g_settings->set("viewing_range", itos(range_new));
+}
+
+
+void Game::decreaseViewRange()
+{
+       s16 range = g_settings->getS16("viewing_range");
+       s16 range_new = range - 10;
+
+       wchar_t buf[255];
+       const wchar_t *str;
+       if (range_new < 20) {
+               range_new = 20;
+               str = wgettext("Viewing range is at minimum: %d");
+               swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
+               delete[] str;
+               m_game_ui->showStatusText(buf);
+       } else {
+               str = wgettext("Viewing range changed to %d");
+               swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
+               delete[] str;
+               m_game_ui->showStatusText(buf);
+       }
+       g_settings->set("viewing_range", itos(range_new));
+}
+
+
+void Game::toggleFullViewRange()
+{
+       draw_control->range_all = !draw_control->range_all;
+       if (draw_control->range_all)
+               m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
+       else
+               m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
+}
+
+
+void Game::checkZoomEnabled()
+{
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
+       if (player->getZoomFOV() < 0.001f)
+               m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
+}
+
+
+void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
+{
+       if ((device->isWindowActive() && device->isWindowFocused()
+                       && !isMenuActive()) || random_input) {
+
+#ifndef __ANDROID__
+               if (!random_input) {
+                       // Mac OSX gets upset if this is set every frame
+                       if (device->getCursorControl()->isVisible())
+                               device->getCursorControl()->setVisible(false);
+               }
+#endif
+
+               if (m_first_loop_after_window_activation) {
+                       m_first_loop_after_window_activation = false;
+
+                       input->setMousePos(driver->getScreenSize().Width / 2,
+                               driver->getScreenSize().Height / 2);
+               } else {
+                       updateCameraOrientation(cam, dtime);
+               }
+
+       } else {
+
+#ifndef ANDROID
+               // Mac OSX gets upset if this is set every frame
+               if (!device->getCursorControl()->isVisible())
+                       device->getCursorControl()->setVisible(true);
+#endif
+
+               m_first_loop_after_window_activation = true;
+
+       }
+}
+
+void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
+{
+#ifdef HAVE_TOUCHSCREENGUI
+       if (g_touchscreengui) {
+               cam->camera_yaw   += g_touchscreengui->getYawChange();
+               cam->camera_pitch  = g_touchscreengui->getPitch();
+       } else {
+#endif
+               v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
+               v2s32 dist = input->getMousePos() - center;
+
+               if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
+                       dist.Y = -dist.Y;
+               }
+
+               cam->camera_yaw   -= dist.X * m_cache_mouse_sensitivity;
+               cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity;
+
+               if (dist.X != 0 || dist.Y != 0)
+                       input->setMousePos(center.X, center.Y);
+#ifdef HAVE_TOUCHSCREENGUI
+       }
+#endif
+
+       if (m_cache_enable_joysticks) {
+               f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime;
+               cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
+               cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
+       }
+
+       cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
+}
+
+
+void Game::updatePlayerControl(const CameraOrientation &cam)
+{
+       //TimeTaker tt("update player control", NULL, PRECISION_NANO);
+
+       // DO NOT use the isKeyDown method for the forward, backward, left, right
+       // buttons, as the code that uses the controls needs to be able to
+       // distinguish between the two in order to know when to use joysticks.
+
+       PlayerControl control(
+               input->isKeyDown(KeyType::FORWARD),
+               input->isKeyDown(KeyType::BACKWARD),
+               input->isKeyDown(KeyType::LEFT),
+               input->isKeyDown(KeyType::RIGHT),
+               isKeyDown(KeyType::JUMP),
+               isKeyDown(KeyType::SPECIAL1),
+               isKeyDown(KeyType::SNEAK),
+               isKeyDown(KeyType::ZOOM),
+               input->getLeftState(),
+               input->getRightState(),
+               cam.camera_pitch,
+               cam.camera_yaw,
+               input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE),
+               input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE)
+       );
+
+       u32 keypress_bits =
+                       ( (u32)(isKeyDown(KeyType::FORWARD)                       & 0x1) << 0) |
+                       ( (u32)(isKeyDown(KeyType::BACKWARD)                      & 0x1) << 1) |
+                       ( (u32)(isKeyDown(KeyType::LEFT)                          & 0x1) << 2) |
+                       ( (u32)(isKeyDown(KeyType::RIGHT)                         & 0x1) << 3) |
+                       ( (u32)(isKeyDown(KeyType::JUMP)                          & 0x1) << 4) |
+                       ( (u32)(isKeyDown(KeyType::SPECIAL1)                      & 0x1) << 5) |
+                       ( (u32)(isKeyDown(KeyType::SNEAK)                         & 0x1) << 6) |
+                       ( (u32)(input->getLeftState()                             & 0x1) << 7) |
+                       ( (u32)(input->getRightState()                            & 0x1) << 8
+               );
+
+#ifdef ANDROID
+       /* For Android, simulate holding down AUX1 (fast move) if the user has
+        * the fast_move setting toggled on. If there is an aux1 key defined for
+        * Android then its meaning is inverted (i.e. holding aux1 means walk and
+        * not fast)
+        */
+       if (m_cache_hold_aux1) {
+               control.aux1 = control.aux1 ^ true;
+               keypress_bits ^= ((u32)(1U << 5));
+       }
+#endif
+
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
+
+       // autojump if set: simulate "jump" key
+       if (player->getAutojump()) {
+               control.jump = true;
+               keypress_bits |= 1U << 4;
+       }
+
+       client->setPlayerControl(control);
+       player->keyPressed = keypress_bits;
+
+       //tt.stop();
+}
+
+
+inline void Game::step(f32 *dtime)
+{
+       bool can_be_and_is_paused =
+                       (simple_singleplayer_mode && g_menumgr.pausesGame());
+
+       if (can_be_and_is_paused) {     // This is for a singleplayer server
+               *dtime = 0;             // No time passes
+       } else {
+               if (server)
+                       server->step(*dtime);
+
+               client->step(*dtime);
+       }
+}
+
+const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
+       {&Game::handleClientEvent_None},
+       {&Game::handleClientEvent_PlayerDamage},
+       {&Game::handleClientEvent_PlayerForceMove},
+       {&Game::handleClientEvent_Deathscreen},
+       {&Game::handleClientEvent_ShowFormSpec},
+       {&Game::handleClientEvent_ShowLocalFormSpec},
+       {&Game::handleClientEvent_HandleParticleEvent},
+       {&Game::handleClientEvent_HandleParticleEvent},
+       {&Game::handleClientEvent_HandleParticleEvent},
+       {&Game::handleClientEvent_HudAdd},
+       {&Game::handleClientEvent_HudRemove},
+       {&Game::handleClientEvent_HudChange},
+       {&Game::handleClientEvent_SetSky},
+       {&Game::handleClientEvent_OverrideDayNigthRatio},
+       {&Game::handleClientEvent_CloudParams},
+};
+
+void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
+{
+       FATAL_ERROR("ClientEvent type None received");
+}
+
+void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
+{
+       if (client->moddingEnabled()) {
+               client->getScript()->on_damage_taken(event->player_damage.amount);
+       }
+
+       // Damage flash and hurt tilt are not used at death
+       if (client->getHP() > 0) {
+               runData.damage_flash += 95.0f + 3.2f * event->player_damage.amount;
+               runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
+
+               LocalPlayer *player = client->getEnv().getLocalPlayer();
+
+               player->hurt_tilt_timer = 1.5f;
+               player->hurt_tilt_strength =
+                       rangelim(event->player_damage.amount / 4.0f, 1.0f, 4.0f);
+       }
+
+       // Play damage sound
+       client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
+}
+
+void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
+{
+       cam->camera_yaw = event->player_force_move.yaw;
+       cam->camera_pitch = event->player_force_move.pitch;
+}
+
+void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
+{
+       // If CSM enabled, deathscreen is handled by CSM code in
+       // builtin/client/init.lua
+       if (client->moddingEnabled())
+               client->getScript()->on_death();
+       else
+               showDeathFormspec();
+
+       /* Handle visualization */
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
+       runData.damage_flash = 0;
+       player->hurt_tilt_timer = 0;
+       player->hurt_tilt_strength = 0;
+}
+
+void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
+{
+       if (event->show_formspec.formspec->empty()) {
+               if (current_formspec && (event->show_formspec.formname->empty()
+                       || *(event->show_formspec.formname) == cur_formname)) {
+                       current_formspec->quitMenu();
+               }
+       } else {
+               FormspecFormSource *fs_src =
+                       new FormspecFormSource(*(event->show_formspec.formspec));
+               TextDestPlayerInventory *txt_dst =
+                       new TextDestPlayerInventory(client, *(event->show_formspec.formname));
+
+               GUIFormSpecMenu::create(current_formspec, client, &input->joystick,
+                       fs_src, txt_dst, client->getFormspecPrepend());
+               cur_formname = *(event->show_formspec.formname);
+       }
+
+       delete event->show_formspec.formspec;
+       delete event->show_formspec.formname;
+}
+
+void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
+{
+       FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
+       LocalFormspecHandler *txt_dst =
+               new LocalFormspecHandler(*event->show_formspec.formname, client);
+       GUIFormSpecMenu::create(current_formspec, client, &input->joystick,
+                       fs_src, txt_dst, client->getFormspecPrepend());
+
+       delete event->show_formspec.formspec;
+       delete event->show_formspec.formname;
+}
+
+void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
+               CameraOrientation *cam)
+{
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
+       client->getParticleManager()->handleParticleEvent(event, client, player);
+}
+
+void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
+{
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
+       auto &hud_server_to_client = client->getHUDTranslationMap();
+
+       u32 server_id = event->hudadd.server_id;
+       // ignore if we already have a HUD with that ID
+       auto i = hud_server_to_client.find(server_id);
+       if (i != hud_server_to_client.end()) {
+               delete event->hudadd.pos;
+               delete event->hudadd.name;
+               delete event->hudadd.scale;
+               delete event->hudadd.text;
+               delete event->hudadd.align;
+               delete event->hudadd.offset;
+               delete event->hudadd.world_pos;
+               delete event->hudadd.size;
+               return;
+       }
+
+       HudElement *e = new HudElement;
+       e->type   = (HudElementType)event->hudadd.type;
+       e->pos    = *event->hudadd.pos;
+       e->name   = *event->hudadd.name;
+       e->scale  = *event->hudadd.scale;
+       e->text   = *event->hudadd.text;
+       e->number = event->hudadd.number;
+       e->item   = event->hudadd.item;
+       e->dir    = event->hudadd.dir;
+       e->align  = *event->hudadd.align;
+       e->offset = *event->hudadd.offset;
+       e->world_pos = *event->hudadd.world_pos;
+       e->size = *event->hudadd.size;
+       hud_server_to_client[server_id] = player->addHud(e);
+
+       delete event->hudadd.pos;
+       delete event->hudadd.name;
+       delete event->hudadd.scale;
+       delete event->hudadd.text;
+       delete event->hudadd.align;
+       delete event->hudadd.offset;
+       delete event->hudadd.world_pos;
+       delete event->hudadd.size;
+}
+
+void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
+{
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
+       HudElement *e = player->removeHud(event->hudrm.id);
+       delete e;
+}
+
+void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
+{
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
+
+       u32 id = event->hudchange.id;
+       HudElement *e = player->getHud(id);
+
+       if (e == NULL) {
+               delete event->hudchange.v3fdata;
+               delete event->hudchange.v2fdata;
+               delete event->hudchange.sdata;
+               delete event->hudchange.v2s32data;
+               return;
+       }
+
+       switch (event->hudchange.stat) {
+               case HUD_STAT_POS:
+                       e->pos = *event->hudchange.v2fdata;
+                       break;
+
+               case HUD_STAT_NAME:
+                       e->name = *event->hudchange.sdata;
+                       break;
+
+               case HUD_STAT_SCALE:
+                       e->scale = *event->hudchange.v2fdata;
+                       break;
+
+               case HUD_STAT_TEXT:
+                       e->text = *event->hudchange.sdata;
+                       break;
+
+               case HUD_STAT_NUMBER:
+                       e->number = event->hudchange.data;
+                       break;
+
+               case HUD_STAT_ITEM:
+                       e->item = event->hudchange.data;
+                       break;
+
+               case HUD_STAT_DIR:
+                       e->dir = event->hudchange.data;
+                       break;
+
+               case HUD_STAT_ALIGN:
+                       e->align = *event->hudchange.v2fdata;
+                       break;
+
+               case HUD_STAT_OFFSET:
+                       e->offset = *event->hudchange.v2fdata;
+                       break;
+
+               case HUD_STAT_WORLD_POS:
+                       e->world_pos = *event->hudchange.v3fdata;
+                       break;
+
+               case HUD_STAT_SIZE:
+                       e->size = *event->hudchange.v2s32data;
+                       break;
+       }
+
+       delete event->hudchange.v3fdata;
+       delete event->hudchange.v2fdata;
+       delete event->hudchange.sdata;
+       delete event->hudchange.v2s32data;
+}
+
+void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
+{
+       sky->setVisible(false);
+       // Whether clouds are visible in front of a custom skybox
+       sky->setCloudsEnabled(event->set_sky.clouds);
+
+       if (skybox) {
+               skybox->remove();
+               skybox = NULL;
+       }
+
+       // Handle according to type
+       if (*event->set_sky.type == "regular") {
+               sky->setVisible(true);
+               sky->setCloudsEnabled(true);
+       } else if (*event->set_sky.type == "skybox" &&
+               event->set_sky.params->size() == 6) {
+               sky->setFallbackBgColor(*event->set_sky.bgcolor);
+               skybox = RenderingEngine::get_scene_manager()->addSkyBoxSceneNode(
+                       texture_src->getTextureForMesh((*event->set_sky.params)[0]),
+                       texture_src->getTextureForMesh((*event->set_sky.params)[1]),
+                       texture_src->getTextureForMesh((*event->set_sky.params)[2]),
+                       texture_src->getTextureForMesh((*event->set_sky.params)[3]),
+                       texture_src->getTextureForMesh((*event->set_sky.params)[4]),
+                       texture_src->getTextureForMesh((*event->set_sky.params)[5]));
+       }
+               // Handle everything else as plain color
+       else {
+               if (*event->set_sky.type != "plain")
+                       infostream << "Unknown sky type: "
+                               << (*event->set_sky.type) << std::endl;
+
+               sky->setFallbackBgColor(*event->set_sky.bgcolor);
+       }
+
+       delete event->set_sky.bgcolor;
+       delete event->set_sky.type;
+       delete event->set_sky.params;
+}
+
+void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
+               CameraOrientation *cam)
+{
+       client->getEnv().setDayNightRatioOverride(
+               event->override_day_night_ratio.do_override,
+               event->override_day_night_ratio.ratio_f * 1000.0f);
+}
+
+void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
+{
+       if (!clouds)
+               return;
+
+       clouds->setDensity(event->cloud_params.density);
+       clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
+       clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
+       clouds->setHeight(event->cloud_params.height);
+       clouds->setThickness(event->cloud_params.thickness);
+       clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
+}
+
+void Game::processClientEvents(CameraOrientation *cam)
+{
+       while (client->hasClientEvents()) {
+               std::unique_ptr<ClientEvent> event(client->getClientEvent());
+               FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
+               const ClientEventHandler& evHandler = clientEventHandler[event->type];
+               (this->*evHandler.handler)(event.get(), cam);
+       }
+}
+
+void Game::updateChat(f32 dtime, const v2u32 &screensize)
+{
+       // Add chat log output for errors to be shown in chat
+       static LogOutputBuffer chat_log_error_buf(g_logger, LL_ERROR);
+
+       // Get new messages from error log buffer
+       while (!chat_log_error_buf.empty()) {
+               std::wstring error_message = utf8_to_wide(chat_log_error_buf.get());
+               if (!g_settings->getBool("disable_escape_sequences")) {
+                       error_message.insert(0, L"\x1b(c@red)");
+                       error_message.append(L"\x1b(c@white)");
+               }
+               chat_backend->addMessage(L"", error_message);
+       }
+
+       // Get new messages from client
+       std::wstring message;
+       while (client->getChatMessage(message)) {
+               chat_backend->addUnparsedMessage(message);
+       }
+
+       // Remove old messages
+       chat_backend->step(dtime);
+
+       // Display all messages in a static text element
+       m_game_ui->setChatText(chat_backend->getRecentChat(),
+               chat_backend->getRecentBuffer().getLineCount());
+}
+
+void Game::updateCamera(u32 busy_time, f32 dtime)
+{
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
+
+       /*
+               For interaction purposes, get info about the held item
+               - What item is it?
+               - Is it a usable item?
+               - Can it point to liquids?
+       */
+       ItemStack playeritem;
+       {
+               InventoryList *mlist = local_inventory->getList("main");
+
+               if (mlist && client->getPlayerItem() < mlist->getSize())
+                       playeritem = mlist->getItem(client->getPlayerItem());
+       }
+
+       if (playeritem.getDefinition(itemdef_manager).name.empty()) { // override the hand
+               InventoryList *hlist = local_inventory->getList("hand");
+               if (hlist)
+                       playeritem = hlist->getItem(0);
+       }
+
+
+       ToolCapabilities playeritem_toolcap =
+               playeritem.getToolCapabilities(itemdef_manager);
+
+       v3s16 old_camera_offset = camera->getOffset();
+
+       if (wasKeyDown(KeyType::CAMERA_MODE)) {
+               GenericCAO *playercao = player->getCAO();
+
+               // If playercao not loaded, don't change camera
+               if (!playercao)
+                       return;
+
+               camera->toggleCameraMode();
+
+               playercao->setVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
+               playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
+       }
+
+       float full_punch_interval = playeritem_toolcap.full_punch_interval;
+       float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
+
+       tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
+       camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
+       camera->step(dtime);
+
+       v3f camera_position = camera->getPosition();
+       v3f camera_direction = camera->getDirection();
+       f32 camera_fov = camera->getFovMax();
+       v3s16 camera_offset = camera->getOffset();
+
+       m_camera_offset_changed = (camera_offset != old_camera_offset);
+
+       if (!m_flags.disable_camera_update) {
+               client->getEnv().getClientMap().updateCamera(camera_position,
+                               camera_direction, camera_fov, camera_offset);
+
+               if (m_camera_offset_changed) {
+                       client->updateCameraOffset(camera_offset);
+                       client->getEnv().updateCameraOffset(camera_offset);
+
+                       if (clouds)
+                               clouds->updateCameraOffset(camera_offset);
+               }
+       }
+}
+
+
+void Game::updateSound(f32 dtime)
+{
+       // Update sound listener
+       v3s16 camera_offset = camera->getOffset();
+       sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
+                             v3f(0, 0, 0), // velocity
+                             camera->getDirection(),
+                             camera->getCameraNode()->getUpVector());
+
+       bool mute_sound = g_settings->getBool("mute_sound");
+       if (mute_sound) {
+               sound->setListenerGain(0.0f);
+       } else {
+               // Check if volume is in the proper range, else fix it.
+               float old_volume = g_settings->getFloat("sound_volume");
+               float new_volume = rangelim(old_volume, 0.0f, 1.0f);
+               sound->setListenerGain(new_volume);
+
+               if (old_volume != new_volume) {
+                       g_settings->setFloat("sound_volume", new_volume);
+               }
+       }
+
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
+
+       // Tell the sound maker whether to make footstep sounds
+       soundmaker->makes_footstep_sound = player->makes_footstep_sound;
+
+       //      Update sound maker
+       if (player->makes_footstep_sound)
+               soundmaker->step(dtime);
+
+       ClientMap &map = client->getEnv().getClientMap();
+       MapNode n = map.getNodeNoEx(player->getFootstepNodePos());
+       soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
+}
+
+
+void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
+{
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
+
+       ItemStack playeritem;
+       {
+               InventoryList *mlist = local_inventory->getList("main");
+
+               if (mlist && client->getPlayerItem() < mlist->getSize())
+                       playeritem = mlist->getItem(client->getPlayerItem());
+       }
+
+       const ItemDefinition &playeritem_def =
+                       playeritem.getDefinition(itemdef_manager);
+       InventoryList *hlist = local_inventory->getList("hand");
+       const ItemDefinition &hand_def =
+               hlist ? hlist->getItem(0).getDefinition(itemdef_manager) : itemdef_manager->get("");
+
+       v3f player_position  = player->getPosition();
+       v3f camera_position  = camera->getPosition();
+       v3f camera_direction = camera->getDirection();
+       v3s16 camera_offset  = camera->getOffset();
+
+
+       /*
+               Calculate what block is the crosshair pointing to
+       */
+
+       f32 d = playeritem_def.range; // max. distance
+       f32 d_hand = hand_def.range;
+
+       if (d < 0 && d_hand >= 0)
+               d = d_hand;
+       else if (d < 0)
+               d = 4.0;
+
+       core::line3d<f32> shootline;
+
+       if (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT) {
+               shootline = core::line3d<f32>(camera_position,
+                       camera_position + camera_direction * BS * d);
+       } else {
+           // prevent player pointing anything in front-view
+               shootline = core::line3d<f32>(camera_position,camera_position);
+       }
+
+#ifdef HAVE_TOUCHSCREENGUI
+
+       if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
+               shootline = g_touchscreengui->getShootline();
+               // Scale shootline to the acual distance the player can reach
+               shootline.end = shootline.start
+                       + shootline.getVector().normalize() * BS * d;
+               shootline.start += intToFloat(camera_offset, BS);
+               shootline.end += intToFloat(camera_offset, BS);
+       }
+
+#endif
+
+       PointedThing pointed = updatePointedThing(shootline,
+                       playeritem_def.liquids_pointable,
+                       !runData.ldown_for_dig,
+                       camera_offset);
+
+       if (pointed != runData.pointed_old) {
+               infostream << "Pointing at " << pointed.dump() << std::endl;
+               hud->updateSelectionMesh(camera_offset);
+       }
+
+       if (runData.digging_blocked && !input->getLeftState()) {
+               // allow digging again if button is not pressed
+               runData.digging_blocked = false;
+       }
+
+       /*
+               Stop digging when
+               - releasing left mouse button
+               - pointing away from node
+       */
+       if (runData.digging) {
+               if (input->getLeftReleased()) {
+                       infostream << "Left button released"
+                                  << " (stopped digging)" << std::endl;
+                       runData.digging = false;
+               } else if (pointed != runData.pointed_old) {
+                       if (pointed.type == POINTEDTHING_NODE
+                                       && runData.pointed_old.type == POINTEDTHING_NODE
+                                       && pointed.node_undersurface
+                                                       == runData.pointed_old.node_undersurface) {
+                               // Still pointing to the same node, but a different face.
+                               // Don't reset.
+                       } else {
+                               infostream << "Pointing away from node"
+                                          << " (stopped digging)" << std::endl;
+                               runData.digging = false;
+                               hud->updateSelectionMesh(camera_offset);
+                       }
+               }
+
+               if (!runData.digging) {
+                       client->interact(1, runData.pointed_old);
+                       client->setCrack(-1, v3s16(0, 0, 0));
+                       runData.dig_time = 0.0;
+               }
+       } else if (runData.dig_instantly && input->getLeftReleased()) {
+               // Remove e.g. torches faster when clicking instead of holding LMB
+               runData.nodig_delay_timer = 0;
+               runData.dig_instantly = false;
+       }
+
+       if (!runData.digging && runData.ldown_for_dig && !input->getLeftState()) {
+               runData.ldown_for_dig = false;
+       }
+
+       runData.left_punch = false;
+
+       soundmaker->m_player_leftpunch_sound.name = "";
+
+       // Prepare for repeating, unless we're not supposed to
+       if (input->getRightState() && !g_settings->getBool("safe_dig_and_place"))
+               runData.repeat_rightclick_timer += dtime;
+       else
+               runData.repeat_rightclick_timer = 0;
+
+       if (playeritem_def.usable && input->getLeftState()) {
+               if (input->getLeftClicked() && (!client->moddingEnabled()
+                               || !client->getScript()->on_item_use(playeritem, pointed)))
+                       client->interact(4, pointed);
+       } else if (pointed.type == POINTEDTHING_NODE) {
+               ToolCapabilities playeritem_toolcap =
+                               playeritem.getToolCapabilities(itemdef_manager);
+               if (playeritem.name.empty()) {
+                       const ToolCapabilities *handToolcap = hlist
+                               ? &hlist->getItem(0).getToolCapabilities(itemdef_manager)
+                               : itemdef_manager->get("").tool_capabilities;
+
+                       if (handToolcap != nullptr)
+                               playeritem_toolcap = *handToolcap;
+               }
+               handlePointingAtNode(pointed, playeritem_def, playeritem,
+                       playeritem_toolcap, dtime);
+       } else if (pointed.type == POINTEDTHING_OBJECT) {
+               handlePointingAtObject(pointed, playeritem, player_position, show_debug);
+       } else if (input->getLeftState()) {
+               // When button is held down in air, show continuous animation
+               runData.left_punch = true;
+       } else if (input->getRightClicked()) {
+               handlePointingAtNothing(playeritem);
+       }
+
+       runData.pointed_old = pointed;
+
+       if (runData.left_punch || input->getLeftClicked())
+               camera->setDigging(0); // left click animation
+
+       input->resetLeftClicked();
+       input->resetRightClicked();
+
+       input->resetLeftReleased();
+       input->resetRightReleased();
+}
+
+
+PointedThing Game::updatePointedThing(
+       const core::line3d<f32> &shootline,
+       bool liquids_pointable,
+       bool look_for_object,
+       const v3s16 &camera_offset)
+{
+       std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
+       selectionboxes->clear();
+       hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
+       static thread_local const bool show_entity_selectionbox = g_settings->getBool(
+               "show_entity_selectionbox");
+
+       ClientEnvironment &env = client->getEnv();
+       ClientMap &map = env.getClientMap();
+       const NodeDefManager *nodedef = map.getNodeDefManager();
+
+       runData.selected_object = NULL;
+
+       RaycastState s(shootline, look_for_object, liquids_pointable);
+       PointedThing result;
+       env.continueRaycast(&s, &result);
+       if (result.type == POINTEDTHING_OBJECT) {
+               runData.selected_object = client->getEnv().getActiveObject(result.object_id);
+               aabb3f selection_box;
+               if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
+                               runData.selected_object->getSelectionBox(&selection_box)) {
+                       v3f pos = runData.selected_object->getPosition();
+                       selectionboxes->push_back(aabb3f(selection_box));
+                       hud->setSelectionPos(pos, camera_offset);
+               }
+       } else if (result.type == POINTEDTHING_NODE) {
+               // Update selection boxes
+               MapNode n = map.getNodeNoEx(result.node_undersurface);
+               std::vector<aabb3f> boxes;
+               n.getSelectionBoxes(nodedef, &boxes,
+                       n.getNeighbors(result.node_undersurface, &map));
+
+               f32 d = 0.002 * BS;
+               for (std::vector<aabb3f>::const_iterator i = boxes.begin();
+                       i != boxes.end(); ++i) {
+                       aabb3f box = *i;
+                       box.MinEdge -= v3f(d, d, d);
+                       box.MaxEdge += v3f(d, d, d);
+                       selectionboxes->push_back(box);
+               }
+               hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
+                       camera_offset);
+               hud->setSelectedFaceNormal(v3f(
+                       result.intersection_normal.X,
+                       result.intersection_normal.Y,
+                       result.intersection_normal.Z));
+       }
+
+       // Update selection mesh light level and vertex colors
+       if (!selectionboxes->empty()) {
+               v3f pf = hud->getSelectionPos();
+               v3s16 p = floatToInt(pf, BS);
+
+               // Get selection mesh light level
+               MapNode n = map.getNodeNoEx(p);
+               u16 node_light = getInteriorLight(n, -1, nodedef);
+               u16 light_level = node_light;
+
+               for (const v3s16 &dir : g_6dirs) {
+                       n = map.getNodeNoEx(p + dir);
+                       node_light = getInteriorLight(n, -1, nodedef);
+                       if (node_light > light_level)
+                               light_level = node_light;
+               }
+
+               u32 daynight_ratio = client->getEnv().getDayNightRatio();
+               video::SColor c;
+               final_color_blend(&c, light_level, daynight_ratio);
+
+               // Modify final color a bit with time
+               u32 timer = porting::getTimeMs() % 5000;
+               float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
+               float sin_r = 0.08f * std::sin(timerf);
+               float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
+               float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
+               c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
+               c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
+               c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
+
+               // Set mesh final color
+               hud->setSelectionMeshColor(c);
+       }
+       return result;
+}
+
+
+void Game::handlePointingAtNothing(const ItemStack &playerItem)
+{
+       infostream << "Right Clicked in Air" << std::endl;
+       PointedThing fauxPointed;
+       fauxPointed.type = POINTEDTHING_NOTHING;
+       client->interact(5, fauxPointed);
+}
+
+
+void Game::handlePointingAtNode(const PointedThing &pointed,
+       const ItemDefinition &playeritem_def, const ItemStack &playeritem,
+       const ToolCapabilities &playeritem_toolcap, f32 dtime)
+{
+       v3s16 nodepos = pointed.node_undersurface;
+       v3s16 neighbourpos = pointed.node_abovesurface;
+
+       /*
+               Check information text of node
+       */
+
+       ClientMap &map = client->getEnv().getClientMap();
+
+       if (runData.nodig_delay_timer <= 0.0 && input->getLeftState()
+                       && !runData.digging_blocked
+                       && client->checkPrivilege("interact")) {
+               handleDigging(pointed, nodepos, playeritem_toolcap, dtime);
+       }
+
+       // This should be done after digging handling
+       NodeMetadata *meta = map.getNodeMetadata(nodepos);
+
+       if (meta) {
+               m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
+                       meta->getString("infotext"))));
+       } else {
+               MapNode n = map.getNodeNoEx(nodepos);
+
+               if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
+                       m_game_ui->setInfoText(L"Unknown node: " +
+                               utf8_to_wide(nodedef_manager->get(n).name));
+               }
+       }
+
+       if ((input->getRightClicked() ||
+                       runData.repeat_rightclick_timer >= m_repeat_right_click_time) &&
+                       client->checkPrivilege("interact")) {
+               runData.repeat_rightclick_timer = 0;
+               infostream << "Ground right-clicked" << std::endl;
+
+               if (meta && !meta->getString("formspec").empty() && !random_input
+                               && !isKeyDown(KeyType::SNEAK)) {
+                       // Report right click to server
+                       if (nodedef_manager->get(map.getNodeNoEx(nodepos)).rightclickable) {
+                               client->interact(3, pointed);
+                       }
+
+                       infostream << "Launching custom inventory view" << std::endl;
+
+                       InventoryLocation inventoryloc;
+                       inventoryloc.setNodeMeta(nodepos);
+
+                       NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
+                               &client->getEnv().getClientMap(), nodepos);
+                       TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
+
+                       GUIFormSpecMenu::create(current_formspec, client, &input->joystick, fs_src,
+                               txt_dst, client->getFormspecPrepend());
+                       cur_formname.clear();
+
+                       current_formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
+               } else {
+                       // Report right click to server
+
+                       camera->setDigging(1);  // right click animation (always shown for feedback)
+
+                       // If the wielded item has node placement prediction,
+                       // make that happen
+                       bool placed = nodePlacementPrediction(playeritem_def, playeritem, nodepos,
+                               neighbourpos);
+
+                       if (placed) {
+                               // Report to server
+                               client->interact(3, pointed);
+                               // Read the sound
+                               soundmaker->m_player_rightpunch_sound =
+                                               playeritem_def.sound_place;
+
+                               if (client->moddingEnabled())
+                                       client->getScript()->on_placenode(pointed, playeritem_def);
+                       } else {
+                               soundmaker->m_player_rightpunch_sound =
+                                               SimpleSoundSpec();
+
+                               if (playeritem_def.node_placement_prediction.empty() ||
+                                               nodedef_manager->get(map.getNodeNoEx(nodepos)).rightclickable) {
+                                       client->interact(3, pointed); // Report to server
+                               } else {
+                                       soundmaker->m_player_rightpunch_sound =
+                                               playeritem_def.sound_place_failed;
+                               }
+                       }
+               }
+       }
+}
+
+bool Game::nodePlacementPrediction(const ItemDefinition &playeritem_def,
+       const ItemStack &playeritem, const v3s16 &nodepos, const v3s16 &neighbourpos)
+{
+       std::string prediction = playeritem_def.node_placement_prediction;
+       const NodeDefManager *nodedef = client->ndef();
+       ClientMap &map = client->getEnv().getClientMap();
+       MapNode node;
+       bool is_valid_position;
+
+       node = map.getNodeNoEx(nodepos, &is_valid_position);
+       if (!is_valid_position)
+               return false;
+
+       if (!prediction.empty() && !nodedef->get(node).rightclickable) {
+               verbosestream << "Node placement prediction for "
+                       << playeritem_def.name << " is "
+                       << prediction << std::endl;
+               v3s16 p = neighbourpos;
+
+               // Place inside node itself if buildable_to
+               MapNode n_under = map.getNodeNoEx(nodepos, &is_valid_position);
+               if (is_valid_position)
+               {
+                       if (nodedef->get(n_under).buildable_to)
+                               p = nodepos;
+                       else {
+                               node = map.getNodeNoEx(p, &is_valid_position);
+                               if (is_valid_position &&!nodedef->get(node).buildable_to)
+                                       return false;
+                       }
+               }
+
+               // Find id of predicted node
+               content_t id;
+               bool found = nodedef->getId(prediction, id);
+
+               if (!found) {
+                       errorstream << "Node placement prediction failed for "
+                               << playeritem_def.name << " (places "
+                               << prediction
+                               << ") - Name not known" << std::endl;
+                       return false;
+               }
+
+               const ContentFeatures &predicted_f = nodedef->get(id);
+
+               // Predict param2 for facedir and wallmounted nodes
+               u8 param2 = 0;
+
+               if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
+                       predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
+                       v3s16 dir = nodepos - neighbourpos;
+
+                       if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
+                               param2 = dir.Y < 0 ? 1 : 0;
+                       } else if (abs(dir.X) > abs(dir.Z)) {
+                               param2 = dir.X < 0 ? 3 : 2;
+                       } else {
+                               param2 = dir.Z < 0 ? 5 : 4;
+                       }
+               }
+
+               if (predicted_f.param_type_2 == CPT2_FACEDIR ||
+                       predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
+                       v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
+
+                       if (abs(dir.X) > abs(dir.Z)) {
+                               param2 = dir.X < 0 ? 3 : 1;
+                       } else {
+                               param2 = dir.Z < 0 ? 2 : 0;
+                       }
+               }
+
+               assert(param2 <= 5);
+
+               //Check attachment if node is in group attached_node
+               if (((ItemGroupList) predicted_f.groups)["attached_node"] != 0) {
+                       static v3s16 wallmounted_dirs[8] = {
+                               v3s16(0, 1, 0),
+                               v3s16(0, -1, 0),
+                               v3s16(1, 0, 0),
+                               v3s16(-1, 0, 0),
+                               v3s16(0, 0, 1),
+                               v3s16(0, 0, -1),
+                       };
+                       v3s16 pp;
+
+                       if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
+                               predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
+                               pp = p + wallmounted_dirs[param2];
+                       else
+                               pp = p + v3s16(0, -1, 0);
+
+                       if (!nodedef->get(map.getNodeNoEx(pp)).walkable)
+                               return false;
+               }
+
+               // Apply color
+               if ((predicted_f.param_type_2 == CPT2_COLOR
+                       || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
+                       || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
+                       const std::string &indexstr = playeritem.metadata.getString(
+                               "palette_index", 0);
+                       if (!indexstr.empty()) {
+                               s32 index = mystoi(indexstr);
+                               if (predicted_f.param_type_2 == CPT2_COLOR) {
+                                       param2 = index;
+                               } else if (predicted_f.param_type_2
+                                       == CPT2_COLORED_WALLMOUNTED) {
+                                       // param2 = pure palette index + other
+                                       param2 = (index & 0xf8) | (param2 & 0x07);
+                               } else if (predicted_f.param_type_2
+                                       == CPT2_COLORED_FACEDIR) {
+                                       // param2 = pure palette index + other
+                                       param2 = (index & 0xe0) | (param2 & 0x1f);
+                               }
+                       }
+               }
+
+               // Add node to client map
+               MapNode n(id, 0, param2);
+
+               try {
+                       LocalPlayer *player = client->getEnv().getLocalPlayer();
+
+                       // Dont place node when player would be inside new node
+                       // NOTE: This is to be eventually implemented by a mod as client-side Lua
+                       if (!nodedef->get(n).walkable ||
+                               g_settings->getBool("enable_build_where_you_stand") ||
+                               (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
+                               (nodedef->get(n).walkable &&
+                                       neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
+                                       neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
+
+                               // This triggers the required mesh update too
+                               client->addNode(p, n);
+                               return true;
+                       }
+               } catch (InvalidPositionException &e) {
+                       errorstream << "Node placement prediction failed for "
+                               << playeritem_def.name << " (places "
+                               << prediction
+                               << ") - Position not loaded" << std::endl;
+               }
+       }
+
+       return false;
+}
+
+void Game::handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
+               const v3f &player_position, bool show_debug)
+{
+       std::wstring infotext = unescape_translate(
+               utf8_to_wide(runData.selected_object->infoText()));
+
+       if (show_debug) {
+               if (!infotext.empty()) {
+                       infotext += L"\n";
+               }
+               infotext += utf8_to_wide(runData.selected_object->debugInfoText());
+       }
+
+       m_game_ui->setInfoText(infotext);
+
+       if (input->getLeftState()) {
+               bool do_punch = false;
+               bool do_punch_damage = false;
+
+               if (runData.object_hit_delay_timer <= 0.0) {
+                       do_punch = true;
+                       do_punch_damage = true;
+                       runData.object_hit_delay_timer = object_hit_delay;
+               }
+
+               if (input->getLeftClicked())
+                       do_punch = true;
+
+               if (do_punch) {
+                       infostream << "Left-clicked object" << std::endl;
+                       runData.left_punch = true;
+               }
+
+               if (do_punch_damage) {
+                       // Report direct punch
+                       v3f objpos = runData.selected_object->getPosition();
+                       v3f dir = (objpos - player_position).normalize();
+                       ItemStack item = playeritem;
+                       if (playeritem.name.empty()) {
+                               InventoryList *hlist = local_inventory->getList("hand");
+                               if (hlist) {
+                                       item = hlist->getItem(0);
+                               }
+                       }
+
+                       bool disable_send = runData.selected_object->directReportPunch(
+                                       dir, &item, runData.time_from_last_punch);
+                       runData.time_from_last_punch = 0;
+
+                       if (!disable_send)
+                               client->interact(0, pointed);
+               }
+       } else if (input->getRightClicked()) {
+               infostream << "Right-clicked object" << std::endl;
+               client->interact(3, pointed);  // place
+       }
+}
+
+
+void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
+               const ToolCapabilities &playeritem_toolcap, f32 dtime)
+{
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
+       ClientMap &map = client->getEnv().getClientMap();
+       MapNode n = client->getEnv().getClientMap().getNodeNoEx(nodepos);
+
+       // NOTE: Similar piece of code exists on the server side for
+       // cheat detection.
+       // Get digging parameters
+       DigParams params = getDigParams(nodedef_manager->get(n).groups,
+                       &playeritem_toolcap);
+
+       // If can't dig, try hand
+       if (!params.diggable) {
+               InventoryList *hlist = local_inventory->getList("hand");
+               const ToolCapabilities *tp = hlist
+                       ? &hlist->getItem(0).getToolCapabilities(itemdef_manager)
+                       : itemdef_manager->get("").tool_capabilities;
+
+               if (tp)
+                       params = getDigParams(nodedef_manager->get(n).groups, tp);
+       }
+
+       if (!params.diggable) {
+               // I guess nobody will wait for this long
+               runData.dig_time_complete = 10000000.0;
+       } else {
+               runData.dig_time_complete = params.time;
+
+               if (m_cache_enable_particles) {
+                       const ContentFeatures &features = client->getNodeDefManager()->get(n);
+                       client->getParticleManager()->addNodeParticle(client,
+                                       player, nodepos, n, features);
+               }
+       }
+
+       if (!runData.digging) {
+               infostream << "Started digging" << std::endl;
+               runData.dig_instantly = runData.dig_time_complete == 0;
+               if (client->moddingEnabled() && client->getScript()->on_punchnode(nodepos, n))
+                       return;
+               client->interact(0, pointed);
+               runData.digging = true;
+               runData.ldown_for_dig = true;
+       }
+
+       if (!runData.dig_instantly) {
+               runData.dig_index = (float)crack_animation_length
+                               * runData.dig_time
+                               / runData.dig_time_complete;
+       } else {
+               // This is for e.g. torches
+               runData.dig_index = crack_animation_length;
+       }
+
+       SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
+
+       if (sound_dig.exists() && params.diggable) {
+               if (sound_dig.name == "__group") {
+                       if (!params.main_group.empty()) {
+                               soundmaker->m_player_leftpunch_sound.gain = 0.5;
+                               soundmaker->m_player_leftpunch_sound.name =
+                                               std::string("default_dig_") +
+                                               params.main_group;
+                       }
+               } else {
+                       soundmaker->m_player_leftpunch_sound = sound_dig;
+               }
+       }
+
+       // Don't show cracks if not diggable
+       if (runData.dig_time_complete >= 100000.0) {
+       } else if (runData.dig_index < crack_animation_length) {
+               //TimeTaker timer("client.setTempMod");
+               //infostream<<"dig_index="<<dig_index<<std::endl;
+               client->setCrack(runData.dig_index, nodepos);
+       } else {
+               infostream << "Digging completed" << std::endl;
+               client->setCrack(-1, v3s16(0, 0, 0));
+
+               runData.dig_time = 0;
+               runData.digging = false;
+               // we successfully dug, now block it from repeating if we want to be safe
+               if (g_settings->getBool("safe_dig_and_place"))
+                       runData.digging_blocked = true;
+
+               runData.nodig_delay_timer =
+                               runData.dig_time_complete / (float)crack_animation_length;
+
+               // We don't want a corresponding delay to very time consuming nodes
+               // and nodes without digging time (e.g. torches) get a fixed delay.
+               if (runData.nodig_delay_timer > 0.3)
+                       runData.nodig_delay_timer = 0.3;
+               else if (runData.dig_instantly)
+                       runData.nodig_delay_timer = 0.15;
+
+               bool is_valid_position;
+               MapNode wasnode = map.getNodeNoEx(nodepos, &is_valid_position);
+               if (is_valid_position) {
+                       if (client->moddingEnabled() &&
+                                       client->getScript()->on_dignode(nodepos, wasnode)) {
+                               return;
+                       }
+
+                       const ContentFeatures &f = client->ndef()->get(wasnode);
+                       if (f.node_dig_prediction == "air") {
+                               client->removeNode(nodepos);
+                       } else if (!f.node_dig_prediction.empty()) {
+                               content_t id;
+                               bool found = client->ndef()->getId(f.node_dig_prediction, id);
+                               if (found)
+                                       client->addNode(nodepos, id, true);
+                       }
+                       // implicit else: no prediction
+               }
+
+               client->interact(2, pointed);
+
+               if (m_cache_enable_particles) {
+                       const ContentFeatures &features =
+                               client->getNodeDefManager()->get(wasnode);
+                       client->getParticleManager()->addDiggingParticles(client,
+                               player, nodepos, wasnode, features);
+               }
+
+
+               // Send event to trigger sound
+               client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
+       }
+
+       if (runData.dig_time_complete < 100000.0) {
+               runData.dig_time += dtime;
+       } else {
+               runData.dig_time = 0;
+               client->setCrack(-1, nodepos);
+       }
+
+       camera->setDigging(0);  // left click animation
+}
+
+
+void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
+               const CameraOrientation &cam)
+{
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
+
+       /*
+               Fog range
+       */
+
+       if (draw_control->range_all) {
+               runData.fog_range = 100000 * BS;
+       } else {
+               runData.fog_range = draw_control->wanted_range * BS;
+       }
+
+       /*
+               Calculate general brightness
+       */
+       u32 daynight_ratio = client->getEnv().getDayNightRatio();
+       float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
+       float direct_brightness;
+       bool sunlight_seen;
+
+       if (m_cache_enable_noclip && m_cache_enable_free_move) {
+               direct_brightness = time_brightness;
+               sunlight_seen = true;
+       } else {
+               ScopeProfiler sp(g_profiler, "Detecting background light", SPT_AVG);
+               float old_brightness = sky->getBrightness();
+               direct_brightness = client->getEnv().getClientMap()
+                               .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
+                                       daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
+                                   / 255.0;
+       }
+
+       float time_of_day_smooth = runData.time_of_day_smooth;
+       float time_of_day = client->getEnv().getTimeOfDayF();
+
+       static const float maxsm = 0.05f;
+       static const float todsm = 0.05f;
+
+       if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
+                       std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
+                       std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
+               time_of_day_smooth = time_of_day;
+
+       if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
+               time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
+                               + (time_of_day + 1.0) * todsm;
+       else
+               time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
+                               + time_of_day * todsm;
+
+       runData.time_of_day_smooth = time_of_day_smooth;
+
+       sky->update(time_of_day_smooth, time_brightness, direct_brightness,
+                       sunlight_seen, camera->getCameraMode(), player->getYaw(),
+                       player->getPitch());
+
+       /*
+               Update clouds
+       */
+       if (clouds) {
+               if (sky->getCloudsVisible()) {
+                       clouds->setVisible(true);
+                       clouds->step(dtime);
+                       // camera->getPosition is not enough for 3rd person views
+                       v3f camera_node_position = camera->getCameraNode()->getPosition();
+                       v3s16 camera_offset      = camera->getOffset();
+                       camera_node_position.X   = camera_node_position.X + camera_offset.X * BS;
+                       camera_node_position.Y   = camera_node_position.Y + camera_offset.Y * BS;
+                       camera_node_position.Z   = camera_node_position.Z + camera_offset.Z * BS;
+                       clouds->update(camera_node_position,
+                                       sky->getCloudColor());
+                       if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
+                               // if inside clouds, and fog enabled, use that as sky
+                               // color(s)
+                               video::SColor clouds_dark = clouds->getColor()
+                                               .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
+                               sky->overrideColors(clouds_dark, clouds->getColor());
+                               sky->setBodiesVisible(false);
+                               runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
+                               // do not draw clouds after all
+                               clouds->setVisible(false);
+                       }
+               } else {
+                       clouds->setVisible(false);
+               }
+       }
+
+       /*
+               Update particles
+       */
+       client->getParticleManager()->step(dtime);
+
+       /*
+               Fog
+       */
+
+       if (m_cache_enable_fog) {
+               driver->setFog(
+                               sky->getBgColor(),
+                               video::EFT_FOG_LINEAR,
+                               runData.fog_range * m_cache_fog_start,
+                               runData.fog_range * 1.0,
+                               0.01,
+                               false, // pixel fog
+                               true // range fog
+               );
+       } else {
+               driver->setFog(
+                               sky->getBgColor(),
+                               video::EFT_FOG_LINEAR,
+                               100000 * BS,
+                               110000 * BS,
+                               0.01f,
+                               false, // pixel fog
+                               false // range fog
+               );
+       }
+
+       /*
+               Get chat messages from client
+       */
+
+       v2u32 screensize = driver->getScreenSize();
+
+       updateChat(dtime, screensize);
+
+       /*
+               Inventory
+       */
+
+       if (client->getPlayerItem() != runData.new_playeritem)
+               client->selectPlayerItem(runData.new_playeritem);
+
+       // Update local inventory if it has changed
+       if (client->getLocalInventoryUpdated()) {
+               //infostream<<"Updating local inventory"<<std::endl;
+               client->getLocalInventory(*local_inventory);
+               runData.update_wielded_item_trigger = true;
+       }
+
+       if (runData.update_wielded_item_trigger) {
+               // Update wielded tool
+               InventoryList *mlist = local_inventory->getList("main");
+
+               if (mlist && (client->getPlayerItem() < mlist->getSize())) {
+                       ItemStack item = mlist->getItem(client->getPlayerItem());
+                       if (item.getDefinition(itemdef_manager).name.empty()) { // override the hand
+                               InventoryList *hlist = local_inventory->getList("hand");
+                               if (hlist)
+                                       item = hlist->getItem(0);
+                       }
+                       camera->wield(item);
+               }
+
+               runData.update_wielded_item_trigger = false;
+       }
+
+       /*
+               Update block draw list every 200ms or when camera direction has
+               changed much
+       */
+       runData.update_draw_list_timer += dtime;
+
+       v3f camera_direction = camera->getDirection();
+       if (runData.update_draw_list_timer >= 0.2
+                       || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
+                       || m_camera_offset_changed) {
+               runData.update_draw_list_timer = 0;
+               client->getEnv().getClientMap().updateDrawList();
+               runData.update_draw_list_last_cam_dir = camera_direction;
+       }
+
+       m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, dtime);
+
+       /*
+          make sure menu is on top
+          1. Delete formspec menu reference if menu was removed
+          2. Else, make sure formspec menu is on top
+       */
+       if (current_formspec) {
+               if (current_formspec->getReferenceCount() == 1) {
+                       current_formspec->drop();
+                       current_formspec = NULL;
+               } else if (isMenuActive()) {
+                       guiroot->bringToFront(current_formspec);
+               }
+       }
+
+       /*
+               Drawing begins
+       */
+       const video::SColor &skycolor = sky->getSkyColor();
+
+       TimeTaker tt_draw("mainloop: draw");
+       driver->beginScene(true, true, skycolor);
+
+       bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
+                       (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
+                       (camera->getCameraMode() == CAMERA_MODE_FIRST));
+       bool draw_crosshair = (
+                       (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
+                       (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
+#ifdef HAVE_TOUCHSCREENGUI
+       try {
+               draw_crosshair = !g_settings->getBool("touchtarget");
+       } catch (SettingNotFoundException) {
+       }
+#endif
+       RenderingEngine::draw_scene(skycolor, m_game_ui->m_flags.show_hud,
+                       m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
+
+       /*
+               Profiler graph
+       */
+       if (m_game_ui->m_flags.show_profiler_graph)
+               graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
+
+       /*
+               Damage flash
+       */
+       if (runData.damage_flash > 0.0f) {
+               video::SColor color(runData.damage_flash, 180, 0, 0);
+               driver->draw2DRectangle(color,
+                                       core::rect<s32>(0, 0, screensize.X, screensize.Y),
+                                       NULL);
+
+               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)
+                       player->hurt_tilt_strength = 0.0f;
+       }
+
+       /*
+               Update minimap pos and rotation
+       */
+       if (mapper && m_game_ui->m_flags.show_minimap && m_game_ui->m_flags.show_hud) {
+               mapper->setPos(floatToInt(player->getPosition(), BS));
+               mapper->setAngle(player->getYaw());
+       }
+
+       /*
+               End scene
+       */
+       driver->endScene();
+
+       stats->drawtime = tt_draw.stop(true);
+       g_profiler->graphAdd("mainloop_draw", stats->drawtime / 1000.0f);
+}
+
+/* Log times and stuff for visualization */
+inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
+{
+       Profiler::GraphValues values;
+       g_profiler->graphGet(values);
+       graph->put(values);
+}
+
+
+
+/****************************************************************************
+ Misc
+ ****************************************************************************/
+
+/* On some computers framerate doesn't seem to be automatically limited
+ */
+inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
+{
+       // not using getRealTime is necessary for wine
+       device->getTimer()->tick(); // Maker sure device time is up-to-date
+       u32 time = device->getTimer()->getTime();
+       u32 last_time = fps_timings->last_time;
+
+       if (time > last_time)  // Make sure time hasn't overflowed
+               fps_timings->busy_time = time - last_time;
+       else
+               fps_timings->busy_time = 0;
+
+       u32 frametime_min = 1000 / (g_menumgr.pausesGame()
+                       ? g_settings->getFloat("pause_fps_max")
+                       : g_settings->getFloat("fps_max"));
+
+       if (fps_timings->busy_time < frametime_min) {
+               fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
+               device->sleep(fps_timings->sleep_time);
+       } else {
+               fps_timings->sleep_time = 0;
+       }
+
+       /* Get the new value of the device timer. Note that device->sleep() may
+        * not sleep for the entire requested time as sleep may be interrupted and
+        * therefore it is arguably more accurate to get the new time from the
+        * device rather than calculating it by adding sleep_time to time.
+        */
+
+       device->getTimer()->tick(); // Update device timer
+       time = device->getTimer()->getTime();
+
+       if (time > last_time)  // Make sure last_time hasn't overflowed
+               *dtime = (time - last_time) / 1000.0;
+       else
+               *dtime = 0;
+
+       fps_timings->last_time = time;
+}
+
+void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
+{
+       const wchar_t *wmsg = wgettext(msg);
+       RenderingEngine::draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
+               draw_clouds);
+       delete[] wmsg;
+}
+
+void Game::settingChangedCallback(const std::string &setting_name, void *data)
+{
+       ((Game *)data)->readSettings();
+}
+
+void Game::readSettings()
+{
+       m_cache_doubletap_jump               = g_settings->getBool("doubletap_jump");
+       m_cache_enable_clouds                = g_settings->getBool("enable_clouds");
+       m_cache_enable_joysticks             = g_settings->getBool("enable_joysticks");
+       m_cache_enable_particles             = g_settings->getBool("enable_particles");
+       m_cache_enable_fog                   = g_settings->getBool("enable_fog");
+       m_cache_mouse_sensitivity            = g_settings->getFloat("mouse_sensitivity");
+       m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
+       m_repeat_right_click_time            = g_settings->getFloat("repeat_rightclick_time");
+
+       m_cache_enable_noclip                = g_settings->getBool("noclip");
+       m_cache_enable_free_move             = g_settings->getBool("free_move");
+
+       m_cache_fog_start                    = g_settings->getFloat("fog_start");
+
+       m_cache_cam_smoothing = 0;
+       if (g_settings->getBool("cinematic"))
+               m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
+       else
+               m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
+
+       m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
+       m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
+       m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
+
+       m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
+}
+
+/****************************************************************************/
+/****************************************************************************
+ Shutdown / cleanup
+ ****************************************************************************/
+/****************************************************************************/
+
+void Game::extendedResourceCleanup()
+{
+       // Extended resource accounting
+       infostream << "Irrlicht resources after cleanup:" << std::endl;
+       infostream << "\tRemaining meshes   : "
+                  << RenderingEngine::get_mesh_cache()->getMeshCount() << std::endl;
+       infostream << "\tRemaining textures : "
+                  << driver->getTextureCount() << std::endl;
+
+       for (unsigned int i = 0; i < driver->getTextureCount(); i++) {
+               irr::video::ITexture *texture = driver->getTextureByIndex(i);
+               infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str()
+                          << std::endl;
+       }
+
+       clearTextureNameCache();
+       infostream << "\tRemaining materials: "
+               << driver-> getMaterialRendererCount()
+                      << " (note: irrlicht doesn't support removing renderers)" << std::endl;
+}
+
+void Game::showDeathFormspec()
+{
+       static std::string formspec =
+               std::string(FORMSPEC_VERSION_STRING) +
+               SIZE_TAG
+               "bgcolor[#320000b4;true]"
+               "label[4.85,1.35;" + gettext("You died") + "]"
+               "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
+               ;
+
+       /* Create menu */
+       /* Note: FormspecFormSource and LocalFormspecHandler  *
+        * are deleted by guiFormSpecMenu                     */
+       FormspecFormSource *fs_src = new FormspecFormSource(formspec);
+       LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
+
+       GUIFormSpecMenu::create(current_formspec, client, &input->joystick, fs_src,
+               txt_dst, client->getFormspecPrepend());
+       current_formspec->setFocus("btn_respawn");
+}
+
+#define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
+void Game::showPauseMenu()
+{
+#ifdef __ANDROID__
+       static const std::string control_text = strgettext("Default Controls:\n"
+               "No menu visible:\n"
+               "- single tap: button activate\n"
+               "- double tap: place/use\n"
+               "- slide finger: look around\n"
+               "Menu/Inventory visible:\n"
+               "- double tap (outside):\n"
+               " -->close\n"
+               "- touch stack, touch slot:\n"
+               " --> move stack\n"
+               "- touch&drag, tap 2nd finger\n"
+               " --> place single item to slot\n"
+               );
+#else
+       static const std::string control_text_template = strgettext("Controls:\n"
+               "- %s: move forwards\n"
+               "- %s: move backwards\n"
+               "- %s: move left\n"
+               "- %s: move right\n"
+               "- %s: jump/climb\n"
+               "- %s: sneak/go down\n"
+               "- %s: drop item\n"
+               "- %s: inventory\n"
+               "- Mouse: turn/look\n"
+               "- Mouse left: dig/punch\n"
+               "- Mouse right: place/use\n"
+               "- Mouse wheel: select item\n"
+               "- %s: chat\n"
+       );
+
+        char control_text_buf[600];
+
+        porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
+                       GET_KEY_NAME(keymap_forward),
+                       GET_KEY_NAME(keymap_backward),
+                       GET_KEY_NAME(keymap_left),
+                       GET_KEY_NAME(keymap_right),
+                       GET_KEY_NAME(keymap_jump),
+                       GET_KEY_NAME(keymap_sneak),
+                       GET_KEY_NAME(keymap_drop),
+                       GET_KEY_NAME(keymap_inventory),
+                       GET_KEY_NAME(keymap_chat)
+                       );
+
+       std::string control_text = std::string(control_text_buf);
+       str_formspec_escape(control_text);
+#endif
+
+       float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
+       std::ostringstream os;
+
+       os << FORMSPEC_VERSION_STRING  << SIZE_TAG
+               << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
+               << strgettext("Continue") << "]";
+
+       if (!simple_singleplayer_mode) {
+               os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
+                       << strgettext("Change Password") << "]";
+       } else {
+               os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
+       }
+
+#ifndef __ANDROID__
+       os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
+               << strgettext("Sound Volume") << "]";
+       os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
+               << strgettext("Change Keys")  << "]";
+#endif
+       os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
+               << strgettext("Exit to Menu") << "]";
+       os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
+               << strgettext("Exit to OS")   << "]"
+               << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
+               << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
+               << "\n"
+               <<  strgettext("Game info:") << "\n";
+       const std::string &address = client->getAddressName();
+       static const std::string mode = strgettext("- Mode: ");
+       if (!simple_singleplayer_mode) {
+               Address serverAddress = client->getServerAddress();
+               if (!address.empty()) {
+                       os << mode << strgettext("Remote server") << "\n"
+                                       << strgettext("- Address: ") << address;
+               } else {
+                       os << mode << strgettext("Hosting server");
+               }
+               os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
+       } else {
+               os << mode << strgettext("Singleplayer") << "\n";
+       }
+       if (simple_singleplayer_mode || address.empty()) {
+               static const std::string on = strgettext("On");
+               static const std::string off = strgettext("Off");
+               const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
+               const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
+               const std::string &announced = g_settings->getBool("server_announce") ? on : off;
+               os << strgettext("- Damage: ") << damage << "\n"
+                               << strgettext("- Creative Mode: ") << creative << "\n";
+               if (!simple_singleplayer_mode) {
+                       const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
+                       os << strgettext("- PvP: ") << pvp << "\n"
+                                       << strgettext("- Public: ") << announced << "\n";
+                       std::string server_name = g_settings->get("server_name");
+                       str_formspec_escape(server_name);
+                       if (announced == on && !server_name.empty())
+                               os << strgettext("- Server Name: ") << server_name;
+
+               }
+       }
+       os << ";]";
+
+       /* Create menu */
+       /* Note: FormspecFormSource and LocalFormspecHandler  *
+        * are deleted by guiFormSpecMenu                     */
+       FormspecFormSource *fs_src = new FormspecFormSource(os.str());
+       LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
+
+       GUIFormSpecMenu::create(current_formspec, client, &input->joystick,
+                       fs_src, txt_dst, client->getFormspecPrepend());
+       current_formspec->setFocus("btn_continue");
+       current_formspec->doPause = true;
+}
+
+/****************************************************************************/
+/****************************************************************************
+ extern function for launching the game
+ ****************************************************************************/
+/****************************************************************************/
+
+void the_game(bool *kill,
+               bool random_input,
+               InputHandler *input,
+               const std::string &map_dir,
+               const std::string &playername,
+               const std::string &password,
+               const std::string &address,         // If empty local server is created
+               u16 port,
+
+               std::string &error_message,
+               ChatBackend &chat_backend,
+               bool *reconnect_requested,
+               const SubgameSpec &gamespec,        // Used for local game
+               bool simple_singleplayer_mode)
+{
+       Game game;
+
+       /* Make a copy of the server address because if a local singleplayer server
+        * is created then this is updated and we don't want to change the value
+        * passed to us by the calling function
+        */
+       std::string server_address = address;
+
+       try {
+
+               if (game.startup(kill, random_input, input, map_dir,
+                               playername, password, &server_address, port, error_message,
+                               reconnect_requested, &chat_backend, gamespec,
+                               simple_singleplayer_mode)) {
+                       game.run();
+                       game.shutdown();
+               }
+
+       } catch (SerializationError &e) {
+               error_message = std::string("A serialization error occurred:\n")
+                               + e.what() + "\n\nThe server is probably "
+                               " running a different version of " PROJECT_NAME_C ".";
+               errorstream << error_message << std::endl;
+       } catch (ServerError &e) {
+               error_message = e.what();
+               errorstream << "ServerError: " << error_message << std::endl;
+       } catch (ModError &e) {
+               error_message = e.what() + strgettext("\nCheck debug.txt for details.");
+               errorstream << "ModError: " << error_message << std::endl;
+       }
+}
diff --git a/src/client/game.h b/src/client/game.h
new file mode 100644 (file)
index 0000000..69e6eed
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "irrlichttypes.h"
+#include <string>
+
+class InputHandler;
+class ChatBackend;  /* to avoid having to include chat.h */
+struct SubgameSpec;
+
+struct Jitter {
+       f32 max, min, avg, counter, max_sample, min_sample, max_fraction;
+};
+
+struct RunStats {
+       u32 drawtime;
+
+       Jitter dtime_jitter, busy_time_jitter;
+};
+
+struct CameraOrientation {
+       f32 camera_yaw;    // "right/left"
+       f32 camera_pitch;  // "up/down"
+};
+
+void the_game(bool *kill,
+               bool random_input,
+               InputHandler *input,
+               const std::string &map_dir,
+               const std::string &playername,
+               const std::string &password,
+               const std::string &address, // If "", local server is used
+               u16 port,
+               std::string &error_message,
+               ChatBackend &chat_backend,
+               bool *reconnect_requested,
+               const SubgameSpec &gamespec, // Used for local game
+               bool simple_singleplayer_mode);
diff --git a/src/client/guiscalingfilter.cpp b/src/client/guiscalingfilter.cpp
new file mode 100644 (file)
index 0000000..3b4377d
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
+
+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 "guiscalingfilter.h"
+#include "imagefilters.h"
+#include "porting.h"
+#include "settings.h"
+#include "util/numeric.h"
+#include <cstdio>
+#include "client/renderingengine.h"
+
+/* Maintain a static cache to store the images that correspond to textures
+ * in a format that's manipulable by code.  Some platforms exhibit issues
+ * converting textures back into images repeatedly, and some don't even
+ * allow it at all.
+ */
+std::map<io::path, video::IImage *> g_imgCache;
+
+/* Maintain a static cache of all pre-scaled textures.  These need to be
+ * cleared as well when the cached images.
+ */
+std::map<io::path, video::ITexture *> g_txrCache;
+
+/* Manually insert an image into the cache, useful to avoid texture-to-image
+ * conversion whenever we can intercept it.
+ */
+void guiScalingCache(io::path key, video::IVideoDriver *driver, video::IImage *value)
+{
+       if (!g_settings->getBool("gui_scaling_filter"))
+               return;
+       video::IImage *copied = driver->createImage(value->getColorFormat(),
+                       value->getDimension());
+       value->copyTo(copied);
+       g_imgCache[key] = copied;
+}
+
+// Manually clear the cache, e.g. when switching to different worlds.
+void guiScalingCacheClear()
+{
+       for (auto &it : g_imgCache) {
+               if (it.second)
+                       it.second->drop();
+       }
+       g_imgCache.clear();
+       for (auto &it : g_txrCache) {
+               if (it.second)
+                       RenderingEngine::get_video_driver()->removeTexture(it.second);
+       }
+       g_txrCache.clear();
+}
+
+/* Get a cached, high-quality pre-scaled texture for display purposes.  If the
+ * texture is not already cached, attempt to create it.  Returns a pre-scaled texture,
+ * or the original texture if unable to pre-scale it.
+ */
+video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver,
+               video::ITexture *src, const core::rect<s32> &srcrect,
+               const core::rect<s32> &destrect)
+{
+       if (src == NULL)
+               return src;
+       if (!g_settings->getBool("gui_scaling_filter"))
+               return src;
+
+       // Calculate scaled texture name.
+       char rectstr[200];
+       porting::mt_snprintf(rectstr, sizeof(rectstr), "%d:%d:%d:%d:%d:%d",
+               srcrect.UpperLeftCorner.X,
+               srcrect.UpperLeftCorner.Y,
+               srcrect.getWidth(),
+               srcrect.getHeight(),
+               destrect.getWidth(),
+               destrect.getHeight());
+       io::path origname = src->getName().getPath();
+       io::path scalename = origname + "@guiScalingFilter:" + rectstr;
+
+       // Search for existing scaled texture.
+       video::ITexture *scaled = g_txrCache[scalename];
+       if (scaled)
+               return scaled;
+
+       // Try to find the texture converted to an image in the cache.
+       // If the image was not found, try to extract it from the texture.
+       video::IImage* srcimg = g_imgCache[origname];
+       if (srcimg == NULL) {
+               if (!g_settings->getBool("gui_scaling_filter_txr2img"))
+                       return src;
+               srcimg = driver->createImageFromData(src->getColorFormat(),
+                       src->getSize(), src->lock(), false);
+               src->unlock();
+               g_imgCache[origname] = srcimg;
+       }
+
+       // Create a new destination image and scale the source into it.
+       imageCleanTransparent(srcimg, 0);
+       video::IImage *destimg = driver->createImage(src->getColorFormat(),
+                       core::dimension2d<u32>((u32)destrect.getWidth(),
+                       (u32)destrect.getHeight()));
+       imageScaleNNAA(srcimg, srcrect, destimg);
+
+#ifdef __ANDROID__
+       // Android is very picky about textures being powers of 2, so expand
+       // the image dimensions to the next power of 2, if necessary, for
+       // that platform.
+       video::IImage *po2img = driver->createImage(src->getColorFormat(),
+                       core::dimension2d<u32>(npot2((u32)destrect.getWidth()),
+                       npot2((u32)destrect.getHeight())));
+       po2img->fill(video::SColor(0, 0, 0, 0));
+       destimg->copyTo(po2img);
+       destimg->drop();
+       destimg = po2img;
+#endif
+
+       // Convert the scaled image back into a texture.
+       scaled = driver->addTexture(scalename, destimg, NULL);
+       destimg->drop();
+       g_txrCache[scalename] = scaled;
+
+       return scaled;
+}
+
+/* Convenience wrapper for guiScalingResizeCached that accepts parameters that
+ * are available at GUI imagebutton creation time.
+ */
+video::ITexture *guiScalingImageButton(video::IVideoDriver *driver,
+               video::ITexture *src, s32 width, s32 height)
+{
+       if (src == NULL)
+               return src;
+       return guiScalingResizeCached(driver, src,
+               core::rect<s32>(0, 0, src->getSize().Width, src->getSize().Height),
+               core::rect<s32>(0, 0, width, height));
+}
+
+/* Replacement for driver->draw2DImage() that uses the high-quality pre-scaled
+ * texture, if configured.
+ */
+void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr,
+               const core::rect<s32> &destrect, const core::rect<s32> &srcrect,
+               const core::rect<s32> *cliprect, const video::SColor *const colors,
+               bool usealpha)
+{
+       // Attempt to pre-scale image in software in high quality.
+       video::ITexture *scaled = guiScalingResizeCached(driver, txr, srcrect, destrect);
+       if (scaled == NULL)
+               return;
+
+       // Correct source rect based on scaled image.
+       const core::rect<s32> mysrcrect = (scaled != txr)
+               ? core::rect<s32>(0, 0, destrect.getWidth(), destrect.getHeight())
+               : srcrect;
+
+       driver->draw2DImage(scaled, destrect, mysrcrect, cliprect, colors, usealpha);
+}
diff --git a/src/client/guiscalingfilter.h b/src/client/guiscalingfilter.h
new file mode 100644 (file)
index 0000000..4661bf8
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+
+/* Manually insert an image into the cache, useful to avoid texture-to-image
+ * conversion whenever we can intercept it.
+ */
+void guiScalingCache(io::path key, video::IVideoDriver *driver, video::IImage *value);
+
+// Manually clear the cache, e.g. when switching to different worlds.
+void guiScalingCacheClear();
+
+/* Get a cached, high-quality pre-scaled texture for display purposes.  If the
+ * texture is not already cached, attempt to create it.  Returns a pre-scaled texture,
+ * or the original texture if unable to pre-scale it.
+ */
+video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, video::ITexture *src,
+               const core::rect<s32> &srcrect, const core::rect<s32> &destrect);
+
+/* Convenience wrapper for guiScalingResizeCached that accepts parameters that
+ * are available at GUI imagebutton creation time.
+ */
+video::ITexture *guiScalingImageButton(video::IVideoDriver *driver, video::ITexture *src,
+               s32 width, s32 height);
+
+/* Replacement for driver->draw2DImage() that uses the high-quality pre-scaled
+ * texture, if configured.
+ */
+void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr,
+               const core::rect<s32> &destrect, const core::rect<s32> &srcrect,
+               const core::rect<s32> *cliprect = 0, const video::SColor *const colors = 0,
+               bool usealpha = false);
diff --git a/src/client/imagefilters.cpp b/src/client/imagefilters.cpp
new file mode 100644 (file)
index 0000000..dd02962
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
+
+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 "imagefilters.h"
+#include "util/numeric.h"
+#include <cmath>
+
+/* Fill in RGB values for transparent pixels, to correct for odd colors
+ * appearing at borders when blending.  This is because many PNG optimizers
+ * like to discard RGB values of transparent pixels, but when blending then
+ * with non-transparent neighbors, their RGB values will shpw up nonetheless.
+ *
+ * This function modifies the original image in-place.
+ *
+ * Parameter "threshold" is the alpha level below which pixels are considered
+ * transparent.  Should be 127 for 3d where alpha is threshold, but 0 for
+ * 2d where alpha is blended.
+ */
+void imageCleanTransparent(video::IImage *src, u32 threshold)
+{
+       core::dimension2d<u32> dim = src->getDimension();
+
+       // Walk each pixel looking for fully transparent ones.
+       // Note: loop y around x for better cache locality.
+       for (u32 ctry = 0; ctry < dim.Height; ctry++)
+       for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
+
+               // Ignore opaque pixels.
+               irr::video::SColor c = src->getPixel(ctrx, ctry);
+               if (c.getAlpha() > threshold)
+                       continue;
+
+               // Sample size and total weighted r, g, b values.
+               u32 ss = 0, sr = 0, sg = 0, sb = 0;
+
+               // Walk each neighbor pixel (clipped to image bounds).
+               for (u32 sy = (ctry < 1) ? 0 : (ctry - 1);
+                               sy <= (ctry + 1) && sy < dim.Height; sy++)
+               for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
+                               sx <= (ctrx + 1) && sx < dim.Width; sx++) {
+
+                       // Ignore transparent pixels.
+                       irr::video::SColor d = src->getPixel(sx, sy);
+                       if (d.getAlpha() <= threshold)
+                               continue;
+
+                       // Add RGB values weighted by alpha.
+                       u32 a = d.getAlpha();
+                       ss += a;
+                       sr += a * d.getRed();
+                       sg += a * d.getGreen();
+                       sb += a * d.getBlue();
+               }
+
+               // If we found any neighbor RGB data, set pixel to average
+               // weighted by alpha.
+               if (ss > 0) {
+                       c.setRed(sr / ss);
+                       c.setGreen(sg / ss);
+                       c.setBlue(sb / ss);
+                       src->setPixel(ctrx, ctry, c);
+               }
+       }
+}
+
+/* Scale a region of an image into another image, using nearest-neighbor with
+ * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries
+ * to prevent non-integer scaling ratio artifacts.  Note that this may cause
+ * some blending at the edges where pixels don't line up perfectly, but this
+ * filter is designed to produce the most accurate results for both upscaling
+ * and downscaling.
+ */
+void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::IImage *dest)
+{
+       double sx, sy, minsx, maxsx, minsy, maxsy, area, ra, ga, ba, aa, pw, ph, pa;
+       u32 dy, dx;
+       video::SColor pxl;
+
+       // Cache rectsngle boundaries.
+       double sox = srcrect.UpperLeftCorner.X * 1.0;
+       double soy = srcrect.UpperLeftCorner.Y * 1.0;
+       double sw = srcrect.getWidth() * 1.0;
+       double sh = srcrect.getHeight() * 1.0;
+
+       // Walk each destination image pixel.
+       // Note: loop y around x for better cache locality.
+       core::dimension2d<u32> dim = dest->getDimension();
+       for (dy = 0; dy < dim.Height; dy++)
+       for (dx = 0; dx < dim.Width; dx++) {
+
+               // Calculate floating-point source rectangle bounds.
+               // Do some basic clipping, and for mirrored/flipped rects,
+               // make sure min/max are in the right order.
+               minsx = sox + (dx * sw / dim.Width);
+               minsx = rangelim(minsx, 0, sw);
+               maxsx = minsx + sw / dim.Width;
+               maxsx = rangelim(maxsx, 0, sw);
+               if (minsx > maxsx)
+                       SWAP(double, minsx, maxsx);
+               minsy = soy + (dy * sh / dim.Height);
+               minsy = rangelim(minsy, 0, sh);
+               maxsy = minsy + sh / dim.Height;
+               maxsy = rangelim(maxsy, 0, sh);
+               if (minsy > maxsy)
+                       SWAP(double, minsy, maxsy);
+
+               // Total area, and integral of r, g, b values over that area,
+               // initialized to zero, to be summed up in next loops.
+               area = 0;
+               ra = 0;
+               ga = 0;
+               ba = 0;
+               aa = 0;
+
+               // Loop over the integral pixel positions described by those bounds.
+               for (sy = floor(minsy); sy < maxsy; sy++)
+               for (sx = floor(minsx); sx < maxsx; sx++) {
+
+                       // Calculate width, height, then area of dest pixel
+                       // that's covered by this source pixel.
+                       pw = 1;
+                       if (minsx > sx)
+                               pw += sx - minsx;
+                       if (maxsx < (sx + 1))
+                               pw += maxsx - sx - 1;
+                       ph = 1;
+                       if (minsy > sy)
+                               ph += sy - minsy;
+                       if (maxsy < (sy + 1))
+                               ph += maxsy - sy - 1;
+                       pa = pw * ph;
+
+                       // Get source pixel and add it to totals, weighted
+                       // by covered area and alpha.
+                       pxl = src->getPixel((u32)sx, (u32)sy);
+                       area += pa;
+                       ra += pa * pxl.getRed();
+                       ga += pa * pxl.getGreen();
+                       ba += pa * pxl.getBlue();
+                       aa += pa * pxl.getAlpha();
+               }
+
+               // Set the destination image pixel to the average color.
+               if (area > 0) {
+                       pxl.setRed(ra / area + 0.5);
+                       pxl.setGreen(ga / area + 0.5);
+                       pxl.setBlue(ba / area + 0.5);
+                       pxl.setAlpha(aa / area + 0.5);
+               } else {
+                       pxl.setRed(0);
+                       pxl.setGreen(0);
+                       pxl.setBlue(0);
+                       pxl.setAlpha(0);
+               }
+               dest->setPixel(dx, dy, pxl);
+       }
+}
diff --git a/src/client/imagefilters.h b/src/client/imagefilters.h
new file mode 100644 (file)
index 0000000..5676faf
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+
+/* Fill in RGB values for transparent pixels, to correct for odd colors
+ * appearing at borders when blending.  This is because many PNG optimizers
+ * like to discard RGB values of transparent pixels, but when blending then
+ * with non-transparent neighbors, their RGB values will shpw up nonetheless.
+ *
+ * This function modifies the original image in-place.
+ *
+ * Parameter "threshold" is the alpha level below which pixels are considered
+ * transparent.  Should be 127 for 3d where alpha is threshold, but 0 for
+ * 2d where alpha is blended.
+ */
+void imageCleanTransparent(video::IImage *src, u32 threshold);
+
+/* Scale a region of an image into another image, using nearest-neighbor with
+ * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries
+ * to prevent non-integer scaling ratio artifacts.  Note that this may cause
+ * some blending at the edges where pixels don't line up perfectly, but this
+ * filter is designed to produce the most accurate results for both upscaling
+ * and downscaling.
+ */
+void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::IImage *dest);
diff --git a/src/client/keycode.cpp b/src/client/keycode.cpp
new file mode 100644 (file)
index 0000000..646d181
--- /dev/null
@@ -0,0 +1,384 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 "keycode.h"
+#include "exceptions.h"
+#include "settings.h"
+#include "log.h"
+#include "debug.h"
+#include "util/hex.h"
+#include "util/string.h"
+#include "util/basic_macros.h"
+
+class UnknownKeycode : public BaseException
+{
+public:
+       UnknownKeycode(const char *s) :
+               BaseException(s) {};
+};
+
+struct table_key {
+       const char *Name;
+       irr::EKEY_CODE Key;
+       wchar_t Char; // L'\0' means no character assigned
+       const char *LangName; // NULL means it doesn't have a human description
+};
+
+#define DEFINEKEY1(x, lang) /* Irrlicht key without character */ \
+       { #x, irr::x, L'\0', lang },
+#define DEFINEKEY2(x, ch, lang) /* Irrlicht key with character */ \
+       { #x, irr::x, ch, lang },
+#define DEFINEKEY3(ch) /* single Irrlicht key (e.g. KEY_KEY_X) */ \
+       { "KEY_KEY_" TOSTRING(ch), irr::KEY_KEY_ ## ch, (wchar_t) *TOSTRING(ch), TOSTRING(ch) },
+#define DEFINEKEY4(ch) /* single Irrlicht function key (e.g. KEY_F3) */ \
+       { "KEY_F" TOSTRING(ch), irr::KEY_F ## ch, L'\0', "F" TOSTRING(ch) },
+#define DEFINEKEY5(ch) /* key without Irrlicht keycode */ \
+       { ch, irr::KEY_KEY_CODES_COUNT, (wchar_t) *ch, ch },
+
+#define N_(text) text
+
+static const struct table_key table[] = {
+       // Keys that can be reliably mapped between Char and Key
+       DEFINEKEY3(0)
+       DEFINEKEY3(1)
+       DEFINEKEY3(2)
+       DEFINEKEY3(3)
+       DEFINEKEY3(4)
+       DEFINEKEY3(5)
+       DEFINEKEY3(6)
+       DEFINEKEY3(7)
+       DEFINEKEY3(8)
+       DEFINEKEY3(9)
+       DEFINEKEY3(A)
+       DEFINEKEY3(B)
+       DEFINEKEY3(C)
+       DEFINEKEY3(D)
+       DEFINEKEY3(E)
+       DEFINEKEY3(F)
+       DEFINEKEY3(G)
+       DEFINEKEY3(H)
+       DEFINEKEY3(I)
+       DEFINEKEY3(J)
+       DEFINEKEY3(K)
+       DEFINEKEY3(L)
+       DEFINEKEY3(M)
+       DEFINEKEY3(N)
+       DEFINEKEY3(O)
+       DEFINEKEY3(P)
+       DEFINEKEY3(Q)
+       DEFINEKEY3(R)
+       DEFINEKEY3(S)
+       DEFINEKEY3(T)
+       DEFINEKEY3(U)
+       DEFINEKEY3(V)
+       DEFINEKEY3(W)
+       DEFINEKEY3(X)
+       DEFINEKEY3(Y)
+       DEFINEKEY3(Z)
+       DEFINEKEY2(KEY_PLUS, L'+', "+")
+       DEFINEKEY2(KEY_COMMA, L',', ",")
+       DEFINEKEY2(KEY_MINUS, L'-', "-")
+       DEFINEKEY2(KEY_PERIOD, L'.', ".")
+
+       // Keys without a Char
+       DEFINEKEY1(KEY_LBUTTON, N_("Left Button"))
+       DEFINEKEY1(KEY_RBUTTON, N_("Right Button"))
+       DEFINEKEY1(KEY_CANCEL, N_("Cancel"))
+       DEFINEKEY1(KEY_MBUTTON, N_("Middle Button"))
+       DEFINEKEY1(KEY_XBUTTON1, N_("X Button 1"))
+       DEFINEKEY1(KEY_XBUTTON2, N_("X Button 2"))
+       DEFINEKEY1(KEY_BACK, N_("Backspace"))
+       DEFINEKEY1(KEY_TAB, N_("Tab"))
+       DEFINEKEY1(KEY_CLEAR, N_("Clear"))
+       DEFINEKEY1(KEY_RETURN, N_("Return"))
+       DEFINEKEY1(KEY_SHIFT, N_("Shift"))
+       DEFINEKEY1(KEY_CONTROL, N_("Control"))
+       DEFINEKEY1(KEY_MENU, N_("Menu"))
+       DEFINEKEY1(KEY_PAUSE, N_("Pause"))
+       DEFINEKEY1(KEY_CAPITAL, N_("Caps Lock"))
+       DEFINEKEY1(KEY_SPACE, N_("Space"))
+       DEFINEKEY1(KEY_PRIOR, N_("Page up"))
+       DEFINEKEY1(KEY_NEXT, N_("Page down"))
+       DEFINEKEY1(KEY_END, N_("End"))
+       DEFINEKEY1(KEY_HOME, N_("Home"))
+       DEFINEKEY1(KEY_LEFT, N_("Left"))
+       DEFINEKEY1(KEY_UP, N_("Up"))
+       DEFINEKEY1(KEY_RIGHT, N_("Right"))
+       DEFINEKEY1(KEY_DOWN, N_("Down"))
+       DEFINEKEY1(KEY_SELECT, N_("Select"))
+       DEFINEKEY1(KEY_PRINT, N_("Print"))
+       DEFINEKEY1(KEY_EXECUT, N_("Execute"))
+       DEFINEKEY1(KEY_SNAPSHOT, N_("Snapshot"))
+       DEFINEKEY1(KEY_INSERT, N_("Insert"))
+       DEFINEKEY1(KEY_DELETE, N_("Delete"))
+       DEFINEKEY1(KEY_HELP, N_("Help"))
+       DEFINEKEY1(KEY_LWIN, N_("Left Windows"))
+       DEFINEKEY1(KEY_RWIN, N_("Right Windows"))
+       DEFINEKEY1(KEY_NUMPAD0, N_("Numpad 0")) // These are not assigned to a char
+       DEFINEKEY1(KEY_NUMPAD1, N_("Numpad 1")) // to prevent interference with KEY_KEY_[0-9].
+       DEFINEKEY1(KEY_NUMPAD2, N_("Numpad 2"))
+       DEFINEKEY1(KEY_NUMPAD3, N_("Numpad 3"))
+       DEFINEKEY1(KEY_NUMPAD4, N_("Numpad 4"))
+       DEFINEKEY1(KEY_NUMPAD5, N_("Numpad 5"))
+       DEFINEKEY1(KEY_NUMPAD6, N_("Numpad 6"))
+       DEFINEKEY1(KEY_NUMPAD7, N_("Numpad 7"))
+       DEFINEKEY1(KEY_NUMPAD8, N_("Numpad 8"))
+       DEFINEKEY1(KEY_NUMPAD9, N_("Numpad 9"))
+       DEFINEKEY1(KEY_MULTIPLY, N_("Numpad *"))
+       DEFINEKEY1(KEY_ADD, N_("Numpad +"))
+       DEFINEKEY1(KEY_SEPARATOR, N_("Numpad ."))
+       DEFINEKEY1(KEY_SUBTRACT, N_("Numpad -"))
+       DEFINEKEY1(KEY_DECIMAL, NULL)
+       DEFINEKEY1(KEY_DIVIDE, N_("Numpad /"))
+       DEFINEKEY4(1)
+       DEFINEKEY4(2)
+       DEFINEKEY4(3)
+       DEFINEKEY4(4)
+       DEFINEKEY4(5)
+       DEFINEKEY4(6)
+       DEFINEKEY4(7)
+       DEFINEKEY4(8)
+       DEFINEKEY4(9)
+       DEFINEKEY4(10)
+       DEFINEKEY4(11)
+       DEFINEKEY4(12)
+       DEFINEKEY4(13)
+       DEFINEKEY4(14)
+       DEFINEKEY4(15)
+       DEFINEKEY4(16)
+       DEFINEKEY4(17)
+       DEFINEKEY4(18)
+       DEFINEKEY4(19)
+       DEFINEKEY4(20)
+       DEFINEKEY4(21)
+       DEFINEKEY4(22)
+       DEFINEKEY4(23)
+       DEFINEKEY4(24)
+       DEFINEKEY1(KEY_NUMLOCK, N_("Num Lock"))
+       DEFINEKEY1(KEY_SCROLL, N_("Scroll Lock"))
+       DEFINEKEY1(KEY_LSHIFT, N_("Left Shift"))
+       DEFINEKEY1(KEY_RSHIFT, N_("Right Shift"))
+       DEFINEKEY1(KEY_LCONTROL, N_("Left Control"))
+       DEFINEKEY1(KEY_RCONTROL, N_("Right Control"))
+       DEFINEKEY1(KEY_LMENU, N_("Left Menu"))
+       DEFINEKEY1(KEY_RMENU, N_("Right Menu"))
+
+       // Rare/weird keys
+       DEFINEKEY1(KEY_KANA, "Kana")
+       DEFINEKEY1(KEY_HANGUEL, "Hangul")
+       DEFINEKEY1(KEY_HANGUL, "Hangul")
+       DEFINEKEY1(KEY_JUNJA, "Junja")
+       DEFINEKEY1(KEY_FINAL, "Final")
+       DEFINEKEY1(KEY_KANJI, "Kanji")
+       DEFINEKEY1(KEY_HANJA, "Hanja")
+       DEFINEKEY1(KEY_ESCAPE, N_("IME Escape"))
+       DEFINEKEY1(KEY_CONVERT, N_("IME Convert"))
+       DEFINEKEY1(KEY_NONCONVERT, N_("IME Nonconvert"))
+       DEFINEKEY1(KEY_ACCEPT, N_("IME Accept"))
+       DEFINEKEY1(KEY_MODECHANGE, N_("IME Mode Change"))
+       DEFINEKEY1(KEY_APPS, N_("Apps"))
+       DEFINEKEY1(KEY_SLEEP, N_("Sleep"))
+#if !(IRRLICHT_VERSION_MAJOR <= 1 && IRRLICHT_VERSION_MINOR <= 7 && IRRLICHT_VERSION_REVISION < 3)
+       DEFINEKEY1(KEY_OEM_1, "OEM 1") // KEY_OEM_[0-9] and KEY_OEM_102 are assigned to multiple
+       DEFINEKEY1(KEY_OEM_2, "OEM 2") // different chars (on different platforms too) and thus w/o char
+       DEFINEKEY1(KEY_OEM_3, "OEM 3")
+       DEFINEKEY1(KEY_OEM_4, "OEM 4")
+       DEFINEKEY1(KEY_OEM_5, "OEM 5")
+       DEFINEKEY1(KEY_OEM_6, "OEM 6")
+       DEFINEKEY1(KEY_OEM_7, "OEM 7")
+       DEFINEKEY1(KEY_OEM_8, "OEM 8")
+       DEFINEKEY1(KEY_OEM_AX, "OEM AX")
+       DEFINEKEY1(KEY_OEM_102, "OEM 102")
+#endif
+       DEFINEKEY1(KEY_ATTN, "Attn")
+       DEFINEKEY1(KEY_CRSEL, "CrSel")
+       DEFINEKEY1(KEY_EXSEL, "ExSel")
+       DEFINEKEY1(KEY_EREOF, N_("Erase EOF"))
+       DEFINEKEY1(KEY_PLAY, N_("Play"))
+       DEFINEKEY1(KEY_ZOOM, N_("Zoom"))
+       DEFINEKEY1(KEY_PA1, "PA1")
+       DEFINEKEY1(KEY_OEM_CLEAR, N_("OEM Clear"))
+
+       // Keys without Irrlicht keycode
+       DEFINEKEY5("!")
+       DEFINEKEY5("\"")
+       DEFINEKEY5("#")
+       DEFINEKEY5("$")
+       DEFINEKEY5("%")
+       DEFINEKEY5("&")
+       DEFINEKEY5("'")
+       DEFINEKEY5("(")
+       DEFINEKEY5(")")
+       DEFINEKEY5("*")
+       DEFINEKEY5("/")
+       DEFINEKEY5(":")
+       DEFINEKEY5(";")
+       DEFINEKEY5("<")
+       DEFINEKEY5("=")
+       DEFINEKEY5(">")
+       DEFINEKEY5("?")
+       DEFINEKEY5("@")
+       DEFINEKEY5("[")
+       DEFINEKEY5("\\")
+       DEFINEKEY5("]")
+       DEFINEKEY5("^")
+       DEFINEKEY5("_")
+};
+
+#undef N_
+
+
+struct table_key lookup_keyname(const char *name)
+{
+       for (const auto &table_key : table) {
+               if (strcmp(table_key.Name, name) == 0)
+                       return table_key;
+       }
+
+       throw UnknownKeycode(name);
+}
+
+struct table_key lookup_keykey(irr::EKEY_CODE key)
+{
+       for (const auto &table_key : table) {
+               if (table_key.Key == key)
+                       return table_key;
+       }
+
+       std::ostringstream os;
+       os << "<Keycode " << (int) key << ">";
+       throw UnknownKeycode(os.str().c_str());
+}
+
+struct table_key lookup_keychar(wchar_t Char)
+{
+       for (const auto &table_key : table) {
+               if (table_key.Char == Char)
+                       return table_key;
+       }
+
+       std::ostringstream os;
+       os << "<Char " << hex_encode((char*) &Char, sizeof(wchar_t)) << ">";
+       throw UnknownKeycode(os.str().c_str());
+}
+
+KeyPress::KeyPress(const char *name)
+{
+       if (strlen(name) == 0) {
+               Key = irr::KEY_KEY_CODES_COUNT;
+               Char = L'\0';
+               m_name = "";
+               return;
+       }
+
+       if (strlen(name) <= 4) {
+               // Lookup by resulting character
+               int chars_read = mbtowc(&Char, name, 1);
+               FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character");
+               try {
+                       struct table_key k = lookup_keychar(Char);
+                       m_name = k.Name;
+                       Key = k.Key;
+                       return;
+               } catch (UnknownKeycode &e) {};
+       } else {
+               // Lookup by name
+               m_name = name;
+               try {
+                       struct table_key k = lookup_keyname(name);
+                       Key = k.Key;
+                       Char = k.Char;
+                       return;
+               } catch (UnknownKeycode &e) {};
+       }
+
+       // It's not a known key, complain and try to do something
+       Key = irr::KEY_KEY_CODES_COUNT;
+       int chars_read = mbtowc(&Char, name, 1);
+       FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character");
+       m_name = "";
+       warningstream << "KeyPress: Unknown key '" << name << "', falling back to first char.";
+}
+
+KeyPress::KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character)
+{
+       if (prefer_character)
+               Key = irr::KEY_KEY_CODES_COUNT;
+       else
+               Key = in.Key;
+       Char = in.Char;
+
+       try {
+               if (valid_kcode(Key))
+                       m_name = lookup_keykey(Key).Name;
+               else
+                       m_name = lookup_keychar(Char).Name;
+       } catch (UnknownKeycode &e) {
+               m_name = "";
+       };
+}
+
+const char *KeyPress::sym() const
+{
+       return m_name.c_str();
+}
+
+const char *KeyPress::name() const
+{
+       if (m_name.empty())
+               return "";
+       const char *ret;
+       if (valid_kcode(Key))
+               ret = lookup_keykey(Key).LangName;
+       else
+               ret = lookup_keychar(Char).LangName;
+       return ret ? ret : "<Unnamed key>";
+}
+
+const KeyPress EscapeKey("KEY_ESCAPE");
+const KeyPress CancelKey("KEY_CANCEL");
+
+/*
+       Key config
+*/
+
+// A simple cache for quicker lookup
+std::unordered_map<std::string, KeyPress> g_key_setting_cache;
+
+KeyPress getKeySetting(const char *settingname)
+{
+       std::unordered_map<std::string, KeyPress>::iterator n;
+       n = g_key_setting_cache.find(settingname);
+       if (n != g_key_setting_cache.end())
+               return n->second;
+
+       KeyPress k(g_settings->get(settingname).c_str());
+       g_key_setting_cache[settingname] = k;
+       return k;
+}
+
+void clearKeyCache()
+{
+       g_key_setting_cache.clear();
+}
+
+irr::EKEY_CODE keyname_to_keycode(const char *name)
+{
+       return lookup_keyname(name).Key;
+}
diff --git a/src/client/keycode.h b/src/client/keycode.h
new file mode 100644 (file)
index 0000000..7036705
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "irrlichttypes.h"
+#include "Keycodes.h"
+#include <IEventReceiver.h>
+#include <string>
+
+/* A key press, consisting of either an Irrlicht keycode
+   or an actual char */
+
+class KeyPress
+{
+public:
+       KeyPress() = default;
+
+       KeyPress(const char *name);
+
+       KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character = false);
+
+       bool operator==(const KeyPress &o) const
+       {
+               return (Char > 0 && Char == o.Char) || (valid_kcode(Key) && Key == o.Key);
+       }
+
+       const char *sym() const;
+       const char *name() const;
+
+protected:
+       static bool valid_kcode(irr::EKEY_CODE k)
+       {
+               return k > 0 && k < irr::KEY_KEY_CODES_COUNT;
+       }
+
+       irr::EKEY_CODE Key = irr::KEY_KEY_CODES_COUNT;
+       wchar_t Char = L'\0';
+       std::string m_name = "";
+};
+
+extern const KeyPress EscapeKey;
+extern const KeyPress CancelKey;
+
+// Key configuration getter
+KeyPress getKeySetting(const char *settingname);
+
+// Clear fast lookup cache
+void clearKeyCache();
+
+irr::EKEY_CODE keyname_to_keycode(const char *name);
diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp
new file mode 100644 (file)
index 0000000..1c65d3b
--- /dev/null
@@ -0,0 +1,1158 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 "localplayer.h"
+#include <cmath>
+#include "event.h"
+#include "collision.h"
+#include "nodedef.h"
+#include "settings.h"
+#include "environment.h"
+#include "map.h"
+#include "client.h"
+#include "content_cao.h"
+
+/*
+       LocalPlayer
+*/
+
+LocalPlayer::LocalPlayer(Client *client, const char *name):
+       Player(name, client->idef()),
+       m_client(client)
+{
+}
+
+static aabb3f getNodeBoundingBox(const std::vector<aabb3f> &nodeboxes)
+{
+       if (nodeboxes.empty())
+               return aabb3f(0, 0, 0, 0, 0, 0);
+
+       aabb3f b_max;
+
+       std::vector<aabb3f>::const_iterator it = nodeboxes.begin();
+       b_max = aabb3f(it->MinEdge, it->MaxEdge);
+
+       ++it;
+       for (; it != nodeboxes.end(); ++it)
+               b_max.addInternalBox(*it);
+
+       return b_max;
+}
+
+bool LocalPlayer::updateSneakNode(Map *map, const v3f &position,
+               const v3f &sneak_max)
+{
+       static const v3s16 dir9_center[9] = {
+               v3s16( 0, 0,  0),
+               v3s16( 1, 0,  0),
+               v3s16(-1, 0,  0),
+               v3s16( 0, 0,  1),
+               v3s16( 0, 0, -1),
+               v3s16( 1, 0,  1),
+               v3s16(-1, 0,  1),
+               v3s16( 1, 0, -1),
+               v3s16(-1, 0, -1)
+       };
+
+       const NodeDefManager *nodemgr = m_client->ndef();
+       MapNode node;
+       bool is_valid_position;
+       bool new_sneak_node_exists = m_sneak_node_exists;
+
+       // We want the top of the sneak node to be below the players feet
+       f32 position_y_mod = 0.05 * BS;
+       if (m_sneak_node_exists)
+               position_y_mod = m_sneak_node_bb_top.MaxEdge.Y - position_y_mod;
+
+       // Get position of current standing node
+       const v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), BS);
+
+       if (current_node != m_sneak_node) {
+               new_sneak_node_exists = false;
+       } else {
+               node = map->getNodeNoEx(current_node, &is_valid_position);
+               if (!is_valid_position || !nodemgr->get(node).walkable)
+                       new_sneak_node_exists = false;
+       }
+
+       // Keep old sneak node
+       if (new_sneak_node_exists)
+               return true;
+
+       // Get new sneak node
+       m_sneak_ladder_detected = false;
+       f32 min_distance_f = 100000.0 * BS;
+
+       for (const auto &d : dir9_center) {
+               const v3s16 p = current_node + d;
+               const v3f pf = intToFloat(p, BS);
+               const v2f diff(position.X - pf.X, position.Z - pf.Z);
+               f32 distance_f = diff.getLength();
+
+               if (distance_f > min_distance_f ||
+                               fabs(diff.X) > (.5 + .1) * BS + sneak_max.X ||
+                               fabs(diff.Y) > (.5 + .1) * BS + sneak_max.Z)
+                       continue;
+
+
+               // The node to be sneaked on has to be walkable
+               node = map->getNodeNoEx(p, &is_valid_position);
+               if (!is_valid_position || !nodemgr->get(node).walkable)
+                       continue;
+               // And the node(s) above have to be nonwalkable
+               bool ok = true;
+               if (!physics_override_sneak_glitch) {
+                       u16 height = ceilf(
+                                       (m_collisionbox.MaxEdge.Y - m_collisionbox.MinEdge.Y) / BS
+                       );
+                       for (u16 y = 1; y <= height; y++) {
+                               node = map->getNodeNoEx(p + v3s16(0, y, 0), &is_valid_position);
+                               if (!is_valid_position || nodemgr->get(node).walkable) {
+                                       ok = false;
+                                       break;
+                               }
+                       }
+               } else {
+                       // legacy behaviour: check just one node
+                       node = map->getNodeNoEx(p + v3s16(0, 1, 0), &is_valid_position);
+                       ok = is_valid_position && !nodemgr->get(node).walkable;
+               }
+               if (!ok)
+                       continue;
+
+               min_distance_f = distance_f;
+               m_sneak_node = p;
+               new_sneak_node_exists = true;
+       }
+
+       if (!new_sneak_node_exists)
+               return false;
+
+       // Update saved top bounding box of sneak node
+       node = map->getNodeNoEx(m_sneak_node);
+       std::vector<aabb3f> nodeboxes;
+       node.getCollisionBoxes(nodemgr, &nodeboxes);
+       m_sneak_node_bb_top = getNodeBoundingBox(nodeboxes);
+
+       if (physics_override_sneak_glitch) {
+               // Detect sneak ladder:
+               // Node two meters above sneak node must be solid
+               node = map->getNodeNoEx(m_sneak_node + v3s16(0, 2, 0),
+                       &is_valid_position);
+               if (is_valid_position && nodemgr->get(node).walkable) {
+                       // Node three meters above: must be non-solid
+                       node = map->getNodeNoEx(m_sneak_node + v3s16(0, 3, 0),
+                               &is_valid_position);
+                       m_sneak_ladder_detected = is_valid_position &&
+                               !nodemgr->get(node).walkable;
+               }
+       }
+       return true;
+}
+
+void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
+               std::vector<CollisionInfo> *collision_info)
+{
+       if (!collision_info || collision_info->empty()) {
+               // Node below the feet, update each ClientEnvironment::step()
+               m_standing_node = floatToInt(m_position, BS) - v3s16(0, 1, 0);
+       }
+
+       // Temporary option for old move code
+       if (!physics_override_new_move) {
+               old_move(dtime, env, pos_max_d, collision_info);
+               return;
+       }
+
+       Map *map = &env->getMap();
+       const NodeDefManager *nodemgr = m_client->ndef();
+
+       v3f position = getPosition();
+
+       // Copy parent position if local player is attached
+       if (isAttached) {
+               setPosition(overridePosition);
+               return;
+       }
+
+       PlayerSettings &player_settings = getPlayerSettings();
+
+       // Skip collision detection if noclip mode is used
+       bool fly_allowed = m_client->checkLocalPrivilege("fly");
+       bool noclip = m_client->checkLocalPrivilege("noclip") && player_settings.noclip;
+       bool free_move = player_settings.free_move && fly_allowed;
+
+       if (noclip && free_move) {
+               position += m_speed * dtime;
+               setPosition(position);
+               return;
+       }
+
+       /*
+               Collision detection
+       */
+
+       bool is_valid_position;
+       MapNode node;
+       v3s16 pp;
+
+       /*
+               Check if player is in liquid (the oscillating value)
+       */
+
+       // If in liquid, the threshold of coming out is at higher y
+       if (in_liquid)
+       {
+               pp = floatToInt(position + v3f(0,BS*0.1,0), BS);
+               node = map->getNodeNoEx(pp, &is_valid_position);
+               if (is_valid_position) {
+                       in_liquid = nodemgr->get(node.getContent()).isLiquid();
+                       liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
+               } else {
+                       in_liquid = false;
+               }
+       }
+       // If not in liquid, the threshold of going in is at lower y
+       else
+       {
+               pp = floatToInt(position + v3f(0,BS*0.5,0), BS);
+               node = map->getNodeNoEx(pp, &is_valid_position);
+               if (is_valid_position) {
+                       in_liquid = nodemgr->get(node.getContent()).isLiquid();
+                       liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
+               } else {
+                       in_liquid = false;
+               }
+       }
+
+
+       /*
+               Check if player is in liquid (the stable value)
+       */
+       pp = floatToInt(position + v3f(0,0,0), BS);
+       node = map->getNodeNoEx(pp, &is_valid_position);
+       if (is_valid_position) {
+               in_liquid_stable = nodemgr->get(node.getContent()).isLiquid();
+       } else {
+               in_liquid_stable = false;
+       }
+
+       /*
+               Check if player is climbing
+       */
+
+
+       pp = floatToInt(position + v3f(0,0.5*BS,0), BS);
+       v3s16 pp2 = floatToInt(position + v3f(0,-0.2*BS,0), BS);
+       node = map->getNodeNoEx(pp, &is_valid_position);
+       bool is_valid_position2;
+       MapNode node2 = map->getNodeNoEx(pp2, &is_valid_position2);
+
+       if (!(is_valid_position && is_valid_position2)) {
+               is_climbing = false;
+       } else {
+               is_climbing = (nodemgr->get(node.getContent()).climbable
+                               || nodemgr->get(node2.getContent()).climbable) && !free_move;
+       }
+
+       /*
+               Collision uncertainty radius
+               Make it a bit larger than the maximum distance of movement
+       */
+       //f32 d = pos_max_d * 1.1;
+       // A fairly large value in here makes moving smoother
+       f32 d = 0.15*BS;
+
+       // This should always apply, otherwise there are glitches
+       sanity_check(d > pos_max_d);
+
+       // Player object property step height is multiplied by BS in
+       // /src/script/common/c_content.cpp and /src/content_sao.cpp
+       float player_stepheight = (m_cao == nullptr) ? 0.0f :
+               (touching_ground ? m_cao->getStepHeight() : (0.2f * BS));
+
+       v3f accel_f = v3f(0,0,0);
+       const v3f initial_position = position;
+       const v3f initial_speed = m_speed;
+
+       collisionMoveResult result = collisionMoveSimple(env, m_client,
+               pos_max_d, m_collisionbox, player_stepheight, dtime,
+               &position, &m_speed, accel_f);
+
+       bool could_sneak = control.sneak && !free_move && !in_liquid &&
+               !is_climbing && physics_override_sneak;
+
+       // Add new collisions to the vector
+       if (collision_info && !free_move) {
+               v3f diff = intToFloat(m_standing_node, BS) - position;
+               f32 distance = diff.getLength();
+               // Force update each ClientEnvironment::step()
+               bool is_first = collision_info->empty();
+
+               for (const auto &colinfo : result.collisions) {
+                       collision_info->push_back(colinfo);
+
+                       if (colinfo.type != COLLISION_NODE ||
+                                       colinfo.new_speed.Y != 0 ||
+                                       (could_sneak && m_sneak_node_exists))
+                               continue;
+
+                       diff = intToFloat(colinfo.node_p, BS) - position;
+
+                       // Find nearest colliding node
+                       f32 len = diff.getLength();
+                       if (is_first || len < distance) {
+                               m_standing_node = colinfo.node_p;
+                               distance = len;
+                       }
+               }
+       }
+
+       /*
+               If the player's feet touch the topside of any node, this is
+               set to true.
+
+               Player is allowed to jump when this is true.
+       */
+       bool touching_ground_was = touching_ground;
+       touching_ground = result.touching_ground;
+       bool sneak_can_jump = false;
+
+       // Max. distance (X, Z) over border for sneaking determined by collision box
+       // * 0.49 to keep the center just barely on the node
+       v3f sneak_max = m_collisionbox.getExtent() * 0.49;
+
+       if (m_sneak_ladder_detected) {
+               // restore legacy behaviour (this makes the m_speed.Y hack necessary)
+               sneak_max = v3f(0.4 * BS, 0, 0.4 * BS);
+       }
+
+       /*
+               If sneaking, keep on top of last walked node and don't fall off
+       */
+       if (could_sneak && m_sneak_node_exists) {
+               const v3f sn_f = intToFloat(m_sneak_node, BS);
+               const v3f bmin = sn_f + m_sneak_node_bb_top.MinEdge;
+               const v3f bmax = sn_f + m_sneak_node_bb_top.MaxEdge;
+               const v3f old_pos = position;
+               const v3f old_speed = m_speed;
+               f32 y_diff = bmax.Y - position.Y;
+               m_standing_node = m_sneak_node;
+
+               // (BS * 0.6f) is the basic stepheight while standing on ground
+               if (y_diff < BS * 0.6f) {
+                       // Only center player when they're on the node
+                       position.X = rangelim(position.X,
+                               bmin.X - sneak_max.X, bmax.X + sneak_max.X);
+                       position.Z = rangelim(position.Z,
+                               bmin.Z - sneak_max.Z, bmax.Z + sneak_max.Z);
+
+                       if (position.X != old_pos.X)
+                               m_speed.X = 0;
+                       if (position.Z != old_pos.Z)
+                               m_speed.Z = 0;
+               }
+
+               if (y_diff > 0 && m_speed.Y <= 0 &&
+                               (physics_override_sneak_glitch || y_diff < BS * 0.6f)) {
+                       // Move player to the maximal height when falling or when
+                       // the ledge is climbed on the next step.
+
+                       // Smoothen the movement (based on 'position.Y = bmax.Y')
+                       position.Y += y_diff * dtime * 22.0f + BS * 0.01f;
+                       position.Y = std::min(position.Y, bmax.Y);
+                       m_speed.Y = 0;
+               }
+
+               // Allow jumping on node edges while sneaking
+               if (m_speed.Y == 0 || m_sneak_ladder_detected)
+                       sneak_can_jump = true;
+
+               if (collision_info &&
+                               m_speed.Y - old_speed.Y > BS) {
+                       // Collide with sneak node, report fall damage
+                       CollisionInfo sn_info;
+                       sn_info.node_p = m_sneak_node;
+                       sn_info.old_speed = old_speed;
+                       sn_info.new_speed = m_speed;
+                       collision_info->push_back(sn_info);
+               }
+       }
+
+       /*
+               Find the next sneak node if necessary
+       */
+       bool new_sneak_node_exists = false;
+
+       if (could_sneak)
+               new_sneak_node_exists = updateSneakNode(map, position, sneak_max);
+
+       /*
+               Set new position but keep sneak node set
+       */
+       setPosition(position);
+       m_sneak_node_exists = new_sneak_node_exists;
+
+       /*
+               Report collisions
+       */
+
+       if(!result.standing_on_object && !touching_ground_was && touching_ground) {
+               m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_REGAIN_GROUND));
+
+               // Set camera impact value to be used for view bobbing
+               camera_impact = getSpeed().Y * -1;
+       }
+
+       {
+               camera_barely_in_ceiling = false;
+               v3s16 camera_np = floatToInt(getEyePosition(), BS);
+               MapNode n = map->getNodeNoEx(camera_np);
+               if(n.getContent() != CONTENT_IGNORE){
+                       if(nodemgr->get(n).walkable && nodemgr->get(n).solidness == 2){
+                               camera_barely_in_ceiling = true;
+                       }
+               }
+       }
+
+       /*
+               Check properties of the node on which the player is standing
+       */
+       const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(m_standing_node));
+       // Determine if jumping is possible
+       m_can_jump = (touching_ground && !in_liquid && !is_climbing)
+                       || sneak_can_jump;
+       if (itemgroup_get(f.groups, "disable_jump"))
+               m_can_jump = false;
+
+       // Jump key pressed while jumping off from a bouncy block
+       if (m_can_jump && control.jump && itemgroup_get(f.groups, "bouncy") &&
+               m_speed.Y >= -0.5 * BS) {
+               float jumpspeed = movement_speed_jump * physics_override_jump;
+               if (m_speed.Y > 1) {
+                       // Reduce boost when speed already is high
+                       m_speed.Y += jumpspeed / (1 + (m_speed.Y / 16 ));
+               } else {
+                       m_speed.Y += jumpspeed;
+               }
+               setSpeed(m_speed);
+               m_can_jump = false;
+       }
+
+       // Autojump
+       handleAutojump(dtime, env, result, initial_position, initial_speed, pos_max_d);
+}
+
+void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d)
+{
+       move(dtime, env, pos_max_d, NULL);
+}
+
+void LocalPlayer::applyControl(float dtime, Environment *env)
+{
+       // Clear stuff
+       swimming_vertical = false;
+
+       setPitch(control.pitch);
+       setYaw(control.yaw);
+
+       // Nullify speed and don't run positioning code if the player is attached
+       if(isAttached)
+       {
+               setSpeed(v3f(0,0,0));
+               return;
+       }
+
+       PlayerSettings &player_settings = getPlayerSettings();
+
+       v3f move_direction = v3f(0,0,1);
+       move_direction.rotateXZBy(getYaw());
+
+       v3f speedH = v3f(0,0,0); // Horizontal (X, Z)
+       v3f speedV = v3f(0,0,0); // Vertical (Y)
+
+       bool fly_allowed = m_client->checkLocalPrivilege("fly");
+       bool fast_allowed = m_client->checkLocalPrivilege("fast");
+
+       bool free_move = fly_allowed && player_settings.free_move;
+       bool fast_move = fast_allowed && player_settings.fast_move;
+       // When aux1_descends is enabled the fast key is used to go down, so fast isn't possible
+       bool fast_climb = fast_move && control.aux1 && !player_settings.aux1_descends;
+       bool continuous_forward = player_settings.continuous_forward;
+       bool always_fly_fast = player_settings.always_fly_fast;
+
+       // Whether superspeed mode is used or not
+       bool superspeed = false;
+
+       if (always_fly_fast && free_move && fast_move)
+               superspeed = true;
+
+       // Old descend control
+       if (player_settings.aux1_descends)
+       {
+               // If free movement and fast movement, always move fast
+               if(free_move && fast_move)
+                       superspeed = true;
+
+               // Auxiliary button 1 (E)
+               if(control.aux1)
+               {
+                       if(free_move)
+                       {
+                               // In free movement mode, aux1 descends
+                               if(fast_move)
+                                       speedV.Y = -movement_speed_fast;
+                               else
+                                       speedV.Y = -movement_speed_walk;
+                       }
+                       else if(in_liquid || in_liquid_stable)
+                       {
+                               speedV.Y = -movement_speed_walk;
+                               swimming_vertical = true;
+                       }
+                       else if(is_climbing)
+                       {
+                               speedV.Y = -movement_speed_climb;
+                       }
+                       else
+                       {
+                               // If not free movement but fast is allowed, aux1 is
+                               // "Turbo button"
+                               if(fast_move)
+                                       superspeed = true;
+                       }
+               }
+       }
+       // New minecraft-like descend control
+       else
+       {
+               // Auxiliary button 1 (E)
+               if(control.aux1)
+               {
+                       if(!is_climbing)
+                       {
+                               // aux1 is "Turbo button"
+                               if(fast_move)
+                                       superspeed = true;
+                       }
+               }
+
+               if(control.sneak)
+               {
+                       if(free_move)
+                       {
+                               // In free movement mode, sneak descends
+                               if (fast_move && (control.aux1 || always_fly_fast))
+                                       speedV.Y = -movement_speed_fast;
+                               else
+                                       speedV.Y = -movement_speed_walk;
+                       }
+                       else if(in_liquid || in_liquid_stable)
+                       {
+                               if(fast_climb)
+                                       speedV.Y = -movement_speed_fast;
+                               else
+                                       speedV.Y = -movement_speed_walk;
+                               swimming_vertical = true;
+                       }
+                       else if(is_climbing)
+                       {
+                               if(fast_climb)
+                                       speedV.Y = -movement_speed_fast;
+                               else
+                                       speedV.Y = -movement_speed_climb;
+                       }
+               }
+       }
+
+       if (continuous_forward)
+               speedH += move_direction;
+
+       if (control.up) {
+               if (continuous_forward) {
+                       if (fast_move)
+                               superspeed = true;
+               } else {
+                       speedH += move_direction;
+               }
+       }
+       if (control.down) {
+               speedH -= move_direction;
+       }
+       if (!control.up && !control.down) {
+               speedH -= move_direction *
+                       (control.forw_move_joystick_axis / 32767.f);
+       }
+       if (control.left) {
+               speedH += move_direction.crossProduct(v3f(0,1,0));
+       }
+       if (control.right) {
+               speedH += move_direction.crossProduct(v3f(0,-1,0));
+       }
+       if (!control.left && !control.right) {
+               speedH -= move_direction.crossProduct(v3f(0,1,0)) *
+                       (control.sidew_move_joystick_axis / 32767.f);
+       }
+       if(control.jump)
+       {
+               if (free_move) {
+                       if (player_settings.aux1_descends || always_fly_fast) {
+                               if (fast_move)
+                                       speedV.Y = movement_speed_fast;
+                               else
+                                       speedV.Y = movement_speed_walk;
+                       } else {
+                               if(fast_move && control.aux1)
+                                       speedV.Y = movement_speed_fast;
+                               else
+                                       speedV.Y = movement_speed_walk;
+                       }
+               }
+               else if(m_can_jump)
+               {
+                       /*
+                               NOTE: The d value in move() affects jump height by
+                               raising the height at which the jump speed is kept
+                               at its starting value
+                       */
+                       v3f speedJ = getSpeed();
+                       if(speedJ.Y >= -0.5 * BS) {
+                               speedJ.Y = movement_speed_jump * physics_override_jump;
+                               setSpeed(speedJ);
+                               m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_JUMP));
+                       }
+               }
+               else if(in_liquid)
+               {
+                       if(fast_climb)
+                               speedV.Y = movement_speed_fast;
+                       else
+                               speedV.Y = movement_speed_walk;
+                       swimming_vertical = true;
+               }
+               else if(is_climbing)
+               {
+                       if(fast_climb)
+                               speedV.Y = movement_speed_fast;
+                       else
+                               speedV.Y = movement_speed_climb;
+               }
+       }
+
+       // The speed of the player (Y is ignored)
+       if(superspeed || (is_climbing && fast_climb) || ((in_liquid || in_liquid_stable) && fast_climb))
+               speedH = speedH.normalize() * movement_speed_fast;
+       else if(control.sneak && !free_move && !in_liquid && !in_liquid_stable)
+               speedH = speedH.normalize() * movement_speed_crouch;
+       else
+               speedH = speedH.normalize() * movement_speed_walk;
+
+       // Acceleration increase
+       f32 incH = 0; // Horizontal (X, Z)
+       f32 incV = 0; // Vertical (Y)
+       if((!touching_ground && !free_move && !is_climbing && !in_liquid) || (!free_move && m_can_jump && control.jump))
+       {
+               // Jumping and falling
+               if(superspeed || (fast_move && control.aux1))
+                       incH = movement_acceleration_fast * BS * dtime;
+               else
+                       incH = movement_acceleration_air * BS * dtime;
+               incV = 0; // No vertical acceleration in air
+       }
+       else if (superspeed || (is_climbing && fast_climb) || ((in_liquid || in_liquid_stable) && fast_climb))
+               incH = incV = movement_acceleration_fast * BS * dtime;
+       else
+               incH = incV = movement_acceleration_default * BS * dtime;
+
+       float slip_factor = 1.0f;
+       if (!free_move)
+               slip_factor = getSlipFactor(env, speedH);
+
+       // Accelerate to target speed with maximum increment
+       accelerateHorizontal(speedH * physics_override_speed,
+                       incH * physics_override_speed * slip_factor);
+       accelerateVertical(speedV * physics_override_speed,
+                       incV * physics_override_speed);
+}
+
+v3s16 LocalPlayer::getStandingNodePos()
+{
+       if(m_sneak_node_exists)
+               return m_sneak_node;
+       return m_standing_node;
+}
+
+v3s16 LocalPlayer::getFootstepNodePos()
+{
+       if (in_liquid_stable)
+               // Emit swimming sound if the player is in liquid
+               return floatToInt(getPosition(), BS);
+       if (touching_ground)
+               // BS * 0.05 below the player's feet ensures a 1/16th height
+               // nodebox is detected instead of the node below it.
+               return floatToInt(getPosition() - v3f(0, BS * 0.05f, 0), BS);
+       // A larger distance below is necessary for a footstep sound
+       // when landing after a jump or fall. BS * 0.5 ensures water
+       // sounds when swimming in 1 node deep water.
+       return floatToInt(getPosition() - v3f(0, BS * 0.5f, 0), BS);
+}
+
+v3s16 LocalPlayer::getLightPosition() const
+{
+       return floatToInt(m_position + v3f(0,BS+BS/2,0), BS);
+}
+
+v3f LocalPlayer::getEyeOffset() const
+{
+       float eye_height = camera_barely_in_ceiling ?
+               m_eye_height - 0.125f : m_eye_height;
+       return v3f(0, BS * eye_height, 0);
+}
+
+// Horizontal acceleration (X and Z), Y direction is ignored
+void LocalPlayer::accelerateHorizontal(const v3f &target_speed,
+       const f32 max_increase)
+{
+        if (max_increase == 0)
+                return;
+
+       v3f d_wanted = target_speed - m_speed;
+       d_wanted.Y = 0.0f;
+       f32 dl = d_wanted.getLength();
+       if (dl > max_increase)
+               dl = max_increase;
+
+       v3f d = d_wanted.normalize() * dl;
+
+       m_speed.X += d.X;
+       m_speed.Z += d.Z;
+}
+
+// Vertical acceleration (Y), X and Z directions are ignored
+void LocalPlayer::accelerateVertical(const v3f &target_speed, const f32 max_increase)
+{
+       if (max_increase == 0)
+               return;
+
+       f32 d_wanted = target_speed.Y - m_speed.Y;
+       if (d_wanted > max_increase)
+               d_wanted = max_increase;
+       else if (d_wanted < -max_increase)
+               d_wanted = -max_increase;
+
+       m_speed.Y += d_wanted;
+}
+
+// Temporary option for old move code
+void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
+               std::vector<CollisionInfo> *collision_info)
+{
+       Map *map = &env->getMap();
+       const NodeDefManager *nodemgr = m_client->ndef();
+
+       v3f position = getPosition();
+
+       // Copy parent position if local player is attached
+       if (isAttached) {
+               setPosition(overridePosition);
+               m_sneak_node_exists = false;
+               return;
+       }
+
+       PlayerSettings &player_settings = getPlayerSettings();
+
+       // Skip collision detection if noclip mode is used
+       bool fly_allowed = m_client->checkLocalPrivilege("fly");
+       bool noclip = m_client->checkLocalPrivilege("noclip") && player_settings.noclip;
+       bool free_move = noclip && fly_allowed && player_settings.free_move;
+       if (free_move) {
+               position += m_speed * dtime;
+               setPosition(position);
+               m_sneak_node_exists = false;
+               return;
+       }
+
+       /*
+               Collision detection
+       */
+       bool is_valid_position;
+       MapNode node;
+       v3s16 pp;
+
+       /*
+               Check if player is in liquid (the oscillating value)
+       */
+       if (in_liquid) {
+               // If in liquid, the threshold of coming out is at higher y
+               pp = floatToInt(position + v3f(0, BS * 0.1, 0), BS);
+               node = map->getNodeNoEx(pp, &is_valid_position);
+               if (is_valid_position) {
+                       in_liquid = nodemgr->get(node.getContent()).isLiquid();
+                       liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
+               } else {
+                       in_liquid = false;
+               }
+       } else {
+               // If not in liquid, the threshold of going in is at lower y
+               pp = floatToInt(position + v3f(0, BS * 0.5, 0), BS);
+               node = map->getNodeNoEx(pp, &is_valid_position);
+               if (is_valid_position) {
+                       in_liquid = nodemgr->get(node.getContent()).isLiquid();
+                       liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
+               } else {
+                       in_liquid = false;
+               }
+       }
+
+       /*
+               Check if player is in liquid (the stable value)
+       */
+       pp = floatToInt(position + v3f(0, 0, 0), BS);
+       node = map->getNodeNoEx(pp, &is_valid_position);
+       if (is_valid_position)
+               in_liquid_stable = nodemgr->get(node.getContent()).isLiquid();
+       else
+               in_liquid_stable = false;
+
+       /*
+               Check if player is climbing
+       */
+       pp = floatToInt(position + v3f(0, 0.5 * BS, 0), BS);
+       v3s16 pp2 = floatToInt(position + v3f(0, -0.2 * BS, 0), BS);
+       node = map->getNodeNoEx(pp, &is_valid_position);
+       bool is_valid_position2;
+       MapNode node2 = map->getNodeNoEx(pp2, &is_valid_position2);
+
+       if (!(is_valid_position && is_valid_position2))
+               is_climbing = false;
+       else
+               is_climbing = (nodemgr->get(node.getContent()).climbable ||
+                               nodemgr->get(node2.getContent()).climbable) && !free_move;
+
+       /*
+               Collision uncertainty radius
+               Make it a bit larger than the maximum distance of movement
+       */
+       //f32 d = pos_max_d * 1.1;
+       // A fairly large value in here makes moving smoother
+       f32 d = 0.15 * BS;
+       // This should always apply, otherwise there are glitches
+       sanity_check(d > pos_max_d);
+       // Maximum distance over border for sneaking
+       f32 sneak_max = BS * 0.4;
+
+       /*
+               If sneaking, keep in range from the last walked node and don't
+               fall off from it
+       */
+       if (control.sneak && m_sneak_node_exists &&
+                       !(fly_allowed && player_settings.free_move) && !in_liquid &&
+                       physics_override_sneak) {
+               f32 maxd = 0.5 * BS + sneak_max;
+               v3f lwn_f = intToFloat(m_sneak_node, BS);
+               position.X = rangelim(position.X, lwn_f.X - maxd, lwn_f.X + maxd);
+               position.Z = rangelim(position.Z, lwn_f.Z - maxd, lwn_f.Z + maxd);
+
+               if (!is_climbing) {
+                       // Move up if necessary
+                       f32 new_y = (lwn_f.Y - 0.5 * BS) + m_sneak_node_bb_ymax;
+                       if (position.Y < new_y)
+                               position.Y = new_y;
+                       /*
+                               Collision seems broken, since player is sinking when
+                               sneaking over the edges of current sneaking_node.
+                               TODO (when fixed): Set Y-speed only to 0 when position.Y < new_y.
+                       */
+                       if (m_speed.Y < 0)
+                               m_speed.Y = 0;
+               }
+       }
+
+       // this shouldn't be hardcoded but transmitted from server
+       float player_stepheight = touching_ground ? (BS * 0.6) : (BS * 0.2);
+
+       v3f accel_f = v3f(0, 0, 0);
+       const v3f initial_position = position;
+       const v3f initial_speed = m_speed;
+
+       collisionMoveResult result = collisionMoveSimple(env, m_client,
+               pos_max_d, m_collisionbox, player_stepheight, dtime,
+               &position, &m_speed, accel_f);
+
+       /*
+               If the player's feet touch the topside of any node, this is
+               set to true.
+
+               Player is allowed to jump when this is true.
+       */
+       bool touching_ground_was = touching_ground;
+       touching_ground = result.touching_ground;
+
+    //bool standing_on_unloaded = result.standing_on_unloaded;
+
+       /*
+               Check the nodes under the player to see from which node the
+               player is sneaking from, if any.  If the node from under
+               the player has been removed, the player falls.
+       */
+       f32 position_y_mod = 0.05 * BS;
+       if (m_sneak_node_bb_ymax > 0)
+               position_y_mod = m_sneak_node_bb_ymax - position_y_mod;
+       v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), BS);
+       if (m_sneak_node_exists &&
+                       nodemgr->get(map->getNodeNoEx(m_old_node_below)).name == "air" &&
+                       m_old_node_below_type != "air") {
+               // Old node appears to have been removed; that is,
+               // it wasn't air before but now it is
+               m_need_to_get_new_sneak_node = false;
+               m_sneak_node_exists = false;
+       } else if (nodemgr->get(map->getNodeNoEx(current_node)).name != "air") {
+               // We are on something, so make sure to recalculate the sneak
+               // node.
+               m_need_to_get_new_sneak_node = true;
+       }
+
+       if (m_need_to_get_new_sneak_node && physics_override_sneak) {
+               m_sneak_node_bb_ymax = 0;
+               v3s16 pos_i_bottom = floatToInt(position - v3f(0, position_y_mod, 0), BS);
+               v2f player_p2df(position.X, position.Z);
+               f32 min_distance_f = 100000.0 * BS;
+               // If already seeking from some node, compare to it.
+               v3s16 new_sneak_node = m_sneak_node;
+               for (s16 x= -1; x <= 1; x++)
+               for (s16 z= -1; z <= 1; z++) {
+                       v3s16 p = pos_i_bottom + v3s16(x, 0, z);
+                       v3f pf = intToFloat(p, BS);
+                       v2f node_p2df(pf.X, pf.Z);
+                       f32 distance_f = player_p2df.getDistanceFrom(node_p2df);
+                       f32 max_axis_distance_f = MYMAX(
+                                       std::fabs(player_p2df.X - node_p2df.X),
+                                       std::fabs(player_p2df.Y - node_p2df.Y));
+
+                       if (distance_f > min_distance_f ||
+                                       max_axis_distance_f > 0.5 * BS + sneak_max + 0.1 * BS)
+                               continue;
+
+                       // The node to be sneaked on has to be walkable
+                       node = map->getNodeNoEx(p, &is_valid_position);
+                       if (!is_valid_position || !nodemgr->get(node).walkable)
+                               continue;
+                       // And the node above it has to be nonwalkable
+                       node = map->getNodeNoEx(p + v3s16(0, 1, 0), &is_valid_position);
+                       if (!is_valid_position || nodemgr->get(node).walkable)
+                               continue;
+                       // If not 'sneak_glitch' the node 2 nodes above it has to be nonwalkable
+                       if (!physics_override_sneak_glitch) {
+                               node =map->getNodeNoEx(p + v3s16(0, 2, 0), &is_valid_position);
+                               if (!is_valid_position || nodemgr->get(node).walkable)
+                                       continue;
+                       }
+
+                       min_distance_f = distance_f;
+                       new_sneak_node = p;
+               }
+
+               bool sneak_node_found = (min_distance_f < 100000.0 * BS * 0.9);
+
+               m_sneak_node = new_sneak_node;
+               m_sneak_node_exists = sneak_node_found;
+
+               if (sneak_node_found) {
+                       f32 cb_max = 0;
+                       MapNode n = map->getNodeNoEx(m_sneak_node);
+                       std::vector<aabb3f> nodeboxes;
+                       n.getCollisionBoxes(nodemgr, &nodeboxes);
+                       for (const auto &box : nodeboxes) {
+                               if (box.MaxEdge.Y > cb_max)
+                                       cb_max = box.MaxEdge.Y;
+                       }
+                       m_sneak_node_bb_ymax = cb_max;
+               }
+
+               /*
+                       If sneaking, the player's collision box can be in air, so
+                       this has to be set explicitly
+               */
+               if (sneak_node_found && control.sneak)
+                       touching_ground = true;
+       }
+
+       /*
+               Set new position but keep sneak node set
+       */
+       bool sneak_node_exists = m_sneak_node_exists;
+       setPosition(position);
+       m_sneak_node_exists = sneak_node_exists;
+
+       /*
+               Report collisions
+       */
+       // Dont report if flying
+       if (collision_info && !(player_settings.free_move && fly_allowed)) {
+               for (const auto &info : result.collisions) {
+                       collision_info->push_back(info);
+               }
+       }
+
+       if (!result.standing_on_object && !touching_ground_was && touching_ground) {
+               m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_REGAIN_GROUND));
+               // Set camera impact value to be used for view bobbing
+               camera_impact = getSpeed().Y * -1;
+       }
+
+       {
+               camera_barely_in_ceiling = false;
+               v3s16 camera_np = floatToInt(getEyePosition(), BS);
+               MapNode n = map->getNodeNoEx(camera_np);
+               if (n.getContent() != CONTENT_IGNORE) {
+                       if (nodemgr->get(n).walkable && nodemgr->get(n).solidness == 2)
+                               camera_barely_in_ceiling = true;
+               }
+       }
+
+       /*
+               Update the node last under the player
+       */
+       m_old_node_below = floatToInt(position - v3f(0, BS / 2, 0), BS);
+       m_old_node_below_type = nodemgr->get(map->getNodeNoEx(m_old_node_below)).name;
+
+       /*
+               Check properties of the node on which the player is standing
+       */
+       const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(getStandingNodePos()));
+       // Determine if jumping is possible
+       m_can_jump = touching_ground && !in_liquid;
+       if (itemgroup_get(f.groups, "disable_jump"))
+               m_can_jump = false;
+       // Jump key pressed while jumping off from a bouncy block
+       if (m_can_jump && control.jump && itemgroup_get(f.groups, "bouncy") &&
+                       m_speed.Y >= -0.5 * BS) {
+               float jumpspeed = movement_speed_jump * physics_override_jump;
+               if (m_speed.Y > 1) {
+                       // Reduce boost when speed already is high
+                       m_speed.Y += jumpspeed / (1 + (m_speed.Y / 16 ));
+               } else {
+                       m_speed.Y += jumpspeed;
+               }
+               setSpeed(m_speed);
+               m_can_jump = false;
+       }
+
+       // Autojump
+       handleAutojump(dtime, env, result, initial_position, initial_speed, pos_max_d);
+}
+
+float LocalPlayer::getSlipFactor(Environment *env, const v3f &speedH)
+{
+       // Slip on slippery nodes
+       const NodeDefManager *nodemgr = env->getGameDef()->ndef();
+       Map *map = &env->getMap();
+       const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(
+                       getStandingNodePos()));
+       int slippery = 0;
+       if (f.walkable)
+               slippery = itemgroup_get(f.groups, "slippery");
+
+       if (slippery >= 1) {
+               if (speedH == v3f(0.0f)) {
+                       slippery = slippery * 2;
+               }
+               return core::clamp(1.0f / (slippery + 1), 0.001f, 1.0f);
+       }
+       return 1.0f;
+}
+
+void LocalPlayer::handleAutojump(f32 dtime, Environment *env,
+               const collisionMoveResult &result, const v3f &initial_position,
+               const v3f &initial_speed, f32 pos_max_d)
+{
+       PlayerSettings &player_settings = getPlayerSettings();
+       if (!player_settings.autojump)
+               return;
+
+       if (m_autojump) {
+               // release autojump after a given time
+               m_autojump_time -= dtime;
+               if (m_autojump_time <= 0.0f)
+                       m_autojump = false;
+               return;
+       }
+
+       bool control_forward = control.up ||
+                              (!control.up && !control.down &&
+                                              control.forw_move_joystick_axis < -0.05);
+       bool could_autojump =
+                       m_can_jump && !control.jump && !control.sneak && control_forward;
+       if (!could_autojump)
+               return;
+
+       bool horizontal_collision = false;
+       for (const auto &colinfo : result.collisions) {
+               if (colinfo.type == COLLISION_NODE && colinfo.plane != 1) {
+                       horizontal_collision = true;
+                       break; // one is enough
+               }
+       }
+
+       // must be running against something to trigger autojumping
+       if (!horizontal_collision)
+               return;
+
+       // check for nodes above
+       v3f headpos_min = m_position + m_collisionbox.MinEdge * 0.99f;
+       v3f headpos_max = m_position + m_collisionbox.MaxEdge * 0.99f;
+       headpos_min.Y = headpos_max.Y; // top face of collision box
+       v3s16 ceilpos_min = floatToInt(headpos_min, BS) + v3s16(0, 1, 0);
+       v3s16 ceilpos_max = floatToInt(headpos_max, BS) + v3s16(0, 1, 0);
+       const NodeDefManager *ndef = env->getGameDef()->ndef();
+       bool is_position_valid;
+       for (s16 z = ceilpos_min.Z; z <= ceilpos_max.Z; z++) {
+               for (s16 x = ceilpos_min.X; x <= ceilpos_max.X; x++) {
+                       MapNode n = env->getMap().getNodeNoEx(v3s16(x, ceilpos_max.Y, z), &is_position_valid);
+
+                       if (!is_position_valid)
+                               break;  // won't collide with the void outside
+                       if (n.getContent() == CONTENT_IGNORE)
+                               return; // players collide with ignore blocks -> same as walkable
+                       const ContentFeatures &f = ndef->get(n);
+                       if (f.walkable)
+                               return; // would bump head, don't jump
+               }
+       }
+
+       float jump_height = 1.1f; // TODO: better than a magic number
+       v3f jump_pos = initial_position + v3f(0.0f, jump_height * BS, 0.0f);
+       v3f jump_speed = initial_speed;
+
+       // try at peak of jump, zero step height
+       collisionMoveResult jump_result = collisionMoveSimple(env, m_client, pos_max_d,
+                       m_collisionbox, 0.0f, dtime, &jump_pos, &jump_speed,
+                       v3f(0, 0, 0));
+
+       // see if we can get a little bit farther horizontally if we had
+       // jumped
+       v3f run_delta = m_position - initial_position;
+       run_delta.Y = 0.0f;
+       v3f jump_delta = jump_pos - initial_position;
+       jump_delta.Y = 0.0f;
+       if (jump_delta.getLengthSQ() > run_delta.getLengthSQ() * 1.01f) {
+               m_autojump = true;
+               m_autojump_time = 0.1f;
+       }
+}
diff --git a/src/client/localplayer.h b/src/client/localplayer.h
new file mode 100644 (file)
index 0000000..7148bc4
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "player.h"
+#include "environment.h"
+#include "constants.h"
+#include "settings.h"
+#include <list>
+
+class Client;
+class Environment;
+class GenericCAO;
+class ClientActiveObject;
+class ClientEnvironment;
+class IGameDef;
+struct collisionMoveResult;
+
+enum LocalPlayerAnimations
+{
+       NO_ANIM,
+       WALK_ANIM,
+       DIG_ANIM,
+       WD_ANIM
+}; // no local animation, walking, digging, both
+
+class LocalPlayer : public Player
+{
+public:
+       LocalPlayer(Client *client, const char *name);
+       virtual ~LocalPlayer() = default;
+
+       ClientActiveObject *parent = nullptr;
+
+       // Initialize hp to 0, so that no hearts will be shown if server
+       // doesn't support health points
+       u16 hp = 0;
+       bool isAttached = false;
+       bool touching_ground = false;
+       // This oscillates so that the player jumps a bit above the surface
+       bool in_liquid = false;
+       // This is more stable and defines the maximum speed of the player
+       bool in_liquid_stable = false;
+       // Gets the viscosity of water to calculate friction
+       u8 liquid_viscosity = 0;
+       bool is_climbing = false;
+       bool swimming_vertical = false;
+
+       float physics_override_speed = 1.0f;
+       float physics_override_jump = 1.0f;
+       float physics_override_gravity = 1.0f;
+       bool physics_override_sneak = true;
+       bool physics_override_sneak_glitch = false;
+       // Temporary option for old move code
+       bool physics_override_new_move = true;
+
+       v3f overridePosition;
+
+       void move(f32 dtime, Environment *env, f32 pos_max_d);
+       void move(f32 dtime, Environment *env, f32 pos_max_d,
+                       std::vector<CollisionInfo> *collision_info);
+       // Temporary option for old move code
+       void old_move(f32 dtime, Environment *env, f32 pos_max_d,
+                       std::vector<CollisionInfo> *collision_info);
+
+       void applyControl(float dtime, Environment *env);
+
+       v3s16 getStandingNodePos();
+       v3s16 getFootstepNodePos();
+
+       // Used to check if anything changed and prevent sending packets if not
+       v3f last_position;
+       v3f last_speed;
+       float last_pitch = 0.0f;
+       float last_yaw = 0.0f;
+       unsigned int last_keyPressed = 0;
+       u8 last_camera_fov = 0;
+       u8 last_wanted_range = 0;
+
+       float camera_impact = 0.0f;
+
+       bool makes_footstep_sound = true;
+
+       int last_animation = NO_ANIM;
+       float last_animation_speed;
+
+       std::string hotbar_image = "";
+       std::string hotbar_selected_image = "";
+
+       video::SColor light_color = video::SColor(255, 255, 255, 255);
+
+       float hurt_tilt_timer = 0.0f;
+       float hurt_tilt_strength = 0.0f;
+
+       GenericCAO *getCAO() const { return m_cao; }
+
+       void setCAO(GenericCAO *toset)
+       {
+               assert(!m_cao); // Pre-condition
+               m_cao = toset;
+       }
+
+       u32 maxHudId() const { return hud.size(); }
+
+       u16 getBreath() const { return m_breath; }
+       void setBreath(u16 breath) { m_breath = breath; }
+
+       v3s16 getLightPosition() const;
+
+       void setYaw(f32 yaw) { m_yaw = yaw; }
+       f32 getYaw() const { return m_yaw; }
+
+       void setPitch(f32 pitch) { m_pitch = pitch; }
+       f32 getPitch() const { return m_pitch; }
+
+       inline void setPosition(const v3f &position)
+       {
+               m_position = position;
+               m_sneak_node_exists = false;
+       }
+
+       v3f getPosition() const { return m_position; }
+       v3f getEyePosition() const { return m_position + getEyeOffset(); }
+       v3f getEyeOffset() const;
+       void setEyeHeight(float eye_height) { m_eye_height = eye_height; }
+
+       void setCollisionbox(const aabb3f &box) { m_collisionbox = box; }
+
+       float getZoomFOV() const { return m_zoom_fov; }
+       void setZoomFOV(float zoom_fov) { m_zoom_fov = zoom_fov; }
+
+       bool getAutojump() const { return m_autojump; }
+
+private:
+       void accelerateHorizontal(const v3f &target_speed, const f32 max_increase);
+       void accelerateVertical(const v3f &target_speed, const f32 max_increase);
+       bool updateSneakNode(Map *map, const v3f &position, const v3f &sneak_max);
+       float getSlipFactor(Environment *env, const v3f &speedH);
+       void handleAutojump(f32 dtime, Environment *env,
+                       const collisionMoveResult &result,
+                       const v3f &position_before_move, const v3f &speed_before_move,
+                       f32 pos_max_d);
+
+       v3f m_position;
+       v3s16 m_standing_node;
+
+       v3s16 m_sneak_node = v3s16(32767, 32767, 32767);
+       // Stores the top bounding box of m_sneak_node
+       aabb3f m_sneak_node_bb_top = aabb3f(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+       // Whether the player is allowed to sneak
+       bool m_sneak_node_exists = false;
+       // Whether a "sneak ladder" structure is detected at the players pos
+       // see detectSneakLadder() in the .cpp for more info (always false if disabled)
+       bool m_sneak_ladder_detected = false;
+
+       // ***** Variables for temporary option of the old move code *****
+       // Stores the max player uplift by m_sneak_node
+       f32 m_sneak_node_bb_ymax = 0.0f;
+       // Whether recalculation of m_sneak_node and its top bbox is needed
+       bool m_need_to_get_new_sneak_node = true;
+       // Node below player, used to determine whether it has been removed,
+       // and its old type
+       v3s16 m_old_node_below = v3s16(32767, 32767, 32767);
+       std::string m_old_node_below_type = "air";
+       // ***** End of variables for temporary option *****
+
+       bool m_can_jump = false;
+       u16 m_breath = PLAYER_MAX_BREATH_DEFAULT;
+       f32 m_yaw = 0.0f;
+       f32 m_pitch = 0.0f;
+       bool camera_barely_in_ceiling = false;
+       aabb3f m_collisionbox = aabb3f(-BS * 0.30f, 0.0f, -BS * 0.30f, BS * 0.30f,
+                       BS * 1.75f, BS * 0.30f);
+       float m_eye_height = 1.625f;
+       float m_zoom_fov = 0.0f;
+       bool m_autojump = false;
+       float m_autojump_time = 0.0f;
+
+       GenericCAO *m_cao = nullptr;
+       Client *m_client;
+};
diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp
new file mode 100644 (file)
index 0000000..ed8a073
--- /dev/null
@@ -0,0 +1,1389 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 "mapblock_mesh.h"
+#include "client.h"
+#include "mapblock.h"
+#include "map.h"
+#include "profiler.h"
+#include "shader.h"
+#include "mesh.h"
+#include "minimap.h"
+#include "content_mapblock.h"
+#include "util/directiontables.h"
+#include "client/meshgen/collector.h"
+#include "client/renderingengine.h"
+#include <array>
+
+/*
+       MeshMakeData
+*/
+
+MeshMakeData::MeshMakeData(Client *client, bool use_shaders,
+               bool use_tangent_vertices):
+       m_client(client),
+       m_use_shaders(use_shaders),
+       m_use_tangent_vertices(use_tangent_vertices)
+{}
+
+void MeshMakeData::fillBlockDataBegin(const v3s16 &blockpos)
+{
+       m_blockpos = blockpos;
+
+       v3s16 blockpos_nodes = m_blockpos*MAP_BLOCKSIZE;
+
+       m_vmanip.clear();
+       VoxelArea voxel_area(blockpos_nodes - v3s16(1,1,1) * MAP_BLOCKSIZE,
+                       blockpos_nodes + v3s16(1,1,1) * MAP_BLOCKSIZE*2-v3s16(1,1,1));
+       m_vmanip.addArea(voxel_area);
+}
+
+void MeshMakeData::fillBlockData(const v3s16 &block_offset, MapNode *data)
+{
+       v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
+       VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
+
+       v3s16 bp = m_blockpos + block_offset;
+       v3s16 blockpos_nodes = bp * MAP_BLOCKSIZE;
+       m_vmanip.copyFrom(data, data_area, v3s16(0,0,0), blockpos_nodes, data_size);
+}
+
+void MeshMakeData::fill(MapBlock *block)
+{
+       fillBlockDataBegin(block->getPos());
+
+       fillBlockData(v3s16(0,0,0), block->getData());
+
+       // Get map for reading neighbor blocks
+       Map *map = block->getParent();
+
+       for (const v3s16 &dir : g_26dirs) {
+               v3s16 bp = m_blockpos + dir;
+               MapBlock *b = map->getBlockNoCreateNoEx(bp);
+               if(b)
+                       fillBlockData(dir, b->getData());
+       }
+}
+
+void MeshMakeData::fillSingleNode(MapNode *node)
+{
+       m_blockpos = v3s16(0,0,0);
+
+       v3s16 blockpos_nodes = v3s16(0,0,0);
+       VoxelArea area(blockpos_nodes-v3s16(1,1,1)*MAP_BLOCKSIZE,
+                       blockpos_nodes+v3s16(1,1,1)*MAP_BLOCKSIZE*2-v3s16(1,1,1));
+       s32 volume = area.getVolume();
+       s32 our_node_index = area.index(1,1,1);
+
+       // Allocate this block + neighbors
+       m_vmanip.clear();
+       m_vmanip.addArea(area);
+
+       // Fill in data
+       MapNode *data = new MapNode[volume];
+       for(s32 i = 0; i < volume; i++)
+       {
+               if (i == our_node_index)
+                       data[i] = *node;
+               else
+                       data[i] = MapNode(CONTENT_AIR, LIGHT_MAX, 0);
+       }
+       m_vmanip.copyFrom(data, area, area.MinEdge, area.MinEdge, area.getExtent());
+       delete[] data;
+}
+
+void MeshMakeData::setCrack(int crack_level, v3s16 crack_pos)
+{
+       if (crack_level >= 0)
+               m_crack_pos_relative = crack_pos - m_blockpos*MAP_BLOCKSIZE;
+}
+
+void MeshMakeData::setSmoothLighting(bool smooth_lighting)
+{
+       m_smooth_lighting = smooth_lighting;
+}
+
+/*
+       Light and vertex color functions
+*/
+
+/*
+       Calculate non-smooth lighting at interior of node.
+       Single light bank.
+*/
+static u8 getInteriorLight(enum LightBank bank, MapNode n, s32 increment,
+       const NodeDefManager *ndef)
+{
+       u8 light = n.getLight(bank, ndef);
+       if (light > 0)
+               light = rangelim(light + increment, 0, LIGHT_SUN);
+       return decode_light(light);
+}
+
+/*
+       Calculate non-smooth lighting at interior of node.
+       Both light banks.
+*/
+u16 getInteriorLight(MapNode n, s32 increment, const NodeDefManager *ndef)
+{
+       u16 day = getInteriorLight(LIGHTBANK_DAY, n, increment, ndef);
+       u16 night = getInteriorLight(LIGHTBANK_NIGHT, n, increment, ndef);
+       return day | (night << 8);
+}
+
+/*
+       Calculate non-smooth lighting at face of node.
+       Single light bank.
+*/
+static u8 getFaceLight(enum LightBank bank, MapNode n, MapNode n2,
+       v3s16 face_dir, const NodeDefManager *ndef)
+{
+       u8 light;
+       u8 l1 = n.getLight(bank, ndef);
+       u8 l2 = n2.getLight(bank, ndef);
+       if(l1 > l2)
+               light = l1;
+       else
+               light = l2;
+
+       // Boost light level for light sources
+       u8 light_source = MYMAX(ndef->get(n).light_source,
+                       ndef->get(n2).light_source);
+       if(light_source > light)
+               light = light_source;
+
+       return decode_light(light);
+}
+
+/*
+       Calculate non-smooth lighting at face of node.
+       Both light banks.
+*/
+u16 getFaceLight(MapNode n, MapNode n2, const v3s16 &face_dir,
+       const NodeDefManager *ndef)
+{
+       u16 day = getFaceLight(LIGHTBANK_DAY, n, n2, face_dir, ndef);
+       u16 night = getFaceLight(LIGHTBANK_NIGHT, n, n2, face_dir, ndef);
+       return day | (night << 8);
+}
+
+/*
+       Calculate smooth lighting at the XYZ- corner of p.
+       Both light banks
+*/
+static u16 getSmoothLightCombined(const v3s16 &p,
+       const std::array<v3s16,8> &dirs, MeshMakeData *data)
+{
+       const NodeDefManager *ndef = data->m_client->ndef();
+
+       u16 ambient_occlusion = 0;
+       u16 light_count = 0;
+       u8 light_source_max = 0;
+       u16 light_day = 0;
+       u16 light_night = 0;
+       bool direct_sunlight = false;
+
+       auto add_node = [&] (u8 i, bool obstructed = false) -> bool {
+               if (obstructed) {
+                       ambient_occlusion++;
+                       return false;
+               }
+               MapNode n = data->m_vmanip.getNodeNoExNoEmerge(p + dirs[i]);
+               if (n.getContent() == CONTENT_IGNORE)
+                       return true;
+               const ContentFeatures &f = ndef->get(n);
+               if (f.light_source > light_source_max)
+                       light_source_max = f.light_source;
+               // Check f.solidness because fast-style leaves look better this way
+               if (f.param_type == CPT_LIGHT && f.solidness != 2) {
+                       u8 light_level_day = n.getLightNoChecks(LIGHTBANK_DAY, &f);
+                       u8 light_level_night = n.getLightNoChecks(LIGHTBANK_NIGHT, &f);
+                       if (light_level_day == LIGHT_SUN)
+                               direct_sunlight = true;
+                       light_day += decode_light(light_level_day);
+                       light_night += decode_light(light_level_night);
+                       light_count++;
+               } else {
+                       ambient_occlusion++;
+               }
+               return f.light_propagates;
+       };
+
+       std::array<bool, 4> obstructed = {{ 1, 1, 1, 1 }};
+       add_node(0);
+       bool opaque1 = !add_node(1);
+       bool opaque2 = !add_node(2);
+       bool opaque3 = !add_node(3);
+       obstructed[0] = opaque1 && opaque2;
+       obstructed[1] = opaque1 && opaque3;
+       obstructed[2] = opaque2 && opaque3;
+       for (u8 k = 0; k < 3; ++k)
+               if (add_node(k + 4, obstructed[k]))
+                       obstructed[3] = false;
+       if (add_node(7, obstructed[3])) { // wrap light around nodes
+               ambient_occlusion -= 3;
+               for (u8 k = 0; k < 3; ++k)
+                       add_node(k + 4, !obstructed[k]);
+       }
+
+       if (light_count == 0) {
+               light_day = light_night = 0;
+       } else {
+               light_day /= light_count;
+               light_night /= light_count;
+       }
+
+       // boost direct sunlight, if any
+       if (direct_sunlight)
+               light_day = 0xFF;
+
+       // Boost brightness around light sources
+       bool skip_ambient_occlusion_day = false;
+       if (decode_light(light_source_max) >= light_day) {
+               light_day = decode_light(light_source_max);
+               skip_ambient_occlusion_day = true;
+       }
+
+       bool skip_ambient_occlusion_night = false;
+       if(decode_light(light_source_max) >= light_night) {
+               light_night = decode_light(light_source_max);
+               skip_ambient_occlusion_night = true;
+       }
+
+       if (ambient_occlusion > 4) {
+               static thread_local const float ao_gamma = rangelim(
+                       g_settings->getFloat("ambient_occlusion_gamma"), 0.25, 4.0);
+
+               // Table of gamma space multiply factors.
+               static thread_local const float light_amount[3] = {
+                       powf(0.75, 1.0 / ao_gamma),
+                       powf(0.5,  1.0 / ao_gamma),
+                       powf(0.25, 1.0 / ao_gamma)
+               };
+
+               //calculate table index for gamma space multiplier
+               ambient_occlusion -= 5;
+
+               if (!skip_ambient_occlusion_day)
+                       light_day = rangelim(core::round32(
+                                       light_day * light_amount[ambient_occlusion]), 0, 255);
+               if (!skip_ambient_occlusion_night)
+                       light_night = rangelim(core::round32(
+                                       light_night * light_amount[ambient_occlusion]), 0, 255);
+       }
+
+       return light_day | (light_night << 8);
+}
+
+/*
+       Calculate smooth lighting at the given corner of p.
+       Both light banks.
+       Node at p is solid, and thus the lighting is face-dependent.
+*/
+u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, MeshMakeData *data)
+{
+       return getSmoothLightTransparent(p + face_dir, corner - 2 * face_dir, data);
+}
+
+/*
+       Calculate smooth lighting at the given corner of p.
+       Both light banks.
+       Node at p is not solid, and the lighting is not face-dependent.
+*/
+u16 getSmoothLightTransparent(const v3s16 &p, const v3s16 &corner, MeshMakeData *data)
+{
+       const std::array<v3s16,8> dirs = {{
+               // Always shine light
+               v3s16(0,0,0),
+               v3s16(corner.X,0,0),
+               v3s16(0,corner.Y,0),
+               v3s16(0,0,corner.Z),
+
+               // Can be obstructed
+               v3s16(corner.X,corner.Y,0),
+               v3s16(corner.X,0,corner.Z),
+               v3s16(0,corner.Y,corner.Z),
+               v3s16(corner.X,corner.Y,corner.Z)
+       }};
+       return getSmoothLightCombined(p, dirs, data);
+}
+
+void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio){
+       f32 rg = daynight_ratio / 1000.0f - 0.04f;
+       f32 b = (0.98f * daynight_ratio) / 1000.0f + 0.078f;
+       sunlight->r = rg;
+       sunlight->g = rg;
+       sunlight->b = b;
+}
+
+void final_color_blend(video::SColor *result,
+               u16 light, u32 daynight_ratio)
+{
+       video::SColorf dayLight;
+       get_sunlight_color(&dayLight, daynight_ratio);
+       final_color_blend(result,
+               encode_light(light, 0), dayLight);
+}
+
+void final_color_blend(video::SColor *result,
+               const video::SColor &data, const video::SColorf &dayLight)
+{
+       static const video::SColorf artificialColor(1.04f, 1.04f, 1.04f);
+
+       video::SColorf c(data);
+       f32 n = 1 - c.a;
+
+       f32 r = c.r * (c.a * dayLight.r + n * artificialColor.r) * 2.0f;
+       f32 g = c.g * (c.a * dayLight.g + n * artificialColor.g) * 2.0f;
+       f32 b = c.b * (c.a * dayLight.b + n * artificialColor.b) * 2.0f;
+
+       // Emphase blue a bit in darker places
+       // Each entry of this array represents a range of 8 blue levels
+       static const u8 emphase_blue_when_dark[32] = {
+               1, 4, 6, 6, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0,
+               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+       };
+
+       b += emphase_blue_when_dark[irr::core::clamp((s32) ((r + g + b) / 3 * 255),
+               0, 255) / 8] / 255.0f;
+
+       result->setRed(core::clamp((s32) (r * 255.0f), 0, 255));
+       result->setGreen(core::clamp((s32) (g * 255.0f), 0, 255));
+       result->setBlue(core::clamp((s32) (b * 255.0f), 0, 255));
+}
+
+/*
+       Mesh generation helpers
+*/
+
+/*
+       vertex_dirs: v3s16[4]
+*/
+static void getNodeVertexDirs(const v3s16 &dir, v3s16 *vertex_dirs)
+{
+       /*
+               If looked from outside the node towards the face, the corners are:
+               0: bottom-right
+               1: bottom-left
+               2: top-left
+               3: top-right
+       */
+       if (dir == v3s16(0, 0, 1)) {
+               // If looking towards z+, this is the face that is behind
+               // the center point, facing towards z+.
+               vertex_dirs[0] = v3s16(-1,-1, 1);
+               vertex_dirs[1] = v3s16( 1,-1, 1);
+               vertex_dirs[2] = v3s16( 1, 1, 1);
+               vertex_dirs[3] = v3s16(-1, 1, 1);
+       } else if (dir == v3s16(0, 0, -1)) {
+               // faces towards Z-
+               vertex_dirs[0] = v3s16( 1,-1,-1);
+               vertex_dirs[1] = v3s16(-1,-1,-1);
+               vertex_dirs[2] = v3s16(-1, 1,-1);
+               vertex_dirs[3] = v3s16( 1, 1,-1);
+       } else if (dir == v3s16(1, 0, 0)) {
+               // faces towards X+
+               vertex_dirs[0] = v3s16( 1,-1, 1);
+               vertex_dirs[1] = v3s16( 1,-1,-1);
+               vertex_dirs[2] = v3s16( 1, 1,-1);
+               vertex_dirs[3] = v3s16( 1, 1, 1);
+       } else if (dir == v3s16(-1, 0, 0)) {
+               // faces towards X-
+               vertex_dirs[0] = v3s16(-1,-1,-1);
+               vertex_dirs[1] = v3s16(-1,-1, 1);
+               vertex_dirs[2] = v3s16(-1, 1, 1);
+               vertex_dirs[3] = v3s16(-1, 1,-1);
+       } else if (dir == v3s16(0, 1, 0)) {
+               // faces towards Y+ (assume Z- as "down" in texture)
+               vertex_dirs[0] = v3s16( 1, 1,-1);
+               vertex_dirs[1] = v3s16(-1, 1,-1);
+               vertex_dirs[2] = v3s16(-1, 1, 1);
+               vertex_dirs[3] = v3s16( 1, 1, 1);
+       } else if (dir == v3s16(0, -1, 0)) {
+               // faces towards Y- (assume Z+ as "down" in texture)
+               vertex_dirs[0] = v3s16( 1,-1, 1);
+               vertex_dirs[1] = v3s16(-1,-1, 1);
+               vertex_dirs[2] = v3s16(-1,-1,-1);
+               vertex_dirs[3] = v3s16( 1,-1,-1);
+       }
+}
+
+static void getNodeTextureCoords(v3f base, const v3f &scale, const v3s16 &dir, float *u, float *v)
+{
+       if (dir.X > 0 || dir.Y > 0 || dir.Z < 0)
+               base -= scale;
+       if (dir == v3s16(0,0,1)) {
+               *u = -base.X - 1;
+               *v = -base.Y - 1;
+       } else if (dir == v3s16(0,0,-1)) {
+               *u = base.X + 1;
+               *v = -base.Y - 2;
+       } else if (dir == v3s16(1,0,0)) {
+               *u = base.Z + 1;
+               *v = -base.Y - 2;
+       } else if (dir == v3s16(-1,0,0)) {
+               *u = -base.Z - 1;
+               *v = -base.Y - 1;
+       } else if (dir == v3s16(0,1,0)) {
+               *u = base.X + 1;
+               *v = -base.Z - 2;
+       } else if (dir == v3s16(0,-1,0)) {
+               *u = base.X;
+               *v = base.Z;
+       }
+}
+
+struct FastFace
+{
+       TileSpec tile;
+       video::S3DVertex vertices[4]; // Precalculated vertices
+       /*!
+        * The face is divided into two triangles. If this is true,
+        * vertices 0 and 2 are connected, othervise vertices 1 and 3
+        * are connected.
+        */
+       bool vertex_0_2_connected;
+};
+
+static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li3,
+       const v3f &tp, const v3f &p, const v3s16 &dir, const v3f &scale, std::vector<FastFace> &dest)
+{
+       // Position is at the center of the cube.
+       v3f pos = p * BS;
+
+       float x0 = 0.0f;
+       float y0 = 0.0f;
+       float w = 1.0f;
+       float h = 1.0f;
+
+       v3f vertex_pos[4];
+       v3s16 vertex_dirs[4];
+       getNodeVertexDirs(dir, vertex_dirs);
+       if (tile.world_aligned)
+               getNodeTextureCoords(tp, scale, dir, &x0, &y0);
+
+       v3s16 t;
+       u16 t1;
+       switch (tile.rotation) {
+       case 0:
+               break;
+       case 1: //R90
+               t = vertex_dirs[0];
+               vertex_dirs[0] = vertex_dirs[3];
+               vertex_dirs[3] = vertex_dirs[2];
+               vertex_dirs[2] = vertex_dirs[1];
+               vertex_dirs[1] = t;
+               t1  = li0;
+               li0 = li3;
+               li3 = li2;
+               li2 = li1;
+               li1 = t1;
+               break;
+       case 2: //R180
+               t = vertex_dirs[0];
+               vertex_dirs[0] = vertex_dirs[2];
+               vertex_dirs[2] = t;
+               t = vertex_dirs[1];
+               vertex_dirs[1] = vertex_dirs[3];
+               vertex_dirs[3] = t;
+               t1  = li0;
+               li0 = li2;
+               li2 = t1;
+               t1  = li1;
+               li1 = li3;
+               li3 = t1;
+               break;
+       case 3: //R270
+               t = vertex_dirs[0];
+               vertex_dirs[0] = vertex_dirs[1];
+               vertex_dirs[1] = vertex_dirs[2];
+               vertex_dirs[2] = vertex_dirs[3];
+               vertex_dirs[3] = t;
+               t1  = li0;
+               li0 = li1;
+               li1 = li2;
+               li2 = li3;
+               li3 = t1;
+               break;
+       case 4: //FXR90
+               t = vertex_dirs[0];
+               vertex_dirs[0] = vertex_dirs[3];
+               vertex_dirs[3] = vertex_dirs[2];
+               vertex_dirs[2] = vertex_dirs[1];
+               vertex_dirs[1] = t;
+               t1  = li0;
+               li0 = li3;
+               li3 = li2;
+               li2 = li1;
+               li1 = t1;
+               y0 += h;
+               h *= -1;
+               break;
+       case 5: //FXR270
+               t = vertex_dirs[0];
+               vertex_dirs[0] = vertex_dirs[1];
+               vertex_dirs[1] = vertex_dirs[2];
+               vertex_dirs[2] = vertex_dirs[3];
+               vertex_dirs[3] = t;
+               t1  = li0;
+               li0 = li1;
+               li1 = li2;
+               li2 = li3;
+               li3 = t1;
+               y0 += h;
+               h *= -1;
+               break;
+       case 6: //FYR90
+               t = vertex_dirs[0];
+               vertex_dirs[0] = vertex_dirs[3];
+               vertex_dirs[3] = vertex_dirs[2];
+               vertex_dirs[2] = vertex_dirs[1];
+               vertex_dirs[1] = t;
+               t1  = li0;
+               li0 = li3;
+               li3 = li2;
+               li2 = li1;
+               li1 = t1;
+               x0 += w;
+               w *= -1;
+               break;
+       case 7: //FYR270
+               t = vertex_dirs[0];
+               vertex_dirs[0] = vertex_dirs[1];
+               vertex_dirs[1] = vertex_dirs[2];
+               vertex_dirs[2] = vertex_dirs[3];
+               vertex_dirs[3] = t;
+               t1  = li0;
+               li0 = li1;
+               li1 = li2;
+               li2 = li3;
+               li3 = t1;
+               x0 += w;
+               w *= -1;
+               break;
+       case 8: //FX
+               y0 += h;
+               h *= -1;
+               break;
+       case 9: //FY
+               x0 += w;
+               w *= -1;
+               break;
+       default:
+               break;
+       }
+
+       for (u16 i = 0; i < 4; i++) {
+               vertex_pos[i] = v3f(
+                               BS / 2 * vertex_dirs[i].X,
+                               BS / 2 * vertex_dirs[i].Y,
+                               BS / 2 * vertex_dirs[i].Z
+               );
+       }
+
+       for (v3f &vpos : vertex_pos) {
+               vpos.X *= scale.X;
+               vpos.Y *= scale.Y;
+               vpos.Z *= scale.Z;
+               vpos += pos;
+       }
+
+       f32 abs_scale = 1.0f;
+       if      (scale.X < 0.999f || scale.X > 1.001f) abs_scale = scale.X;
+       else if (scale.Y < 0.999f || scale.Y > 1.001f) abs_scale = scale.Y;
+       else if (scale.Z < 0.999f || scale.Z > 1.001f) abs_scale = scale.Z;
+
+       v3f normal(dir.X, dir.Y, dir.Z);
+
+       u16 li[4] = { li0, li1, li2, li3 };
+       u16 day[4];
+       u16 night[4];
+
+       for (u8 i = 0; i < 4; i++) {
+               day[i] = li[i] >> 8;
+               night[i] = li[i] & 0xFF;
+       }
+
+       bool vertex_0_2_connected = abs(day[0] - day[2]) + abs(night[0] - night[2])
+                       < abs(day[1] - day[3]) + abs(night[1] - night[3]);
+
+       v2f32 f[4] = {
+               core::vector2d<f32>(x0 + w * abs_scale, y0 + h),
+               core::vector2d<f32>(x0, y0 + h),
+               core::vector2d<f32>(x0, y0),
+               core::vector2d<f32>(x0 + w * abs_scale, y0) };
+
+       // equivalent to dest.push_back(FastFace()) but faster
+       dest.emplace_back();
+       FastFace& face = *dest.rbegin();
+
+       for (u8 i = 0; i < 4; i++) {
+               video::SColor c = encode_light(li[i], tile.emissive_light);
+               if (!tile.emissive_light)
+                       applyFacesShading(c, normal);
+
+               face.vertices[i] = video::S3DVertex(vertex_pos[i], normal, c, f[i]);
+       }
+
+       /*
+               Revert triangles for nicer looking gradient if the
+               brightness of vertices 1 and 3 differ less than
+               the brightness of vertices 0 and 2.
+               */
+       face.vertex_0_2_connected = vertex_0_2_connected;
+       face.tile = tile;
+}
+
+/*
+       Nodes make a face if contents differ and solidness differs.
+       Return value:
+               0: No face
+               1: Face uses m1's content
+               2: Face uses m2's content
+       equivalent: Whether the blocks share the same face (eg. water and glass)
+
+       TODO: Add 3: Both faces drawn with backface culling, remove equivalent
+*/
+static u8 face_contents(content_t m1, content_t m2, bool *equivalent,
+       const NodeDefManager *ndef)
+{
+       *equivalent = false;
+
+       if (m1 == m2 || m1 == CONTENT_IGNORE || m2 == CONTENT_IGNORE)
+               return 0;
+
+       const ContentFeatures &f1 = ndef->get(m1);
+       const ContentFeatures &f2 = ndef->get(m2);
+
+       // Contents don't differ for different forms of same liquid
+       if (f1.sameLiquid(f2))
+               return 0;
+
+       u8 c1 = f1.solidness;
+       u8 c2 = f2.solidness;
+
+       if (c1 == c2)
+               return 0;
+
+       if (c1 == 0)
+               c1 = f1.visual_solidness;
+       else if (c2 == 0)
+               c2 = f2.visual_solidness;
+
+       if (c1 == c2) {
+               *equivalent = true;
+               // If same solidness, liquid takes precense
+               if (f1.isLiquid())
+                       return 1;
+               if (f2.isLiquid())
+                       return 2;
+       }
+
+       if (c1 > c2)
+               return 1;
+
+       return 2;
+}
+
+/*
+       Gets nth node tile (0 <= n <= 5).
+*/
+void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, TileSpec &tile)
+{
+       const NodeDefManager *ndef = data->m_client->ndef();
+       const ContentFeatures &f = ndef->get(mn);
+       tile = f.tiles[tileindex];
+       bool has_crack = p == data->m_crack_pos_relative;
+       for (TileLayer &layer : tile.layers) {
+               if (layer.texture_id == 0)
+                       continue;
+               if (!layer.has_color)
+                       mn.getColor(f, &(layer.color));
+               // Apply temporary crack
+               if (has_crack)
+                       layer.material_flags |= MATERIAL_FLAG_CRACK;
+       }
+}
+
+/*
+       Gets node tile given a face direction.
+*/
+void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *data, TileSpec &tile)
+{
+       const NodeDefManager *ndef = data->m_client->ndef();
+
+       // Direction must be (1,0,0), (-1,0,0), (0,1,0), (0,-1,0),
+       // (0,0,1), (0,0,-1) or (0,0,0)
+       assert(dir.X * dir.X + dir.Y * dir.Y + dir.Z * dir.Z <= 1);
+
+       // Convert direction to single integer for table lookup
+       //  0 = (0,0,0)
+       //  1 = (1,0,0)
+       //  2 = (0,1,0)
+       //  3 = (0,0,1)
+       //  4 = invalid, treat as (0,0,0)
+       //  5 = (0,0,-1)
+       //  6 = (0,-1,0)
+       //  7 = (-1,0,0)
+       u8 dir_i = ((dir.X + 2 * dir.Y + 3 * dir.Z) & 7) * 2;
+
+       // Get rotation for things like chests
+       u8 facedir = mn.getFaceDir(ndef);
+
+       static const u16 dir_to_tile[24 * 16] =
+       {
+               // 0     +X    +Y    +Z           -Z    -Y    -X   ->   value=tile,rotation
+                  0,0,  2,0 , 0,0 , 4,0 ,  0,0,  5,0 , 1,0 , 3,0 ,  // rotate around y+ 0 - 3
+                  0,0,  4,0 , 0,3 , 3,0 ,  0,0,  2,0 , 1,1 , 5,0 ,
+                  0,0,  3,0 , 0,2 , 5,0 ,  0,0,  4,0 , 1,2 , 2,0 ,
+                  0,0,  5,0 , 0,1 , 2,0 ,  0,0,  3,0 , 1,3 , 4,0 ,
+
+                  0,0,  2,3 , 5,0 , 0,2 ,  0,0,  1,0 , 4,2 , 3,1 ,  // rotate around z+ 4 - 7
+                  0,0,  4,3 , 2,0 , 0,1 ,  0,0,  1,1 , 3,2 , 5,1 ,
+                  0,0,  3,3 , 4,0 , 0,0 ,  0,0,  1,2 , 5,2 , 2,1 ,
+                  0,0,  5,3 , 3,0 , 0,3 ,  0,0,  1,3 , 2,2 , 4,1 ,
+
+                  0,0,  2,1 , 4,2 , 1,2 ,  0,0,  0,0 , 5,0 , 3,3 ,  // rotate around z- 8 - 11
+                  0,0,  4,1 , 3,2 , 1,3 ,  0,0,  0,3 , 2,0 , 5,3 ,
+                  0,0,  3,1 , 5,2 , 1,0 ,  0,0,  0,2 , 4,0 , 2,3 ,
+                  0,0,  5,1 , 2,2 , 1,1 ,  0,0,  0,1 , 3,0 , 4,3 ,
+
+                  0,0,  0,3 , 3,3 , 4,1 ,  0,0,  5,3 , 2,3 , 1,3 ,  // rotate around x+ 12 - 15
+                  0,0,  0,2 , 5,3 , 3,1 ,  0,0,  2,3 , 4,3 , 1,0 ,
+                  0,0,  0,1 , 2,3 , 5,1 ,  0,0,  4,3 , 3,3 , 1,1 ,
+                  0,0,  0,0 , 4,3 , 2,1 ,  0,0,  3,3 , 5,3 , 1,2 ,
+
+                  0,0,  1,1 , 2,1 , 4,3 ,  0,0,  5,1 , 3,1 , 0,1 ,  // rotate around x- 16 - 19
+                  0,0,  1,2 , 4,1 , 3,3 ,  0,0,  2,1 , 5,1 , 0,0 ,
+                  0,0,  1,3 , 3,1 , 5,3 ,  0,0,  4,1 , 2,1 , 0,3 ,
+                  0,0,  1,0 , 5,1 , 2,3 ,  0,0,  3,1 , 4,1 , 0,2 ,
+
+                  0,0,  3,2 , 1,2 , 4,2 ,  0,0,  5,2 , 0,2 , 2,2 ,  // rotate around y- 20 - 23
+                  0,0,  5,2 , 1,3 , 3,2 ,  0,0,  2,2 , 0,1 , 4,2 ,
+                  0,0,  2,2 , 1,0 , 5,2 ,  0,0,  4,2 , 0,0 , 3,2 ,
+                  0,0,  4,2 , 1,1 , 2,2 ,  0,0,  3,2 , 0,3 , 5,2
+
+       };
+       u16 tile_index = facedir * 16 + dir_i;
+       getNodeTileN(mn, p, dir_to_tile[tile_index], data, tile);
+       tile.rotation = tile.world_aligned ? 0 : dir_to_tile[tile_index + 1];
+}
+
+static void getTileInfo(
+               // Input:
+               MeshMakeData *data,
+               const v3s16 &p,
+               const v3s16 &face_dir,
+               // Output:
+               bool &makes_face,
+               v3s16 &p_corrected,
+               v3s16 &face_dir_corrected,
+               u16 *lights,
+               TileSpec &tile
+       )
+{
+       VoxelManipulator &vmanip = data->m_vmanip;
+       const NodeDefManager *ndef = data->m_client->ndef();
+       v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE;
+
+       const MapNode &n0 = vmanip.getNodeRefUnsafe(blockpos_nodes + p);
+
+       // Don't even try to get n1 if n0 is already CONTENT_IGNORE
+       if (n0.getContent() == CONTENT_IGNORE) {
+               makes_face = false;
+               return;
+       }
+
+       const MapNode &n1 = vmanip.getNodeRefUnsafeCheckFlags(blockpos_nodes + p + face_dir);
+
+       if (n1.getContent() == CONTENT_IGNORE) {
+               makes_face = false;
+               return;
+       }
+
+       // This is hackish
+       bool equivalent = false;
+       u8 mf = face_contents(n0.getContent(), n1.getContent(),
+                       &equivalent, ndef);
+
+       if (mf == 0) {
+               makes_face = false;
+               return;
+       }
+
+       makes_face = true;
+
+       MapNode n = n0;
+
+       if (mf == 1) {
+               p_corrected = p;
+               face_dir_corrected = face_dir;
+       } else {
+               n = n1;
+               p_corrected = p + face_dir;
+               face_dir_corrected = -face_dir;
+       }
+
+       getNodeTile(n, p_corrected, face_dir_corrected, data, tile);
+       const ContentFeatures &f = ndef->get(n);
+       tile.emissive_light = f.light_source;
+
+       // eg. water and glass
+       if (equivalent) {
+               for (TileLayer &layer : tile.layers)
+                       layer.material_flags |= MATERIAL_FLAG_BACKFACE_CULLING;
+       }
+
+       if (!data->m_smooth_lighting) {
+               lights[0] = lights[1] = lights[2] = lights[3] =
+                               getFaceLight(n0, n1, face_dir, ndef);
+       } else {
+               v3s16 vertex_dirs[4];
+               getNodeVertexDirs(face_dir_corrected, vertex_dirs);
+
+               v3s16 light_p = blockpos_nodes + p_corrected;
+               for (u16 i = 0; i < 4; i++)
+                       lights[i] = getSmoothLightSolid(light_p, face_dir_corrected, vertex_dirs[i], data);
+       }
+}
+
+/*
+       startpos:
+       translate_dir: unit vector with only one of x, y or z
+       face_dir: unit vector with only one of x, y or z
+*/
+static void updateFastFaceRow(
+               MeshMakeData *data,
+               const v3s16 &&startpos,
+               v3s16 translate_dir,
+               const v3f &&translate_dir_f,
+               const v3s16 &&face_dir,
+               std::vector<FastFace> &dest)
+{
+       v3s16 p = startpos;
+
+       u16 continuous_tiles_count = 1;
+
+       bool makes_face = false;
+       v3s16 p_corrected;
+       v3s16 face_dir_corrected;
+       u16 lights[4] = {0, 0, 0, 0};
+       TileSpec tile;
+       getTileInfo(data, p, face_dir,
+                       makes_face, p_corrected, face_dir_corrected,
+                       lights, tile);
+
+       // Unroll this variable which has a significant build cost
+       TileSpec next_tile;
+       for (u16 j = 0; j < MAP_BLOCKSIZE; j++) {
+               // If tiling can be done, this is set to false in the next step
+               bool next_is_different = true;
+
+               v3s16 p_next;
+
+               bool next_makes_face = false;
+               v3s16 next_p_corrected;
+               v3s16 next_face_dir_corrected;
+               u16 next_lights[4] = {0, 0, 0, 0};
+
+               // If at last position, there is nothing to compare to and
+               // the face must be drawn anyway
+               if (j != MAP_BLOCKSIZE - 1) {
+                       p_next = p + translate_dir;
+
+                       getTileInfo(data, p_next, face_dir,
+                                       next_makes_face, next_p_corrected,
+                                       next_face_dir_corrected, next_lights,
+                                       next_tile);
+
+                       if (next_makes_face == makes_face
+                                       && next_p_corrected == p_corrected + translate_dir
+                                       && next_face_dir_corrected == face_dir_corrected
+                                       && memcmp(next_lights, lights, ARRLEN(lights) * sizeof(u16)) == 0
+                                       && next_tile.isTileable(tile)) {
+                               next_is_different = false;
+                               continuous_tiles_count++;
+                       }
+               }
+               if (next_is_different) {
+                       /*
+                               Create a face if there should be one
+                       */
+                       if (makes_face) {
+                               // Floating point conversion of the position vector
+                               v3f pf(p_corrected.X, p_corrected.Y, p_corrected.Z);
+                               // Center point of face (kind of)
+                               v3f sp = pf - ((f32)continuous_tiles_count * 0.5f - 0.5f)
+                                       * translate_dir_f;
+                               v3f scale(1, 1, 1);
+
+                               if (translate_dir.X != 0)
+                                       scale.X = continuous_tiles_count;
+                               if (translate_dir.Y != 0)
+                                       scale.Y = continuous_tiles_count;
+                               if (translate_dir.Z != 0)
+                                       scale.Z = continuous_tiles_count;
+
+                               makeFastFace(tile, lights[0], lights[1], lights[2], lights[3],
+                                               pf, sp, face_dir_corrected, scale, dest);
+
+                               g_profiler->avg("Meshgen: faces drawn by tiling", 0);
+                               for (int i = 1; i < continuous_tiles_count; i++)
+                                       g_profiler->avg("Meshgen: faces drawn by tiling", 1);
+                       }
+
+                       continuous_tiles_count = 1;
+               }
+
+               makes_face = next_makes_face;
+               p_corrected = next_p_corrected;
+               face_dir_corrected = next_face_dir_corrected;
+               std::memcpy(lights, next_lights, ARRLEN(lights) * sizeof(u16));
+               if (next_is_different)
+                       tile = next_tile;
+               p = p_next;
+       }
+}
+
+static void updateAllFastFaceRows(MeshMakeData *data,
+               std::vector<FastFace> &dest)
+{
+       /*
+               Go through every y,z and get top(y+) faces in rows of x+
+       */
+       for (s16 y = 0; y < MAP_BLOCKSIZE; y++)
+       for (s16 z = 0; z < MAP_BLOCKSIZE; z++)
+               updateFastFaceRow(data,
+                               v3s16(0, y, z),
+                               v3s16(1, 0, 0), //dir
+                               v3f  (1, 0, 0),
+                               v3s16(0, 1, 0), //face dir
+                               dest);
+
+       /*
+               Go through every x,y and get right(x+) faces in rows of z+
+       */
+       for (s16 x = 0; x < MAP_BLOCKSIZE; x++)
+       for (s16 y = 0; y < MAP_BLOCKSIZE; y++)
+               updateFastFaceRow(data,
+                               v3s16(x, y, 0),
+                               v3s16(0, 0, 1), //dir
+                               v3f  (0, 0, 1),
+                               v3s16(1, 0, 0), //face dir
+                               dest);
+
+       /*
+               Go through every y,z and get back(z+) faces in rows of x+
+       */
+       for (s16 z = 0; z < MAP_BLOCKSIZE; z++)
+       for (s16 y = 0; y < MAP_BLOCKSIZE; y++)
+               updateFastFaceRow(data,
+                               v3s16(0, y, z),
+                               v3s16(1, 0, 0), //dir
+                               v3f  (1, 0, 0),
+                               v3s16(0, 0, 1), //face dir
+                               dest);
+}
+
+static void applyTileColor(PreMeshBuffer &pmb)
+{
+       video::SColor tc = pmb.layer.color;
+       if (tc == video::SColor(0xFFFFFFFF))
+               return;
+       for (video::S3DVertex &vertex : pmb.vertices) {
+               video::SColor *c = &vertex.Color;
+               c->set(c->getAlpha(),
+                       c->getRed() * tc.getRed() / 255,
+                       c->getGreen() * tc.getGreen() / 255,
+                       c->getBlue() * tc.getBlue() / 255);
+       }
+}
+
+/*
+       MapBlockMesh
+*/
+
+MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
+       m_minimap_mapblock(NULL),
+       m_tsrc(data->m_client->getTextureSource()),
+       m_shdrsrc(data->m_client->getShaderSource()),
+       m_animation_force_timer(0), // force initial animation
+       m_last_crack(-1),
+       m_last_daynight_ratio((u32) -1)
+{
+       for (auto &m : m_mesh)
+               m = new scene::SMesh();
+       m_enable_shaders = data->m_use_shaders;
+       m_use_tangent_vertices = data->m_use_tangent_vertices;
+       m_enable_vbo = g_settings->getBool("enable_vbo");
+
+       if (g_settings->getBool("enable_minimap")) {
+               m_minimap_mapblock = new MinimapMapblock;
+               m_minimap_mapblock->getMinimapNodes(
+                       &data->m_vmanip, data->m_blockpos * MAP_BLOCKSIZE);
+       }
+
+       // 4-21ms for MAP_BLOCKSIZE=16  (NOTE: probably outdated)
+       // 24-155ms for MAP_BLOCKSIZE=32  (NOTE: probably outdated)
+       //TimeTaker timer1("MapBlockMesh()");
+
+       std::vector<FastFace> fastfaces_new;
+       fastfaces_new.reserve(512);
+
+       /*
+               We are including the faces of the trailing edges of the block.
+               This means that when something changes, the caller must
+               also update the meshes of the blocks at the leading edges.
+
+               NOTE: This is the slowest part of this method.
+       */
+       {
+               // 4-23ms for MAP_BLOCKSIZE=16  (NOTE: probably outdated)
+               //TimeTaker timer2("updateAllFastFaceRows()");
+               updateAllFastFaceRows(data, fastfaces_new);
+       }
+       // End of slow part
+
+       /*
+               Convert FastFaces to MeshCollector
+       */
+
+       MeshCollector collector;
+
+       {
+               // avg 0ms (100ms spikes when loading textures the first time)
+               // (NOTE: probably outdated)
+               //TimeTaker timer2("MeshCollector building");
+
+               for (const FastFace &f : fastfaces_new) {
+                       static const u16 indices[] = {0, 1, 2, 2, 3, 0};
+                       static const u16 indices_alternate[] = {0, 1, 3, 2, 3, 1};
+                       const u16 *indices_p =
+                               f.vertex_0_2_connected ? indices : indices_alternate;
+                       collector.append(f.tile, f.vertices, 4, indices_p, 6);
+               }
+       }
+
+       /*
+               Add special graphics:
+               - torches
+               - flowing water
+               - fences
+               - whatever
+       */
+
+       {
+               MapblockMeshGenerator generator(data, &collector);
+               generator.generate();
+       }
+
+       /*
+               Convert MeshCollector to SMesh
+       */
+
+       for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
+               for(u32 i = 0; i < collector.prebuffers[layer].size(); i++)
+               {
+                       PreMeshBuffer &p = collector.prebuffers[layer][i];
+
+                       applyTileColor(p);
+
+                       // Generate animation data
+                       // - Cracks
+                       if (p.layer.material_flags & MATERIAL_FLAG_CRACK) {
+                               // Find the texture name plus ^[crack:N:
+                               std::ostringstream os(std::ios::binary);
+                               os << m_tsrc->getTextureName(p.layer.texture_id) << "^[crack";
+                               if (p.layer.material_flags & MATERIAL_FLAG_CRACK_OVERLAY)
+                                       os << "o";  // use ^[cracko
+                               u8 tiles = p.layer.scale;
+                               if (tiles > 1)
+                                       os << ":" << (u32)tiles;
+                               os << ":" << (u32)p.layer.animation_frame_count << ":";
+                               m_crack_materials.insert(std::make_pair(
+                                               std::pair<u8, u32>(layer, i), os.str()));
+                               // Replace tile texture with the cracked one
+                               p.layer.texture = m_tsrc->getTextureForMesh(
+                                               os.str() + "0",
+                                               &p.layer.texture_id);
+                       }
+                       // - Texture animation
+                       if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) {
+                               // Add to MapBlockMesh in order to animate these tiles
+                               m_animation_tiles[std::pair<u8, u32>(layer, i)] = p.layer;
+                               m_animation_frames[std::pair<u8, u32>(layer, i)] = 0;
+                               if (g_settings->getBool(
+                                               "desynchronize_mapblock_texture_animation")) {
+                                       // Get starting position from noise
+                                       m_animation_frame_offsets[std::pair<u8, u32>(layer, i)] =
+                                                       100000 * (2.0 + noise3d(
+                                                       data->m_blockpos.X, data->m_blockpos.Y,
+                                                       data->m_blockpos.Z, 0));
+                               } else {
+                                       // Play all synchronized
+                                       m_animation_frame_offsets[std::pair<u8, u32>(layer, i)] = 0;
+                               }
+                               // Replace tile texture with the first animation frame
+                               p.layer.texture = (*p.layer.frames)[0].texture;
+                       }
+
+                       if (!m_enable_shaders) {
+                               // Extract colors for day-night animation
+                               // Dummy sunlight to handle non-sunlit areas
+                               video::SColorf sunlight;
+                               get_sunlight_color(&sunlight, 0);
+                               u32 vertex_count = p.vertices.size();
+                               for (u32 j = 0; j < vertex_count; j++) {
+                                       video::SColor *vc = &p.vertices[j].Color;
+                                       video::SColor copy = *vc;
+                                       if (vc->getAlpha() == 0) // No sunlight - no need to animate
+                                               final_color_blend(vc, copy, sunlight); // Finalize color
+                                       else // Record color to animate
+                                               m_daynight_diffs[std::pair<u8, u32>(layer, i)][j] = copy;
+
+                                       // The sunlight ratio has been stored,
+                                       // delete alpha (for the final rendering).
+                                       vc->setAlpha(255);
+                               }
+                       }
+
+                       // Create material
+                       video::SMaterial material;
+                       material.setFlag(video::EMF_LIGHTING, false);
+                       material.setFlag(video::EMF_BACK_FACE_CULLING, true);
+                       material.setFlag(video::EMF_BILINEAR_FILTER, false);
+                       material.setFlag(video::EMF_FOG_ENABLE, true);
+                       material.setTexture(0, p.layer.texture);
+
+                       if (m_enable_shaders) {
+                               material.MaterialType = m_shdrsrc->getShaderInfo(
+                                               p.layer.shader_id).material;
+                               p.layer.applyMaterialOptionsWithShaders(material);
+                               if (p.layer.normal_texture)
+                                       material.setTexture(1, p.layer.normal_texture);
+                               material.setTexture(2, p.layer.flags_texture);
+                       } else {
+                               p.layer.applyMaterialOptions(material);
+                       }
+
+                       scene::SMesh *mesh = (scene::SMesh *)m_mesh[layer];
+
+                       // Create meshbuffer, add to mesh
+                       if (m_use_tangent_vertices) {
+                               scene::SMeshBufferTangents *buf =
+                                               new scene::SMeshBufferTangents();
+                               buf->Material = material;
+                               buf->Vertices.reallocate(p.vertices.size());
+                               buf->Indices.reallocate(p.indices.size());
+                               for (const video::S3DVertex &v: p.vertices)
+                                       buf->Vertices.push_back(video::S3DVertexTangents(v.Pos, v.Color, v.TCoords));
+                               for (u16 i: p.indices)
+                                       buf->Indices.push_back(i);
+                               buf->recalculateBoundingBox();
+                               mesh->addMeshBuffer(buf);
+                               buf->drop();
+                       } else {
+                               scene::SMeshBuffer *buf = new scene::SMeshBuffer();
+                               buf->Material = material;
+                               buf->append(&p.vertices[0], p.vertices.size(),
+                                       &p.indices[0], p.indices.size());
+                               mesh->addMeshBuffer(buf);
+                               buf->drop();
+                       }
+               }
+
+               /*
+                       Do some stuff to the mesh
+               */
+               m_camera_offset = camera_offset;
+               translateMesh(m_mesh[layer],
+                       intToFloat(data->m_blockpos * MAP_BLOCKSIZE - camera_offset, BS));
+
+               if (m_use_tangent_vertices) {
+                       scene::IMeshManipulator* meshmanip =
+                               RenderingEngine::get_scene_manager()->getMeshManipulator();
+                       meshmanip->recalculateTangents(m_mesh[layer], true, false, false);
+               }
+
+               if (m_mesh[layer]) {
+#if 0
+                       // Usually 1-700 faces and 1-7 materials
+                       std::cout << "Updated MapBlock has " << fastfaces_new.size()
+                                       << " faces and uses " << m_mesh[layer]->getMeshBufferCount()
+                                       << " materials (meshbuffers)" << std::endl;
+#endif
+
+                       // Use VBO for mesh (this just would set this for ever buffer)
+                       if (m_enable_vbo)
+                               m_mesh[layer]->setHardwareMappingHint(scene::EHM_STATIC);
+               }
+       }
+
+       //std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl;
+
+       // Check if animation is required for this mesh
+       m_has_animation =
+               !m_crack_materials.empty() ||
+               !m_daynight_diffs.empty() ||
+               !m_animation_tiles.empty();
+}
+
+MapBlockMesh::~MapBlockMesh()
+{
+       for (scene::IMesh *m : m_mesh) {
+               if (m_enable_vbo && m)
+                       for (u32 i = 0; i < m->getMeshBufferCount(); i++) {
+                               scene::IMeshBuffer *buf = m->getMeshBuffer(i);
+                               RenderingEngine::get_video_driver()->removeHardwareBuffer(buf);
+                       }
+               m->drop();
+               m = NULL;
+       }
+       delete m_minimap_mapblock;
+}
+
+bool MapBlockMesh::animate(bool faraway, float time, int crack,
+       u32 daynight_ratio)
+{
+       if (!m_has_animation) {
+               m_animation_force_timer = 100000;
+               return false;
+       }
+
+       m_animation_force_timer = myrand_range(5, 100);
+
+       // Cracks
+       if (crack != m_last_crack) {
+               for (auto &crack_material : m_crack_materials) {
+                       scene::IMeshBuffer *buf = m_mesh[crack_material.first.first]->
+                               getMeshBuffer(crack_material.first.second);
+                       std::string basename = crack_material.second;
+
+                       // Create new texture name from original
+                       std::ostringstream os;
+                       os << basename << crack;
+                       u32 new_texture_id = 0;
+                       video::ITexture *new_texture =
+                                       m_tsrc->getTextureForMesh(os.str(), &new_texture_id);
+                       buf->getMaterial().setTexture(0, new_texture);
+
+                       // If the current material is also animated,
+                       // update animation info
+                       auto anim_iter = m_animation_tiles.find(crack_material.first);
+                       if (anim_iter != m_animation_tiles.end()) {
+                               TileLayer &tile = anim_iter->second;
+                               tile.texture = new_texture;
+                               tile.texture_id = new_texture_id;
+                               // force animation update
+                               m_animation_frames[crack_material.first] = -1;
+                       }
+               }
+
+               m_last_crack = crack;
+       }
+
+       // Texture animation
+       for (auto &animation_tile : m_animation_tiles) {
+               const TileLayer &tile = animation_tile.second;
+               // Figure out current frame
+               int frameoffset = m_animation_frame_offsets[animation_tile.first];
+               int frame = (int)(time * 1000 / tile.animation_frame_length_ms
+                               + frameoffset) % tile.animation_frame_count;
+               // If frame doesn't change, skip
+               if (frame == m_animation_frames[animation_tile.first])
+                       continue;
+
+               m_animation_frames[animation_tile.first] = frame;
+
+               scene::IMeshBuffer *buf = m_mesh[animation_tile.first.first]->
+                       getMeshBuffer(animation_tile.first.second);
+
+               const FrameSpec &animation_frame = (*tile.frames)[frame];
+               buf->getMaterial().setTexture(0, animation_frame.texture);
+               if (m_enable_shaders) {
+                       if (animation_frame.normal_texture)
+                               buf->getMaterial().setTexture(1,
+                                       animation_frame.normal_texture);
+                       buf->getMaterial().setTexture(2, animation_frame.flags_texture);
+               }
+       }
+
+       // Day-night transition
+       if (!m_enable_shaders && (daynight_ratio != m_last_daynight_ratio)) {
+               // Force reload mesh to VBO
+               if (m_enable_vbo)
+                       for (scene::IMesh *m : m_mesh)
+                               m->setDirty();
+               video::SColorf day_color;
+               get_sunlight_color(&day_color, daynight_ratio);
+
+               for (auto &daynight_diff : m_daynight_diffs) {
+                       scene::IMeshBuffer *buf = m_mesh[daynight_diff.first.first]->
+                               getMeshBuffer(daynight_diff.first.second);
+                       video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices();
+                       for (const auto &j : daynight_diff.second)
+                               final_color_blend(&(vertices[j.first].Color), j.second,
+                                               day_color);
+               }
+               m_last_daynight_ratio = daynight_ratio;
+       }
+
+       return true;
+}
+
+void MapBlockMesh::updateCameraOffset(v3s16 camera_offset)
+{
+       if (camera_offset != m_camera_offset) {
+               for (scene::IMesh *layer : m_mesh) {
+                       translateMesh(layer,
+                               intToFloat(m_camera_offset - camera_offset, BS));
+                       if (m_enable_vbo)
+                               layer->setDirty();
+               }
+               m_camera_offset = camera_offset;
+       }
+}
+
+video::SColor encode_light(u16 light, u8 emissive_light)
+{
+       // Get components
+       u32 day = (light & 0xff);
+       u32 night = (light >> 8);
+       // Add emissive light
+       night += emissive_light * 2.5f;
+       if (night > 255)
+               night = 255;
+       // Since we don't know if the day light is sunlight or
+       // artificial light, assume it is artificial when the night
+       // light bank is also lit.
+       if (day < night)
+               day = 0;
+       else
+               day = day - night;
+       u32 sum = day + night;
+       // Ratio of sunlight:
+       u32 r;
+       if (sum > 0)
+               r = day * 255 / sum;
+       else
+               r = 0;
+       // Average light:
+       float b = (day + night) / 2;
+       return video::SColor(r, b, b, b);
+}
diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h
new file mode 100644 (file)
index 0000000..6af23a6
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "client/tile.h"
+#include "voxel.h"
+#include <array>
+#include <map>
+
+class Client;
+class IShaderSource;
+
+/*
+       Mesh making stuff
+*/
+
+
+class MapBlock;
+struct MinimapMapblock;
+
+struct MeshMakeData
+{
+       VoxelManipulator m_vmanip;
+       v3s16 m_blockpos = v3s16(-1337,-1337,-1337);
+       v3s16 m_crack_pos_relative = v3s16(-1337,-1337,-1337);
+       bool m_smooth_lighting = false;
+
+       Client *m_client;
+       bool m_use_shaders;
+       bool m_use_tangent_vertices;
+
+       MeshMakeData(Client *client, bool use_shaders,
+                       bool use_tangent_vertices = false);
+
+       /*
+               Copy block data manually (to allow optimizations by the caller)
+       */
+       void fillBlockDataBegin(const v3s16 &blockpos);
+       void fillBlockData(const v3s16 &block_offset, MapNode *data);
+
+       /*
+               Copy central data directly from block, and other data from
+               parent of block.
+       */
+       void fill(MapBlock *block);
+
+       /*
+               Set up with only a single node at (1,1,1)
+       */
+       void fillSingleNode(MapNode *node);
+
+       /*
+               Set the (node) position of a crack
+       */
+       void setCrack(int crack_level, v3s16 crack_pos);
+
+       /*
+               Enable or disable smooth lighting
+       */
+       void setSmoothLighting(bool smooth_lighting);
+};
+
+/*
+       Holds a mesh for a mapblock.
+
+       Besides the SMesh*, this contains information used for animating
+       the vertex positions, colors and texture coordinates of the mesh.
+       For example:
+       - cracks [implemented]
+       - day/night transitions [implemented]
+       - animated flowing liquids [not implemented]
+       - animating vertex positions for e.g. axles [not implemented]
+*/
+class MapBlockMesh
+{
+public:
+       // Builds the mesh given
+       MapBlockMesh(MeshMakeData *data, v3s16 camera_offset);
+       ~MapBlockMesh();
+
+       // Main animation function, parameters:
+       //   faraway: whether the block is far away from the camera (~50 nodes)
+       //   time: the global animation time, 0 .. 60 (repeats every minute)
+       //   daynight_ratio: 0 .. 1000
+       //   crack: -1 .. CRACK_ANIMATION_LENGTH-1 (-1 for off)
+       // Returns true if anything has been changed.
+       bool animate(bool faraway, float time, int crack, u32 daynight_ratio);
+
+       scene::IMesh *getMesh()
+       {
+               return m_mesh[0];
+       }
+
+       scene::IMesh *getMesh(u8 layer)
+       {
+               return m_mesh[layer];
+       }
+
+       MinimapMapblock *moveMinimapMapblock()
+       {
+               MinimapMapblock *p = m_minimap_mapblock;
+               m_minimap_mapblock = NULL;
+               return p;
+       }
+
+       bool isAnimationForced() const
+       {
+               return m_animation_force_timer == 0;
+       }
+
+       void decreaseAnimationForceTimer()
+       {
+               if(m_animation_force_timer > 0)
+                       m_animation_force_timer--;
+       }
+
+       void updateCameraOffset(v3s16 camera_offset);
+
+private:
+       scene::IMesh *m_mesh[MAX_TILE_LAYERS];
+       MinimapMapblock *m_minimap_mapblock;
+       ITextureSource *m_tsrc;
+       IShaderSource *m_shdrsrc;
+
+       bool m_enable_shaders;
+       bool m_use_tangent_vertices;
+       bool m_enable_vbo;
+
+       // Must animate() be called before rendering?
+       bool m_has_animation;
+       int m_animation_force_timer;
+
+       // Animation info: cracks
+       // Last crack value passed to animate()
+       int m_last_crack;
+       // Maps mesh and mesh buffer (i.e. material) indices to base texture names
+       std::map<std::pair<u8, u32>, std::string> m_crack_materials;
+
+       // Animation info: texture animationi
+       // Maps mesh and mesh buffer indices to TileSpecs
+       // Keys are pairs of (mesh index, buffer index in the mesh)
+       std::map<std::pair<u8, u32>, TileLayer> m_animation_tiles;
+       std::map<std::pair<u8, u32>, int> m_animation_frames; // last animation frame
+       std::map<std::pair<u8, u32>, int> m_animation_frame_offsets;
+
+       // Animation info: day/night transitions
+       // Last daynight_ratio value passed to animate()
+       u32 m_last_daynight_ratio;
+       // For each mesh and mesh buffer, stores pre-baked colors
+       // of sunlit vertices
+       // Keys are pairs of (mesh index, buffer index in the mesh)
+       std::map<std::pair<u8, u32>, std::map<u32, video::SColor > > m_daynight_diffs;
+
+       // Camera offset info -> do we have to translate the mesh?
+       v3s16 m_camera_offset;
+};
+
+/*!
+ * Encodes light of a node.
+ * The result is not the final color, but a
+ * half-baked vertex color.
+ * You have to multiply the resulting color
+ * with the node's color.
+ *
+ * \param light the first 8 bits are day light,
+ * the last 8 bits are night light
+ * \param emissive_light amount of light the surface emits,
+ * from 0 to LIGHT_SUN.
+ */
+video::SColor encode_light(u16 light, u8 emissive_light);
+
+// Compute light at node
+u16 getInteriorLight(MapNode n, s32 increment, const NodeDefManager *ndef);
+u16 getFaceLight(MapNode n, MapNode n2, const v3s16 &face_dir,
+       const NodeDefManager *ndef);
+u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, MeshMakeData *data);
+u16 getSmoothLightTransparent(const v3s16 &p, const v3s16 &corner, MeshMakeData *data);
+
+/*!
+ * Returns the sunlight's color from the current
+ * day-night ratio.
+ */
+void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio);
+
+/*!
+ * Gives the final  SColor shown on screen.
+ *
+ * \param result output color
+ * \param light first 8 bits are day light, second 8 bits are
+ * night light
+ */
+void final_color_blend(video::SColor *result,
+               u16 light, u32 daynight_ratio);
+
+/*!
+ * Gives the final  SColor shown on screen.
+ *
+ * \param result output color
+ * \param data the half-baked vertex color
+ * \param dayLight color of the sunlight
+ */
+void final_color_blend(video::SColor *result,
+               const video::SColor &data, const video::SColorf &dayLight);
+
+// Retrieves the TileSpec of a face of a node
+// Adds MATERIAL_FLAG_CRACK if the node is cracked
+// TileSpec should be passed as reference due to the underlying TileFrame and its vector
+// TileFrame vector copy cost very much to client
+void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, TileSpec &tile);
+void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *data, TileSpec &tile);
diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp
new file mode 100644 (file)
index 0000000..7fc7531
--- /dev/null
@@ -0,0 +1,1135 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 "mesh.h"
+#include "debug.h"
+#include "log.h"
+#include "irrMap.h"
+#include <iostream>
+#include <IAnimatedMesh.h>
+#include <SAnimatedMesh.h>
+#include <IAnimatedMeshSceneNode.h>
+
+// In Irrlicht 1.8 the signature of ITexture::lock was changed from
+// (bool, u32) to (E_TEXTURE_LOCK_MODE, u32).
+#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7
+#define MY_ETLM_READ_ONLY true
+#else
+#define MY_ETLM_READ_ONLY video::ETLM_READ_ONLY
+#endif
+
+inline static void applyShadeFactor(video::SColor& color, float factor)
+{
+       color.setRed(core::clamp(core::round32(color.getRed()*factor), 0, 255));
+       color.setGreen(core::clamp(core::round32(color.getGreen()*factor), 0, 255));
+       color.setBlue(core::clamp(core::round32(color.getBlue()*factor), 0, 255));
+}
+
+void applyFacesShading(video::SColor &color, const v3f &normal)
+{
+       /*
+               Some drawtypes have normals set to (0, 0, 0), this must result in
+               maximum brightness: shade factor 1.0.
+               Shade factors for aligned cube faces are:
+               +Y 1.000000 sqrt(1.0)
+               -Y 0.447213 sqrt(0.2)
+               +-X 0.670820 sqrt(0.45)
+               +-Z 0.836660 sqrt(0.7)
+       */
+       float x2 = normal.X * normal.X;
+       float y2 = normal.Y * normal.Y;
+       float z2 = normal.Z * normal.Z;
+       if (normal.Y < 0)
+               applyShadeFactor(color, 0.670820f * x2 + 0.447213f * y2 + 0.836660f * z2);
+       else if ((x2 > 1e-3) || (z2 > 1e-3))
+               applyShadeFactor(color, 0.670820f * x2 + 1.000000f * y2 + 0.836660f * z2);
+}
+
+scene::IAnimatedMesh* createCubeMesh(v3f scale)
+{
+       video::SColor c(255,255,255,255);
+       video::S3DVertex vertices[24] =
+       {
+               // Up
+               video::S3DVertex(-0.5,+0.5,-0.5, 0,1,0, c, 0,1),
+               video::S3DVertex(-0.5,+0.5,+0.5, 0,1,0, c, 0,0),
+               video::S3DVertex(+0.5,+0.5,+0.5, 0,1,0, c, 1,0),
+               video::S3DVertex(+0.5,+0.5,-0.5, 0,1,0, c, 1,1),
+               // Down
+               video::S3DVertex(-0.5,-0.5,-0.5, 0,-1,0, c, 0,0),
+               video::S3DVertex(+0.5,-0.5,-0.5, 0,-1,0, c, 1,0),
+               video::S3DVertex(+0.5,-0.5,+0.5, 0,-1,0, c, 1,1),
+               video::S3DVertex(-0.5,-0.5,+0.5, 0,-1,0, c, 0,1),
+               // Right
+               video::S3DVertex(+0.5,-0.5,-0.5, 1,0,0, c, 0,1),
+               video::S3DVertex(+0.5,+0.5,-0.5, 1,0,0, c, 0,0),
+               video::S3DVertex(+0.5,+0.5,+0.5, 1,0,0, c, 1,0),
+               video::S3DVertex(+0.5,-0.5,+0.5, 1,0,0, c, 1,1),
+               // Left
+               video::S3DVertex(-0.5,-0.5,-0.5, -1,0,0, c, 1,1),
+               video::S3DVertex(-0.5,-0.5,+0.5, -1,0,0, c, 0,1),
+               video::S3DVertex(-0.5,+0.5,+0.5, -1,0,0, c, 0,0),
+               video::S3DVertex(-0.5,+0.5,-0.5, -1,0,0, c, 1,0),
+               // Back
+               video::S3DVertex(-0.5,-0.5,+0.5, 0,0,1, c, 1,1),
+               video::S3DVertex(+0.5,-0.5,+0.5, 0,0,1, c, 0,1),
+               video::S3DVertex(+0.5,+0.5,+0.5, 0,0,1, c, 0,0),
+               video::S3DVertex(-0.5,+0.5,+0.5, 0,0,1, c, 1,0),
+               // Front
+               video::S3DVertex(-0.5,-0.5,-0.5, 0,0,-1, c, 0,1),
+               video::S3DVertex(-0.5,+0.5,-0.5, 0,0,-1, c, 0,0),
+               video::S3DVertex(+0.5,+0.5,-0.5, 0,0,-1, c, 1,0),
+               video::S3DVertex(+0.5,-0.5,-0.5, 0,0,-1, c, 1,1),
+       };
+
+       u16 indices[6] = {0,1,2,2,3,0};
+
+       scene::SMesh *mesh = new scene::SMesh();
+       for (u32 i=0; i<6; ++i)
+       {
+               scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+               buf->append(vertices + 4 * i, 4, indices, 6);
+               // Set default material
+               buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+               buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+               buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
+               // Add mesh buffer to mesh
+               mesh->addMeshBuffer(buf);
+               buf->drop();
+       }
+
+       scene::SAnimatedMesh *anim_mesh = new scene::SAnimatedMesh(mesh);
+       mesh->drop();
+       scaleMesh(anim_mesh, scale);  // also recalculates bounding box
+       return anim_mesh;
+}
+
+void scaleMesh(scene::IMesh *mesh, v3f scale)
+{
+       if (mesh == NULL)
+               return;
+
+       aabb3f bbox;
+       bbox.reset(0, 0, 0);
+
+       u32 mc = mesh->getMeshBufferCount();
+       for (u32 j = 0; j < mc; j++) {
+               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+               const u32 stride = getVertexPitchFromType(buf->getVertexType());
+               u32 vertex_count = buf->getVertexCount();
+               u8 *vertices = (u8 *)buf->getVertices();
+               for (u32 i = 0; i < vertex_count; i++)
+                       ((video::S3DVertex *)(vertices + i * stride))->Pos *= scale;
+
+               buf->recalculateBoundingBox();
+
+               // calculate total bounding box
+               if (j == 0)
+                       bbox = buf->getBoundingBox();
+               else
+                       bbox.addInternalBox(buf->getBoundingBox());
+       }
+       mesh->setBoundingBox(bbox);
+}
+
+void translateMesh(scene::IMesh *mesh, v3f vec)
+{
+       if (mesh == NULL)
+               return;
+
+       aabb3f bbox;
+       bbox.reset(0, 0, 0);
+
+       u32 mc = mesh->getMeshBufferCount();
+       for (u32 j = 0; j < mc; j++) {
+               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+               const u32 stride = getVertexPitchFromType(buf->getVertexType());
+               u32 vertex_count = buf->getVertexCount();
+               u8 *vertices = (u8 *)buf->getVertices();
+               for (u32 i = 0; i < vertex_count; i++)
+                       ((video::S3DVertex *)(vertices + i * stride))->Pos += vec;
+
+               buf->recalculateBoundingBox();
+
+               // calculate total bounding box
+               if (j == 0)
+                       bbox = buf->getBoundingBox();
+               else
+                       bbox.addInternalBox(buf->getBoundingBox());
+       }
+       mesh->setBoundingBox(bbox);
+}
+
+void setMeshBufferColor(scene::IMeshBuffer *buf, const video::SColor &color)
+{
+       const u32 stride = getVertexPitchFromType(buf->getVertexType());
+       u32 vertex_count = buf->getVertexCount();
+       u8 *vertices = (u8 *) buf->getVertices();
+       for (u32 i = 0; i < vertex_count; i++)
+               ((video::S3DVertex *) (vertices + i * stride))->Color = color;
+}
+
+void setAnimatedMeshColor(scene::IAnimatedMeshSceneNode *node, const video::SColor &color)
+{
+       for (u32 i = 0; i < node->getMaterialCount(); ++i) {
+               node->getMaterial(i).EmissiveColor = color;
+       }
+}
+
+void setMeshColor(scene::IMesh *mesh, const video::SColor &color)
+{
+       if (mesh == NULL)
+               return;
+
+       u32 mc = mesh->getMeshBufferCount();
+       for (u32 j = 0; j < mc; j++)
+               setMeshBufferColor(mesh->getMeshBuffer(j), color);
+}
+
+void colorizeMeshBuffer(scene::IMeshBuffer *buf, const video::SColor *buffercolor)
+{
+       const u32 stride = getVertexPitchFromType(buf->getVertexType());
+       u32 vertex_count = buf->getVertexCount();
+       u8 *vertices = (u8 *) buf->getVertices();
+       for (u32 i = 0; i < vertex_count; i++) {
+               video::S3DVertex *vertex = (video::S3DVertex *) (vertices + i * stride);
+               video::SColor *vc = &(vertex->Color);
+               // Reset color
+               *vc = *buffercolor;
+               // Apply shading
+               applyFacesShading(*vc, vertex->Normal);
+       }
+}
+
+void setMeshColorByNormalXYZ(scene::IMesh *mesh,
+               const video::SColor &colorX,
+               const video::SColor &colorY,
+               const video::SColor &colorZ)
+{
+       if (mesh == NULL)
+               return;
+
+       u16 mc = mesh->getMeshBufferCount();
+       for (u16 j = 0; j < mc; j++) {
+               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+               const u32 stride = getVertexPitchFromType(buf->getVertexType());
+               u32 vertex_count = buf->getVertexCount();
+               u8 *vertices = (u8 *)buf->getVertices();
+               for (u32 i = 0; i < vertex_count; i++) {
+                       video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride);
+                       f32 x = fabs(vertex->Normal.X);
+                       f32 y = fabs(vertex->Normal.Y);
+                       f32 z = fabs(vertex->Normal.Z);
+                       if (x >= y && x >= z)
+                               vertex->Color = colorX;
+                       else if (y >= z)
+                               vertex->Color = colorY;
+                       else
+                               vertex->Color = colorZ;
+               }
+       }
+}
+
+void setMeshColorByNormal(scene::IMesh *mesh, const v3f &normal,
+               const video::SColor &color)
+{
+       if (!mesh)
+               return;
+
+       u16 mc = mesh->getMeshBufferCount();
+       for (u16 j = 0; j < mc; j++) {
+               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+               const u32 stride = getVertexPitchFromType(buf->getVertexType());
+               u32 vertex_count = buf->getVertexCount();
+               u8 *vertices = (u8 *)buf->getVertices();
+               for (u32 i = 0; i < vertex_count; i++) {
+                       video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride);
+                       if (normal == vertex->Normal) {
+                               vertex->Color = color;
+                       }
+               }
+       }
+}
+
+void rotateMeshXYby(scene::IMesh *mesh, f64 degrees)
+{
+       u16 mc = mesh->getMeshBufferCount();
+       for (u16 j = 0; j < mc; j++) {
+               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+               const u32 stride = getVertexPitchFromType(buf->getVertexType());
+               u32 vertex_count = buf->getVertexCount();
+               u8 *vertices = (u8 *)buf->getVertices();
+               for (u32 i = 0; i < vertex_count; i++)
+                       ((video::S3DVertex *)(vertices + i * stride))->Pos.rotateXYBy(degrees);
+       }
+}
+
+void rotateMeshXZby(scene::IMesh *mesh, f64 degrees)
+{
+       u16 mc = mesh->getMeshBufferCount();
+       for (u16 j = 0; j < mc; j++) {
+               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+               const u32 stride = getVertexPitchFromType(buf->getVertexType());
+               u32 vertex_count = buf->getVertexCount();
+               u8 *vertices = (u8 *)buf->getVertices();
+               for (u32 i = 0; i < vertex_count; i++)
+                       ((video::S3DVertex *)(vertices + i * stride))->Pos.rotateXZBy(degrees);
+       }
+}
+
+void rotateMeshYZby(scene::IMesh *mesh, f64 degrees)
+{
+       u16 mc = mesh->getMeshBufferCount();
+       for (u16 j = 0; j < mc; j++) {
+               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+               const u32 stride = getVertexPitchFromType(buf->getVertexType());
+               u32 vertex_count = buf->getVertexCount();
+               u8 *vertices = (u8 *)buf->getVertices();
+               for (u32 i = 0; i < vertex_count; i++)
+                       ((video::S3DVertex *)(vertices + i * stride))->Pos.rotateYZBy(degrees);
+       }
+}
+
+void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir)
+{
+       int axisdir = facedir >> 2;
+       facedir &= 0x03;
+
+       u16 mc = mesh->getMeshBufferCount();
+       for (u16 j = 0; j < mc; j++) {
+               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+               const u32 stride = getVertexPitchFromType(buf->getVertexType());
+               u32 vertex_count = buf->getVertexCount();
+               u8 *vertices = (u8 *)buf->getVertices();
+               for (u32 i = 0; i < vertex_count; i++) {
+                       video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride);
+                       switch (axisdir) {
+                               case 0:
+                                       if (facedir == 1)
+                                               vertex->Pos.rotateXZBy(-90);
+                                       else if (facedir == 2)
+                                               vertex->Pos.rotateXZBy(180);
+                                       else if (facedir == 3)
+                                               vertex->Pos.rotateXZBy(90);
+                                       break;
+                               case 1: // z+
+                                       vertex->Pos.rotateYZBy(90);
+                                       if (facedir == 1)
+                                               vertex->Pos.rotateXYBy(90);
+                                       else if (facedir == 2)
+                                               vertex->Pos.rotateXYBy(180);
+                                       else if (facedir == 3)
+                                               vertex->Pos.rotateXYBy(-90);
+                                       break;
+                               case 2: //z-
+                                       vertex->Pos.rotateYZBy(-90);
+                                       if (facedir == 1)
+                                               vertex->Pos.rotateXYBy(-90);
+                                       else if (facedir == 2)
+                                               vertex->Pos.rotateXYBy(180);
+                                       else if (facedir == 3)
+                                               vertex->Pos.rotateXYBy(90);
+                                       break;
+                               case 3:  //x+
+                                       vertex->Pos.rotateXYBy(-90);
+                                       if (facedir == 1)
+                                               vertex->Pos.rotateYZBy(90);
+                                       else if (facedir == 2)
+                                               vertex->Pos.rotateYZBy(180);
+                                       else if (facedir == 3)
+                                               vertex->Pos.rotateYZBy(-90);
+                                       break;
+                               case 4:  //x-
+                                       vertex->Pos.rotateXYBy(90);
+                                       if (facedir == 1)
+                                               vertex->Pos.rotateYZBy(-90);
+                                       else if (facedir == 2)
+                                               vertex->Pos.rotateYZBy(180);
+                                       else if (facedir == 3)
+                                               vertex->Pos.rotateYZBy(90);
+                                       break;
+                               case 5:
+                                       vertex->Pos.rotateXYBy(-180);
+                                       if (facedir == 1)
+                                               vertex->Pos.rotateXZBy(90);
+                                       else if (facedir == 2)
+                                               vertex->Pos.rotateXZBy(180);
+                                       else if (facedir == 3)
+                                               vertex->Pos.rotateXZBy(-90);
+                                       break;
+                               default:
+                                       break;
+                       }
+               }
+       }
+}
+
+void recalculateBoundingBox(scene::IMesh *src_mesh)
+{
+       aabb3f bbox;
+       bbox.reset(0,0,0);
+       for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) {
+               scene::IMeshBuffer *buf = src_mesh->getMeshBuffer(j);
+               buf->recalculateBoundingBox();
+               if (j == 0)
+                       bbox = buf->getBoundingBox();
+               else
+                       bbox.addInternalBox(buf->getBoundingBox());
+       }
+       src_mesh->setBoundingBox(bbox);
+}
+
+scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer)
+{
+       switch (mesh_buffer->getVertexType()) {
+       case video::EVT_STANDARD: {
+               video::S3DVertex *v = (video::S3DVertex *) mesh_buffer->getVertices();
+               u16 *indices = mesh_buffer->getIndices();
+               scene::SMeshBuffer *cloned_buffer = new scene::SMeshBuffer();
+               cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
+                       mesh_buffer->getIndexCount());
+               return cloned_buffer;
+       }
+       case video::EVT_2TCOORDS: {
+               video::S3DVertex2TCoords *v =
+                       (video::S3DVertex2TCoords *) mesh_buffer->getVertices();
+               u16 *indices = mesh_buffer->getIndices();
+               scene::SMeshBufferTangents *cloned_buffer =
+                       new scene::SMeshBufferTangents();
+               cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
+                       mesh_buffer->getIndexCount());
+               return cloned_buffer;
+       }
+       case video::EVT_TANGENTS: {
+               video::S3DVertexTangents *v =
+                       (video::S3DVertexTangents *) mesh_buffer->getVertices();
+               u16 *indices = mesh_buffer->getIndices();
+               scene::SMeshBufferTangents *cloned_buffer =
+                       new scene::SMeshBufferTangents();
+               cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
+                       mesh_buffer->getIndexCount());
+               return cloned_buffer;
+       }
+       }
+       // This should not happen.
+       sanity_check(false);
+       return NULL;
+}
+
+scene::SMesh* cloneMesh(scene::IMesh *src_mesh)
+{
+       scene::SMesh* dst_mesh = new scene::SMesh();
+       for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) {
+               scene::IMeshBuffer *temp_buf = cloneMeshBuffer(
+                       src_mesh->getMeshBuffer(j));
+               dst_mesh->addMeshBuffer(temp_buf);
+               temp_buf->drop();
+
+       }
+       return dst_mesh;
+}
+
+scene::IMesh* convertNodeboxesToMesh(const std::vector<aabb3f> &boxes,
+               const f32 *uv_coords, float expand)
+{
+       scene::SMesh* dst_mesh = new scene::SMesh();
+
+       for (u16 j = 0; j < 6; j++)
+       {
+               scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+               buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
+               buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
+               dst_mesh->addMeshBuffer(buf);
+               buf->drop();
+       }
+
+       video::SColor c(255,255,255,255);
+
+       for (aabb3f box : boxes) {
+               box.repair();
+
+               box.MinEdge.X -= expand;
+               box.MinEdge.Y -= expand;
+               box.MinEdge.Z -= expand;
+               box.MaxEdge.X += expand;
+               box.MaxEdge.Y += expand;
+               box.MaxEdge.Z += expand;
+
+               // Compute texture UV coords
+               f32 tx1 = (box.MinEdge.X / BS) + 0.5;
+               f32 ty1 = (box.MinEdge.Y / BS) + 0.5;
+               f32 tz1 = (box.MinEdge.Z / BS) + 0.5;
+               f32 tx2 = (box.MaxEdge.X / BS) + 0.5;
+               f32 ty2 = (box.MaxEdge.Y / BS) + 0.5;
+               f32 tz2 = (box.MaxEdge.Z / BS) + 0.5;
+
+               f32 txc_default[24] = {
+                       // up
+                       tx1, 1 - tz2, tx2, 1 - tz1,
+                       // down
+                       tx1, tz1, tx2, tz2,
+                       // right
+                       tz1, 1 - ty2, tz2, 1 - ty1,
+                       // left
+                       1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1,
+                       // back
+                       1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1,
+                       // front
+                       tx1, 1 - ty2, tx2, 1 - ty1,
+               };
+
+               // use default texture UV mapping if not provided
+               const f32 *txc = uv_coords ? uv_coords : txc_default;
+
+               v3f min = box.MinEdge;
+               v3f max = box.MaxEdge;
+
+               video::S3DVertex vertices[24] =
+               {
+                       // up
+                       video::S3DVertex(min.X,max.Y,max.Z, 0,1,0, c, txc[0],txc[1]),
+                       video::S3DVertex(max.X,max.Y,max.Z, 0,1,0, c, txc[2],txc[1]),
+                       video::S3DVertex(max.X,max.Y,min.Z, 0,1,0, c, txc[2],txc[3]),
+                       video::S3DVertex(min.X,max.Y,min.Z, 0,1,0, c, txc[0],txc[3]),
+                       // down
+                       video::S3DVertex(min.X,min.Y,min.Z, 0,-1,0, c, txc[4],txc[5]),
+                       video::S3DVertex(max.X,min.Y,min.Z, 0,-1,0, c, txc[6],txc[5]),
+                       video::S3DVertex(max.X,min.Y,max.Z, 0,-1,0, c, txc[6],txc[7]),
+                       video::S3DVertex(min.X,min.Y,max.Z, 0,-1,0, c, txc[4],txc[7]),
+                       // right
+                       video::S3DVertex(max.X,max.Y,min.Z, 1,0,0, c, txc[ 8],txc[9]),
+                       video::S3DVertex(max.X,max.Y,max.Z, 1,0,0, c, txc[10],txc[9]),
+                       video::S3DVertex(max.X,min.Y,max.Z, 1,0,0, c, txc[10],txc[11]),
+                       video::S3DVertex(max.X,min.Y,min.Z, 1,0,0, c, txc[ 8],txc[11]),
+                       // left
+                       video::S3DVertex(min.X,max.Y,max.Z, -1,0,0, c, txc[12],txc[13]),
+                       video::S3DVertex(min.X,max.Y,min.Z, -1,0,0, c, txc[14],txc[13]),
+                       video::S3DVertex(min.X,min.Y,min.Z, -1,0,0, c, txc[14],txc[15]),
+                       video::S3DVertex(min.X,min.Y,max.Z, -1,0,0, c, txc[12],txc[15]),
+                       // back
+                       video::S3DVertex(max.X,max.Y,max.Z, 0,0,1, c, txc[16],txc[17]),
+                       video::S3DVertex(min.X,max.Y,max.Z, 0,0,1, c, txc[18],txc[17]),
+                       video::S3DVertex(min.X,min.Y,max.Z, 0,0,1, c, txc[18],txc[19]),
+                       video::S3DVertex(max.X,min.Y,max.Z, 0,0,1, c, txc[16],txc[19]),
+                       // front
+                       video::S3DVertex(min.X,max.Y,min.Z, 0,0,-1, c, txc[20],txc[21]),
+                       video::S3DVertex(max.X,max.Y,min.Z, 0,0,-1, c, txc[22],txc[21]),
+                       video::S3DVertex(max.X,min.Y,min.Z, 0,0,-1, c, txc[22],txc[23]),
+                       video::S3DVertex(min.X,min.Y,min.Z, 0,0,-1, c, txc[20],txc[23]),
+               };
+
+               u16 indices[] = {0,1,2,2,3,0};
+
+               for(u16 j = 0; j < 24; j += 4)
+               {
+                       scene::IMeshBuffer *buf = dst_mesh->getMeshBuffer(j / 4);
+                       buf->append(vertices + j, 4, indices, 6);
+               }
+       }
+       return dst_mesh;
+}
+
+struct vcache
+{
+       core::array<u32> tris;
+       float score;
+       s16 cachepos;
+       u16 NumActiveTris;
+};
+
+struct tcache
+{
+       u16 ind[3];
+       float score;
+       bool drawn;
+};
+
+const u16 cachesize = 32;
+
+float FindVertexScore(vcache *v)
+{
+       const float CacheDecayPower = 1.5f;
+       const float LastTriScore = 0.75f;
+       const float ValenceBoostScale = 2.0f;
+       const float ValenceBoostPower = 0.5f;
+       const float MaxSizeVertexCache = 32.0f;
+
+       if (v->NumActiveTris == 0)
+       {
+               // No tri needs this vertex!
+               return -1.0f;
+       }
+
+       float Score = 0.0f;
+       int CachePosition = v->cachepos;
+       if (CachePosition < 0)
+       {
+               // Vertex is not in FIFO cache - no score.
+       }
+       else
+       {
+               if (CachePosition < 3)
+               {
+                       // This vertex was used in the last triangle,
+                       // so it has a fixed score.
+                       Score = LastTriScore;
+               }
+               else
+               {
+                       // Points for being high in the cache.
+                       const float Scaler = 1.0f / (MaxSizeVertexCache - 3);
+                       Score = 1.0f - (CachePosition - 3) * Scaler;
+                       Score = powf(Score, CacheDecayPower);
+               }
+       }
+
+       // Bonus points for having a low number of tris still to
+       // use the vert, so we get rid of lone verts quickly.
+       float ValenceBoost = powf(v->NumActiveTris,
+                               -ValenceBoostPower);
+       Score += ValenceBoostScale * ValenceBoost;
+
+       return Score;
+}
+
+/*
+       A specialized LRU cache for the Forsyth algorithm.
+*/
+
+class f_lru
+{
+
+public:
+       f_lru(vcache *v, tcache *t): vc(v), tc(t)
+       {
+               for (int &i : cache) {
+                       i = -1;
+               }
+       }
+
+       // Adds this vertex index and returns the highest-scoring triangle index
+       u32 add(u16 vert, bool updatetris = false)
+       {
+               bool found = false;
+
+               // Mark existing pos as empty
+               for (u16 i = 0; i < cachesize; i++)
+               {
+                       if (cache[i] == vert)
+                       {
+                               // Move everything down
+                               for (u16 j = i; j; j--)
+                               {
+                                       cache[j] = cache[j - 1];
+                               }
+
+                               found = true;
+                               break;
+                       }
+               }
+
+               if (!found)
+               {
+                       if (cache[cachesize-1] != -1)
+                               vc[cache[cachesize-1]].cachepos = -1;
+
+                       // Move everything down
+                       for (u16 i = cachesize - 1; i; i--)
+                       {
+                               cache[i] = cache[i - 1];
+                       }
+               }
+
+               cache[0] = vert;
+
+               u32 highest = 0;
+               float hiscore = 0;
+
+               if (updatetris)
+               {
+                       // Update cache positions
+                       for (u16 i = 0; i < cachesize; i++)
+                       {
+                               if (cache[i] == -1)
+                                       break;
+
+                               vc[cache[i]].cachepos = i;
+                               vc[cache[i]].score = FindVertexScore(&vc[cache[i]]);
+                       }
+
+                       // Update triangle scores
+                       for (int i : cache) {
+                               if (i == -1)
+                                       break;
+
+                               const u16 trisize = vc[i].tris.size();
+                               for (u16 t = 0; t < trisize; t++)
+                               {
+                                       tcache *tri = &tc[vc[i].tris[t]];
+
+                                       tri->score =
+                                               vc[tri->ind[0]].score +
+                                               vc[tri->ind[1]].score +
+                                               vc[tri->ind[2]].score;
+
+                                       if (tri->score > hiscore)
+                                       {
+                                               hiscore = tri->score;
+                                               highest = vc[i].tris[t];
+                                       }
+                               }
+                       }
+               }
+
+               return highest;
+       }
+
+private:
+       s32 cache[cachesize];
+       vcache *vc;
+       tcache *tc;
+};
+
+/**
+Vertex cache optimization according to the Forsyth paper:
+http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html
+
+The function is thread-safe (read: you can optimize several meshes in different threads)
+
+\param mesh Source mesh for the operation.  */
+scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh)
+{
+       if (!mesh)
+               return 0;
+
+       scene::SMesh *newmesh = new scene::SMesh();
+       newmesh->BoundingBox = mesh->getBoundingBox();
+
+       const u32 mbcount = mesh->getMeshBufferCount();
+
+       for (u32 b = 0; b < mbcount; ++b)
+       {
+               const scene::IMeshBuffer *mb = mesh->getMeshBuffer(b);
+
+               if (mb->getIndexType() != video::EIT_16BIT)
+               {
+                       //os::Printer::log("Cannot optimize a mesh with 32bit indices", ELL_ERROR);
+                       newmesh->drop();
+                       return 0;
+               }
+
+               const u32 icount = mb->getIndexCount();
+               const u32 tcount = icount / 3;
+               const u32 vcount = mb->getVertexCount();
+               const u16 *ind = mb->getIndices();
+
+               vcache *vc = new vcache[vcount];
+               tcache *tc = new tcache[tcount];
+
+               f_lru lru(vc, tc);
+
+               // init
+               for (u16 i = 0; i < vcount; i++)
+               {
+                       vc[i].score = 0;
+                       vc[i].cachepos = -1;
+                       vc[i].NumActiveTris = 0;
+               }
+
+               // First pass: count how many times a vert is used
+               for (u32 i = 0; i < icount; i += 3)
+               {
+                       vc[ind[i]].NumActiveTris++;
+                       vc[ind[i + 1]].NumActiveTris++;
+                       vc[ind[i + 2]].NumActiveTris++;
+
+                       const u32 tri_ind = i/3;
+                       tc[tri_ind].ind[0] = ind[i];
+                       tc[tri_ind].ind[1] = ind[i + 1];
+                       tc[tri_ind].ind[2] = ind[i + 2];
+               }
+
+               // Second pass: list of each triangle
+               for (u32 i = 0; i < tcount; i++)
+               {
+                       vc[tc[i].ind[0]].tris.push_back(i);
+                       vc[tc[i].ind[1]].tris.push_back(i);
+                       vc[tc[i].ind[2]].tris.push_back(i);
+
+                       tc[i].drawn = false;
+               }
+
+               // Give initial scores
+               for (u16 i = 0; i < vcount; i++)
+               {
+                       vc[i].score = FindVertexScore(&vc[i]);
+               }
+               for (u32 i = 0; i < tcount; i++)
+               {
+                       tc[i].score =
+                                       vc[tc[i].ind[0]].score +
+                                       vc[tc[i].ind[1]].score +
+                                       vc[tc[i].ind[2]].score;
+               }
+
+               switch(mb->getVertexType())
+               {
+                       case video::EVT_STANDARD:
+                       {
+                               video::S3DVertex *v = (video::S3DVertex *) mb->getVertices();
+
+                               scene::SMeshBuffer *buf = new scene::SMeshBuffer();
+                               buf->Material = mb->getMaterial();
+
+                               buf->Vertices.reallocate(vcount);
+                               buf->Indices.reallocate(icount);
+
+                               core::map<const video::S3DVertex, const u16> sind; // search index for fast operation
+                               typedef core::map<const video::S3DVertex, const u16>::Node snode;
+
+                               // Main algorithm
+                               u32 highest = 0;
+                               u32 drawcalls = 0;
+                               for (;;)
+                               {
+                                       if (tc[highest].drawn)
+                                       {
+                                               bool found = false;
+                                               float hiscore = 0;
+                                               for (u32 t = 0; t < tcount; t++)
+                                               {
+                                                       if (!tc[t].drawn)
+                                                       {
+                                                               if (tc[t].score > hiscore)
+                                                               {
+                                                                       highest = t;
+                                                                       hiscore = tc[t].score;
+                                                                       found = true;
+                                                               }
+                                                       }
+                                               }
+                                               if (!found)
+                                                       break;
+                                       }
+
+                                       // Output the best triangle
+                                       u16 newind = buf->Vertices.size();
+
+                                       snode *s = sind.find(v[tc[highest].ind[0]]);
+
+                                       if (!s)
+                                       {
+                                               buf->Vertices.push_back(v[tc[highest].ind[0]]);
+                                               buf->Indices.push_back(newind);
+                                               sind.insert(v[tc[highest].ind[0]], newind);
+                                               newind++;
+                                       }
+                                       else
+                                       {
+                                               buf->Indices.push_back(s->getValue());
+                                       }
+
+                                       s = sind.find(v[tc[highest].ind[1]]);
+
+                                       if (!s)
+                                       {
+                                               buf->Vertices.push_back(v[tc[highest].ind[1]]);
+                                               buf->Indices.push_back(newind);
+                                               sind.insert(v[tc[highest].ind[1]], newind);
+                                               newind++;
+                                       }
+                                       else
+                                       {
+                                               buf->Indices.push_back(s->getValue());
+                                       }
+
+                                       s = sind.find(v[tc[highest].ind[2]]);
+
+                                       if (!s)
+                                       {
+                                               buf->Vertices.push_back(v[tc[highest].ind[2]]);
+                                               buf->Indices.push_back(newind);
+                                               sind.insert(v[tc[highest].ind[2]], newind);
+                                       }
+                                       else
+                                       {
+                                               buf->Indices.push_back(s->getValue());
+                                       }
+
+                                       vc[tc[highest].ind[0]].NumActiveTris--;
+                                       vc[tc[highest].ind[1]].NumActiveTris--;
+                                       vc[tc[highest].ind[2]].NumActiveTris--;
+
+                                       tc[highest].drawn = true;
+
+                                       for (u16 j : tc[highest].ind) {
+                                               vcache *vert = &vc[j];
+                                               for (u16 t = 0; t < vert->tris.size(); t++)
+                                               {
+                                                       if (highest == vert->tris[t])
+                                                       {
+                                                               vert->tris.erase(t);
+                                                               break;
+                                                       }
+                                               }
+                                       }
+
+                                       lru.add(tc[highest].ind[0]);
+                                       lru.add(tc[highest].ind[1]);
+                                       highest = lru.add(tc[highest].ind[2], true);
+                                       drawcalls++;
+                               }
+
+                               buf->setBoundingBox(mb->getBoundingBox());
+                               newmesh->addMeshBuffer(buf);
+                               buf->drop();
+                       }
+                       break;
+                       case video::EVT_2TCOORDS:
+                       {
+                               video::S3DVertex2TCoords *v = (video::S3DVertex2TCoords *) mb->getVertices();
+
+                               scene::SMeshBufferLightMap *buf = new scene::SMeshBufferLightMap();
+                               buf->Material = mb->getMaterial();
+
+                               buf->Vertices.reallocate(vcount);
+                               buf->Indices.reallocate(icount);
+
+                               core::map<const video::S3DVertex2TCoords, const u16> sind; // search index for fast operation
+                               typedef core::map<const video::S3DVertex2TCoords, const u16>::Node snode;
+
+                               // Main algorithm
+                               u32 highest = 0;
+                               u32 drawcalls = 0;
+                               for (;;)
+                               {
+                                       if (tc[highest].drawn)
+                                       {
+                                               bool found = false;
+                                               float hiscore = 0;
+                                               for (u32 t = 0; t < tcount; t++)
+                                               {
+                                                       if (!tc[t].drawn)
+                                                       {
+                                                               if (tc[t].score > hiscore)
+                                                               {
+                                                                       highest = t;
+                                                                       hiscore = tc[t].score;
+                                                                       found = true;
+                                                               }
+                                                       }
+                                               }
+                                               if (!found)
+                                                       break;
+                                       }
+
+                                       // Output the best triangle
+                                       u16 newind = buf->Vertices.size();
+
+                                       snode *s = sind.find(v[tc[highest].ind[0]]);
+
+                                       if (!s)
+                                       {
+                                               buf->Vertices.push_back(v[tc[highest].ind[0]]);
+                                               buf->Indices.push_back(newind);
+                                               sind.insert(v[tc[highest].ind[0]], newind);
+                                               newind++;
+                                       }
+                                       else
+                                       {
+                                               buf->Indices.push_back(s->getValue());
+                                       }
+
+                                       s = sind.find(v[tc[highest].ind[1]]);
+
+                                       if (!s)
+                                       {
+                                               buf->Vertices.push_back(v[tc[highest].ind[1]]);
+                                               buf->Indices.push_back(newind);
+                                               sind.insert(v[tc[highest].ind[1]], newind);
+                                               newind++;
+                                       }
+                                       else
+                                       {
+                                               buf->Indices.push_back(s->getValue());
+                                       }
+
+                                       s = sind.find(v[tc[highest].ind[2]]);
+
+                                       if (!s)
+                                       {
+                                               buf->Vertices.push_back(v[tc[highest].ind[2]]);
+                                               buf->Indices.push_back(newind);
+                                               sind.insert(v[tc[highest].ind[2]], newind);
+                                       }
+                                       else
+                                       {
+                                               buf->Indices.push_back(s->getValue());
+                                       }
+
+                                       vc[tc[highest].ind[0]].NumActiveTris--;
+                                       vc[tc[highest].ind[1]].NumActiveTris--;
+                                       vc[tc[highest].ind[2]].NumActiveTris--;
+
+                                       tc[highest].drawn = true;
+
+                                       for (u16 j : tc[highest].ind) {
+                                               vcache *vert = &vc[j];
+                                               for (u16 t = 0; t < vert->tris.size(); t++)
+                                               {
+                                                       if (highest == vert->tris[t])
+                                                       {
+                                                               vert->tris.erase(t);
+                                                               break;
+                                                       }
+                                               }
+                                       }
+
+                                       lru.add(tc[highest].ind[0]);
+                                       lru.add(tc[highest].ind[1]);
+                                       highest = lru.add(tc[highest].ind[2]);
+                                       drawcalls++;
+                               }
+
+                               buf->setBoundingBox(mb->getBoundingBox());
+                               newmesh->addMeshBuffer(buf);
+                               buf->drop();
+
+                       }
+                       break;
+                       case video::EVT_TANGENTS:
+                       {
+                               video::S3DVertexTangents *v = (video::S3DVertexTangents *) mb->getVertices();
+
+                               scene::SMeshBufferTangents *buf = new scene::SMeshBufferTangents();
+                               buf->Material = mb->getMaterial();
+
+                               buf->Vertices.reallocate(vcount);
+                               buf->Indices.reallocate(icount);
+
+                               core::map<const video::S3DVertexTangents, const u16> sind; // search index for fast operation
+                               typedef core::map<const video::S3DVertexTangents, const u16>::Node snode;
+
+                               // Main algorithm
+                               u32 highest = 0;
+                               u32 drawcalls = 0;
+                               for (;;)
+                               {
+                                       if (tc[highest].drawn)
+                                       {
+                                               bool found = false;
+                                               float hiscore = 0;
+                                               for (u32 t = 0; t < tcount; t++)
+                                               {
+                                                       if (!tc[t].drawn)
+                                                       {
+                                                               if (tc[t].score > hiscore)
+                                                               {
+                                                                       highest = t;
+                                                                       hiscore = tc[t].score;
+                                                                       found = true;
+                                                               }
+                                                       }
+                                               }
+                                               if (!found)
+                                                       break;
+                                       }
+
+                                       // Output the best triangle
+                                       u16 newind = buf->Vertices.size();
+
+                                       snode *s = sind.find(v[tc[highest].ind[0]]);
+
+                                       if (!s)
+                                       {
+                                               buf->Vertices.push_back(v[tc[highest].ind[0]]);
+                                               buf->Indices.push_back(newind);
+                                               sind.insert(v[tc[highest].ind[0]], newind);
+                                               newind++;
+                                       }
+                                       else
+                                       {
+                                               buf->Indices.push_back(s->getValue());
+                                       }
+
+                                       s = sind.find(v[tc[highest].ind[1]]);
+
+                                       if (!s)
+                                       {
+                                               buf->Vertices.push_back(v[tc[highest].ind[1]]);
+                                               buf->Indices.push_back(newind);
+                                               sind.insert(v[tc[highest].ind[1]], newind);
+                                               newind++;
+                                       }
+                                       else
+                                       {
+                                               buf->Indices.push_back(s->getValue());
+                                       }
+
+                                       s = sind.find(v[tc[highest].ind[2]]);
+
+                                       if (!s)
+                                       {
+                                               buf->Vertices.push_back(v[tc[highest].ind[2]]);
+                                               buf->Indices.push_back(newind);
+                                               sind.insert(v[tc[highest].ind[2]], newind);
+                                       }
+                                       else
+                                       {
+                                               buf->Indices.push_back(s->getValue());
+                                       }
+
+                                       vc[tc[highest].ind[0]].NumActiveTris--;
+                                       vc[tc[highest].ind[1]].NumActiveTris--;
+                                       vc[tc[highest].ind[2]].NumActiveTris--;
+
+                                       tc[highest].drawn = true;
+
+                                       for (u16 j : tc[highest].ind) {
+                                               vcache *vert = &vc[j];
+                                               for (u16 t = 0; t < vert->tris.size(); t++)
+                                               {
+                                                       if (highest == vert->tris[t])
+                                                       {
+                                                               vert->tris.erase(t);
+                                                               break;
+                                                       }
+                                               }
+                                       }
+
+                                       lru.add(tc[highest].ind[0]);
+                                       lru.add(tc[highest].ind[1]);
+                                       highest = lru.add(tc[highest].ind[2]);
+                                       drawcalls++;
+                               }
+
+                               buf->setBoundingBox(mb->getBoundingBox());
+                               newmesh->addMeshBuffer(buf);
+                               buf->drop();
+                       }
+                       break;
+               }
+
+               delete [] vc;
+               delete [] tc;
+
+       } // for each meshbuffer
+
+       return newmesh;
+}
diff --git a/src/client/mesh.h b/src/client/mesh.h
new file mode 100644 (file)
index 0000000..0c4094d
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "nodedef.h"
+
+/*!
+ * Applies shading to a color based on the surface's
+ * normal vector.
+ */
+void applyFacesShading(video::SColor &color, const v3f &normal);
+
+/*
+       Create a new cube mesh.
+       Vertices are at (+-scale.X/2, +-scale.Y/2, +-scale.Z/2).
+
+       The resulting mesh has 6 materials (up, down, right, left, back, front)
+       which must be defined by the caller.
+*/
+scene::IAnimatedMesh* createCubeMesh(v3f scale);
+
+/*
+       Multiplies each vertex coordinate by the specified scaling factors
+       (componentwise vector multiplication).
+*/
+void scaleMesh(scene::IMesh *mesh, v3f scale);
+
+/*
+       Translate each vertex coordinate by the specified vector.
+*/
+void translateMesh(scene::IMesh *mesh, v3f vec);
+
+/*!
+ * Sets a constant color for all vertices in the mesh buffer.
+ */
+void setMeshBufferColor(scene::IMeshBuffer *buf, const video::SColor &color);
+
+/*
+       Set a constant color for all vertices in the mesh
+*/
+void setMeshColor(scene::IMesh *mesh, const video::SColor &color);
+
+/*
+       Set a constant color for an animated mesh
+*/
+void setAnimatedMeshColor(scene::IAnimatedMeshSceneNode *node, const video::SColor &color);
+
+/*!
+ * Overwrites the color of a mesh buffer.
+ * The color is darkened based on the normal vector of the vertices.
+ */
+void colorizeMeshBuffer(scene::IMeshBuffer *buf, const video::SColor *buffercolor);
+
+/*
+       Set the color of all vertices in the mesh.
+       For each vertex, determine the largest absolute entry in
+       the normal vector, and choose one of colorX, colorY or
+       colorZ accordingly.
+*/
+void setMeshColorByNormalXYZ(scene::IMesh *mesh,
+               const video::SColor &colorX,
+               const video::SColor &colorY,
+               const video::SColor &colorZ);
+
+void setMeshColorByNormal(scene::IMesh *mesh, const v3f &normal,
+               const video::SColor &color);
+
+/*
+       Rotate the mesh by 6d facedir value.
+       Method only for meshnodes, not suitable for entities.
+*/
+void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir);
+
+/*
+       Rotate the mesh around the axis and given angle in degrees.
+*/
+void rotateMeshXYby (scene::IMesh *mesh, f64 degrees);
+void rotateMeshXZby (scene::IMesh *mesh, f64 degrees);
+void rotateMeshYZby (scene::IMesh *mesh, f64 degrees);
+
+/*
+ *  Clone the mesh buffer.
+ *  The returned pointer should be dropped.
+ */
+scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer);
+
+/*
+       Clone the mesh.
+*/
+scene::SMesh* cloneMesh(scene::IMesh *src_mesh);
+
+/*
+       Convert nodeboxes to mesh. Each tile goes into a different buffer.
+       boxes - set of nodeboxes to be converted into cuboids
+       uv_coords[24] - table of texture uv coords for each cuboid face
+       expand - factor by which cuboids will be resized
+*/
+scene::IMesh* convertNodeboxesToMesh(const std::vector<aabb3f> &boxes,
+               const f32 *uv_coords = NULL, float expand = 0);
+
+/*
+       Update bounding box for a mesh.
+*/
+void recalculateBoundingBox(scene::IMesh *src_mesh);
+
+/*
+       Vertex cache optimization according to the Forsyth paper:
+       http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html
+       Ported from irrlicht 1.8
+*/
+scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh);
diff --git a/src/client/mesh_generator_thread.cpp b/src/client/mesh_generator_thread.cpp
new file mode 100644 (file)
index 0000000..be4bcc1
--- /dev/null
@@ -0,0 +1,308 @@
+/*
+Minetest
+Copyright (C) 2013, 2017 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 "mesh_generator_thread.h"
+#include "settings.h"
+#include "profiler.h"
+#include "client.h"
+#include "mapblock.h"
+#include "map.h"
+
+/*
+       CachedMapBlockData
+*/
+
+CachedMapBlockData::~CachedMapBlockData()
+{
+       assert(refcount_from_queue == 0);
+
+       delete[] data;
+}
+
+/*
+       QueuedMeshUpdate
+*/
+
+QueuedMeshUpdate::~QueuedMeshUpdate()
+{
+       delete data;
+}
+
+/*
+       MeshUpdateQueue
+*/
+
+MeshUpdateQueue::MeshUpdateQueue(Client *client):
+       m_client(client)
+{
+       m_cache_enable_shaders = g_settings->getBool("enable_shaders");
+       m_cache_use_tangent_vertices = m_cache_enable_shaders && (
+               g_settings->getBool("enable_bumpmapping") ||
+               g_settings->getBool("enable_parallax_occlusion"));
+       m_cache_smooth_lighting = g_settings->getBool("smooth_lighting");
+       m_meshgen_block_cache_size = g_settings->getS32("meshgen_block_cache_size");
+}
+
+MeshUpdateQueue::~MeshUpdateQueue()
+{
+       MutexAutoLock lock(m_mutex);
+
+       for (auto &i : m_cache) {
+               delete i.second;
+       }
+
+       for (QueuedMeshUpdate *q : m_queue) {
+               delete q;
+       }
+}
+
+void MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent)
+{
+       MutexAutoLock lock(m_mutex);
+
+       cleanupCache();
+
+       /*
+               Cache the block data (force-update the center block, don't update the
+               neighbors but get them if they aren't already cached)
+       */
+       std::vector<CachedMapBlockData*> cached_blocks;
+       size_t cache_hit_counter = 0;
+       cached_blocks.reserve(3*3*3);
+       v3s16 dp;
+       for (dp.X = -1; dp.X <= 1; dp.X++)
+       for (dp.Y = -1; dp.Y <= 1; dp.Y++)
+       for (dp.Z = -1; dp.Z <= 1; dp.Z++) {
+               v3s16 p1 = p + dp;
+               CachedMapBlockData *cached_block;
+               if (dp == v3s16(0, 0, 0))
+                       cached_block = cacheBlock(map, p1, FORCE_UPDATE);
+               else
+                       cached_block = cacheBlock(map, p1, SKIP_UPDATE_IF_ALREADY_CACHED,
+                                       &cache_hit_counter);
+               cached_blocks.push_back(cached_block);
+       }
+       g_profiler->avg("MeshUpdateQueue MapBlock cache hit %",
+                       100.0f * cache_hit_counter / cached_blocks.size());
+
+       /*
+               Mark the block as urgent if requested
+       */
+       if (urgent)
+               m_urgents.insert(p);
+
+       /*
+               Find if block is already in queue.
+               If it is, update the data and quit.
+       */
+       for (QueuedMeshUpdate *q : m_queue) {
+               if (q->p == p) {
+                       // NOTE: We are not adding a new position to the queue, thus
+                       //       refcount_from_queue stays the same.
+                       if(ack_block_to_server)
+                               q->ack_block_to_server = true;
+                       q->crack_level = m_client->getCrackLevel();
+                       q->crack_pos = m_client->getCrackPos();
+                       return;
+               }
+       }
+
+       /*
+               Add the block
+       */
+       QueuedMeshUpdate *q = new QueuedMeshUpdate;
+       q->p = p;
+       q->ack_block_to_server = ack_block_to_server;
+       q->crack_level = m_client->getCrackLevel();
+       q->crack_pos = m_client->getCrackPos();
+       m_queue.push_back(q);
+
+       // This queue entry is a new reference to the cached blocks
+       for (CachedMapBlockData *cached_block : cached_blocks) {
+               cached_block->refcount_from_queue++;
+       }
+}
+
+// Returned pointer must be deleted
+// Returns NULL if queue is empty
+QueuedMeshUpdate *MeshUpdateQueue::pop()
+{
+       MutexAutoLock lock(m_mutex);
+
+       bool must_be_urgent = !m_urgents.empty();
+       for (std::vector<QueuedMeshUpdate*>::iterator i = m_queue.begin();
+                       i != m_queue.end(); ++i) {
+               QueuedMeshUpdate *q = *i;
+               if(must_be_urgent && m_urgents.count(q->p) == 0)
+                       continue;
+               m_queue.erase(i);
+               m_urgents.erase(q->p);
+               fillDataFromMapBlockCache(q);
+               return q;
+       }
+       return NULL;
+}
+
+CachedMapBlockData* MeshUpdateQueue::cacheBlock(Map *map, v3s16 p, UpdateMode mode,
+                       size_t *cache_hit_counter)
+{
+       std::map<v3s16, CachedMapBlockData*>::iterator it =
+                       m_cache.find(p);
+       if (it != m_cache.end()) {
+               // Already in cache
+               CachedMapBlockData *cached_block = it->second;
+               if (mode == SKIP_UPDATE_IF_ALREADY_CACHED) {
+                       if (cache_hit_counter)
+                               (*cache_hit_counter)++;
+                       return cached_block;
+               }
+               MapBlock *b = map->getBlockNoCreateNoEx(p);
+               if (b) {
+                       if (cached_block->data == NULL)
+                               cached_block->data =
+                                               new MapNode[MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE];
+                       memcpy(cached_block->data, b->getData(),
+                                       MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE * sizeof(MapNode));
+               } else {
+                       delete[] cached_block->data;
+                       cached_block->data = NULL;
+               }
+               return cached_block;
+       }
+
+       // Not yet in cache
+       CachedMapBlockData *cached_block = new CachedMapBlockData();
+       m_cache[p] = cached_block;
+       MapBlock *b = map->getBlockNoCreateNoEx(p);
+       if (b) {
+               cached_block->data =
+                               new MapNode[MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE];
+               memcpy(cached_block->data, b->getData(),
+                               MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE * sizeof(MapNode));
+       }
+       return cached_block;
+}
+
+CachedMapBlockData* MeshUpdateQueue::getCachedBlock(const v3s16 &p)
+{
+       std::map<v3s16, CachedMapBlockData*>::iterator it = m_cache.find(p);
+       if (it != m_cache.end()) {
+               return it->second;
+       }
+       return NULL;
+}
+
+void MeshUpdateQueue::fillDataFromMapBlockCache(QueuedMeshUpdate *q)
+{
+       MeshMakeData *data = new MeshMakeData(m_client, m_cache_enable_shaders,
+                       m_cache_use_tangent_vertices);
+       q->data = data;
+
+       data->fillBlockDataBegin(q->p);
+
+       std::time_t t_now = std::time(0);
+
+       // Collect data for 3*3*3 blocks from cache
+       v3s16 dp;
+       for (dp.X = -1; dp.X <= 1; dp.X++)
+       for (dp.Y = -1; dp.Y <= 1; dp.Y++)
+       for (dp.Z = -1; dp.Z <= 1; dp.Z++) {
+               v3s16 p = q->p + dp;
+               CachedMapBlockData *cached_block = getCachedBlock(p);
+               if (cached_block) {
+                       cached_block->refcount_from_queue--;
+                       cached_block->last_used_timestamp = t_now;
+                       if (cached_block->data)
+                               data->fillBlockData(dp, cached_block->data);
+               }
+       }
+
+       data->setCrack(q->crack_level, q->crack_pos);
+       data->setSmoothLighting(m_cache_smooth_lighting);
+}
+
+void MeshUpdateQueue::cleanupCache()
+{
+       const int mapblock_kB = MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE *
+                       sizeof(MapNode) / 1000;
+       g_profiler->avg("MeshUpdateQueue MapBlock cache size kB",
+                       mapblock_kB * m_cache.size());
+
+       // The cache size is kept roughly below cache_soft_max_size, not letting
+       // anything get older than cache_seconds_max or deleted before 2 seconds.
+       const int cache_seconds_max = 10;
+       const int cache_soft_max_size = m_meshgen_block_cache_size * 1000 / mapblock_kB;
+       int cache_seconds = MYMAX(2, cache_seconds_max -
+                       m_cache.size() / (cache_soft_max_size / cache_seconds_max));
+
+       int t_now = time(0);
+
+       for (std::map<v3s16, CachedMapBlockData*>::iterator it = m_cache.begin();
+                       it != m_cache.end(); ) {
+               CachedMapBlockData *cached_block = it->second;
+               if (cached_block->refcount_from_queue == 0 &&
+                               cached_block->last_used_timestamp < t_now - cache_seconds) {
+                       m_cache.erase(it++);
+                       delete cached_block;
+               } else {
+                       ++it;
+               }
+       }
+}
+
+/*
+       MeshUpdateThread
+*/
+
+MeshUpdateThread::MeshUpdateThread(Client *client):
+       UpdateThread("Mesh"),
+       m_queue_in(client)
+{
+       m_generation_interval = g_settings->getU16("mesh_generation_interval");
+       m_generation_interval = rangelim(m_generation_interval, 0, 50);
+}
+
+void MeshUpdateThread::updateBlock(Map *map, v3s16 p, bool ack_block_to_server,
+               bool urgent)
+{
+       // Allow the MeshUpdateQueue to do whatever it wants
+       m_queue_in.addBlock(map, p, ack_block_to_server, urgent);
+       deferUpdate();
+}
+
+void MeshUpdateThread::doUpdate()
+{
+       QueuedMeshUpdate *q;
+       while ((q = m_queue_in.pop())) {
+               if (m_generation_interval)
+                       sleep_ms(m_generation_interval);
+               ScopeProfiler sp(g_profiler, "Client: Mesh making");
+
+               MapBlockMesh *mesh_new = new MapBlockMesh(q->data, m_camera_offset);
+
+               MeshUpdateResult r;
+               r.p = q->p;
+               r.mesh = mesh_new;
+               r.ack_block_to_server = q->ack_block_to_server;
+
+               m_queue_out.push_back(r);
+
+               delete q;
+       }
+}
diff --git a/src/client/mesh_generator_thread.h b/src/client/mesh_generator_thread.h
new file mode 100644 (file)
index 0000000..9a42852
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+Minetest
+Copyright (C) 2013, 2017 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include <ctime>
+#include <mutex>
+#include "mapblock_mesh.h"
+#include "threading/mutex_auto_lock.h"
+#include "util/thread.h"
+
+struct CachedMapBlockData
+{
+       v3s16 p = v3s16(-1337, -1337, -1337);
+       MapNode *data = nullptr; // A copy of the MapBlock's data member
+       int refcount_from_queue = 0;
+       std::time_t last_used_timestamp = std::time(0);
+
+       CachedMapBlockData() = default;
+       ~CachedMapBlockData();
+};
+
+struct QueuedMeshUpdate
+{
+       v3s16 p = v3s16(-1337, -1337, -1337);
+       bool ack_block_to_server = false;
+       bool urgent = false;
+       int crack_level = -1;
+       v3s16 crack_pos;
+       MeshMakeData *data = nullptr; // This is generated in MeshUpdateQueue::pop()
+
+       QueuedMeshUpdate() = default;
+       ~QueuedMeshUpdate();
+};
+
+/*
+       A thread-safe queue of mesh update tasks and a cache of MapBlock data
+*/
+class MeshUpdateQueue
+{
+       enum UpdateMode
+       {
+               FORCE_UPDATE,
+               SKIP_UPDATE_IF_ALREADY_CACHED,
+       };
+
+public:
+       MeshUpdateQueue(Client *client);
+
+       ~MeshUpdateQueue();
+
+       // Caches the block at p and its neighbors (if needed) and queues a mesh
+       // update for the block at p
+       void addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent);
+
+       // Returned pointer must be deleted
+       // Returns NULL if queue is empty
+       QueuedMeshUpdate *pop();
+
+       u32 size()
+       {
+               MutexAutoLock lock(m_mutex);
+               return m_queue.size();
+       }
+
+private:
+       Client *m_client;
+       std::vector<QueuedMeshUpdate *> m_queue;
+       std::set<v3s16> m_urgents;
+       std::map<v3s16, CachedMapBlockData *> m_cache;
+       std::mutex m_mutex;
+
+       // TODO: Add callback to update these when g_settings changes
+       bool m_cache_enable_shaders;
+       bool m_cache_use_tangent_vertices;
+       bool m_cache_smooth_lighting;
+       int m_meshgen_block_cache_size;
+
+       CachedMapBlockData *cacheBlock(Map *map, v3s16 p, UpdateMode mode,
+                       size_t *cache_hit_counter = NULL);
+       CachedMapBlockData *getCachedBlock(const v3s16 &p);
+       void fillDataFromMapBlockCache(QueuedMeshUpdate *q);
+       void cleanupCache();
+};
+
+struct MeshUpdateResult
+{
+       v3s16 p = v3s16(-1338, -1338, -1338);
+       MapBlockMesh *mesh = nullptr;
+       bool ack_block_to_server = false;
+
+       MeshUpdateResult() = default;
+};
+
+class MeshUpdateThread : public UpdateThread
+{
+public:
+       MeshUpdateThread(Client *client);
+
+       // Caches the block at p and its neighbors (if needed) and queues a mesh
+       // update for the block at p
+       void updateBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent);
+
+       v3s16 m_camera_offset;
+       MutexedQueue<MeshUpdateResult> m_queue_out;
+
+private:
+       MeshUpdateQueue m_queue_in;
+
+       // TODO: Add callback to update these when g_settings changes
+       int m_generation_interval;
+
+protected:
+       virtual void doUpdate();
+};
index c317a0772666a8b9869bf53a5ba73fd8dd726923..25457c8687b35b1d26ebe0e011513ade5a46af6b 100644 (file)
@@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "collector.h"
 #include <stdexcept>
 #include "log.h"
-#include "mesh.h"
+#include "client/mesh.h"
 
 void MeshCollector::append(const TileSpec &tile, const video::S3DVertex *vertices,
                u32 numVertices, const u16 *indices, u32 numIndices)
diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp
new file mode 100644 (file)
index 0000000..4d83c08
--- /dev/null
@@ -0,0 +1,624 @@
+/*
+Minetest
+Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 "minimap.h"
+#include <cmath>
+#include "client.h"
+#include "clientmap.h"
+#include "settings.h"
+#include "shader.h"
+#include "mapblock.h"
+#include "client/renderingengine.h"
+
+
+////
+//// MinimapUpdateThread
+////
+
+MinimapUpdateThread::~MinimapUpdateThread()
+{
+       for (auto &it : m_blocks_cache) {
+               delete it.second;
+       }
+
+       for (auto &q : m_update_queue) {
+               delete q.data;
+       }
+}
+
+bool MinimapUpdateThread::pushBlockUpdate(v3s16 pos, MinimapMapblock *data)
+{
+       MutexAutoLock lock(m_queue_mutex);
+
+       // Find if block is already in queue.
+       // If it is, update the data and quit.
+       for (QueuedMinimapUpdate &q : m_update_queue) {
+               if (q.pos == pos) {
+                       delete q.data;
+                       q.data = data;
+                       return false;
+               }
+       }
+
+       // Add the block
+       QueuedMinimapUpdate q;
+       q.pos  = pos;
+       q.data = data;
+       m_update_queue.push_back(q);
+
+       return true;
+}
+
+bool MinimapUpdateThread::popBlockUpdate(QueuedMinimapUpdate *update)
+{
+       MutexAutoLock lock(m_queue_mutex);
+
+       if (m_update_queue.empty())
+               return false;
+
+       *update = m_update_queue.front();
+       m_update_queue.pop_front();
+
+       return true;
+}
+
+void MinimapUpdateThread::enqueueBlock(v3s16 pos, MinimapMapblock *data)
+{
+       pushBlockUpdate(pos, data);
+       deferUpdate();
+}
+
+
+void MinimapUpdateThread::doUpdate()
+{
+       QueuedMinimapUpdate update;
+
+       while (popBlockUpdate(&update)) {
+               if (update.data) {
+                       // Swap two values in the map using single lookup
+                       std::pair<std::map<v3s16, MinimapMapblock*>::iterator, bool>
+                           result = m_blocks_cache.insert(std::make_pair(update.pos, update.data));
+                       if (!result.second) {
+                               delete result.first->second;
+                               result.first->second = update.data;
+                       }
+               } else {
+                       std::map<v3s16, MinimapMapblock *>::iterator it;
+                       it = m_blocks_cache.find(update.pos);
+                       if (it != m_blocks_cache.end()) {
+                               delete it->second;
+                               m_blocks_cache.erase(it);
+                       }
+               }
+       }
+
+       if (data->map_invalidated && data->mode != MINIMAP_MODE_OFF) {
+               getMap(data->pos, data->map_size, data->scan_height);
+               data->map_invalidated = false;
+       }
+}
+
+void MinimapUpdateThread::getMap(v3s16 pos, s16 size, s16 height)
+{
+       v3s16 pos_min(pos.X - size / 2, pos.Y - height / 2, pos.Z - size / 2);
+       v3s16 pos_max(pos_min.X + size - 1, pos.Y + height / 2, pos_min.Z + size - 1);
+       v3s16 blockpos_min = getNodeBlockPos(pos_min);
+       v3s16 blockpos_max = getNodeBlockPos(pos_max);
+
+// clear the map
+       for (int z = 0; z < size; z++)
+       for (int x = 0; x < size; x++) {
+               MinimapPixel &mmpixel = data->minimap_scan[x + z * size];
+               mmpixel.air_count = 0;
+               mmpixel.height = 0;
+               mmpixel.n = MapNode(CONTENT_AIR);
+       }
+
+// draw the map
+       v3s16 blockpos;
+       for (blockpos.Z = blockpos_min.Z; blockpos.Z <= blockpos_max.Z; ++blockpos.Z)
+       for (blockpos.Y = blockpos_min.Y; blockpos.Y <= blockpos_max.Y; ++blockpos.Y)
+       for (blockpos.X = blockpos_min.X; blockpos.X <= blockpos_max.X; ++blockpos.X) {
+               std::map<v3s16, MinimapMapblock *>::const_iterator pblock =
+                       m_blocks_cache.find(blockpos);
+               if (pblock == m_blocks_cache.end())
+                       continue;
+               const MinimapMapblock &block = *pblock->second;
+
+               v3s16 block_node_min(blockpos * MAP_BLOCKSIZE);
+               v3s16 block_node_max(block_node_min + MAP_BLOCKSIZE - 1);
+               // clip
+               v3s16 range_min = componentwise_max(block_node_min, pos_min);
+               v3s16 range_max = componentwise_min(block_node_max, pos_max);
+
+               v3s16 pos;
+               pos.Y = range_min.Y;
+               for (pos.Z = range_min.Z; pos.Z <= range_max.Z; ++pos.Z)
+               for (pos.X = range_min.X; pos.X <= range_max.X; ++pos.X) {
+                       v3s16 inblock_pos = pos - block_node_min;
+                       const MinimapPixel &in_pixel =
+                               block.data[inblock_pos.Z * MAP_BLOCKSIZE + inblock_pos.X];
+
+                       v3s16 inmap_pos = pos - pos_min;
+                       MinimapPixel &out_pixel =
+                               data->minimap_scan[inmap_pos.X + inmap_pos.Z * size];
+
+                       out_pixel.air_count += in_pixel.air_count;
+                       if (in_pixel.n.param0 != CONTENT_AIR) {
+                               out_pixel.n = in_pixel.n;
+                               out_pixel.height = inmap_pos.Y + in_pixel.height;
+                       }
+               }
+       }
+}
+
+////
+//// Mapper
+////
+
+Minimap::Minimap(Client *client)
+{
+       this->client    = client;
+       this->driver    = RenderingEngine::get_video_driver();
+       this->m_tsrc    = client->getTextureSource();
+       this->m_shdrsrc = client->getShaderSource();
+       this->m_ndef    = client->getNodeDefManager();
+
+       m_angle = 0.f;
+
+       // Initialize static settings
+       m_enable_shaders = g_settings->getBool("enable_shaders");
+       m_surface_mode_scan_height =
+               g_settings->getBool("minimap_double_scan_height") ? 256 : 128;
+
+       // Initialize minimap data
+       data = new MinimapData;
+       data->mode              = MINIMAP_MODE_OFF;
+       data->is_radar          = false;
+       data->map_invalidated   = true;
+       data->texture           = NULL;
+       data->heightmap_texture = NULL;
+       data->minimap_shape_round = g_settings->getBool("minimap_shape_round");
+
+       // Get round minimap textures
+       data->minimap_mask_round = driver->createImage(
+               m_tsrc->getTexture("minimap_mask_round.png"),
+               core::position2d<s32>(0, 0),
+               core::dimension2d<u32>(MINIMAP_MAX_SX, MINIMAP_MAX_SY));
+       data->minimap_overlay_round = m_tsrc->getTexture("minimap_overlay_round.png");
+
+       // Get square minimap textures
+       data->minimap_mask_square = driver->createImage(
+               m_tsrc->getTexture("minimap_mask_square.png"),
+               core::position2d<s32>(0, 0),
+               core::dimension2d<u32>(MINIMAP_MAX_SX, MINIMAP_MAX_SY));
+       data->minimap_overlay_square = m_tsrc->getTexture("minimap_overlay_square.png");
+
+       // Create player marker texture
+       data->player_marker = m_tsrc->getTexture("player_marker.png");
+       // Create object marker texture
+       data->object_marker_red = m_tsrc->getTexture("object_marker_red.png");
+
+       // Create mesh buffer for minimap
+       m_meshbuffer = getMinimapMeshBuffer();
+
+       // Initialize and start thread
+       m_minimap_update_thread = new MinimapUpdateThread();
+       m_minimap_update_thread->data = data;
+       m_minimap_update_thread->start();
+}
+
+Minimap::~Minimap()
+{
+       m_minimap_update_thread->stop();
+       m_minimap_update_thread->wait();
+
+       m_meshbuffer->drop();
+
+       data->minimap_mask_round->drop();
+       data->minimap_mask_square->drop();
+
+       driver->removeTexture(data->texture);
+       driver->removeTexture(data->heightmap_texture);
+       driver->removeTexture(data->minimap_overlay_round);
+       driver->removeTexture(data->minimap_overlay_square);
+       driver->removeTexture(data->object_marker_red);
+
+       delete data;
+       delete m_minimap_update_thread;
+}
+
+void Minimap::addBlock(v3s16 pos, MinimapMapblock *data)
+{
+       m_minimap_update_thread->enqueueBlock(pos, data);
+}
+
+void Minimap::toggleMinimapShape()
+{
+       MutexAutoLock lock(m_mutex);
+
+       data->minimap_shape_round = !data->minimap_shape_round;
+       g_settings->setBool("minimap_shape_round", data->minimap_shape_round);
+       m_minimap_update_thread->deferUpdate();
+}
+
+void Minimap::setMinimapShape(MinimapShape shape)
+{
+       MutexAutoLock lock(m_mutex);
+
+       if (shape == MINIMAP_SHAPE_SQUARE)
+               data->minimap_shape_round = false;
+       else if (shape == MINIMAP_SHAPE_ROUND)
+               data->minimap_shape_round = true;
+
+       g_settings->setBool("minimap_shape_round", data->minimap_shape_round);
+       m_minimap_update_thread->deferUpdate();
+}
+
+MinimapShape Minimap::getMinimapShape()
+{
+       if (data->minimap_shape_round) {
+               return MINIMAP_SHAPE_ROUND;
+       }
+
+       return MINIMAP_SHAPE_SQUARE;
+}
+
+void Minimap::setMinimapMode(MinimapMode mode)
+{
+       static const MinimapModeDef modedefs[MINIMAP_MODE_COUNT] = {
+               {false, 0, 0},
+               {false, m_surface_mode_scan_height, 256},
+               {false, m_surface_mode_scan_height, 128},
+               {false, m_surface_mode_scan_height, 64},
+               {true, 32, 128},
+               {true, 32, 64},
+               {true, 32, 32}
+       };
+
+       if (mode >= MINIMAP_MODE_COUNT)
+               return;
+
+       MutexAutoLock lock(m_mutex);
+
+       data->is_radar    = modedefs[mode].is_radar;
+       data->scan_height = modedefs[mode].scan_height;
+       data->map_size    = modedefs[mode].map_size;
+       data->mode        = mode;
+
+       m_minimap_update_thread->deferUpdate();
+}
+
+void Minimap::setPos(v3s16 pos)
+{
+       bool do_update = false;
+
+       {
+               MutexAutoLock lock(m_mutex);
+
+               if (pos != data->old_pos) {
+                       data->old_pos = data->pos;
+                       data->pos = pos;
+                       do_update = true;
+               }
+       }
+
+       if (do_update)
+               m_minimap_update_thread->deferUpdate();
+}
+
+void Minimap::setAngle(f32 angle)
+{
+       m_angle = angle;
+}
+
+void Minimap::blitMinimapPixelsToImageRadar(video::IImage *map_image)
+{
+       video::SColor c(240, 0, 0, 0);
+       for (s16 x = 0; x < data->map_size; x++)
+       for (s16 z = 0; z < data->map_size; z++) {
+               MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size];
+
+               if (mmpixel->air_count > 0)
+                       c.setGreen(core::clamp(core::round32(32 + mmpixel->air_count * 8), 0, 255));
+               else
+                       c.setGreen(0);
+
+               map_image->setPixel(x, data->map_size - z - 1, c);
+       }
+}
+
+void Minimap::blitMinimapPixelsToImageSurface(
+       video::IImage *map_image, video::IImage *heightmap_image)
+{
+       // This variable creation/destruction has a 1% cost on rendering minimap
+       video::SColor tilecolor;
+       for (s16 x = 0; x < data->map_size; x++)
+       for (s16 z = 0; z < data->map_size; z++) {
+               MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size];
+
+               const ContentFeatures &f = m_ndef->get(mmpixel->n);
+               const TileDef *tile = &f.tiledef[0];
+
+               // Color of the 0th tile (mostly this is the topmost)
+               if(tile->has_color)
+                       tilecolor = tile->color;
+               else
+                       mmpixel->n.getColor(f, &tilecolor);
+
+               tilecolor.setRed(tilecolor.getRed() * f.minimap_color.getRed() / 255);
+               tilecolor.setGreen(tilecolor.getGreen() * f.minimap_color.getGreen() / 255);
+               tilecolor.setBlue(tilecolor.getBlue() * f.minimap_color.getBlue() / 255);
+               tilecolor.setAlpha(240);
+
+               map_image->setPixel(x, data->map_size - z - 1, tilecolor);
+
+               u32 h = mmpixel->height;
+               heightmap_image->setPixel(x,data->map_size - z - 1,
+                       video::SColor(255, h, h, h));
+       }
+}
+
+video::ITexture *Minimap::getMinimapTexture()
+{
+       // update minimap textures when new scan is ready
+       if (data->map_invalidated)
+               return data->texture;
+
+       // create minimap and heightmap images in memory
+       core::dimension2d<u32> dim(data->map_size, data->map_size);
+       video::IImage *map_image       = driver->createImage(video::ECF_A8R8G8B8, dim);
+       video::IImage *heightmap_image = driver->createImage(video::ECF_A8R8G8B8, dim);
+       video::IImage *minimap_image   = driver->createImage(video::ECF_A8R8G8B8,
+               core::dimension2d<u32>(MINIMAP_MAX_SX, MINIMAP_MAX_SY));
+
+       // Blit MinimapPixels to images
+       if (data->is_radar)
+               blitMinimapPixelsToImageRadar(map_image);
+       else
+               blitMinimapPixelsToImageSurface(map_image, heightmap_image);
+
+       map_image->copyToScaling(minimap_image);
+       map_image->drop();
+
+       video::IImage *minimap_mask = data->minimap_shape_round ?
+               data->minimap_mask_round : data->minimap_mask_square;
+
+       if (minimap_mask) {
+               for (s16 y = 0; y < MINIMAP_MAX_SY; y++)
+               for (s16 x = 0; x < MINIMAP_MAX_SX; x++) {
+                       const video::SColor &mask_col = minimap_mask->getPixel(x, y);
+                       if (!mask_col.getAlpha())
+                               minimap_image->setPixel(x, y, video::SColor(0,0,0,0));
+               }
+       }
+
+       if (data->texture)
+               driver->removeTexture(data->texture);
+       if (data->heightmap_texture)
+               driver->removeTexture(data->heightmap_texture);
+
+       data->texture = driver->addTexture("minimap__", minimap_image);
+       data->heightmap_texture =
+               driver->addTexture("minimap_heightmap__", heightmap_image);
+       minimap_image->drop();
+       heightmap_image->drop();
+
+       data->map_invalidated = true;
+
+       return data->texture;
+}
+
+v3f Minimap::getYawVec()
+{
+       if (data->minimap_shape_round) {
+               return v3f(
+                       std::cos(m_angle * core::DEGTORAD),
+                       std::sin(m_angle * core::DEGTORAD),
+                       1.0);
+       }
+
+       return v3f(1.0, 0.0, 1.0);
+}
+
+scene::SMeshBuffer *Minimap::getMinimapMeshBuffer()
+{
+       scene::SMeshBuffer *buf = new scene::SMeshBuffer();
+       buf->Vertices.set_used(4);
+       buf->Indices.set_used(6);
+       static const video::SColor c(255, 255, 255, 255);
+
+       buf->Vertices[0] = video::S3DVertex(-1, -1, 0, 0, 0, 1, c, 0, 1);
+       buf->Vertices[1] = video::S3DVertex(-1,  1, 0, 0, 0, 1, c, 0, 0);
+       buf->Vertices[2] = video::S3DVertex( 1,  1, 0, 0, 0, 1, c, 1, 0);
+       buf->Vertices[3] = video::S3DVertex( 1, -1, 0, 0, 0, 1, c, 1, 1);
+
+       buf->Indices[0] = 0;
+       buf->Indices[1] = 1;
+       buf->Indices[2] = 2;
+       buf->Indices[3] = 2;
+       buf->Indices[4] = 3;
+       buf->Indices[5] = 0;
+
+       return buf;
+}
+
+void Minimap::drawMinimap()
+{
+       video::ITexture *minimap_texture = getMinimapTexture();
+       if (!minimap_texture)
+               return;
+
+       updateActiveMarkers();
+       v2u32 screensize = RenderingEngine::get_instance()->getWindowSize();
+       const u32 size = 0.25 * screensize.Y;
+
+       core::rect<s32> oldViewPort = driver->getViewPort();
+       core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
+       core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
+
+       driver->setViewPort(core::rect<s32>(
+               screensize.X - size - 10, 10,
+               screensize.X - 10, size + 10));
+       driver->setTransform(video::ETS_PROJECTION, core::matrix4());
+       driver->setTransform(video::ETS_VIEW, core::matrix4());
+
+       core::matrix4 matrix;
+       matrix.makeIdentity();
+
+       video::SMaterial &material = m_meshbuffer->getMaterial();
+       material.setFlag(video::EMF_TRILINEAR_FILTER, true);
+       material.Lighting = false;
+       material.TextureLayer[0].Texture = minimap_texture;
+       material.TextureLayer[1].Texture = data->heightmap_texture;
+
+       if (m_enable_shaders && !data->is_radar) {
+               u16 sid = m_shdrsrc->getShader("minimap_shader", 1, 1);
+               material.MaterialType = m_shdrsrc->getShaderInfo(sid).material;
+       } else {
+               material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+       }
+
+       if (data->minimap_shape_round)
+               matrix.setRotationDegrees(core::vector3df(0, 0, 360 - m_angle));
+
+       // Draw minimap
+       driver->setTransform(video::ETS_WORLD, matrix);
+       driver->setMaterial(material);
+       driver->drawMeshBuffer(m_meshbuffer);
+
+       // Draw overlay
+       video::ITexture *minimap_overlay = data->minimap_shape_round ?
+               data->minimap_overlay_round : data->minimap_overlay_square;
+       material.TextureLayer[0].Texture = minimap_overlay;
+       material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+       driver->setMaterial(material);
+       driver->drawMeshBuffer(m_meshbuffer);
+
+       // Draw player marker on minimap
+       if (data->minimap_shape_round) {
+               matrix.setRotationDegrees(core::vector3df(0, 0, 0));
+       } else {
+               matrix.setRotationDegrees(core::vector3df(0, 0, m_angle));
+       }
+
+       material.TextureLayer[0].Texture = data->player_marker;
+       driver->setTransform(video::ETS_WORLD, matrix);
+       driver->setMaterial(material);
+       driver->drawMeshBuffer(m_meshbuffer);
+
+       // Reset transformations
+       driver->setTransform(video::ETS_VIEW, oldViewMat);
+       driver->setTransform(video::ETS_PROJECTION, oldProjMat);
+       driver->setViewPort(oldViewPort);
+
+       // Draw player markers
+       v2s32 s_pos(screensize.X - size - 10, 10);
+       core::dimension2di imgsize(data->object_marker_red->getOriginalSize());
+       core::rect<s32> img_rect(0, 0, imgsize.Width, imgsize.Height);
+       static const video::SColor col(255, 255, 255, 255);
+       static const video::SColor c[4] = {col, col, col, col};
+       f32 sin_angle = std::sin(m_angle * core::DEGTORAD);
+       f32 cos_angle = std::cos(m_angle * core::DEGTORAD);
+       s32 marker_size2 =  0.025 * (float)size;
+       for (std::list<v2f>::const_iterator
+                       i = m_active_markers.begin();
+                       i != m_active_markers.end(); ++i) {
+               v2f posf = *i;
+               if (data->minimap_shape_round) {
+                       f32 t1 = posf.X * cos_angle - posf.Y * sin_angle;
+                       f32 t2 = posf.X * sin_angle + posf.Y * cos_angle;
+                       posf.X = t1;
+                       posf.Y = t2;
+               }
+               posf.X = (posf.X + 0.5) * (float)size;
+               posf.Y = (posf.Y + 0.5) * (float)size;
+               core::rect<s32> dest_rect(
+                       s_pos.X + posf.X - marker_size2,
+                       s_pos.Y + posf.Y - marker_size2,
+                       s_pos.X + posf.X + marker_size2,
+                       s_pos.Y + posf.Y + marker_size2);
+               driver->draw2DImage(data->object_marker_red, dest_rect,
+                       img_rect, &dest_rect, &c[0], true);
+       }
+}
+
+void Minimap::updateActiveMarkers()
+{
+       video::IImage *minimap_mask = data->minimap_shape_round ?
+               data->minimap_mask_round : data->minimap_mask_square;
+
+       const std::list<Nametag *> &nametags = client->getCamera()->getNametags();
+
+       m_active_markers.clear();
+
+       for (Nametag *nametag : nametags) {
+               v3s16 pos = floatToInt(nametag->parent_node->getPosition() +
+                       intToFloat(client->getCamera()->getOffset(), BS), BS);
+               pos -= data->pos - v3s16(data->map_size / 2,
+                               data->scan_height / 2,
+                               data->map_size / 2);
+               if (pos.X < 0 || pos.X > data->map_size ||
+                               pos.Y < 0 || pos.Y > data->scan_height ||
+                               pos.Z < 0 || pos.Z > data->map_size) {
+                       continue;
+               }
+               pos.X = ((float)pos.X / data->map_size) * MINIMAP_MAX_SX;
+               pos.Z = ((float)pos.Z / data->map_size) * MINIMAP_MAX_SY;
+               const video::SColor &mask_col = minimap_mask->getPixel(pos.X, pos.Z);
+               if (!mask_col.getAlpha()) {
+                       continue;
+               }
+
+               m_active_markers.emplace_back(((float)pos.X / (float)MINIMAP_MAX_SX) - 0.5,
+                       (1.0 - (float)pos.Z / (float)MINIMAP_MAX_SY) - 0.5);
+       }
+}
+
+////
+//// MinimapMapblock
+////
+
+void MinimapMapblock::getMinimapNodes(VoxelManipulator *vmanip, const v3s16 &pos)
+{
+
+       for (s16 x = 0; x < MAP_BLOCKSIZE; x++)
+       for (s16 z = 0; z < MAP_BLOCKSIZE; z++) {
+               s16 air_count = 0;
+               bool surface_found = false;
+               MinimapPixel *mmpixel = &data[z * MAP_BLOCKSIZE + x];
+
+               for (s16 y = MAP_BLOCKSIZE -1; y >= 0; y--) {
+                       v3s16 p(x, y, z);
+                       MapNode n = vmanip->getNodeNoEx(pos + p);
+                       if (!surface_found && n.getContent() != CONTENT_AIR) {
+                               mmpixel->height = y;
+                               mmpixel->n = n;
+                               surface_found = true;
+                       } else if (n.getContent() == CONTENT_AIR) {
+                               air_count++;
+                       }
+               }
+
+               if (!surface_found)
+                       mmpixel->n = MapNode(CONTENT_AIR);
+
+               mmpixel->air_count = air_count;
+       }
+}
diff --git a/src/client/minimap.h b/src/client/minimap.h
new file mode 100644 (file)
index 0000000..258d533
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+Minetest
+Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "util/thread.h"
+#include "voxel.h"
+#include <map>
+#include <string>
+#include <vector>
+
+class Client;
+class ITextureSource;
+class IShaderSource;
+
+#define MINIMAP_MAX_SX 512
+#define MINIMAP_MAX_SY 512
+
+enum MinimapMode {
+       MINIMAP_MODE_OFF,
+       MINIMAP_MODE_SURFACEx1,
+       MINIMAP_MODE_SURFACEx2,
+       MINIMAP_MODE_SURFACEx4,
+       MINIMAP_MODE_RADARx1,
+       MINIMAP_MODE_RADARx2,
+       MINIMAP_MODE_RADARx4,
+       MINIMAP_MODE_COUNT,
+};
+
+enum MinimapShape {
+       MINIMAP_SHAPE_SQUARE,
+       MINIMAP_SHAPE_ROUND,
+};
+
+struct MinimapModeDef {
+       bool is_radar;
+       u16 scan_height;
+       u16 map_size;
+};
+
+struct MinimapPixel {
+       //! The topmost node that the minimap displays.
+       MapNode n;
+       u16 height;
+       u16 air_count;
+};
+
+struct MinimapMapblock {
+       void getMinimapNodes(VoxelManipulator *vmanip, const v3s16 &pos);
+
+       MinimapPixel data[MAP_BLOCKSIZE * MAP_BLOCKSIZE];
+};
+
+struct MinimapData {
+       bool is_radar;
+       MinimapMode mode;
+       v3s16 pos;
+       v3s16 old_pos;
+       u16 scan_height;
+       u16 map_size;
+       MinimapPixel minimap_scan[MINIMAP_MAX_SX * MINIMAP_MAX_SY];
+       bool map_invalidated;
+       bool minimap_shape_round;
+       video::IImage *minimap_mask_round = nullptr;
+       video::IImage *minimap_mask_square = nullptr;
+       video::ITexture *texture = nullptr;
+       video::ITexture *heightmap_texture = nullptr;
+       video::ITexture *minimap_overlay_round = nullptr;
+       video::ITexture *minimap_overlay_square = nullptr;
+       video::ITexture *player_marker = nullptr;
+       video::ITexture *object_marker_red = nullptr;
+};
+
+struct QueuedMinimapUpdate {
+       v3s16 pos;
+       MinimapMapblock *data = nullptr;
+};
+
+class MinimapUpdateThread : public UpdateThread {
+public:
+       MinimapUpdateThread() : UpdateThread("Minimap") {}
+       virtual ~MinimapUpdateThread();
+
+       void getMap(v3s16 pos, s16 size, s16 height);
+       void enqueueBlock(v3s16 pos, MinimapMapblock *data);
+       bool pushBlockUpdate(v3s16 pos, MinimapMapblock *data);
+       bool popBlockUpdate(QueuedMinimapUpdate *update);
+
+       MinimapData *data = nullptr;
+
+protected:
+       virtual void doUpdate();
+
+private:
+       std::mutex m_queue_mutex;
+       std::deque<QueuedMinimapUpdate> m_update_queue;
+       std::map<v3s16, MinimapMapblock *> m_blocks_cache;
+};
+
+class Minimap {
+public:
+       Minimap(Client *client);
+       ~Minimap();
+
+       void addBlock(v3s16 pos, MinimapMapblock *data);
+
+       v3f getYawVec();
+
+       void setPos(v3s16 pos);
+       v3s16 getPos() const { return data->pos; }
+       void setAngle(f32 angle);
+       f32 getAngle() const { return m_angle; }
+       void setMinimapMode(MinimapMode mode);
+       MinimapMode getMinimapMode() const { return data->mode; }
+       void toggleMinimapShape();
+       void setMinimapShape(MinimapShape shape);
+       MinimapShape getMinimapShape();
+
+
+       video::ITexture *getMinimapTexture();
+
+       void blitMinimapPixelsToImageRadar(video::IImage *map_image);
+       void blitMinimapPixelsToImageSurface(video::IImage *map_image,
+               video::IImage *heightmap_image);
+
+       scene::SMeshBuffer *getMinimapMeshBuffer();
+
+       void updateActiveMarkers();
+       void drawMinimap();
+
+       video::IVideoDriver *driver;
+       Client* client;
+       MinimapData *data;
+
+private:
+       ITextureSource *m_tsrc;
+       IShaderSource *m_shdrsrc;
+       const NodeDefManager *m_ndef;
+       MinimapUpdateThread *m_minimap_update_thread;
+       scene::SMeshBuffer *m_meshbuffer;
+       bool m_enable_shaders;
+       u16 m_surface_mode_scan_height;
+       f32 m_angle;
+       std::mutex m_mutex;
+       std::list<v2f> m_active_markers;
+};
diff --git a/src/client/particles.cpp b/src/client/particles.cpp
new file mode 100644 (file)
index 0000000..25cfa08
--- /dev/null
@@ -0,0 +1,684 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 "particles.h"
+#include <cmath>
+#include "client.h"
+#include "collision.h"
+#include "client/clientevent.h"
+#include "client/renderingengine.h"
+#include "util/numeric.h"
+#include "light.h"
+#include "environment.h"
+#include "clientmap.h"
+#include "mapnode.h"
+#include "nodedef.h"
+#include "client.h"
+#include "settings.h"
+
+/*
+       Utility
+*/
+
+v3f random_v3f(v3f min, v3f max)
+{
+       return v3f( rand()/(float)RAND_MAX*(max.X-min.X)+min.X,
+                       rand()/(float)RAND_MAX*(max.Y-min.Y)+min.Y,
+                       rand()/(float)RAND_MAX*(max.Z-min.Z)+min.Z);
+}
+
+Particle::Particle(
+       IGameDef *gamedef,
+       LocalPlayer *player,
+       ClientEnvironment *env,
+       v3f pos,
+       v3f velocity,
+       v3f acceleration,
+       float expirationtime,
+       float size,
+       bool collisiondetection,
+       bool collision_removal,
+       bool object_collision,
+       bool vertical,
+       video::ITexture *texture,
+       v2f texpos,
+       v2f texsize,
+       const struct TileAnimationParams &anim,
+       u8 glow,
+       video::SColor color
+):
+       scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(),
+               RenderingEngine::get_scene_manager())
+{
+       // Misc
+       m_gamedef = gamedef;
+       m_env = env;
+
+       // Texture
+       m_material.setFlag(video::EMF_LIGHTING, false);
+       m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
+       m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
+       m_material.setFlag(video::EMF_FOG_ENABLE, true);
+       m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+       m_material.setTexture(0, texture);
+       m_texpos = texpos;
+       m_texsize = texsize;
+       m_animation = anim;
+
+       // Color
+       m_base_color = color;
+       m_color = color;
+
+       // Particle related
+       m_pos = pos;
+       m_velocity = velocity;
+       m_acceleration = acceleration;
+       m_expiration = expirationtime;
+       m_player = player;
+       m_size = size;
+       m_collisiondetection = collisiondetection;
+       m_collision_removal = collision_removal;
+       m_object_collision = object_collision;
+       m_vertical = vertical;
+       m_glow = glow;
+
+       // Irrlicht stuff
+       m_collisionbox = aabb3f
+                       (-size/2,-size/2,-size/2,size/2,size/2,size/2);
+       this->setAutomaticCulling(scene::EAC_OFF);
+
+       // Init lighting
+       updateLight();
+
+       // Init model
+       updateVertices();
+}
+
+void Particle::OnRegisterSceneNode()
+{
+       if (IsVisible)
+               SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
+
+       ISceneNode::OnRegisterSceneNode();
+}
+
+void Particle::render()
+{
+       video::IVideoDriver* driver = SceneManager->getVideoDriver();
+       driver->setMaterial(m_material);
+       driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
+
+       u16 indices[] = {0,1,2, 2,3,0};
+       driver->drawVertexPrimitiveList(m_vertices, 4,
+                       indices, 2, video::EVT_STANDARD,
+                       scene::EPT_TRIANGLES, video::EIT_16BIT);
+}
+
+void Particle::step(float dtime)
+{
+       m_time += dtime;
+       if (m_collisiondetection) {
+               aabb3f box = m_collisionbox;
+               v3f p_pos = m_pos * BS;
+               v3f p_velocity = m_velocity * BS;
+               collisionMoveResult r = collisionMoveSimple(m_env, m_gamedef, BS * 0.5f,
+                       box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr,
+                       m_object_collision);
+               if (m_collision_removal && r.collides) {
+                       // force expiration of the particle
+                       m_expiration = -1.0;
+               } else {
+                       m_pos = p_pos / BS;
+                       m_velocity = p_velocity / BS;
+               }
+       } else {
+               m_velocity += m_acceleration * dtime;
+               m_pos += m_velocity * dtime;
+       }
+       if (m_animation.type != TAT_NONE) {
+               m_animation_time += dtime;
+               int frame_length_i, frame_count;
+               m_animation.determineParams(
+                               m_material.getTexture(0)->getSize(),
+                               &frame_count, &frame_length_i, NULL);
+               float frame_length = frame_length_i / 1000.0;
+               while (m_animation_time > frame_length) {
+                       m_animation_frame++;
+                       m_animation_time -= frame_length;
+               }
+       }
+
+       // Update lighting
+       updateLight();
+
+       // Update model
+       updateVertices();
+}
+
+void Particle::updateLight()
+{
+       u8 light = 0;
+       bool pos_ok;
+
+       v3s16 p = v3s16(
+               floor(m_pos.X+0.5),
+               floor(m_pos.Y+0.5),
+               floor(m_pos.Z+0.5)
+       );
+       MapNode n = m_env->getClientMap().getNodeNoEx(p, &pos_ok);
+       if (pos_ok)
+               light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef());
+       else
+               light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
+
+       u8 m_light = decode_light(light + m_glow);
+       m_color.set(255,
+               m_light * m_base_color.getRed() / 255,
+               m_light * m_base_color.getGreen() / 255,
+               m_light * m_base_color.getBlue() / 255);
+}
+
+void Particle::updateVertices()
+{
+       f32 tx0, tx1, ty0, ty1;
+
+       if (m_animation.type != TAT_NONE) {
+               const v2u32 texsize = m_material.getTexture(0)->getSize();
+               v2f texcoord, framesize_f;
+               v2u32 framesize;
+               texcoord = m_animation.getTextureCoords(texsize, m_animation_frame);
+               m_animation.determineParams(texsize, NULL, NULL, &framesize);
+               framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y);
+
+               tx0 = m_texpos.X + texcoord.X;
+               tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X;
+               ty0 = m_texpos.Y + texcoord.Y;
+               ty1 = m_texpos.Y + texcoord.Y + framesize_f.Y * m_texsize.Y;
+       } else {
+               tx0 = m_texpos.X;
+               tx1 = m_texpos.X + m_texsize.X;
+               ty0 = m_texpos.Y;
+               ty1 = m_texpos.Y + m_texsize.Y;
+       }
+
+       m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2,
+               0, 0, 0, 0, m_color, tx0, ty1);
+       m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2,
+               0, 0, 0, 0, m_color, tx1, ty1);
+       m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2,
+               0, 0, 0, 0, m_color, tx1, ty0);
+       m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2,
+               0, 0, 0, 0, m_color, tx0, ty0);
+
+       v3s16 camera_offset = m_env->getCameraOffset();
+       for (video::S3DVertex &vertex : m_vertices) {
+               if (m_vertical) {
+                       v3f ppos = m_player->getPosition()/BS;
+                       vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) /
+                               core::DEGTORAD + 90);
+               } else {
+                       vertex.Pos.rotateYZBy(m_player->getPitch());
+                       vertex.Pos.rotateXZBy(m_player->getYaw());
+               }
+               m_box.addInternalPoint(vertex.Pos);
+               vertex.Pos += m_pos*BS - intToFloat(camera_offset, BS);
+       }
+}
+
+/*
+       ParticleSpawner
+*/
+
+ParticleSpawner::ParticleSpawner(
+       IGameDef *gamedef,
+       LocalPlayer *player,
+       u16 amount,
+       float time,
+       v3f minpos, v3f maxpos,
+       v3f minvel, v3f maxvel,
+       v3f minacc, v3f maxacc,
+       float minexptime, float maxexptime,
+       float minsize, float maxsize,
+       bool collisiondetection,
+       bool collision_removal,
+       bool object_collision,
+       u16 attached_id,
+       bool vertical,
+       video::ITexture *texture,
+       u32 id,
+       const struct TileAnimationParams &anim,
+       u8 glow,
+       ParticleManager *p_manager
+):
+       m_particlemanager(p_manager)
+{
+       m_gamedef = gamedef;
+       m_player = player;
+       m_amount = amount;
+       m_spawntime = time;
+       m_minpos = minpos;
+       m_maxpos = maxpos;
+       m_minvel = minvel;
+       m_maxvel = maxvel;
+       m_minacc = minacc;
+       m_maxacc = maxacc;
+       m_minexptime = minexptime;
+       m_maxexptime = maxexptime;
+       m_minsize = minsize;
+       m_maxsize = maxsize;
+       m_collisiondetection = collisiondetection;
+       m_collision_removal = collision_removal;
+       m_object_collision = object_collision;
+       m_attached_id = attached_id;
+       m_vertical = vertical;
+       m_texture = texture;
+       m_time = 0;
+       m_animation = anim;
+       m_glow = glow;
+
+       for (u16 i = 0; i<=m_amount; i++)
+       {
+               float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
+               m_spawntimes.push_back(spawntime);
+       }
+}
+
+void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
+       bool is_attached, const v3f &attached_pos, float attached_yaw)
+{
+       v3f ppos = m_player->getPosition() / BS;
+       v3f pos = random_v3f(m_minpos, m_maxpos);
+
+       // Need to apply this first or the following check
+       // will be wrong for attached spawners
+       if (is_attached) {
+               pos.rotateXZBy(attached_yaw);
+               pos += attached_pos;
+       }
+
+       if (pos.getDistanceFrom(ppos) > radius)
+               return;
+
+       v3f vel = random_v3f(m_minvel, m_maxvel);
+       v3f acc = random_v3f(m_minacc, m_maxacc);
+
+       if (is_attached) {
+               // Apply attachment yaw
+               vel.rotateXZBy(attached_yaw);
+               acc.rotateXZBy(attached_yaw);
+       }
+
+       float exptime = rand() / (float)RAND_MAX
+                       * (m_maxexptime - m_minexptime)
+                       + m_minexptime;
+       float size = rand() / (float)RAND_MAX
+                       * (m_maxsize - m_minsize)
+                       + m_minsize;
+
+       m_particlemanager->addParticle(new Particle(
+               m_gamedef,
+               m_player,
+               env,
+               pos,
+               vel,
+               acc,
+               exptime,
+               size,
+               m_collisiondetection,
+               m_collision_removal,
+               m_object_collision,
+               m_vertical,
+               m_texture,
+               v2f(0.0, 0.0),
+               v2f(1.0, 1.0),
+               m_animation,
+               m_glow
+       ));
+}
+
+void ParticleSpawner::step(float dtime, ClientEnvironment* env)
+{
+       m_time += dtime;
+
+       static thread_local const float radius =
+                       g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
+
+       bool unloaded = false;
+       bool is_attached = false;
+       v3f attached_pos = v3f(0,0,0);
+       float attached_yaw = 0;
+       if (m_attached_id != 0) {
+               if (ClientActiveObject *attached = env->getActiveObject(m_attached_id)) {
+                       attached_pos = attached->getPosition() / BS;
+                       attached_yaw = attached->getYaw();
+                       is_attached = true;
+               } else {
+                       unloaded = true;
+               }
+       }
+
+       if (m_spawntime != 0) {
+               // Spawner exists for a predefined timespan
+               for (std::vector<float>::iterator i = m_spawntimes.begin();
+                               i != m_spawntimes.end();) {
+                       if ((*i) <= m_time && m_amount > 0) {
+                               m_amount--;
+
+                               // Pretend to, but don't actually spawn a particle if it is
+                               // attached to an unloaded object or distant from player.
+                               if (!unloaded)
+                                       spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
+
+                               i = m_spawntimes.erase(i);
+                       } else {
+                               ++i;
+                       }
+               }
+       } else {
+               // Spawner exists for an infinity timespan, spawn on a per-second base
+
+               // Skip this step if attached to an unloaded object
+               if (unloaded)
+                       return;
+
+               for (int i = 0; i <= m_amount; i++) {
+                       if (rand() / (float)RAND_MAX < dtime)
+                               spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
+               }
+       }
+}
+
+
+ParticleManager::ParticleManager(ClientEnvironment* env) :
+       m_env(env)
+{}
+
+ParticleManager::~ParticleManager()
+{
+       clearAll();
+}
+
+void ParticleManager::step(float dtime)
+{
+       stepParticles (dtime);
+       stepSpawners (dtime);
+}
+
+void ParticleManager::stepSpawners (float dtime)
+{
+       MutexAutoLock lock(m_spawner_list_lock);
+       for (std::map<u32, ParticleSpawner*>::iterator i =
+                       m_particle_spawners.begin();
+                       i != m_particle_spawners.end();)
+       {
+               if (i->second->get_expired())
+               {
+                       delete i->second;
+                       m_particle_spawners.erase(i++);
+               }
+               else
+               {
+                       i->second->step(dtime, m_env);
+                       ++i;
+               }
+       }
+}
+
+void ParticleManager::stepParticles (float dtime)
+{
+       MutexAutoLock lock(m_particle_list_lock);
+       for(std::vector<Particle*>::iterator i = m_particles.begin();
+                       i != m_particles.end();)
+       {
+               if ((*i)->get_expired())
+               {
+                       (*i)->remove();
+                       delete *i;
+                       i = m_particles.erase(i);
+               }
+               else
+               {
+                       (*i)->step(dtime);
+                       ++i;
+               }
+       }
+}
+
+void ParticleManager::clearAll ()
+{
+       MutexAutoLock lock(m_spawner_list_lock);
+       MutexAutoLock lock2(m_particle_list_lock);
+       for(std::map<u32, ParticleSpawner*>::iterator i =
+                       m_particle_spawners.begin();
+                       i != m_particle_spawners.end();)
+       {
+               delete i->second;
+               m_particle_spawners.erase(i++);
+       }
+
+       for(std::vector<Particle*>::iterator i =
+                       m_particles.begin();
+                       i != m_particles.end();)
+       {
+               (*i)->remove();
+               delete *i;
+               i = m_particles.erase(i);
+       }
+}
+
+void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
+       LocalPlayer *player)
+{
+       switch (event->type) {
+               case CE_DELETE_PARTICLESPAWNER: {
+                       MutexAutoLock lock(m_spawner_list_lock);
+                       if (m_particle_spawners.find(event->delete_particlespawner.id) !=
+                                       m_particle_spawners.end()) {
+                               delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
+                               m_particle_spawners.erase(event->delete_particlespawner.id);
+                       }
+                       // no allocated memory in delete event
+                       break;
+               }
+               case CE_ADD_PARTICLESPAWNER: {
+                       {
+                               MutexAutoLock lock(m_spawner_list_lock);
+                               if (m_particle_spawners.find(event->add_particlespawner.id) !=
+                                               m_particle_spawners.end()) {
+                                       delete m_particle_spawners.find(event->add_particlespawner.id)->second;
+                                       m_particle_spawners.erase(event->add_particlespawner.id);
+                               }
+                       }
+
+                       video::ITexture *texture =
+                               client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
+
+                       ParticleSpawner *toadd = new ParticleSpawner(client, player,
+                                       event->add_particlespawner.amount,
+                                       event->add_particlespawner.spawntime,
+                                       *event->add_particlespawner.minpos,
+                                       *event->add_particlespawner.maxpos,
+                                       *event->add_particlespawner.minvel,
+                                       *event->add_particlespawner.maxvel,
+                                       *event->add_particlespawner.minacc,
+                                       *event->add_particlespawner.maxacc,
+                                       event->add_particlespawner.minexptime,
+                                       event->add_particlespawner.maxexptime,
+                                       event->add_particlespawner.minsize,
+                                       event->add_particlespawner.maxsize,
+                                       event->add_particlespawner.collisiondetection,
+                                       event->add_particlespawner.collision_removal,
+                                       event->add_particlespawner.object_collision,
+                                       event->add_particlespawner.attached_id,
+                                       event->add_particlespawner.vertical,
+                                       texture,
+                                       event->add_particlespawner.id,
+                                       event->add_particlespawner.animation,
+                                       event->add_particlespawner.glow,
+                                       this);
+
+                       /* delete allocated content of event */
+                       delete event->add_particlespawner.minpos;
+                       delete event->add_particlespawner.maxpos;
+                       delete event->add_particlespawner.minvel;
+                       delete event->add_particlespawner.maxvel;
+                       delete event->add_particlespawner.minacc;
+                       delete event->add_particlespawner.texture;
+                       delete event->add_particlespawner.maxacc;
+
+                       {
+                               MutexAutoLock lock(m_spawner_list_lock);
+                               m_particle_spawners.insert(
+                                               std::pair<u32, ParticleSpawner*>(
+                                                               event->add_particlespawner.id,
+                                                               toadd));
+                       }
+                       break;
+               }
+               case CE_SPAWN_PARTICLE: {
+                       video::ITexture *texture =
+                               client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
+
+                       Particle *toadd = new Particle(client, player, m_env,
+                                       *event->spawn_particle.pos,
+                                       *event->spawn_particle.vel,
+                                       *event->spawn_particle.acc,
+                                       event->spawn_particle.expirationtime,
+                                       event->spawn_particle.size,
+                                       event->spawn_particle.collisiondetection,
+                                       event->spawn_particle.collision_removal,
+                                       event->spawn_particle.object_collision,
+                                       event->spawn_particle.vertical,
+                                       texture,
+                                       v2f(0.0, 0.0),
+                                       v2f(1.0, 1.0),
+                                       event->spawn_particle.animation,
+                                       event->spawn_particle.glow);
+
+                       addParticle(toadd);
+
+                       delete event->spawn_particle.pos;
+                       delete event->spawn_particle.vel;
+                       delete event->spawn_particle.acc;
+                       delete event->spawn_particle.texture;
+
+                       break;
+               }
+               default: break;
+       }
+}
+
+// The final burst of particles when a node is finally dug, *not* particles
+// spawned during the digging of a node.
+
+void ParticleManager::addDiggingParticles(IGameDef* gamedef,
+       LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
+{
+       // No particles for "airlike" nodes
+       if (f.drawtype == NDT_AIRLIKE)
+               return;
+
+       for (u16 j = 0; j < 16; j++) {
+               addNodeParticle(gamedef, player, pos, n, f);
+       }
+}
+
+// During the digging of a node particles are spawned individually by this
+// function, called from Game::handleDigging() in game.cpp.
+
+void ParticleManager::addNodeParticle(IGameDef* gamedef,
+       LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
+{
+       // No particles for "airlike" nodes
+       if (f.drawtype == NDT_AIRLIKE)
+               return;
+
+       // Texture
+       u8 texid = myrand_range(0, 5);
+       const TileLayer &tile = f.tiles[texid].layers[0];
+       video::ITexture *texture;
+       struct TileAnimationParams anim;
+       anim.type = TAT_NONE;
+
+       // Only use first frame of animated texture
+       if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
+               texture = (*tile.frames)[0].texture;
+       else
+               texture = tile.texture;
+
+       float size = (rand() % 8) / 64.0f;
+       float visual_size = BS * size;
+       if (tile.scale)
+               size /= tile.scale;
+       v2f texsize(size * 2.0f, size * 2.0f);
+       v2f texpos;
+       texpos.X = (rand() % 64) / 64.0f - texsize.X;
+       texpos.Y = (rand() % 64) / 64.0f - texsize.Y;
+
+       // Physics
+       v3f velocity(
+               (rand() % 150) / 50.0f - 1.5f,
+               (rand() % 150) / 50.0f,
+               (rand() % 150) / 50.0f - 1.5f
+       );
+       v3f acceleration(
+               0.0f,
+               -player->movement_gravity * player->physics_override_gravity / BS,
+               0.0f
+       );
+       v3f particlepos = v3f(
+               (f32)pos.X + (rand() % 100) / 200.0f - 0.25f,
+               (f32)pos.Y + (rand() % 100) / 200.0f - 0.25f,
+               (f32)pos.Z + (rand() % 100) / 200.0f - 0.25f
+       );
+
+       video::SColor color;
+       if (tile.has_color)
+               color = tile.color;
+       else
+               n.getColor(f, &color);
+
+       Particle* toadd = new Particle(
+               gamedef,
+               player,
+               m_env,
+               particlepos,
+               velocity,
+               acceleration,
+               (rand() % 100) / 100.0f, // expiration time
+               visual_size,
+               true,
+               false,
+               false,
+               false,
+               texture,
+               texpos,
+               texsize,
+               anim,
+               0,
+               color);
+
+       addParticle(toadd);
+}
+
+void ParticleManager::addParticle(Particle* toadd)
+{
+       MutexAutoLock lock(m_particle_list_lock);
+       m_particles.push_back(toadd);
+}
diff --git a/src/client/particles.h b/src/client/particles.h
new file mode 100644 (file)
index 0000000..3392e7e
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include <iostream>
+#include "irrlichttypes_extrabloated.h"
+#include "client/tile.h"
+#include "localplayer.h"
+#include "tileanimation.h"
+
+struct ClientEvent;
+class ParticleManager;
+class ClientEnvironment;
+struct MapNode;
+struct ContentFeatures;
+
+class Particle : public scene::ISceneNode
+{
+       public:
+       Particle(
+               IGameDef* gamedef,
+               LocalPlayer *player,
+               ClientEnvironment *env,
+               v3f pos,
+               v3f velocity,
+               v3f acceleration,
+               float expirationtime,
+               float size,
+               bool collisiondetection,
+               bool collision_removal,
+               bool object_collision,
+               bool vertical,
+               video::ITexture *texture,
+               v2f texpos,
+               v2f texsize,
+               const struct TileAnimationParams &anim,
+               u8 glow,
+               video::SColor color = video::SColor(0xFFFFFFFF)
+       );
+       ~Particle() = default;
+
+       virtual const aabb3f &getBoundingBox() const
+       {
+               return m_box;
+       }
+
+       virtual u32 getMaterialCount() const
+       {
+               return 1;
+       }
+
+       virtual video::SMaterial& getMaterial(u32 i)
+       {
+               return m_material;
+       }
+
+       virtual void OnRegisterSceneNode();
+       virtual void render();
+
+       void step(float dtime);
+
+       bool get_expired ()
+       { return m_expiration < m_time; }
+
+private:
+       void updateLight();
+       void updateVertices();
+
+       video::S3DVertex m_vertices[4];
+       float m_time = 0.0f;
+       float m_expiration;
+
+       ClientEnvironment *m_env;
+       IGameDef *m_gamedef;
+       aabb3f m_box;
+       aabb3f m_collisionbox;
+       video::SMaterial m_material;
+       v2f m_texpos;
+       v2f m_texsize;
+       v3f m_pos;
+       v3f m_velocity;
+       v3f m_acceleration;
+       LocalPlayer *m_player;
+       float m_size;
+       //! Color without lighting
+       video::SColor m_base_color;
+       //! Final rendered color
+       video::SColor m_color;
+       bool m_collisiondetection;
+       bool m_collision_removal;
+       bool m_object_collision;
+       bool m_vertical;
+       v3s16 m_camera_offset;
+       struct TileAnimationParams m_animation;
+       float m_animation_time = 0.0f;
+       int m_animation_frame = 0;
+       u8 m_glow;
+};
+
+class ParticleSpawner
+{
+public:
+       ParticleSpawner(IGameDef* gamedef,
+               LocalPlayer *player,
+               u16 amount,
+               float time,
+               v3f minp, v3f maxp,
+               v3f minvel, v3f maxvel,
+               v3f minacc, v3f maxacc,
+               float minexptime, float maxexptime,
+               float minsize, float maxsize,
+               bool collisiondetection,
+               bool collision_removal,
+               bool object_collision,
+               u16 attached_id,
+               bool vertical,
+               video::ITexture *texture,
+               u32 id,
+               const struct TileAnimationParams &anim, u8 glow,
+               ParticleManager* p_manager);
+
+       ~ParticleSpawner() = default;
+
+       void step(float dtime, ClientEnvironment *env);
+
+       bool get_expired ()
+       { return (m_amount <= 0) && m_spawntime != 0; }
+
+private:
+       void spawnParticle(ClientEnvironment *env, float radius,
+                       bool is_attached, const v3f &attached_pos,
+                       float attached_yaw);
+
+       ParticleManager *m_particlemanager;
+       float m_time;
+       IGameDef *m_gamedef;
+       LocalPlayer *m_player;
+       u16 m_amount;
+       float m_spawntime;
+       v3f m_minpos;
+       v3f m_maxpos;
+       v3f m_minvel;
+       v3f m_maxvel;
+       v3f m_minacc;
+       v3f m_maxacc;
+       float m_minexptime;
+       float m_maxexptime;
+       float m_minsize;
+       float m_maxsize;
+       video::ITexture *m_texture;
+       std::vector<float> m_spawntimes;
+       bool m_collisiondetection;
+       bool m_collision_removal;
+       bool m_object_collision;
+       bool m_vertical;
+       u16 m_attached_id;
+       struct TileAnimationParams m_animation;
+       u8 m_glow;
+};
+
+/**
+ * Class doing particle as well as their spawners handling
+ */
+class ParticleManager
+{
+friend class ParticleSpawner;
+public:
+       ParticleManager(ClientEnvironment* env);
+       ~ParticleManager();
+
+       void step (float dtime);
+
+       void handleParticleEvent(ClientEvent *event, Client *client,
+                       LocalPlayer *player);
+
+       void addDiggingParticles(IGameDef *gamedef, LocalPlayer *player, v3s16 pos,
+               const MapNode &n, const ContentFeatures &f);
+
+       void addNodeParticle(IGameDef *gamedef, LocalPlayer *player, v3s16 pos,
+               const MapNode &n, const ContentFeatures &f);
+
+       u32 getSpawnerId() const
+       {
+               for (u32 id = 0;; ++id) { // look for unused particlespawner id
+                       if (m_particle_spawners.find(id) == m_particle_spawners.end())
+                               return id;
+               }
+       }
+
+protected:
+       void addParticle(Particle* toadd);
+
+private:
+
+       void stepParticles (float dtime);
+       void stepSpawners (float dtime);
+
+       void clearAll ();
+
+       std::vector<Particle*> m_particles;
+       std::map<u32, ParticleSpawner*> m_particle_spawners;
+
+       ClientEnvironment* m_env;
+       std::mutex m_particle_list_lock;
+       std::mutex m_spawner_list_lock;
+};
index bd280bc73b5ffe28f909edf2036efef1c67c6d64..8c70b36c6a03d7b2da091c27f06fd41c829e12fd 100644 (file)
@@ -19,11 +19,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 */
 
 #include "core.h"
-#include "camera.h"
-#include "client.h"
-#include "clientmap.h"
-#include "hud.h"
-#include "minimap.h"
+#include "client/camera.h"
+#include "client/client.h"
+#include "client/clientmap.h"
+#include "client/hud.h"
+#include "client/minimap.h"
 
 RenderingCore::RenderingCore(IrrlichtDevice *_device, Client *_client, Hud *_hud)
        : device(_device), driver(device->getVideoDriver()), smgr(device->getSceneManager()),
index 87d75de30bf4865a184d6823a7e3a9aa8737c1cf..2aadadc17a785fdee2a79ba46135c4d88ca2eb7b 100644 (file)
@@ -19,8 +19,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 */
 
 #include "interlaced.h"
-#include "client.h"
-#include "shader.h"
+#include "client/client.h"
+#include "client/shader.h"
 #include "client/tile.h"
 
 RenderingCoreInterlaced::RenderingCoreInterlaced(
index 4a5761bb7d87ed9cc9fd67a125f8bc65a8cb1691..967b5a78f57132ee969589c361b943cbcd0f54f0 100644 (file)
@@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 */
 
 #include "stereo.h"
-#include "camera.h"
+#include "client/camera.h"
 #include "constants.h"
 #include "settings.h"
 
diff --git a/src/client/shader.cpp b/src/client/shader.cpp
new file mode 100644 (file)
index 0000000..3b49a36
--- /dev/null
@@ -0,0 +1,873 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013 Kahrl <kahrl@gmx.net>
+
+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 <fstream>
+#include <iterator>
+#include "shader.h"
+#include "irrlichttypes_extrabloated.h"
+#include "debug.h"
+#include "filesys.h"
+#include "util/container.h"
+#include "util/thread.h"
+#include "settings.h"
+#include <ICameraSceneNode.h>
+#include <IGPUProgrammingServices.h>
+#include <IMaterialRenderer.h>
+#include <IMaterialRendererServices.h>
+#include <IShaderConstantSetCallBack.h>
+#include "client/renderingengine.h"
+#include "EShaderTypes.h"
+#include "log.h"
+#include "gamedef.h"
+#include "client/tile.h"
+
+/*
+       A cache from shader name to shader path
+*/
+MutexedMap<std::string, std::string> g_shadername_to_path_cache;
+
+/*
+       Gets the path to a shader by first checking if the file
+         name_of_shader/filename
+       exists in shader_path and if not, using the data path.
+
+       If not found, returns "".
+
+       Utilizes a thread-safe cache.
+*/
+std::string getShaderPath(const std::string &name_of_shader,
+               const std::string &filename)
+{
+       std::string combined = name_of_shader + DIR_DELIM + filename;
+       std::string fullpath;
+       /*
+               Check from cache
+       */
+       bool incache = g_shadername_to_path_cache.get(combined, &fullpath);
+       if(incache)
+               return fullpath;
+
+       /*
+               Check from shader_path
+       */
+       std::string shader_path = g_settings->get("shader_path");
+       if (!shader_path.empty()) {
+               std::string testpath = shader_path + DIR_DELIM + combined;
+               if(fs::PathExists(testpath))
+                       fullpath = testpath;
+       }
+
+       /*
+               Check from default data directory
+       */
+       if (fullpath.empty()) {
+               std::string rel_path = std::string("client") + DIR_DELIM
+                               + "shaders" + DIR_DELIM
+                               + name_of_shader + DIR_DELIM
+                               + filename;
+               std::string testpath = porting::path_share + DIR_DELIM + rel_path;
+               if(fs::PathExists(testpath))
+                       fullpath = testpath;
+       }
+
+       // Add to cache (also an empty result is cached)
+       g_shadername_to_path_cache.set(combined, fullpath);
+
+       // Finally return it
+       return fullpath;
+}
+
+/*
+       SourceShaderCache: A cache used for storing source shaders.
+*/
+
+class SourceShaderCache
+{
+public:
+       void insert(const std::string &name_of_shader, const std::string &filename,
+               const std::string &program, bool prefer_local)
+       {
+               std::string combined = name_of_shader + DIR_DELIM + filename;
+               // Try to use local shader instead if asked to
+               if(prefer_local){
+                       std::string path = getShaderPath(name_of_shader, filename);
+                       if(!path.empty()){
+                               std::string p = readFile(path);
+                               if (!p.empty()) {
+                                       m_programs[combined] = p;
+                                       return;
+                               }
+                       }
+               }
+               m_programs[combined] = program;
+       }
+
+       std::string get(const std::string &name_of_shader,
+               const std::string &filename)
+       {
+               std::string combined = name_of_shader + DIR_DELIM + filename;
+               StringMap::iterator n = m_programs.find(combined);
+               if (n != m_programs.end())
+                       return n->second;
+               return "";
+       }
+
+       // Primarily fetches from cache, secondarily tries to read from filesystem
+       std::string getOrLoad(const std::string &name_of_shader,
+               const std::string &filename)
+       {
+               std::string combined = name_of_shader + DIR_DELIM + filename;
+               StringMap::iterator n = m_programs.find(combined);
+               if (n != m_programs.end())
+                       return n->second;
+               std::string path = getShaderPath(name_of_shader, filename);
+               if (path.empty()) {
+                       infostream << "SourceShaderCache::getOrLoad(): No path found for \""
+                               << combined << "\"" << std::endl;
+                       return "";
+               }
+               infostream << "SourceShaderCache::getOrLoad(): Loading path \""
+                       << path << "\"" << std::endl;
+               std::string p = readFile(path);
+               if (!p.empty()) {
+                       m_programs[combined] = p;
+                       return p;
+               }
+               return "";
+       }
+private:
+       StringMap m_programs;
+
+       std::string readFile(const std::string &path)
+       {
+               std::ifstream is(path.c_str(), std::ios::binary);
+               if(!is.is_open())
+                       return "";
+               std::ostringstream tmp_os;
+               tmp_os << is.rdbuf();
+               return tmp_os.str();
+       }
+};
+
+
+/*
+       ShaderCallback: Sets constants that can be used in shaders
+*/
+
+class ShaderCallback : public video::IShaderConstantSetCallBack
+{
+       std::vector<IShaderConstantSetter*> m_setters;
+
+public:
+       ShaderCallback(const std::vector<IShaderConstantSetterFactory *> &factories)
+       {
+               for (IShaderConstantSetterFactory *factory : factories)
+                       m_setters.push_back(factory->create());
+       }
+
+       ~ShaderCallback()
+       {
+               for (IShaderConstantSetter *setter : m_setters)
+                       delete setter;
+       }
+
+       virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData)
+       {
+               video::IVideoDriver *driver = services->getVideoDriver();
+               sanity_check(driver != NULL);
+
+               bool is_highlevel = userData;
+
+               for (IShaderConstantSetter *setter : m_setters)
+                       setter->onSetConstants(services, is_highlevel);
+       }
+};
+
+
+/*
+       MainShaderConstantSetter: Set basic constants required for almost everything
+*/
+
+class MainShaderConstantSetter : public IShaderConstantSetter
+{
+       CachedVertexShaderSetting<float, 16> m_world_view_proj;
+       CachedVertexShaderSetting<float, 16> m_world;
+
+public:
+       MainShaderConstantSetter() :
+               m_world_view_proj("mWorldViewProj"),
+               m_world("mWorld")
+       {}
+       ~MainShaderConstantSetter() = default;
+
+       virtual void onSetConstants(video::IMaterialRendererServices *services,
+                       bool is_highlevel)
+       {
+               video::IVideoDriver *driver = services->getVideoDriver();
+               sanity_check(driver);
+
+               // Set clip matrix
+               core::matrix4 worldViewProj;
+               worldViewProj = driver->getTransform(video::ETS_PROJECTION);
+               worldViewProj *= driver->getTransform(video::ETS_VIEW);
+               worldViewProj *= driver->getTransform(video::ETS_WORLD);
+               if (is_highlevel)
+                       m_world_view_proj.set(*reinterpret_cast<float(*)[16]>(worldViewProj.pointer()), services);
+               else
+                       services->setVertexShaderConstant(worldViewProj.pointer(), 0, 4);
+
+               // Set world matrix
+               core::matrix4 world = driver->getTransform(video::ETS_WORLD);
+               if (is_highlevel)
+                       m_world.set(*reinterpret_cast<float(*)[16]>(world.pointer()), services);
+               else
+                       services->setVertexShaderConstant(world.pointer(), 4, 4);
+
+       }
+};
+
+
+class MainShaderConstantSetterFactory : public IShaderConstantSetterFactory
+{
+public:
+       virtual IShaderConstantSetter* create()
+               { return new MainShaderConstantSetter(); }
+};
+
+
+/*
+       ShaderSource
+*/
+
+class ShaderSource : public IWritableShaderSource
+{
+public:
+       ShaderSource();
+       ~ShaderSource();
+
+       /*
+               - If shader material specified by name is found from cache,
+                 return the cached id.
+               - Otherwise generate the shader material, add to cache and return id.
+
+               The id 0 points to a null shader. Its material is EMT_SOLID.
+       */
+       u32 getShaderIdDirect(const std::string &name,
+               const u8 material_type, const u8 drawtype);
+
+       /*
+               If shader specified by the name pointed by the id doesn't
+               exist, create it, then return id.
+
+               Can be called from any thread. If called from some other thread
+               and not found in cache, the call is queued to the main thread
+               for processing.
+       */
+
+       u32 getShader(const std::string &name,
+               const u8 material_type, const u8 drawtype);
+
+       ShaderInfo getShaderInfo(u32 id);
+
+       // Processes queued shader requests from other threads.
+       // Shall be called from the main thread.
+       void processQueue();
+
+       // Insert a shader program into the cache without touching the
+       // filesystem. Shall be called from the main thread.
+       void insertSourceShader(const std::string &name_of_shader,
+               const std::string &filename, const std::string &program);
+
+       // Rebuild shaders from the current set of source shaders
+       // Shall be called from the main thread.
+       void rebuildShaders();
+
+       void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter)
+       {
+               m_setter_factories.push_back(setter);
+       }
+
+private:
+
+       // The id of the thread that is allowed to use irrlicht directly
+       std::thread::id m_main_thread;
+
+       // Cache of source shaders
+       // This should be only accessed from the main thread
+       SourceShaderCache m_sourcecache;
+
+       // A shader id is index in this array.
+       // The first position contains a dummy shader.
+       std::vector<ShaderInfo> m_shaderinfo_cache;
+       // The former container is behind this mutex
+       std::mutex m_shaderinfo_cache_mutex;
+
+       // Queued shader fetches (to be processed by the main thread)
+       RequestQueue<std::string, u32, u8, u8> m_get_shader_queue;
+
+       // Global constant setter factories
+       std::vector<IShaderConstantSetterFactory *> m_setter_factories;
+
+       // Shader callbacks
+       std::vector<ShaderCallback *> m_callbacks;
+};
+
+IWritableShaderSource *createShaderSource()
+{
+       return new ShaderSource();
+}
+
+/*
+       Generate shader given the shader name.
+*/
+ShaderInfo generate_shader(const std::string &name,
+               u8 material_type, u8 drawtype, std::vector<ShaderCallback *> &callbacks,
+               const std::vector<IShaderConstantSetterFactory *> &setter_factories,
+               SourceShaderCache *sourcecache);
+
+/*
+       Load shader programs
+*/
+void load_shaders(const std::string &name, SourceShaderCache *sourcecache,
+               video::E_DRIVER_TYPE drivertype, bool enable_shaders,
+               std::string &vertex_program, std::string &pixel_program,
+               std::string &geometry_program, bool &is_highlevel);
+
+ShaderSource::ShaderSource()
+{
+       m_main_thread = std::this_thread::get_id();
+
+       // Add a dummy ShaderInfo as the first index, named ""
+       m_shaderinfo_cache.emplace_back();
+
+       // Add main global constant setter
+       addShaderConstantSetterFactory(new MainShaderConstantSetterFactory());
+}
+
+ShaderSource::~ShaderSource()
+{
+       for (ShaderCallback *callback : m_callbacks) {
+               delete callback;
+       }
+       for (IShaderConstantSetterFactory *setter_factorie : m_setter_factories) {
+               delete setter_factorie;
+       }
+}
+
+u32 ShaderSource::getShader(const std::string &name,
+               const u8 material_type, const u8 drawtype)
+{
+       /*
+               Get shader
+       */
+
+       if (std::this_thread::get_id() == m_main_thread) {
+               return getShaderIdDirect(name, material_type, drawtype);
+       }
+
+       /*errorstream<<"getShader(): Queued: name=\""<<name<<"\""<<std::endl;*/
+
+       // We're gonna ask the result to be put into here
+
+       static ResultQueue<std::string, u32, u8, u8> result_queue;
+
+       // Throw a request in
+       m_get_shader_queue.add(name, 0, 0, &result_queue);
+
+       /* infostream<<"Waiting for shader from main thread, name=\""
+                       <<name<<"\""<<std::endl;*/
+
+       while(true) {
+               GetResult<std::string, u32, u8, u8>
+                       result = result_queue.pop_frontNoEx();
+
+               if (result.key == name) {
+                       return result.item;
+               }
+
+               errorstream << "Got shader with invalid name: " << result.key << std::endl;
+       }
+
+       infostream << "getShader(): Failed" << std::endl;
+
+       return 0;
+}
+
+/*
+       This method generates all the shaders
+*/
+u32 ShaderSource::getShaderIdDirect(const std::string &name,
+               const u8 material_type, const u8 drawtype)
+{
+       //infostream<<"getShaderIdDirect(): name=\""<<name<<"\""<<std::endl;
+
+       // Empty name means shader 0
+       if (name.empty()) {
+               infostream<<"getShaderIdDirect(): name is empty"<<std::endl;
+               return 0;
+       }
+
+       // Check if already have such instance
+       for(u32 i=0; i<m_shaderinfo_cache.size(); i++){
+               ShaderInfo *info = &m_shaderinfo_cache[i];
+               if(info->name == name && info->material_type == material_type &&
+                       info->drawtype == drawtype)
+                       return i;
+       }
+
+       /*
+               Calling only allowed from main thread
+       */
+       if (std::this_thread::get_id() != m_main_thread) {
+               errorstream<<"ShaderSource::getShaderIdDirect() "
+                               "called not from main thread"<<std::endl;
+               return 0;
+       }
+
+       ShaderInfo info = generate_shader(name, material_type, drawtype,
+                       m_callbacks, m_setter_factories, &m_sourcecache);
+
+       /*
+               Add shader to caches (add dummy shaders too)
+       */
+
+       MutexAutoLock lock(m_shaderinfo_cache_mutex);
+
+       u32 id = m_shaderinfo_cache.size();
+       m_shaderinfo_cache.push_back(info);
+
+       infostream<<"getShaderIdDirect(): "
+                       <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;
+
+       return id;
+}
+
+
+ShaderInfo ShaderSource::getShaderInfo(u32 id)
+{
+       MutexAutoLock lock(m_shaderinfo_cache_mutex);
+
+       if(id >= m_shaderinfo_cache.size())
+               return ShaderInfo();
+
+       return m_shaderinfo_cache[id];
+}
+
+void ShaderSource::processQueue()
+{
+
+
+}
+
+void ShaderSource::insertSourceShader(const std::string &name_of_shader,
+               const std::string &filename, const std::string &program)
+{
+       /*infostream<<"ShaderSource::insertSourceShader(): "
+                       "name_of_shader=\""<<name_of_shader<<"\", "
+                       "filename=\""<<filename<<"\""<<std::endl;*/
+
+       sanity_check(std::this_thread::get_id() == m_main_thread);
+
+       m_sourcecache.insert(name_of_shader, filename, program, true);
+}
+
+void ShaderSource::rebuildShaders()
+{
+       MutexAutoLock lock(m_shaderinfo_cache_mutex);
+
+       /*// Oh well... just clear everything, they'll load sometime.
+       m_shaderinfo_cache.clear();
+       m_name_to_id.clear();*/
+
+       /*
+               FIXME: Old shader materials can't be deleted in Irrlicht,
+               or can they?
+               (This would be nice to do in the destructor too)
+       */
+
+       // Recreate shaders
+       for (ShaderInfo &i : m_shaderinfo_cache) {
+               ShaderInfo *info = &i;
+               if (!info->name.empty()) {
+                       *info = generate_shader(info->name, info->material_type,
+                                       info->drawtype, m_callbacks,
+                                       m_setter_factories, &m_sourcecache);
+               }
+       }
+}
+
+
+ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtype,
+               std::vector<ShaderCallback *> &callbacks,
+               const std::vector<IShaderConstantSetterFactory *> &setter_factories,
+               SourceShaderCache *sourcecache)
+{
+       ShaderInfo shaderinfo;
+       shaderinfo.name = name;
+       shaderinfo.material_type = material_type;
+       shaderinfo.drawtype = drawtype;
+       shaderinfo.material = video::EMT_SOLID;
+       switch (material_type) {
+       case TILE_MATERIAL_OPAQUE:
+       case TILE_MATERIAL_LIQUID_OPAQUE:
+               shaderinfo.base_material = video::EMT_SOLID;
+               break;
+       case TILE_MATERIAL_ALPHA:
+       case TILE_MATERIAL_LIQUID_TRANSPARENT:
+               shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+               break;
+       case TILE_MATERIAL_BASIC:
+       case TILE_MATERIAL_WAVING_LEAVES:
+       case TILE_MATERIAL_WAVING_PLANTS:
+               shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
+               break;
+       }
+
+       bool enable_shaders = g_settings->getBool("enable_shaders");
+       if (!enable_shaders)
+               return shaderinfo;
+
+       video::IVideoDriver *driver = RenderingEngine::get_video_driver();
+
+       video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices();
+       if(!gpu){
+               errorstream<<"generate_shader(): "
+                               "failed to generate \""<<name<<"\", "
+                               "GPU programming not supported."
+                               <<std::endl;
+               return shaderinfo;
+       }
+
+       // Choose shader language depending on driver type and settings
+       // Then load shaders
+       std::string vertex_program;
+       std::string pixel_program;
+       std::string geometry_program;
+       bool is_highlevel;
+       load_shaders(name, sourcecache, driver->getDriverType(),
+                       enable_shaders, vertex_program, pixel_program,
+                       geometry_program, is_highlevel);
+       // Check hardware/driver support
+       if (!vertex_program.empty() &&
+                       !driver->queryFeature(video::EVDF_VERTEX_SHADER_1_1) &&
+                       !driver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1)){
+               infostream<<"generate_shader(): vertex shaders disabled "
+                               "because of missing driver/hardware support."
+                               <<std::endl;
+               vertex_program = "";
+       }
+       if (!pixel_program.empty() &&
+                       !driver->queryFeature(video::EVDF_PIXEL_SHADER_1_1) &&
+                       !driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1)){
+               infostream<<"generate_shader(): pixel shaders disabled "
+                               "because of missing driver/hardware support."
+                               <<std::endl;
+               pixel_program = "";
+       }
+       if (!geometry_program.empty() &&
+                       !driver->queryFeature(video::EVDF_GEOMETRY_SHADER)){
+               infostream<<"generate_shader(): geometry shaders disabled "
+                               "because of missing driver/hardware support."
+                               <<std::endl;
+               geometry_program = "";
+       }
+
+       // If no shaders are used, don't make a separate material type
+       if (vertex_program.empty() && pixel_program.empty() && geometry_program.empty())
+               return shaderinfo;
+
+       // Create shaders header
+       std::string shaders_header = "#version 120\n";
+
+       static const char* drawTypes[] = {
+               "NDT_NORMAL",
+               "NDT_AIRLIKE",
+               "NDT_LIQUID",
+               "NDT_FLOWINGLIQUID",
+               "NDT_GLASSLIKE",
+               "NDT_ALLFACES",
+               "NDT_ALLFACES_OPTIONAL",
+               "NDT_TORCHLIKE",
+               "NDT_SIGNLIKE",
+               "NDT_PLANTLIKE",
+               "NDT_FENCELIKE",
+               "NDT_RAILLIKE",
+               "NDT_NODEBOX",
+               "NDT_GLASSLIKE_FRAMED",
+               "NDT_FIRELIKE",
+               "NDT_GLASSLIKE_FRAMED_OPTIONAL",
+               "NDT_PLANTLIKE_ROOTED",
+       };
+
+       for (int i = 0; i < 14; i++){
+               shaders_header += "#define ";
+               shaders_header += drawTypes[i];
+               shaders_header += " ";
+               shaders_header += itos(i);
+               shaders_header += "\n";
+       }
+
+       static const char* materialTypes[] = {
+               "TILE_MATERIAL_BASIC",
+               "TILE_MATERIAL_ALPHA",
+               "TILE_MATERIAL_LIQUID_TRANSPARENT",
+               "TILE_MATERIAL_LIQUID_OPAQUE",
+               "TILE_MATERIAL_WAVING_LEAVES",
+               "TILE_MATERIAL_WAVING_PLANTS",
+               "TILE_MATERIAL_OPAQUE"
+       };
+
+       for (int i = 0; i < 7; i++){
+               shaders_header += "#define ";
+               shaders_header += materialTypes[i];
+               shaders_header += " ";
+               shaders_header += itos(i);
+               shaders_header += "\n";
+       }
+
+       shaders_header += "#define MATERIAL_TYPE ";
+       shaders_header += itos(material_type);
+       shaders_header += "\n";
+       shaders_header += "#define DRAW_TYPE ";
+       shaders_header += itos(drawtype);
+       shaders_header += "\n";
+
+       if (g_settings->getBool("generate_normalmaps")) {
+               shaders_header += "#define GENERATE_NORMALMAPS 1\n";
+       } else {
+               shaders_header += "#define GENERATE_NORMALMAPS 0\n";
+       }
+       shaders_header += "#define NORMALMAPS_STRENGTH ";
+       shaders_header += ftos(g_settings->getFloat("normalmaps_strength"));
+       shaders_header += "\n";
+       float sample_step;
+       int smooth = (int)g_settings->getFloat("normalmaps_smooth");
+       switch (smooth){
+       case 0:
+               sample_step = 0.0078125; // 1.0 / 128.0
+               break;
+       case 1:
+               sample_step = 0.00390625; // 1.0 / 256.0
+               break;
+       case 2:
+               sample_step = 0.001953125; // 1.0 / 512.0
+               break;
+       default:
+               sample_step = 0.0078125;
+               break;
+       }
+       shaders_header += "#define SAMPLE_STEP ";
+       shaders_header += ftos(sample_step);
+       shaders_header += "\n";
+
+       if (g_settings->getBool("enable_bumpmapping"))
+               shaders_header += "#define ENABLE_BUMPMAPPING\n";
+
+       if (g_settings->getBool("enable_parallax_occlusion")){
+               int mode = g_settings->getFloat("parallax_occlusion_mode");
+               float scale = g_settings->getFloat("parallax_occlusion_scale");
+               float bias = g_settings->getFloat("parallax_occlusion_bias");
+               int iterations = g_settings->getFloat("parallax_occlusion_iterations");
+               shaders_header += "#define ENABLE_PARALLAX_OCCLUSION\n";
+               shaders_header += "#define PARALLAX_OCCLUSION_MODE ";
+               shaders_header += itos(mode);
+               shaders_header += "\n";
+               shaders_header += "#define PARALLAX_OCCLUSION_SCALE ";
+               shaders_header += ftos(scale);
+               shaders_header += "\n";
+               shaders_header += "#define PARALLAX_OCCLUSION_BIAS ";
+               shaders_header += ftos(bias);
+               shaders_header += "\n";
+               shaders_header += "#define PARALLAX_OCCLUSION_ITERATIONS ";
+               shaders_header += itos(iterations);
+               shaders_header += "\n";
+       }
+
+       shaders_header += "#define USE_NORMALMAPS ";
+       if (g_settings->getBool("enable_bumpmapping") || g_settings->getBool("enable_parallax_occlusion"))
+               shaders_header += "1\n";
+       else
+               shaders_header += "0\n";
+
+       if (g_settings->getBool("enable_waving_water")){
+               shaders_header += "#define ENABLE_WAVING_WATER 1\n";
+               shaders_header += "#define WATER_WAVE_HEIGHT ";
+               shaders_header += ftos(g_settings->getFloat("water_wave_height"));
+               shaders_header += "\n";
+               shaders_header += "#define WATER_WAVE_LENGTH ";
+               shaders_header += ftos(g_settings->getFloat("water_wave_length"));
+               shaders_header += "\n";
+               shaders_header += "#define WATER_WAVE_SPEED ";
+               shaders_header += ftos(g_settings->getFloat("water_wave_speed"));
+               shaders_header += "\n";
+       } else{
+               shaders_header += "#define ENABLE_WAVING_WATER 0\n";
+       }
+
+       shaders_header += "#define ENABLE_WAVING_LEAVES ";
+       if (g_settings->getBool("enable_waving_leaves"))
+               shaders_header += "1\n";
+       else
+               shaders_header += "0\n";
+
+       shaders_header += "#define ENABLE_WAVING_PLANTS ";
+       if (g_settings->getBool("enable_waving_plants"))
+               shaders_header += "1\n";
+       else
+               shaders_header += "0\n";
+
+       if (g_settings->getBool("tone_mapping"))
+               shaders_header += "#define ENABLE_TONE_MAPPING\n";
+
+       shaders_header += "#define FOG_START ";
+       shaders_header += ftos(rangelim(g_settings->getFloat("fog_start"), 0.0f, 0.99f));
+       shaders_header += "\n";
+
+       // Call addHighLevelShaderMaterial() or addShaderMaterial()
+       const c8* vertex_program_ptr = 0;
+       const c8* pixel_program_ptr = 0;
+       const c8* geometry_program_ptr = 0;
+       if (!vertex_program.empty()) {
+               vertex_program = shaders_header + vertex_program;
+               vertex_program_ptr = vertex_program.c_str();
+       }
+       if (!pixel_program.empty()) {
+               pixel_program = shaders_header + pixel_program;
+               pixel_program_ptr = pixel_program.c_str();
+       }
+       if (!geometry_program.empty()) {
+               geometry_program = shaders_header + geometry_program;
+               geometry_program_ptr = geometry_program.c_str();
+       }
+       ShaderCallback *cb = new ShaderCallback(setter_factories);
+       s32 shadermat = -1;
+       if(is_highlevel){
+               infostream<<"Compiling high level shaders for "<<name<<std::endl;
+               shadermat = gpu->addHighLevelShaderMaterial(
+                       vertex_program_ptr,   // Vertex shader program
+                       "vertexMain",         // Vertex shader entry point
+                       video::EVST_VS_1_1,   // Vertex shader version
+                       pixel_program_ptr,    // Pixel shader program
+                       "pixelMain",          // Pixel shader entry point
+                       video::EPST_PS_1_2,   // Pixel shader version
+                       geometry_program_ptr, // Geometry shader program
+                       "geometryMain",       // Geometry shader entry point
+                       video::EGST_GS_4_0,   // Geometry shader version
+                       scene::EPT_TRIANGLES,      // Geometry shader input
+                       scene::EPT_TRIANGLE_STRIP, // Geometry shader output
+                       0,                         // Support maximum number of vertices
+                       cb, // Set-constant callback
+                       shaderinfo.base_material,  // Base material
+                       1                          // Userdata passed to callback
+                       );
+               if(shadermat == -1){
+                       errorstream<<"generate_shader(): "
+                                       "failed to generate \""<<name<<"\", "
+                                       "addHighLevelShaderMaterial failed."
+                                       <<std::endl;
+                       dumpShaderProgram(warningstream, "Vertex", vertex_program);
+                       dumpShaderProgram(warningstream, "Pixel", pixel_program);
+                       dumpShaderProgram(warningstream, "Geometry", geometry_program);
+                       delete cb;
+                       return shaderinfo;
+               }
+       }
+       else{
+               infostream<<"Compiling assembly shaders for "<<name<<std::endl;
+               shadermat = gpu->addShaderMaterial(
+                       vertex_program_ptr,   // Vertex shader program
+                       pixel_program_ptr,    // Pixel shader program
+                       cb, // Set-constant callback
+                       shaderinfo.base_material,  // Base material
+                       0                     // Userdata passed to callback
+                       );
+
+               if(shadermat == -1){
+                       errorstream<<"generate_shader(): "
+                                       "failed to generate \""<<name<<"\", "
+                                       "addShaderMaterial failed."
+                                       <<std::endl;
+                       dumpShaderProgram(warningstream, "Vertex", vertex_program);
+                       dumpShaderProgram(warningstream,"Pixel", pixel_program);
+                       delete cb;
+                       return shaderinfo;
+               }
+       }
+       callbacks.push_back(cb);
+
+       // HACK, TODO: investigate this better
+       // Grab the material renderer once more so minetest doesn't crash on exit
+       driver->getMaterialRenderer(shadermat)->grab();
+
+       // Apply the newly created material type
+       shaderinfo.material = (video::E_MATERIAL_TYPE) shadermat;
+       return shaderinfo;
+}
+
+void load_shaders(const std::string &name, SourceShaderCache *sourcecache,
+               video::E_DRIVER_TYPE drivertype, bool enable_shaders,
+               std::string &vertex_program, std::string &pixel_program,
+               std::string &geometry_program, bool &is_highlevel)
+{
+       vertex_program = "";
+       pixel_program = "";
+       geometry_program = "";
+       is_highlevel = false;
+
+       if(enable_shaders){
+               // Look for high level shaders
+               if(drivertype == video::EDT_DIRECT3D9){
+                       // Direct3D 9: HLSL
+                       // (All shaders in one file)
+                       vertex_program = sourcecache->getOrLoad(name, "d3d9.hlsl");
+                       pixel_program = vertex_program;
+                       geometry_program = vertex_program;
+               }
+               else if(drivertype == video::EDT_OPENGL){
+                       // OpenGL: GLSL
+                       vertex_program = sourcecache->getOrLoad(name, "opengl_vertex.glsl");
+                       pixel_program = sourcecache->getOrLoad(name, "opengl_fragment.glsl");
+                       geometry_program = sourcecache->getOrLoad(name, "opengl_geometry.glsl");
+               }
+               if (!vertex_program.empty() || !pixel_program.empty() || !geometry_program.empty()){
+                       is_highlevel = true;
+                       return;
+               }
+       }
+
+}
+
+void dumpShaderProgram(std::ostream &output_stream,
+               const std::string &program_type, const std::string &program)
+{
+       output_stream << program_type << " shader program:" << std::endl <<
+               "----------------------------------" << std::endl;
+       size_t pos = 0;
+       size_t prev = 0;
+       s16 line = 1;
+       while ((pos = program.find('\n', prev)) != std::string::npos) {
+               output_stream << line++ << ": "<< program.substr(prev, pos - prev) <<
+                       std::endl;
+               prev = pos + 1;
+       }
+       output_stream << line << ": " << program.substr(prev) << std::endl <<
+               "End of " << program_type << " shader program." << std::endl <<
+               " " << std::endl;
+}
diff --git a/src/client/shader.h b/src/client/shader.h
new file mode 100644 (file)
index 0000000..583c776
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013 Kahrl <kahrl@gmx.net>
+
+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.
+*/
+
+#pragma once
+
+#include <IMaterialRendererServices.h>
+#include "irrlichttypes_bloated.h"
+#include <string>
+
+class IGameDef;
+
+/*
+       shader.{h,cpp}: Shader handling stuff.
+*/
+
+/*
+       Gets the path to a shader by first checking if the file
+         name_of_shader/filename
+       exists in shader_path and if not, using the data path.
+
+       If not found, returns "".
+
+       Utilizes a thread-safe cache.
+*/
+std::string getShaderPath(const std::string &name_of_shader,
+               const std::string &filename);
+
+struct ShaderInfo {
+       std::string name = "";
+       video::E_MATERIAL_TYPE base_material = video::EMT_SOLID;
+       video::E_MATERIAL_TYPE material = video::EMT_SOLID;
+       u8 drawtype = 0;
+       u8 material_type = 0;
+
+       ShaderInfo() = default;
+       virtual ~ShaderInfo() = default;
+};
+
+/*
+       Setter of constants for shaders
+*/
+
+namespace irr { namespace video {
+       class IMaterialRendererServices;
+} }
+
+
+class IShaderConstantSetter {
+public:
+       virtual ~IShaderConstantSetter() = default;
+       virtual void onSetConstants(video::IMaterialRendererServices *services,
+                       bool is_highlevel) = 0;
+};
+
+
+class IShaderConstantSetterFactory {
+public:
+       virtual ~IShaderConstantSetterFactory() = default;
+       virtual IShaderConstantSetter* create() = 0;
+};
+
+
+template <typename T, std::size_t count=1>
+class CachedShaderSetting {
+       const char *m_name;
+       T m_sent[count];
+       bool has_been_set = false;
+       bool is_pixel;
+protected:
+       CachedShaderSetting(const char *name, bool is_pixel) :
+               m_name(name), is_pixel(is_pixel)
+       {}
+public:
+       void set(const T value[count], video::IMaterialRendererServices *services)
+       {
+               if (has_been_set && std::equal(m_sent, m_sent + count, value))
+                       return;
+               if (is_pixel)
+                       services->setPixelShaderConstant(m_name, value, count);
+               else
+                       services->setVertexShaderConstant(m_name, value, count);
+               std::copy(value, value + count, m_sent);
+               has_been_set = true;
+       }
+};
+
+template <typename T, std::size_t count = 1>
+class CachedPixelShaderSetting : public CachedShaderSetting<T, count> {
+public:
+       CachedPixelShaderSetting(const char *name) :
+               CachedShaderSetting<T, count>(name, true){}
+};
+
+template <typename T, std::size_t count = 1>
+class CachedVertexShaderSetting : public CachedShaderSetting<T, count> {
+public:
+       CachedVertexShaderSetting(const char *name) :
+               CachedShaderSetting<T, count>(name, false){}
+};
+
+
+/*
+       ShaderSource creates and caches shaders.
+*/
+
+class IShaderSource {
+public:
+       IShaderSource() = default;
+       virtual ~IShaderSource() = default;
+
+       virtual u32 getShaderIdDirect(const std::string &name,
+               const u8 material_type, const u8 drawtype){return 0;}
+       virtual ShaderInfo getShaderInfo(u32 id){return ShaderInfo();}
+       virtual u32 getShader(const std::string &name,
+               const u8 material_type, const u8 drawtype){return 0;}
+};
+
+class IWritableShaderSource : public IShaderSource {
+public:
+       IWritableShaderSource() = default;
+       virtual ~IWritableShaderSource() = default;
+
+       virtual u32 getShaderIdDirect(const std::string &name,
+               const u8 material_type, const u8 drawtype){return 0;}
+       virtual ShaderInfo getShaderInfo(u32 id){return ShaderInfo();}
+       virtual u32 getShader(const std::string &name,
+               const u8 material_type, const u8 drawtype){return 0;}
+
+       virtual void processQueue()=0;
+       virtual void insertSourceShader(const std::string &name_of_shader,
+               const std::string &filename, const std::string &program)=0;
+       virtual void rebuildShaders()=0;
+       virtual void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter) = 0;
+};
+
+IWritableShaderSource *createShaderSource();
+
+void dumpShaderProgram(std::ostream &output_stream,
+       const std::string &program_type, const std::string &program);
diff --git a/src/client/sky.cpp b/src/client/sky.cpp
new file mode 100644 (file)
index 0000000..faf12ba
--- /dev/null
@@ -0,0 +1,755 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 "sky.h"
+#include "IVideoDriver.h"
+#include "ISceneManager.h"
+#include "ICameraSceneNode.h"
+#include "S3DVertex.h"
+#include "client/tile.h"
+#include "noise.h"  // easeCurve
+#include "profiler.h"
+#include "util/numeric.h"
+#include <cmath>
+#include "client/renderingengine.h"
+#include "settings.h"
+#include "camera.h"  // CameraModes
+
+
+Sky::Sky(s32 id, ITextureSource *tsrc):
+               scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(),
+                       RenderingEngine::get_scene_manager(), id)
+{
+       setAutomaticCulling(scene::EAC_OFF);
+       m_box.MaxEdge.set(0, 0, 0);
+       m_box.MinEdge.set(0, 0, 0);
+
+       // Create material
+
+       video::SMaterial mat;
+       mat.Lighting = false;
+#ifdef __ANDROID__
+       mat.ZBuffer = video::ECFN_DISABLED;
+#else
+       mat.ZBuffer = video::ECFN_NEVER;
+#endif
+       mat.ZWriteEnable = false;
+       mat.AntiAliasing = 0;
+       mat.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE;
+       mat.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE;
+       mat.BackfaceCulling = false;
+
+       m_materials[0] = mat;
+
+       m_materials[1] = mat;
+       //m_materials[1].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
+       m_materials[1].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+
+       m_materials[2] = mat;
+       m_materials[2].setTexture(0, tsrc->getTextureForMesh("sunrisebg.png"));
+       m_materials[2].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+       //m_materials[2].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR;
+
+       m_sun_texture = tsrc->isKnownSourceImage("sun.png") ?
+               tsrc->getTextureForMesh("sun.png") : NULL;
+       m_moon_texture = tsrc->isKnownSourceImage("moon.png") ?
+               tsrc->getTextureForMesh("moon.png") : NULL;
+       m_sun_tonemap = tsrc->isKnownSourceImage("sun_tonemap.png") ?
+               tsrc->getTexture("sun_tonemap.png") : NULL;
+       m_moon_tonemap = tsrc->isKnownSourceImage("moon_tonemap.png") ?
+               tsrc->getTexture("moon_tonemap.png") : NULL;
+
+       if (m_sun_texture) {
+               m_materials[3] = mat;
+               m_materials[3].setTexture(0, m_sun_texture);
+               m_materials[3].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+               if (m_sun_tonemap)
+                       m_materials[3].Lighting = true;
+       }
+
+       if (m_moon_texture) {
+               m_materials[4] = mat;
+               m_materials[4].setTexture(0, m_moon_texture);
+               m_materials[4].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+               if (m_moon_tonemap)
+                       m_materials[4].Lighting = true;
+       }
+
+       for (v3f &star : m_stars) {
+               star = v3f(
+                       myrand_range(-10000, 10000),
+                       myrand_range(-10000, 10000),
+                       myrand_range(-10000, 10000)
+               );
+               star.normalize();
+       }
+
+       m_directional_colored_fog = g_settings->getBool("directional_colored_fog");
+}
+
+
+void Sky::OnRegisterSceneNode()
+{
+       if (IsVisible)
+               SceneManager->registerNodeForRendering(this, scene::ESNRP_SKY_BOX);
+
+       scene::ISceneNode::OnRegisterSceneNode();
+}
+
+
+void Sky::render()
+{
+       if (!m_visible)
+               return;
+
+       video::IVideoDriver* driver = SceneManager->getVideoDriver();
+       scene::ICameraSceneNode* camera = SceneManager->getActiveCamera();
+
+       if (!camera || !driver)
+               return;
+
+       ScopeProfiler sp(g_profiler, "Sky::render()", SPT_AVG);
+
+       // Draw perspective skybox
+
+       core::matrix4 translate(AbsoluteTransformation);
+       translate.setTranslation(camera->getAbsolutePosition());
+
+       // Draw the sky box between the near and far clip plane
+       const f32 viewDistance = (camera->getNearValue() + camera->getFarValue()) * 0.5f;
+       core::matrix4 scale;
+       scale.setScale(core::vector3df(viewDistance, viewDistance, viewDistance));
+
+       driver->setTransform(video::ETS_WORLD, translate * scale);
+
+       if (m_sunlight_seen) {
+               float sunsize = 0.07;
+               video::SColorf suncolor_f(1, 1, 0, 1);
+               //suncolor_f.r = 1;
+               //suncolor_f.g = MYMAX(0.3, MYMIN(1.0, 0.7 + m_time_brightness * 0.5));
+               //suncolor_f.b = MYMAX(0.0, m_brightness * 0.95);
+               video::SColorf suncolor2_f(1, 1, 1, 1);
+               // The values below were probably meant to be suncolor2_f instead of a
+               // reassignment of suncolor_f. However, the resulting colour was chosen
+               // and is our long-running classic colour. So preserve, but comment-out
+               // the unnecessary first assignments above.
+               suncolor_f.r = 1;
+               suncolor_f.g = MYMAX(0.3, MYMIN(1.0, 0.85 + m_time_brightness * 0.5));
+               suncolor_f.b = MYMAX(0.0, m_brightness);
+
+               float moonsize = 0.04;
+               video::SColorf mooncolor_f(0.50, 0.57, 0.65, 1);
+               video::SColorf mooncolor2_f(0.85, 0.875, 0.9, 1);
+
+               float nightlength = 0.415;
+               float wn = nightlength / 2;
+               float wicked_time_of_day = 0;
+               if (m_time_of_day > wn && m_time_of_day < 1.0 - wn)
+                       wicked_time_of_day = (m_time_of_day - wn) / (1.0 - wn * 2) * 0.5 + 0.25;
+               else if (m_time_of_day < 0.5)
+                       wicked_time_of_day = m_time_of_day / wn * 0.25;
+               else
+                       wicked_time_of_day = 1.0 - ((1.0 - m_time_of_day) / wn * 0.25);
+               /*std::cerr<<"time_of_day="<<m_time_of_day<<" -> "
+                               <<"wicked_time_of_day="<<wicked_time_of_day<<std::endl;*/
+
+               video::SColor suncolor = suncolor_f.toSColor();
+               video::SColor suncolor2 = suncolor2_f.toSColor();
+               video::SColor mooncolor = mooncolor_f.toSColor();
+               video::SColor mooncolor2 = mooncolor2_f.toSColor();
+
+               // Calculate offset normalized to the X dimension of a 512x1 px tonemap
+               float offset = (1.0 - fabs(sin((m_time_of_day - 0.5) * irr::core::PI))) * 511;
+
+               if (m_sun_tonemap) {
+                       u8 * texels = (u8 *)m_sun_tonemap->lock();
+                       video::SColor* texel = (video::SColor *)(texels + (u32)offset * 4);
+                       video::SColor texel_color (255, texel->getRed(),
+                               texel->getGreen(), texel->getBlue());
+                       m_sun_tonemap->unlock();
+                       m_materials[3].EmissiveColor = texel_color;
+               }
+
+               if (m_moon_tonemap) {
+                       u8 * texels = (u8 *)m_moon_tonemap->lock();
+                       video::SColor* texel = (video::SColor *)(texels + (u32)offset * 4);
+                       video::SColor texel_color (255, texel->getRed(),
+                               texel->getGreen(), texel->getBlue());
+                       m_moon_tonemap->unlock();
+                       m_materials[4].EmissiveColor = texel_color;
+               }
+
+               const f32 t = 1.0f;
+               const f32 o = 0.0f;
+               static const u16 indices[4] = {0, 1, 2, 3};
+               video::S3DVertex vertices[4];
+
+               driver->setMaterial(m_materials[1]);
+
+               video::SColor cloudyfogcolor = m_bgcolor;
+
+               // Draw far cloudy fog thing blended with skycolor
+               for (u32 j = 0; j < 4; j++) {
+                       video::SColor c = cloudyfogcolor.getInterpolated(m_skycolor, 0.45);
+                       vertices[0] = video::S3DVertex(-1, 0.08, -1, 0, 0, 1, c, t, t);
+                       vertices[1] = video::S3DVertex( 1, 0.08, -1, 0, 0, 1, c, o, t);
+                       vertices[2] = video::S3DVertex( 1, 0.12, -1, 0, 0, 1, c, o, o);
+                       vertices[3] = video::S3DVertex(-1, 0.12, -1, 0, 0, 1, c, t, o);
+                       for (video::S3DVertex &vertex : vertices) {
+                               if (j == 0)
+                                       // Don't switch
+                                       {}
+                               else if (j == 1)
+                                       // Switch from -Z (south) to +X (east)
+                                       vertex.Pos.rotateXZBy(90);
+                               else if (j == 2)
+                                       // Switch from -Z (south) to -X (west)
+                                       vertex.Pos.rotateXZBy(-90);
+                               else
+                                       // Switch from -Z (south) to +Z (north)
+                                       vertex.Pos.rotateXZBy(-180);
+                       }
+                       driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+               }
+
+               // Draw far cloudy fog thing
+               for (u32 j = 0; j < 4; j++) {
+                       video::SColor c = cloudyfogcolor;
+                       vertices[0] = video::S3DVertex(-1, -1.0, -1, 0, 0, 1, c, t, t);
+                       vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 0, 1, c, o, t);
+                       vertices[2] = video::S3DVertex( 1, 0.08, -1, 0, 0, 1, c, o, o);
+                       vertices[3] = video::S3DVertex(-1, 0.08, -1, 0, 0, 1, c, t, o);
+                       for (video::S3DVertex &vertex : vertices) {
+                               if (j == 0)
+                                       // Don't switch
+                                       {}
+                               else if (j == 1)
+                                       // Switch from -Z (south) to +X (east)
+                                       vertex.Pos.rotateXZBy(90);
+                               else if (j == 2)
+                                       // Switch from -Z (south) to -X (west)
+                                       vertex.Pos.rotateXZBy(-90);
+                               else
+                                       // Switch from -Z (south) to +Z (north)
+                                       vertex.Pos.rotateXZBy(-180);
+                       }
+                       driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+               }
+
+               // Draw bottom far cloudy fog thing
+               video::SColor c = cloudyfogcolor;
+               vertices[0] = video::S3DVertex(-1, -1.0, -1, 0, 1, 0, c, t, t);
+               vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 1, 0, c, o, t);
+               vertices[2] = video::S3DVertex( 1, -1.0, 1, 0, 1, 0, c, o, o);
+               vertices[3] = video::S3DVertex(-1, -1.0, 1, 0, 1, 0, c, t, o);
+               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+
+               // If sun, moon and stars are (temporarily) disabled, abort here
+               if (!m_bodies_visible)
+                       return;
+
+               driver->setMaterial(m_materials[2]);
+
+               // Draw sunrise/sunset horizon glow texture (textures/base/pack/sunrisebg.png)
+               {
+                       float mid1 = 0.25;
+                       float mid = wicked_time_of_day < 0.5 ? mid1 : (1.0 - mid1);
+                       float a_ = 1.0f - std::fabs(wicked_time_of_day - mid) * 35.0f;
+                       float a = easeCurve(MYMAX(0, MYMIN(1, a_)));
+                       //std::cerr<<"a_="<<a_<<" a="<<a<<std::endl;
+                       video::SColor c(255, 255, 255, 255);
+                       float y = -(1.0 - a) * 0.22;
+                       vertices[0] = video::S3DVertex(-1, -0.05 + y, -1, 0, 0, 1, c, t, t);
+                       vertices[1] = video::S3DVertex( 1, -0.05 + y, -1, 0, 0, 1, c, o, t);
+                       vertices[2] = video::S3DVertex( 1,   0.2 + y, -1, 0, 0, 1, c, o, o);
+                       vertices[3] = video::S3DVertex(-1,   0.2 + y, -1, 0, 0, 1, c, t, o);
+                       for (video::S3DVertex &vertex : vertices) {
+                               if (wicked_time_of_day < 0.5)
+                                       // Switch from -Z (south) to +X (east)
+                                       vertex.Pos.rotateXZBy(90);
+                               else
+                                       // Switch from -Z (south) to -X (west)
+                                       vertex.Pos.rotateXZBy(-90);
+                       }
+                       driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+               }
+
+               // Draw stars
+               do {
+                       driver->setMaterial(m_materials[1]);
+                       float starbrightness = MYMAX(0, MYMIN(1,
+                               (0.285 - fabs(wicked_time_of_day < 0.5 ?
+                               wicked_time_of_day : (1.0 - wicked_time_of_day))) * 10));
+                       float f = starbrightness;
+                       float d = 0.007/2;
+                       video::SColor starcolor(255, f * 90, f * 90, f * 90);
+                       if (starcolor.getBlue() < m_skycolor.getBlue())
+                               break;
+#ifdef __ANDROID__
+                       u16 indices[SKY_STAR_COUNT * 3];
+                       video::S3DVertex vertices[SKY_STAR_COUNT * 3];
+                       for (u32 i = 0; i < SKY_STAR_COUNT; i++) {
+                               indices[i * 3 + 0] = i * 3 + 0;
+                               indices[i * 3 + 1] = i * 3 + 1;
+                               indices[i * 3 + 2] = i * 3 + 2;
+                               v3f r = m_stars[i];
+                               core::CMatrix4<f32> a;
+                               a.buildRotateFromTo(v3f(0, 1, 0), r);
+                               v3f p = v3f(-d, 1, -d);
+                               v3f p1 = v3f(d, 1, 0);
+                               v3f p2 = v3f(-d, 1, d);
+                               a.rotateVect(p);
+                               a.rotateVect(p1);
+                               a.rotateVect(p2);
+                               p.rotateXYBy(wicked_time_of_day * 360 - 90);
+                               p1.rotateXYBy(wicked_time_of_day * 360 - 90);
+                               p2.rotateXYBy(wicked_time_of_day * 360 - 90);
+                               vertices[i * 3 + 0].Pos = p;
+                               vertices[i * 3 + 0].Color = starcolor;
+                               vertices[i * 3 + 1].Pos = p1;
+                               vertices[i * 3 + 1].Color = starcolor;
+                               vertices[i * 3 + 2].Pos = p2;
+                               vertices[i * 3 + 2].Color = starcolor;
+                       }
+                       driver->drawIndexedTriangleList(vertices, SKY_STAR_COUNT * 3,
+                                       indices, SKY_STAR_COUNT);
+#else
+                       u16 indices[SKY_STAR_COUNT * 4];
+                       video::S3DVertex vertices[SKY_STAR_COUNT * 4];
+                       for (u32 i = 0; i < SKY_STAR_COUNT; i++) {
+                               indices[i * 4 + 0] = i * 4 + 0;
+                               indices[i * 4 + 1] = i * 4 + 1;
+                               indices[i * 4 + 2] = i * 4 + 2;
+                               indices[i * 4 + 3] = i * 4 + 3;
+                               v3f r = m_stars[i];
+                               core::CMatrix4<f32> a;
+                               a.buildRotateFromTo(v3f(0, 1, 0), r);
+                               v3f p = v3f(-d, 1, -d);
+                               v3f p1 = v3f( d, 1, -d);
+                               v3f p2 = v3f( d, 1, d);
+                               v3f p3 = v3f(-d, 1, d);
+                               a.rotateVect(p);
+                               a.rotateVect(p1);
+                               a.rotateVect(p2);
+                               a.rotateVect(p3);
+                               p.rotateXYBy(wicked_time_of_day * 360 - 90);
+                               p1.rotateXYBy(wicked_time_of_day * 360 - 90);
+                               p2.rotateXYBy(wicked_time_of_day * 360 - 90);
+                               p3.rotateXYBy(wicked_time_of_day * 360 - 90);
+                               vertices[i * 4 + 0].Pos = p;
+                               vertices[i * 4 + 0].Color = starcolor;
+                               vertices[i * 4 + 1].Pos = p1;
+                               vertices[i * 4 + 1].Color = starcolor;
+                               vertices[i * 4 + 2].Pos = p2;
+                               vertices[i * 4 + 2].Color = starcolor;
+                               vertices[i * 4 + 3].Pos = p3;
+                               vertices[i * 4 + 3].Color = starcolor;
+                       }
+                       driver->drawVertexPrimitiveList(vertices, SKY_STAR_COUNT * 4,
+                               indices, SKY_STAR_COUNT, video::EVT_STANDARD,
+                               scene::EPT_QUADS, video::EIT_16BIT);
+#endif
+               } while(false);
+
+               // Draw sun
+               if (wicked_time_of_day > 0.15 && wicked_time_of_day < 0.85) {
+                       if (!m_sun_texture) {
+                               driver->setMaterial(m_materials[1]);
+                               float d = sunsize * 1.7;
+                               video::SColor c = suncolor;
+                               c.setAlpha(0.05 * 255);
+                               vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
+                               vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
+                               vertices[2] = video::S3DVertex( d,  d, -1, 0, 0, 1, c, o, o);
+                               vertices[3] = video::S3DVertex(-d,  d, -1, 0, 0, 1, c, t, o);
+                               for (video::S3DVertex &vertex : vertices) {
+                                       // Switch from -Z (south) to +X (east)
+                                       vertex.Pos.rotateXZBy(90);
+                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+                               }
+                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+
+                               d = sunsize * 1.2;
+                               c = suncolor;
+                               c.setAlpha(0.15 * 255);
+                               vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
+                               vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
+                               vertices[2] = video::S3DVertex( d,  d, -1, 0, 0, 1, c, o, o);
+                               vertices[3] = video::S3DVertex(-d,  d, -1, 0, 0, 1, c, t, o);
+                               for (video::S3DVertex &vertex : vertices) {
+                                       // Switch from -Z (south) to +X (east)
+                                       vertex.Pos.rotateXZBy(90);
+                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+                               }
+                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+
+                               d = sunsize;
+                               vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, suncolor, t, t);
+                               vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, suncolor, o, t);
+                               vertices[2] = video::S3DVertex( d,  d, -1, 0, 0, 1, suncolor, o, o);
+                               vertices[3] = video::S3DVertex(-d,  d, -1, 0, 0, 1, suncolor, t, o);
+                               for (video::S3DVertex &vertex : vertices) {
+                                       // Switch from -Z (south) to +X (east)
+                                       vertex.Pos.rotateXZBy(90);
+                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+                               }
+                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+
+                               d = sunsize * 0.7;
+                               vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, suncolor2, t, t);
+                               vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, suncolor2, o, t);
+                               vertices[2] = video::S3DVertex( d,  d, -1, 0, 0, 1, suncolor2, o, o);
+                               vertices[3] = video::S3DVertex(-d,  d, -1, 0, 0, 1, suncolor2, t, o);
+                               for (video::S3DVertex &vertex : vertices) {
+                                       // Switch from -Z (south) to +X (east)
+                                       vertex.Pos.rotateXZBy(90);
+                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+                               }
+                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+                       } else {
+                               driver->setMaterial(m_materials[3]);
+                               float d = sunsize * 1.7;
+                               video::SColor c;
+                               if (m_sun_tonemap)
+                                       c = video::SColor (0, 0, 0, 0);
+                               else
+                                       c = video::SColor (255, 255, 255, 255);
+                               vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
+                               vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
+                               vertices[2] = video::S3DVertex( d,  d, -1, 0, 0, 1, c, o, o);
+                               vertices[3] = video::S3DVertex(-d,  d, -1, 0, 0, 1, c, t, o);
+                               for (video::S3DVertex &vertex : vertices) {
+                                       // Switch from -Z (south) to +X (east)
+                                       vertex.Pos.rotateXZBy(90);
+                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+                               }
+                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+                       }
+               }
+
+               // Draw moon
+               if (wicked_time_of_day < 0.3 || wicked_time_of_day > 0.7) {
+                       if (!m_moon_texture) {
+                               driver->setMaterial(m_materials[1]);
+                               float d = moonsize * 1.9;
+                               video::SColor c = mooncolor;
+                               c.setAlpha(0.05 * 255);
+                               vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
+                               vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
+                               vertices[2] = video::S3DVertex( d,  d, -1, 0, 0, 1, c, o, o);
+                               vertices[3] = video::S3DVertex(-d,  d, -1, 0, 0, 1, c, t, o);
+                               for (video::S3DVertex &vertex : vertices) {
+                                       // Switch from -Z (south) to -X (west)
+                                       vertex.Pos.rotateXZBy(-90);
+                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+                               }
+                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+
+                               d = moonsize * 1.3;
+                               c = mooncolor;
+                               c.setAlpha(0.15 * 255);
+                               vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
+                               vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
+                               vertices[2] = video::S3DVertex( d,  d, -1, 0, 0, 1, c, o, o);
+                               vertices[3] = video::S3DVertex(-d,  d, -1, 0, 0, 1, c, t, o);
+                               for (video::S3DVertex &vertex : vertices) {
+                                       // Switch from -Z (south) to -X (west)
+                                       vertex.Pos.rotateXZBy(-90);
+                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+                               }
+                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+
+                               d = moonsize;
+                               vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, mooncolor, t, t);
+                               vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, mooncolor, o, t);
+                               vertices[2] = video::S3DVertex( d,  d, -1, 0, 0, 1, mooncolor, o, o);
+                               vertices[3] = video::S3DVertex(-d,  d, -1, 0, 0, 1, mooncolor, t, o);
+                               for (video::S3DVertex &vertex : vertices) {
+                                       // Switch from -Z (south) to -X (west)
+                                       vertex.Pos.rotateXZBy(-90);
+                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+                               }
+                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+
+                               float d2 = moonsize * 0.6;
+                               vertices[0] = video::S3DVertex(-d, -d,  -1, 0, 0, 1, mooncolor2, t, t);
+                               vertices[1] = video::S3DVertex( d2,-d,  -1, 0, 0, 1, mooncolor2, o, t);
+                               vertices[2] = video::S3DVertex( d2, d2, -1, 0, 0, 1, mooncolor2, o, o);
+                               vertices[3] = video::S3DVertex(-d,  d2, -1, 0, 0, 1, mooncolor2, t, o);
+                               for (video::S3DVertex &vertex : vertices) {
+                                       // Switch from -Z (south) to -X (west)
+                                       vertex.Pos.rotateXZBy(-90);
+                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+                               }
+                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+                       } else {
+                               driver->setMaterial(m_materials[4]);
+                               float d = moonsize * 1.9;
+                               video::SColor c;
+                               if (m_moon_tonemap)
+                                       c = video::SColor (0, 0, 0, 0);
+                               else
+                                       c = video::SColor (255, 255, 255, 255);
+                               vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
+                               vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
+                               vertices[2] = video::S3DVertex( d,  d, -1, 0, 0, 1, c, o, o);
+                               vertices[3] = video::S3DVertex(-d,  d, -1, 0, 0, 1, c, t, o);
+                               for (video::S3DVertex &vertex : vertices) {
+                                       // Switch from -Z (south) to -X (west)
+                                       vertex.Pos.rotateXZBy(-90);
+                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
+                               }
+                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+                       }
+               }
+
+               // Draw far cloudy fog thing below east and west horizons
+               for (u32 j = 0; j < 2; j++) {
+                       video::SColor c = cloudyfogcolor;
+                       vertices[0] = video::S3DVertex(-1, -1.0,  -1, 0, 0, 1, c, t, t);
+                       vertices[1] = video::S3DVertex( 1, -1.0,  -1, 0, 0, 1, c, o, t);
+                       vertices[2] = video::S3DVertex( 1, -0.02, -1, 0, 0, 1, c, o, o);
+                       vertices[3] = video::S3DVertex(-1, -0.02, -1, 0, 0, 1, c, t, o);
+                       for (video::S3DVertex &vertex : vertices) {
+                               //if (wicked_time_of_day < 0.5)
+                               if (j == 0)
+                                       // Switch from -Z (south) to +X (east)
+                                       vertex.Pos.rotateXZBy(90);
+                               else
+                                       // Switch from -Z (south) to -X (west)
+                                       vertex.Pos.rotateXZBy(-90);
+                       }
+                       driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+               }
+       }
+}
+
+
+void Sky::update(float time_of_day, float time_brightness,
+               float direct_brightness, bool sunlight_seen,
+               CameraMode cam_mode, float yaw, float pitch)
+{
+       // Stabilize initial brightness and color values by flooding updates
+       if (m_first_update) {
+               /*dstream<<"First update with time_of_day="<<time_of_day
+                               <<" time_brightness="<<time_brightness
+                               <<" direct_brightness="<<direct_brightness
+                               <<" sunlight_seen="<<sunlight_seen<<std::endl;*/
+               m_first_update = false;
+               for (u32 i = 0; i < 100; i++) {
+                       update(time_of_day, time_brightness, direct_brightness,
+                               sunlight_seen, cam_mode, yaw, pitch);
+               }
+               return;
+       }
+
+       m_time_of_day = time_of_day;
+       m_time_brightness = time_brightness;
+       m_sunlight_seen = sunlight_seen;
+       m_bodies_visible = true;
+
+       bool is_dawn = (time_brightness >= 0.20 && time_brightness < 0.35);
+
+       /*
+       Development colours
+
+       video::SColorf bgcolor_bright_normal_f(170. / 255, 200. / 255, 230. / 255, 1.0);
+       video::SColorf bgcolor_bright_dawn_f(0.666, 200. / 255 * 0.7, 230. / 255 * 0.5, 1.0);
+       video::SColorf bgcolor_bright_dawn_f(0.666, 0.549, 0.220, 1.0);
+       video::SColorf bgcolor_bright_dawn_f(0.666 * 1.2, 0.549 * 1.0, 0.220 * 1.0, 1.0);
+       video::SColorf bgcolor_bright_dawn_f(0.666 * 1.2, 0.549 * 1.0, 0.220 * 1.2, 1.0);
+
+       video::SColorf cloudcolor_bright_dawn_f(1.0, 0.591, 0.4);
+       video::SColorf cloudcolor_bright_dawn_f(1.0, 0.65, 0.44);
+       video::SColorf cloudcolor_bright_dawn_f(1.0, 0.7, 0.5);
+       */
+
+       video::SColorf bgcolor_bright_normal_f = video::SColor(255, 155, 193, 240);
+       video::SColorf bgcolor_bright_indoor_f = video::SColor(255, 100, 100, 100);
+       video::SColorf bgcolor_bright_dawn_f = video::SColor(255, 186, 193, 240);
+       video::SColorf bgcolor_bright_night_f = video::SColor(255, 64, 144, 255);
+
+       video::SColorf skycolor_bright_normal_f = video::SColor(255, 140, 186, 250);
+       video::SColorf skycolor_bright_dawn_f = video::SColor(255, 180, 186, 250);
+       video::SColorf skycolor_bright_night_f = video::SColor(255, 0, 107, 255);
+
+       // pure white: becomes "diffuse light component" for clouds
+       video::SColorf cloudcolor_bright_normal_f = video::SColor(255, 255, 255, 255);
+       // dawn-factoring version of pure white (note: R is above 1.0)
+       video::SColorf cloudcolor_bright_dawn_f(255.0f/240.0f, 223.0f/240.0f, 191.0f/255.0f);
+
+       float cloud_color_change_fraction = 0.95;
+       if (sunlight_seen) {
+               if (std::fabs(time_brightness - m_brightness) < 0.2f) {
+                       m_brightness = m_brightness * 0.95 + time_brightness * 0.05;
+               } else {
+                       m_brightness = m_brightness * 0.80 + time_brightness * 0.20;
+                       cloud_color_change_fraction = 0.0;
+               }
+       } else {
+               if (direct_brightness < m_brightness)
+                       m_brightness = m_brightness * 0.95 + direct_brightness * 0.05;
+               else
+                       m_brightness = m_brightness * 0.98 + direct_brightness * 0.02;
+       }
+
+       m_clouds_visible = true;
+       float color_change_fraction = 0.98f;
+       if (sunlight_seen) {
+               if (is_dawn) { // Dawn
+                       m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated(
+                               bgcolor_bright_dawn_f, color_change_fraction);
+                       m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated(
+                               skycolor_bright_dawn_f, color_change_fraction);
+                       m_cloudcolor_bright_f = m_cloudcolor_bright_f.getInterpolated(
+                               cloudcolor_bright_dawn_f, color_change_fraction);
+               } else {
+                       if (time_brightness < 0.13f) { // Night
+                               m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated(
+                                       bgcolor_bright_night_f, color_change_fraction);
+                               m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated(
+                                       skycolor_bright_night_f, color_change_fraction);
+                       } else { // Day
+                               m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated(
+                                       bgcolor_bright_normal_f, color_change_fraction);
+                               m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated(
+                                       skycolor_bright_normal_f, color_change_fraction);
+                       }
+
+                       m_cloudcolor_bright_f = m_cloudcolor_bright_f.getInterpolated(
+                               cloudcolor_bright_normal_f, color_change_fraction);
+               }
+       } else {
+               m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated(
+                       bgcolor_bright_indoor_f, color_change_fraction);
+               m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated(
+                       bgcolor_bright_indoor_f, color_change_fraction);
+               m_cloudcolor_bright_f = m_cloudcolor_bright_f.getInterpolated(
+                       cloudcolor_bright_normal_f, color_change_fraction);
+               m_clouds_visible = false;
+       }
+
+       video::SColor bgcolor_bright = m_bgcolor_bright_f.toSColor();
+       m_bgcolor = video::SColor(
+               255,
+               bgcolor_bright.getRed() * m_brightness,
+               bgcolor_bright.getGreen() * m_brightness,
+               bgcolor_bright.getBlue() * m_brightness
+       );
+
+       video::SColor skycolor_bright = m_skycolor_bright_f.toSColor();
+       m_skycolor = video::SColor(
+               255,
+               skycolor_bright.getRed() * m_brightness,
+               skycolor_bright.getGreen() * m_brightness,
+               skycolor_bright.getBlue() * m_brightness
+       );
+
+       // Horizon coloring based on sun and moon direction during sunset and sunrise
+       video::SColor pointcolor = video::SColor(m_bgcolor.getAlpha(), 255, 255, 255);
+       if (m_directional_colored_fog) {
+               if (m_horizon_blend() != 0) {
+                       // Calculate hemisphere value from yaw, (inverted in third person front view)
+                       s8 dir_factor = 1;
+                       if (cam_mode > CAMERA_MODE_THIRD)
+                               dir_factor = -1;
+                       f32 pointcolor_blend = wrapDegrees_0_360(yaw * dir_factor + 90);
+                       if (pointcolor_blend > 180)
+                               pointcolor_blend = 360 - pointcolor_blend;
+                       pointcolor_blend /= 180;
+                       // Bound view angle to determine where transition starts and ends
+                       pointcolor_blend = rangelim(1 - pointcolor_blend * 1.375, 0, 1 / 1.375) *
+                               1.375;
+                       // Combine the colors when looking up or down, otherwise turning looks weird
+                       pointcolor_blend += (0.5 - pointcolor_blend) *
+                               (1 - MYMIN((90 - std::fabs(pitch)) / 90 * 1.5, 1));
+                       // Invert direction to match where the sun and moon are rising
+                       if (m_time_of_day > 0.5)
+                               pointcolor_blend = 1 - pointcolor_blend;
+                       // Horizon colors of sun and moon
+                       f32 pointcolor_light = rangelim(m_time_brightness * 3, 0.2, 1);
+
+                       video::SColorf pointcolor_sun_f(1, 1, 1, 1);
+                       if (m_sun_tonemap) {
+                               pointcolor_sun_f.r = pointcolor_light *
+                                       (float)m_materials[3].EmissiveColor.getRed() / 255;
+                               pointcolor_sun_f.b = pointcolor_light *
+                                       (float)m_materials[3].EmissiveColor.getBlue() / 255;
+                               pointcolor_sun_f.g = pointcolor_light *
+                                       (float)m_materials[3].EmissiveColor.getGreen() / 255;
+                       } else {
+                               pointcolor_sun_f.r = pointcolor_light * 1;
+                               pointcolor_sun_f.b = pointcolor_light *
+                                       (0.25 + (rangelim(m_time_brightness, 0.25, 0.75) - 0.25) * 2 * 0.75);
+                               pointcolor_sun_f.g = pointcolor_light * (pointcolor_sun_f.b * 0.375 +
+                                       (rangelim(m_time_brightness, 0.05, 0.15) - 0.05) * 10 * 0.625);
+                       }
+
+                       video::SColorf pointcolor_moon_f(0.5 * pointcolor_light,
+                               0.6 * pointcolor_light, 0.8 * pointcolor_light, 1);
+                       if (m_moon_tonemap) {
+                               pointcolor_moon_f.r = pointcolor_light *
+                                       (float)m_materials[4].EmissiveColor.getRed() / 255;
+                               pointcolor_moon_f.b = pointcolor_light *
+                                       (float)m_materials[4].EmissiveColor.getBlue() / 255;
+                               pointcolor_moon_f.g = pointcolor_light *
+                                       (float)m_materials[4].EmissiveColor.getGreen() / 255;
+                       }
+
+                       video::SColor pointcolor_sun = pointcolor_sun_f.toSColor();
+                       video::SColor pointcolor_moon = pointcolor_moon_f.toSColor();
+                       // Calculate the blend color
+                       pointcolor = m_mix_scolor(pointcolor_moon, pointcolor_sun, pointcolor_blend);
+               }
+               m_bgcolor = m_mix_scolor(m_bgcolor, pointcolor, m_horizon_blend() * 0.5);
+               m_skycolor = m_mix_scolor(m_skycolor, pointcolor, m_horizon_blend() * 0.25);
+       }
+
+       float cloud_direct_brightness = 0.0f;
+       if (sunlight_seen) {
+               if (!m_directional_colored_fog) {
+                       cloud_direct_brightness = time_brightness;
+                       // Boost cloud brightness relative to sky, at dawn, dusk and at night
+                       if (time_brightness < 0.7f)
+                               cloud_direct_brightness *= 1.3f;
+               } else {
+                       cloud_direct_brightness = std::fmin(m_horizon_blend() * 0.15f +
+                               m_time_brightness, 1.0f);
+                       // Set the same minimum cloud brightness at night
+                       if (time_brightness < 0.5f)
+                               cloud_direct_brightness = std::fmax(cloud_direct_brightness,
+                                       time_brightness * 1.3f);
+               }
+       } else {
+               cloud_direct_brightness = direct_brightness;
+       }
+
+       m_cloud_brightness = m_cloud_brightness * cloud_color_change_fraction +
+               cloud_direct_brightness * (1.0 - cloud_color_change_fraction);
+       m_cloudcolor_f = video::SColorf(
+               m_cloudcolor_bright_f.r * m_cloud_brightness,
+               m_cloudcolor_bright_f.g * m_cloud_brightness,
+               m_cloudcolor_bright_f.b * m_cloud_brightness,
+               1.0
+       );
+       if (m_directional_colored_fog) {
+               m_cloudcolor_f = m_mix_scolorf(m_cloudcolor_f,
+                       video::SColorf(pointcolor), m_horizon_blend() * 0.25);
+       }
+}
diff --git a/src/client/sky.h b/src/client/sky.h
new file mode 100644 (file)
index 0000000..b66a499
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 <ISceneNode.h>
+#include "camera.h"
+#include "irrlichttypes_extrabloated.h"
+
+#pragma once
+
+#define SKY_MATERIAL_COUNT 5
+#define SKY_STAR_COUNT 200
+
+class ITextureSource;
+
+// Skybox, rendered with zbuffer turned off, before all other nodes.
+class Sky : public scene::ISceneNode
+{
+public:
+       //! constructor
+       Sky(s32 id, ITextureSource *tsrc);
+
+       virtual void OnRegisterSceneNode();
+
+       //! renders the node.
+       virtual void render();
+
+       virtual const aabb3f &getBoundingBox() const { return m_box; }
+
+       // Used by Irrlicht for optimizing rendering
+       virtual video::SMaterial &getMaterial(u32 i) { return m_materials[i]; }
+
+       // Used by Irrlicht for optimizing rendering
+       virtual u32 getMaterialCount() const { return SKY_MATERIAL_COUNT; }
+
+       void update(float m_time_of_day, float time_brightness, float direct_brightness,
+                       bool sunlight_seen, CameraMode cam_mode, float yaw, float pitch);
+
+       float getBrightness() { return m_brightness; }
+
+       const video::SColor &getBgColor() const
+       {
+               return m_visible ? m_bgcolor : m_fallback_bg_color;
+       }
+
+       const video::SColor &getSkyColor() const
+       {
+               return m_visible ? m_skycolor : m_fallback_bg_color;
+       }
+
+       bool getCloudsVisible() const { return m_clouds_visible && m_clouds_enabled; }
+       const video::SColorf &getCloudColor() const { return m_cloudcolor_f; }
+
+       void setVisible(bool visible) { m_visible = visible; }
+       // Set only from set_sky API
+       void setCloudsEnabled(bool clouds_enabled) { m_clouds_enabled = clouds_enabled; }
+       void setFallbackBgColor(const video::SColor &fallback_bg_color)
+       {
+               m_fallback_bg_color = fallback_bg_color;
+       }
+       void overrideColors(const video::SColor &bgcolor, const video::SColor &skycolor)
+       {
+               m_bgcolor = bgcolor;
+               m_skycolor = skycolor;
+       }
+       void setBodiesVisible(bool visible) { m_bodies_visible = visible; }
+
+private:
+       aabb3f m_box;
+       video::SMaterial m_materials[SKY_MATERIAL_COUNT];
+
+       // How much sun & moon transition should affect horizon color
+       float m_horizon_blend()
+       {
+               if (!m_sunlight_seen)
+                       return 0;
+               float x = m_time_of_day >= 0.5 ? (1 - m_time_of_day) * 2
+                                              : m_time_of_day * 2;
+
+               if (x <= 0.3)
+                       return 0;
+               if (x <= 0.4) // when the sun and moon are aligned
+                       return (x - 0.3) * 10;
+               if (x <= 0.5)
+                       return (0.5 - x) * 10;
+               return 0;
+       }
+
+       // Mix two colors by a given amount
+       video::SColor m_mix_scolor(video::SColor col1, video::SColor col2, f32 factor)
+       {
+               video::SColor result = video::SColor(
+                               col1.getAlpha() * (1 - factor) + col2.getAlpha() * factor,
+                               col1.getRed() * (1 - factor) + col2.getRed() * factor,
+                               col1.getGreen() * (1 - factor) + col2.getGreen() * factor,
+                               col1.getBlue() * (1 - factor) + col2.getBlue() * factor);
+               return result;
+       }
+       video::SColorf m_mix_scolorf(video::SColorf col1, video::SColorf col2, f32 factor)
+       {
+               video::SColorf result =
+                               video::SColorf(col1.r * (1 - factor) + col2.r * factor,
+                                               col1.g * (1 - factor) + col2.g * factor,
+                                               col1.b * (1 - factor) + col2.b * factor,
+                                               col1.a * (1 - factor) + col2.a * factor);
+               return result;
+       }
+
+       bool m_visible = true;
+       // Used when m_visible=false
+       video::SColor m_fallback_bg_color = video::SColor(255, 255, 255, 255);
+       bool m_first_update = true;
+       float m_time_of_day;
+       float m_time_brightness;
+       bool m_sunlight_seen;
+       float m_brightness = 0.5f;
+       float m_cloud_brightness = 0.5f;
+       bool m_clouds_visible; // Whether clouds are disabled due to player underground
+       bool m_clouds_enabled = true; // Initialised to true, reset only by set_sky API
+       bool m_directional_colored_fog;
+       bool m_bodies_visible = true; // sun, moon, stars
+       video::SColorf m_bgcolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f);
+       video::SColorf m_skycolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f);
+       video::SColorf m_cloudcolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f);
+       video::SColor m_bgcolor;
+       video::SColor m_skycolor;
+       video::SColorf m_cloudcolor_f;
+       v3f m_stars[SKY_STAR_COUNT];
+       video::ITexture *m_sun_texture;
+       video::ITexture *m_moon_texture;
+       video::ITexture *m_sun_tonemap;
+       video::ITexture *m_moon_tonemap;
+};
diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp
new file mode 100644 (file)
index 0000000..7791a5a
--- /dev/null
@@ -0,0 +1,685 @@
+/*
+Minetest
+Copyright (C) 2010-2014 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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 "wieldmesh.h"
+#include "settings.h"
+#include "shader.h"
+#include "inventory.h"
+#include "client.h"
+#include "itemdef.h"
+#include "nodedef.h"
+#include "mesh.h"
+#include "content_mapblock.h"
+#include "mapblock_mesh.h"
+#include "client/meshgen/collector.h"
+#include "client/tile.h"
+#include "log.h"
+#include "util/numeric.h"
+#include <map>
+#include <IMeshManipulator.h>
+
+#define WIELD_SCALE_FACTOR 30.0
+#define WIELD_SCALE_FACTOR_EXTRUDED 40.0
+
+#define MIN_EXTRUSION_MESH_RESOLUTION 16
+#define MAX_EXTRUSION_MESH_RESOLUTION 512
+
+static scene::IMesh *createExtrusionMesh(int resolution_x, int resolution_y)
+{
+       const f32 r = 0.5;
+
+       scene::IMeshBuffer *buf = new scene::SMeshBuffer();
+       video::SColor c(255,255,255,255);
+       v3f scale(1.0, 1.0, 0.1);
+
+       // Front and back
+       {
+               video::S3DVertex vertices[8] = {
+                       // z-
+                       video::S3DVertex(-r,+r,-r, 0,0,-1, c, 0,0),
+                       video::S3DVertex(+r,+r,-r, 0,0,-1, c, 1,0),
+                       video::S3DVertex(+r,-r,-r, 0,0,-1, c, 1,1),
+                       video::S3DVertex(-r,-r,-r, 0,0,-1, c, 0,1),
+                       // z+
+                       video::S3DVertex(-r,+r,+r, 0,0,+1, c, 0,0),
+                       video::S3DVertex(-r,-r,+r, 0,0,+1, c, 0,1),
+                       video::S3DVertex(+r,-r,+r, 0,0,+1, c, 1,1),
+                       video::S3DVertex(+r,+r,+r, 0,0,+1, c, 1,0),
+               };
+               u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
+               buf->append(vertices, 8, indices, 12);
+       }
+
+       f32 pixelsize_x = 1 / (f32) resolution_x;
+       f32 pixelsize_y = 1 / (f32) resolution_y;
+
+       for (int i = 0; i < resolution_x; ++i) {
+               f32 pixelpos_x = i * pixelsize_x - 0.5;
+               f32 x0 = pixelpos_x;
+               f32 x1 = pixelpos_x + pixelsize_x;
+               f32 tex0 = (i + 0.1) * pixelsize_x;
+               f32 tex1 = (i + 0.9) * pixelsize_x;
+               video::S3DVertex vertices[8] = {
+                       // x-
+                       video::S3DVertex(x0,-r,-r, -1,0,0, c, tex0,1),
+                       video::S3DVertex(x0,-r,+r, -1,0,0, c, tex1,1),
+                       video::S3DVertex(x0,+r,+r, -1,0,0, c, tex1,0),
+                       video::S3DVertex(x0,+r,-r, -1,0,0, c, tex0,0),
+                       // x+
+                       video::S3DVertex(x1,-r,-r, +1,0,0, c, tex0,1),
+                       video::S3DVertex(x1,+r,-r, +1,0,0, c, tex0,0),
+                       video::S3DVertex(x1,+r,+r, +1,0,0, c, tex1,0),
+                       video::S3DVertex(x1,-r,+r, +1,0,0, c, tex1,1),
+               };
+               u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
+               buf->append(vertices, 8, indices, 12);
+       }
+       for (int i = 0; i < resolution_y; ++i) {
+               f32 pixelpos_y = i * pixelsize_y - 0.5;
+               f32 y0 = -pixelpos_y - pixelsize_y;
+               f32 y1 = -pixelpos_y;
+               f32 tex0 = (i + 0.1) * pixelsize_y;
+               f32 tex1 = (i + 0.9) * pixelsize_y;
+               video::S3DVertex vertices[8] = {
+                       // y-
+                       video::S3DVertex(-r,y0,-r, 0,-1,0, c, 0,tex0),
+                       video::S3DVertex(+r,y0,-r, 0,-1,0, c, 1,tex0),
+                       video::S3DVertex(+r,y0,+r, 0,-1,0, c, 1,tex1),
+                       video::S3DVertex(-r,y0,+r, 0,-1,0, c, 0,tex1),
+                       // y+
+                       video::S3DVertex(-r,y1,-r, 0,+1,0, c, 0,tex0),
+                       video::S3DVertex(-r,y1,+r, 0,+1,0, c, 0,tex1),
+                       video::S3DVertex(+r,y1,+r, 0,+1,0, c, 1,tex1),
+                       video::S3DVertex(+r,y1,-r, 0,+1,0, c, 1,tex0),
+               };
+               u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
+               buf->append(vertices, 8, indices, 12);
+       }
+
+       // Create mesh object
+       scene::SMesh *mesh = new scene::SMesh();
+       mesh->addMeshBuffer(buf);
+       buf->drop();
+       scaleMesh(mesh, scale);  // also recalculates bounding box
+       return mesh;
+}
+
+/*
+       Caches extrusion meshes so that only one of them per resolution
+       is needed. Also caches one cube (for convenience).
+
+       E.g. there is a single extrusion mesh that is used for all
+       16x16 px images, another for all 256x256 px images, and so on.
+
+       WARNING: Not thread safe. This should not be a problem since
+       rendering related classes (such as WieldMeshSceneNode) will be
+       used from the rendering thread only.
+*/
+class ExtrusionMeshCache: public IReferenceCounted
+{
+public:
+       // Constructor
+       ExtrusionMeshCache()
+       {
+               for (int resolution = MIN_EXTRUSION_MESH_RESOLUTION;
+                               resolution <= MAX_EXTRUSION_MESH_RESOLUTION;
+                               resolution *= 2) {
+                       m_extrusion_meshes[resolution] =
+                               createExtrusionMesh(resolution, resolution);
+               }
+               m_cube = createCubeMesh(v3f(1.0, 1.0, 1.0));
+       }
+       // Destructor
+       virtual ~ExtrusionMeshCache()
+       {
+               for (auto &extrusion_meshe : m_extrusion_meshes) {
+                       extrusion_meshe.second->drop();
+               }
+               m_cube->drop();
+       }
+       // Get closest extrusion mesh for given image dimensions
+       // Caller must drop the returned pointer
+       scene::IMesh* create(core::dimension2d<u32> dim)
+       {
+               // handle non-power of two textures inefficiently without cache
+               if (!is_power_of_two(dim.Width) || !is_power_of_two(dim.Height)) {
+                       return createExtrusionMesh(dim.Width, dim.Height);
+               }
+
+               int maxdim = MYMAX(dim.Width, dim.Height);
+
+               std::map<int, scene::IMesh*>::iterator
+                       it = m_extrusion_meshes.lower_bound(maxdim);
+
+               if (it == m_extrusion_meshes.end()) {
+                       // no viable resolution found; use largest one
+                       it = m_extrusion_meshes.find(MAX_EXTRUSION_MESH_RESOLUTION);
+                       sanity_check(it != m_extrusion_meshes.end());
+               }
+
+               scene::IMesh *mesh = it->second;
+               mesh->grab();
+               return mesh;
+       }
+       // Returns a 1x1x1 cube mesh with one meshbuffer (material) per face
+       // Caller must drop the returned pointer
+       scene::IMesh* createCube()
+       {
+               m_cube->grab();
+               return m_cube;
+       }
+
+private:
+       std::map<int, scene::IMesh*> m_extrusion_meshes;
+       scene::IMesh *m_cube;
+};
+
+ExtrusionMeshCache *g_extrusion_mesh_cache = NULL;
+
+
+WieldMeshSceneNode::WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id, bool lighting):
+       scene::ISceneNode(mgr->getRootSceneNode(), mgr, id),
+       m_material_type(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF),
+       m_lighting(lighting)
+{
+       m_enable_shaders = g_settings->getBool("enable_shaders");
+       m_anisotropic_filter = g_settings->getBool("anisotropic_filter");
+       m_bilinear_filter = g_settings->getBool("bilinear_filter");
+       m_trilinear_filter = g_settings->getBool("trilinear_filter");
+
+       // If this is the first wield mesh scene node, create a cache
+       // for extrusion meshes (and a cube mesh), otherwise reuse it
+       if (!g_extrusion_mesh_cache)
+               g_extrusion_mesh_cache = new ExtrusionMeshCache();
+       else
+               g_extrusion_mesh_cache->grab();
+
+       // Disable bounding box culling for this scene node
+       // since we won't calculate the bounding box.
+       setAutomaticCulling(scene::EAC_OFF);
+
+       // Create the child scene node
+       scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube();
+       m_meshnode = SceneManager->addMeshSceneNode(dummymesh, this, -1);
+       m_meshnode->setReadOnlyMaterials(false);
+       m_meshnode->setVisible(false);
+       dummymesh->drop(); // m_meshnode grabbed it
+}
+
+WieldMeshSceneNode::~WieldMeshSceneNode()
+{
+       sanity_check(g_extrusion_mesh_cache);
+       if (g_extrusion_mesh_cache->drop())
+               g_extrusion_mesh_cache = nullptr;
+}
+
+void WieldMeshSceneNode::setCube(const ContentFeatures &f,
+                       v3f wield_scale)
+{
+       scene::IMesh *cubemesh = g_extrusion_mesh_cache->createCube();
+       scene::SMesh *copy = cloneMesh(cubemesh);
+       cubemesh->drop();
+       postProcessNodeMesh(copy, f, false, true, &m_material_type, &m_colors, true);
+       changeToMesh(copy);
+       copy->drop();
+       m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR);
+}
+
+void WieldMeshSceneNode::setExtruded(const std::string &imagename,
+       const std::string &overlay_name, v3f wield_scale, ITextureSource *tsrc,
+       u8 num_frames)
+{
+       video::ITexture *texture = tsrc->getTexture(imagename);
+       if (!texture) {
+               changeToMesh(nullptr);
+               return;
+       }
+       video::ITexture *overlay_texture =
+               overlay_name.empty() ? NULL : tsrc->getTexture(overlay_name);
+
+       core::dimension2d<u32> dim = texture->getSize();
+       // Detect animation texture and pull off top frame instead of using entire thing
+       if (num_frames > 1) {
+               u32 frame_height = dim.Height / num_frames;
+               dim = core::dimension2d<u32>(dim.Width, frame_height);
+       }
+       scene::IMesh *original = g_extrusion_mesh_cache->create(dim);
+       scene::SMesh *mesh = cloneMesh(original);
+       original->drop();
+       //set texture
+       mesh->getMeshBuffer(0)->getMaterial().setTexture(0,
+               tsrc->getTexture(imagename));
+       if (overlay_texture) {
+               scene::IMeshBuffer *copy = cloneMeshBuffer(mesh->getMeshBuffer(0));
+               copy->getMaterial().setTexture(0, overlay_texture);
+               mesh->addMeshBuffer(copy);
+               copy->drop();
+       }
+       changeToMesh(mesh);
+       mesh->drop();
+
+       m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR_EXTRUDED);
+
+       // Customize materials
+       for (u32 layer = 0; layer < m_meshnode->getMaterialCount(); layer++) {
+               video::SMaterial &material = m_meshnode->getMaterial(layer);
+               material.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE;
+               material.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE;
+               material.MaterialType = m_material_type;
+               material.setFlag(video::EMF_BACK_FACE_CULLING, true);
+               // Enable bi/trilinear filtering only for high resolution textures
+               if (dim.Width > 32) {
+                       material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter);
+                       material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter);
+               } else {
+                       material.setFlag(video::EMF_BILINEAR_FILTER, false);
+                       material.setFlag(video::EMF_TRILINEAR_FILTER, false);
+               }
+               material.setFlag(video::EMF_ANISOTROPIC_FILTER, m_anisotropic_filter);
+               // mipmaps cause "thin black line" artifacts
+#if (IRRLICHT_VERSION_MAJOR >= 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR >= 2
+               material.setFlag(video::EMF_USE_MIP_MAPS, false);
+#endif
+               if (m_enable_shaders) {
+                       material.setTexture(2, tsrc->getShaderFlagsTexture(false));
+               }
+       }
+}
+
+scene::SMesh *createSpecialNodeMesh(Client *client, content_t id, std::vector<ItemPartColor> *colors)
+{
+       MeshMakeData mesh_make_data(client, false, false);
+       MeshCollector collector;
+       mesh_make_data.setSmoothLighting(false);
+       MapblockMeshGenerator gen(&mesh_make_data, &collector);
+       gen.renderSingle(id);
+       colors->clear();
+       scene::SMesh *mesh = new scene::SMesh();
+       for (auto &prebuffers : collector.prebuffers)
+               for (PreMeshBuffer &p : prebuffers) {
+                       if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) {
+                               const FrameSpec &frame = (*p.layer.frames)[0];
+                               p.layer.texture = frame.texture;
+                               p.layer.normal_texture = frame.normal_texture;
+                       }
+                       for (video::S3DVertex &v : p.vertices)
+                               v.Color.setAlpha(255);
+                       scene::SMeshBuffer *buf = new scene::SMeshBuffer();
+                       buf->Material.setTexture(0, p.layer.texture);
+                       p.layer.applyMaterialOptions(buf->Material);
+                       mesh->addMeshBuffer(buf);
+                       buf->append(&p.vertices[0], p.vertices.size(),
+                                       &p.indices[0], p.indices.size());
+                       buf->drop();
+                       colors->push_back(
+                               ItemPartColor(p.layer.has_color, p.layer.color));
+               }
+       return mesh;
+}
+
+void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client)
+{
+       ITextureSource *tsrc = client->getTextureSource();
+       IItemDefManager *idef = client->getItemDefManager();
+       IShaderSource *shdrsrc = client->getShaderSource();
+       const NodeDefManager *ndef = client->getNodeDefManager();
+       const ItemDefinition &def = item.getDefinition(idef);
+       const ContentFeatures &f = ndef->get(def.name);
+       content_t id = ndef->getId(def.name);
+
+       scene::SMesh *mesh = nullptr;
+
+       if (m_enable_shaders) {
+               u32 shader_id = shdrsrc->getShader("wielded_shader", TILE_MATERIAL_BASIC, NDT_NORMAL);
+               m_material_type = shdrsrc->getShaderInfo(shader_id).material;
+       }
+
+       // Color-related
+       m_colors.clear();
+       m_base_color = idef->getItemstackColor(item, client);
+
+       // If wield_image is defined, it overrides everything else
+       if (!def.wield_image.empty()) {
+               setExtruded(def.wield_image, def.wield_overlay, def.wield_scale, tsrc,
+                       1);
+               m_colors.emplace_back();
+               // overlay is white, if present
+               m_colors.emplace_back(true, video::SColor(0xFFFFFFFF));
+               return;
+       }
+
+       // Handle nodes
+       // See also CItemDefManager::createClientCached()
+       if (def.type == ITEM_NODE) {
+               if (f.mesh_ptr[0]) {
+                       // e.g. mesh nodes and nodeboxes
+                       mesh = cloneMesh(f.mesh_ptr[0]);
+                       postProcessNodeMesh(mesh, f, m_enable_shaders, true,
+                               &m_material_type, &m_colors);
+                       changeToMesh(mesh);
+                       mesh->drop();
+                       // mesh is pre-scaled by BS * f->visual_scale
+                       m_meshnode->setScale(
+                                       def.wield_scale * WIELD_SCALE_FACTOR
+                                       / (BS * f.visual_scale));
+               } else {
+                       switch (f.drawtype) {
+                               case NDT_AIRLIKE: {
+                                       changeToMesh(nullptr);
+                                       break;
+                               }
+                               case NDT_PLANTLIKE: {
+                                       setExtruded(tsrc->getTextureName(f.tiles[0].layers[0].texture_id),
+                                               tsrc->getTextureName(f.tiles[0].layers[1].texture_id),
+                                               def.wield_scale, tsrc,
+                                               f.tiles[0].layers[0].animation_frame_count);
+                                       // Add color
+                                       const TileLayer &l0 = f.tiles[0].layers[0];
+                                       m_colors.emplace_back(l0.has_color, l0.color);
+                                       const TileLayer &l1 = f.tiles[0].layers[1];
+                                       m_colors.emplace_back(l1.has_color, l1.color);
+                                       break;
+                               }
+                               case NDT_PLANTLIKE_ROOTED: {
+                                       setExtruded(tsrc->getTextureName(f.special_tiles[0].layers[0].texture_id),
+                                               "", def.wield_scale, tsrc,
+                                               f.special_tiles[0].layers[0].animation_frame_count);
+                                       // Add color
+                                       const TileLayer &l0 = f.special_tiles[0].layers[0];
+                                       m_colors.emplace_back(l0.has_color, l0.color);
+                                       break;
+                               }
+                               case NDT_NORMAL:
+                               case NDT_ALLFACES:
+                               case NDT_LIQUID:
+                               case NDT_FLOWINGLIQUID: {
+                                       setCube(f, def.wield_scale);
+                                       break;
+                               }
+                               default: {
+                                       mesh = createSpecialNodeMesh(client, id, &m_colors);
+                                       changeToMesh(mesh);
+                                       mesh->drop();
+                                       m_meshnode->setScale(
+                                                       def.wield_scale * WIELD_SCALE_FACTOR
+                                                       / (BS * f.visual_scale));
+                               }
+                       }
+               }
+               u32 material_count = m_meshnode->getMaterialCount();
+               for (u32 i = 0; i < material_count; ++i) {
+                       video::SMaterial &material = m_meshnode->getMaterial(i);
+                       material.MaterialType = m_material_type;
+                       material.setFlag(video::EMF_BACK_FACE_CULLING, true);
+                       material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter);
+                       material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter);
+               }
+               return;
+       }
+       else if (!def.inventory_image.empty()) {
+               setExtruded(def.inventory_image, def.inventory_overlay, def.wield_scale,
+                       tsrc, 1);
+               m_colors.emplace_back();
+               // overlay is white, if present
+               m_colors.emplace_back(true, video::SColor(0xFFFFFFFF));
+               return;
+       }
+
+       // no wield mesh found
+       changeToMesh(nullptr);
+}
+
+void WieldMeshSceneNode::setColor(video::SColor c)
+{
+       assert(!m_lighting);
+       scene::IMesh *mesh = m_meshnode->getMesh();
+       if (!mesh)
+               return;
+
+       u8 red = c.getRed();
+       u8 green = c.getGreen();
+       u8 blue = c.getBlue();
+       u32 mc = mesh->getMeshBufferCount();
+       for (u32 j = 0; j < mc; j++) {
+               video::SColor bc(m_base_color);
+               if ((m_colors.size() > j) && (m_colors[j].override_base))
+                       bc = m_colors[j].color;
+               video::SColor buffercolor(255,
+                       bc.getRed() * red / 255,
+                       bc.getGreen() * green / 255,
+                       bc.getBlue() * blue / 255);
+               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+               colorizeMeshBuffer(buf, &buffercolor);
+       }
+}
+
+void WieldMeshSceneNode::render()
+{
+       // note: if this method is changed to actually do something,
+       // you probably should implement OnRegisterSceneNode as well
+}
+
+void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh)
+{
+       if (!mesh) {
+               scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube();
+               m_meshnode->setVisible(false);
+               m_meshnode->setMesh(dummymesh);
+               dummymesh->drop();  // m_meshnode grabbed it
+       } else {
+               m_meshnode->setMesh(mesh);
+       }
+
+       m_meshnode->setMaterialFlag(video::EMF_LIGHTING, m_lighting);
+       // need to normalize normals when lighting is enabled (because of setScale())
+       m_meshnode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, m_lighting);
+       m_meshnode->setVisible(true);
+}
+
+void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
+{
+       ITextureSource *tsrc = client->getTextureSource();
+       IItemDefManager *idef = client->getItemDefManager();
+       const NodeDefManager *ndef = client->getNodeDefManager();
+       const ItemDefinition &def = item.getDefinition(idef);
+       const ContentFeatures &f = ndef->get(def.name);
+       content_t id = ndef->getId(def.name);
+
+       FATAL_ERROR_IF(!g_extrusion_mesh_cache, "Extrusion mesh cache is not yet initialized");
+       
+       scene::SMesh *mesh = nullptr;
+
+       // Shading is on by default
+       result->needs_shading = true;
+
+       // If inventory_image is defined, it overrides everything else
+       if (!def.inventory_image.empty()) {
+               mesh = getExtrudedMesh(tsrc, def.inventory_image,
+                       def.inventory_overlay);
+               result->buffer_colors.emplace_back();
+               // overlay is white, if present
+               result->buffer_colors.emplace_back(true, video::SColor(0xFFFFFFFF));
+               // Items with inventory images do not need shading
+               result->needs_shading = false;
+       } else if (def.type == ITEM_NODE) {
+               if (f.mesh_ptr[0]) {
+                       mesh = cloneMesh(f.mesh_ptr[0]);
+                       scaleMesh(mesh, v3f(0.12, 0.12, 0.12));
+                       postProcessNodeMesh(mesh, f, false, false, nullptr,
+                               &result->buffer_colors);
+               } else {
+                       switch (f.drawtype) {
+                               case NDT_PLANTLIKE: {
+                                       mesh = getExtrudedMesh(tsrc,
+                                               tsrc->getTextureName(f.tiles[0].layers[0].texture_id),
+                                               tsrc->getTextureName(f.tiles[0].layers[1].texture_id));
+                                       // Add color
+                                       const TileLayer &l0 = f.tiles[0].layers[0];
+                                       result->buffer_colors.emplace_back(l0.has_color, l0.color);
+                                       const TileLayer &l1 = f.tiles[0].layers[1];
+                                       result->buffer_colors.emplace_back(l1.has_color, l1.color);
+                                       break;
+                               }
+                               case NDT_PLANTLIKE_ROOTED: {
+                                       mesh = getExtrudedMesh(tsrc,
+                                               tsrc->getTextureName(f.special_tiles[0].layers[0].texture_id), "");
+                                       // Add color
+                                       const TileLayer &l0 = f.special_tiles[0].layers[0];
+                                       result->buffer_colors.emplace_back(l0.has_color, l0.color);
+                                       break;
+                               }
+                               case NDT_NORMAL:
+                               case NDT_ALLFACES:
+                               case NDT_LIQUID:
+                               case NDT_FLOWINGLIQUID: {
+                                       scene::IMesh *cube = g_extrusion_mesh_cache->createCube();
+                                       mesh = cloneMesh(cube);
+                                       cube->drop();
+                                       scaleMesh(mesh, v3f(1.2, 1.2, 1.2));
+                                       // add overlays
+                                       postProcessNodeMesh(mesh, f, false, false, nullptr,
+                                               &result->buffer_colors);
+                                       break;
+                               }
+                               default: {
+                                       mesh = createSpecialNodeMesh(client, id, &result->buffer_colors);
+                                       scaleMesh(mesh, v3f(0.12, 0.12, 0.12));
+                               }
+                       }
+               }
+
+               u32 mc = mesh->getMeshBufferCount();
+               for (u32 i = 0; i < mc; ++i) {
+                       scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
+                       video::SMaterial &material = buf->getMaterial();
+                       material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+                       material.setFlag(video::EMF_BILINEAR_FILTER, false);
+                       material.setFlag(video::EMF_TRILINEAR_FILTER, false);
+                       material.setFlag(video::EMF_BACK_FACE_CULLING, true);
+                       material.setFlag(video::EMF_LIGHTING, false);
+               }
+
+               rotateMeshXZby(mesh, -45);
+               rotateMeshYZby(mesh, -30);
+       }
+       result->mesh = mesh;
+}
+
+
+
+scene::SMesh *getExtrudedMesh(ITextureSource *tsrc,
+       const std::string &imagename, const std::string &overlay_name)
+{
+       // check textures
+       video::ITexture *texture = tsrc->getTextureForMesh(imagename);
+       if (!texture) {
+               return NULL;
+       }
+       video::ITexture *overlay_texture =
+               (overlay_name.empty()) ? NULL : tsrc->getTexture(overlay_name);
+
+       // get mesh
+       core::dimension2d<u32> dim = texture->getSize();
+       scene::IMesh *original = g_extrusion_mesh_cache->create(dim);
+       scene::SMesh *mesh = cloneMesh(original);
+       original->drop();
+
+       //set texture
+       mesh->getMeshBuffer(0)->getMaterial().setTexture(0,
+               tsrc->getTexture(imagename));
+       if (overlay_texture) {
+               scene::IMeshBuffer *copy = cloneMeshBuffer(mesh->getMeshBuffer(0));
+               copy->getMaterial().setTexture(0, overlay_texture);
+               mesh->addMeshBuffer(copy);
+               copy->drop();
+       }
+       // Customize materials
+       for (u32 layer = 0; layer < mesh->getMeshBufferCount(); layer++) {
+               video::SMaterial &material = mesh->getMeshBuffer(layer)->getMaterial();
+               material.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE;
+               material.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE;
+               material.setFlag(video::EMF_BILINEAR_FILTER, false);
+               material.setFlag(video::EMF_TRILINEAR_FILTER, false);
+               material.setFlag(video::EMF_BACK_FACE_CULLING, true);
+               material.setFlag(video::EMF_LIGHTING, false);
+               material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
+       }
+       scaleMesh(mesh, v3f(2.0, 2.0, 2.0));
+
+       return mesh;
+}
+
+void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f,
+       bool use_shaders, bool set_material, const video::E_MATERIAL_TYPE *mattype,
+       std::vector<ItemPartColor> *colors, bool apply_scale)
+{
+       u32 mc = mesh->getMeshBufferCount();
+       // Allocate colors for existing buffers
+       colors->clear();
+       for (u32 i = 0; i < mc; ++i)
+               colors->push_back(ItemPartColor());
+
+       for (u32 i = 0; i < mc; ++i) {
+               const TileSpec *tile = &(f.tiles[i]);
+               scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
+               for (int layernum = 0; layernum < MAX_TILE_LAYERS; layernum++) {
+                       const TileLayer *layer = &tile->layers[layernum];
+                       if (layer->texture_id == 0)
+                               continue;
+                       if (layernum != 0) {
+                               scene::IMeshBuffer *copy = cloneMeshBuffer(buf);
+                               copy->getMaterial() = buf->getMaterial();
+                               mesh->addMeshBuffer(copy);
+                               copy->drop();
+                               buf = copy;
+                               colors->push_back(
+                                       ItemPartColor(layer->has_color, layer->color));
+                       } else {
+                               (*colors)[i] = ItemPartColor(layer->has_color, layer->color);
+                       }
+                       video::SMaterial &material = buf->getMaterial();
+                       if (set_material)
+                               layer->applyMaterialOptions(material);
+                       if (mattype) {
+                               material.MaterialType = *mattype;
+                       }
+                       if (layer->animation_frame_count > 1) {
+                               const FrameSpec &animation_frame = (*layer->frames)[0];
+                               material.setTexture(0, animation_frame.texture);
+                       } else {
+                               material.setTexture(0, layer->texture);
+                       }
+                       if (use_shaders) {
+                               if (layer->normal_texture) {
+                                       if (layer->animation_frame_count > 1) {
+                                               const FrameSpec &animation_frame = (*layer->frames)[0];
+                                               material.setTexture(1, animation_frame.normal_texture);
+                                       } else
+                                               material.setTexture(1, layer->normal_texture);
+                               }
+                               material.setTexture(2, layer->flags_texture);
+                       }
+                       if (apply_scale && tile->world_aligned) {
+                               u32 n = buf->getVertexCount();
+                               for (u32 k = 0; k != n; ++k)
+                                       buf->getTCoords(k) /= layer->scale;
+                       }
+               }
+       }
+}
diff --git a/src/client/wieldmesh.h b/src/client/wieldmesh.h
new file mode 100644 (file)
index 0000000..0908d3a
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+Minetest
+Copyright (C) 2010-2014 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include "irrlichttypes_extrabloated.h"
+
+struct ItemStack;
+class Client;
+class ITextureSource;
+struct ContentFeatures;
+
+/*!
+ * Holds color information of an item mesh's buffer.
+ */
+struct ItemPartColor
+{
+       /*!
+        * If this is false, the global base color of the item
+        * will be used instead of the specific color of the
+        * buffer.
+        */
+       bool override_base = false;
+       /*!
+        * The color of the buffer.
+        */
+       video::SColor color = 0;
+
+       ItemPartColor() = default;
+
+       ItemPartColor(bool override, video::SColor color) :
+                       override_base(override), color(color)
+       {
+       }
+};
+
+struct ItemMesh
+{
+       scene::IMesh *mesh = nullptr;
+       /*!
+        * Stores the color of each mesh buffer.
+        */
+       std::vector<ItemPartColor> buffer_colors;
+       /*!
+        * If false, all faces of the item should have the same brightness.
+        * Disables shading based on normal vectors.
+        */
+       bool needs_shading = true;
+
+       ItemMesh() = default;
+};
+
+/*
+       Wield item scene node, renders the wield mesh of some item
+*/
+class WieldMeshSceneNode : public scene::ISceneNode
+{
+public:
+       WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id = -1, bool lighting = false);
+       virtual ~WieldMeshSceneNode();
+
+       void setCube(const ContentFeatures &f, v3f wield_scale);
+       void setExtruded(const std::string &imagename, const std::string &overlay_image,
+                       v3f wield_scale, ITextureSource *tsrc, u8 num_frames);
+       void setItem(const ItemStack &item, Client *client);
+
+       // Sets the vertex color of the wield mesh.
+       // Must only be used if the constructor was called with lighting = false
+       void setColor(video::SColor color);
+
+       scene::IMesh *getMesh() { return m_meshnode->getMesh(); }
+
+       virtual void render();
+
+       virtual const aabb3f &getBoundingBox() const { return m_bounding_box; }
+
+private:
+       void changeToMesh(scene::IMesh *mesh);
+
+       // Child scene node with the current wield mesh
+       scene::IMeshSceneNode *m_meshnode = nullptr;
+       video::E_MATERIAL_TYPE m_material_type;
+
+       // True if EMF_LIGHTING should be enabled.
+       bool m_lighting;
+
+       bool m_enable_shaders;
+       bool m_anisotropic_filter;
+       bool m_bilinear_filter;
+       bool m_trilinear_filter;
+       /*!
+        * Stores the colors of the mesh's mesh buffers.
+        * This does not include lighting.
+        */
+       std::vector<ItemPartColor> m_colors;
+       /*!
+        * The base color of this mesh. This is the default
+        * for all mesh buffers.
+        */
+       video::SColor m_base_color;
+
+       // Bounding box culling is disabled for this type of scene node,
+       // so this variable is just required so we can implement
+       // getBoundingBox() and is set to an empty box.
+       aabb3f m_bounding_box;
+};
+
+void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result);
+
+scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, const std::string &imagename,
+               const std::string &overlay_name);
+
+/*!
+ * Applies overlays, textures and optionally materials to the given mesh and
+ * extracts tile colors for colorization.
+ * \param mattype overrides the buffer's material type, but can also
+ * be NULL to leave the original material.
+ * \param colors returns the colors of the mesh buffers in the mesh.
+ */
+void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, bool use_shaders,
+               bool set_material, const video::E_MATERIAL_TYPE *mattype,
+               std::vector<ItemPartColor> *colors, bool apply_scale = false);
diff --git a/src/clientenvironment.cpp b/src/clientenvironment.cpp
deleted file mode 100644 (file)
index e2f24aa..0000000
+++ /dev/null
@@ -1,540 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2017 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 "util/serialize.h"
-#include "util/pointedthing.h"
-#include "client.h"
-#include "clientenvironment.h"
-#include "clientsimpleobject.h"
-#include "clientmap.h"
-#include "scripting_client.h"
-#include "mapblock_mesh.h"
-#include "event.h"
-#include "collision.h"
-#include "nodedef.h"
-#include "profiler.h"
-#include "raycast.h"
-#include "voxelalgorithms.h"
-#include "settings.h"
-#include "content_cao.h"
-#include <algorithm>
-#include "client/renderingengine.h"
-
-/*
-       ClientEnvironment
-*/
-
-ClientEnvironment::ClientEnvironment(ClientMap *map,
-       ITextureSource *texturesource, Client *client):
-       Environment(client),
-       m_map(map),
-       m_texturesource(texturesource),
-       m_client(client)
-{
-       char zero = 0;
-       memset(attachement_parent_ids, zero, sizeof(attachement_parent_ids));
-}
-
-ClientEnvironment::~ClientEnvironment()
-{
-       // delete active objects
-       for (auto &active_object : m_active_objects) {
-               delete active_object.second;
-       }
-
-       for (auto &simple_object : m_simple_objects) {
-               delete simple_object;
-       }
-
-       // Drop/delete map
-       m_map->drop();
-
-       delete m_local_player;
-}
-
-Map & ClientEnvironment::getMap()
-{
-       return *m_map;
-}
-
-ClientMap & ClientEnvironment::getClientMap()
-{
-       return *m_map;
-}
-
-void ClientEnvironment::setLocalPlayer(LocalPlayer *player)
-{
-       /*
-               It is a failure if already is a local player
-       */
-       FATAL_ERROR_IF(m_local_player != NULL,
-               "Local player already allocated");
-
-       m_local_player = player;
-}
-
-void ClientEnvironment::step(float dtime)
-{
-       /* Step time of day */
-       stepTimeOfDay(dtime);
-
-       // Get some settings
-       bool fly_allowed = m_client->checkLocalPrivilege("fly");
-       bool free_move = fly_allowed && g_settings->getBool("free_move");
-
-       // Get local player
-       LocalPlayer *lplayer = getLocalPlayer();
-       assert(lplayer);
-       // collision info queue
-       std::vector<CollisionInfo> player_collisions;
-
-       /*
-               Get the speed the player is going
-       */
-       bool is_climbing = lplayer->is_climbing;
-
-       f32 player_speed = lplayer->getSpeed().getLength();
-
-       /*
-               Maximum position increment
-       */
-       //f32 position_max_increment = 0.05*BS;
-       f32 position_max_increment = 0.1*BS;
-
-       // Maximum time increment (for collision detection etc)
-       // time = distance / speed
-       f32 dtime_max_increment = 1;
-       if(player_speed > 0.001)
-               dtime_max_increment = position_max_increment / player_speed;
-
-       // Maximum time increment is 10ms or lower
-       if(dtime_max_increment > 0.01)
-               dtime_max_increment = 0.01;
-
-       // Don't allow overly huge dtime
-       if(dtime > 0.5)
-               dtime = 0.5;
-
-       f32 dtime_downcount = dtime;
-
-       /*
-               Stuff that has a maximum time increment
-       */
-
-       u32 loopcount = 0;
-       do
-       {
-               loopcount++;
-
-               f32 dtime_part;
-               if(dtime_downcount > dtime_max_increment)
-               {
-                       dtime_part = dtime_max_increment;
-                       dtime_downcount -= dtime_part;
-               }
-               else
-               {
-                       dtime_part = dtime_downcount;
-                       /*
-                               Setting this to 0 (no -=dtime_part) disables an infinite loop
-                               when dtime_part is so small that dtime_downcount -= dtime_part
-                               does nothing
-                       */
-                       dtime_downcount = 0;
-               }
-
-               /*
-                       Handle local player
-               */
-
-               {
-                       // Apply physics
-                       if (!free_move && !is_climbing) {
-                               // Gravity
-                               v3f speed = lplayer->getSpeed();
-                               if (!lplayer->in_liquid)
-                                       speed.Y -= lplayer->movement_gravity *
-                                               lplayer->physics_override_gravity * dtime_part * 2.0f;
-
-                               // Liquid floating / sinking
-                               if (lplayer->in_liquid && !lplayer->swimming_vertical)
-                                       speed.Y -= lplayer->movement_liquid_sink * dtime_part * 2.0f;
-
-                               // Liquid resistance
-                               if (lplayer->in_liquid_stable || lplayer->in_liquid) {
-                                       // How much the node's viscosity blocks movement, ranges
-                                       // between 0 and 1. Should match the scale at which viscosity
-                                       // increase affects other liquid attributes.
-                                       static const f32 viscosity_factor = 0.3f;
-
-                                       v3f d_wanted = -speed / lplayer->movement_liquid_fluidity;
-                                       f32 dl = d_wanted.getLength();
-                                       if (dl > lplayer->movement_liquid_fluidity_smooth)
-                                               dl = lplayer->movement_liquid_fluidity_smooth;
-
-                                       dl *= (lplayer->liquid_viscosity * viscosity_factor) +
-                                               (1 - viscosity_factor);
-                                       v3f d = d_wanted.normalize() * (dl * dtime_part * 100.0f);
-                                       speed += d;
-                               }
-
-                               lplayer->setSpeed(speed);
-                       }
-
-                       /*
-                               Move the lplayer.
-                               This also does collision detection.
-                       */
-                       lplayer->move(dtime_part, this, position_max_increment,
-                               &player_collisions);
-               }
-       } while (dtime_downcount > 0.001);
-
-       bool player_immortal = lplayer->getCAO() && lplayer->getCAO()->isImmortal();
-
-       for (const CollisionInfo &info : player_collisions) {
-               v3f speed_diff = info.new_speed - info.old_speed;;
-               // Handle only fall damage
-               // (because otherwise walking against something in fast_move kills you)
-               if (speed_diff.Y < 0 || info.old_speed.Y >= 0)
-                       continue;
-               // Get rid of other components
-               speed_diff.X = 0;
-               speed_diff.Z = 0;
-               f32 pre_factor = 1; // 1 hp per node/s
-               f32 tolerance = BS*14; // 5 without damage
-               f32 post_factor = 1; // 1 hp per node/s
-               if (info.type == COLLISION_NODE) {
-                       const ContentFeatures &f = m_client->ndef()->
-                               get(m_map->getNodeNoEx(info.node_p));
-                       // Determine fall damage multiplier
-                       int addp = itemgroup_get(f.groups, "fall_damage_add_percent");
-                       pre_factor = 1.0f + (float)addp / 100.0f;
-               }
-               float speed = pre_factor * speed_diff.getLength();
-               if (speed > tolerance && !player_immortal) {
-                       f32 damage_f = (speed - tolerance) / BS * post_factor;
-                       u8 damage = (u8)MYMIN(damage_f + 0.5, 255);
-                       if (damage != 0) {
-                               damageLocalPlayer(damage, true);
-                               m_client->getEventManager()->put(
-                                       new SimpleTriggerEvent(MtEvent::PLAYER_FALLING_DAMAGE));
-                       }
-               }
-       }
-
-       if (m_client->modsLoaded())
-               m_script->environment_step(dtime);
-
-       // Update lighting on local player (used for wield item)
-       u32 day_night_ratio = getDayNightRatio();
-       {
-               // Get node at head
-
-               // On InvalidPositionException, use this as default
-               // (day: LIGHT_SUN, night: 0)
-               MapNode node_at_lplayer(CONTENT_AIR, 0x0f, 0);
-
-               v3s16 p = lplayer->getLightPosition();
-               node_at_lplayer = m_map->getNodeNoEx(p);
-
-               u16 light = getInteriorLight(node_at_lplayer, 0, m_client->ndef());
-               final_color_blend(&lplayer->light_color, light, day_night_ratio);
-       }
-
-       /*
-               Step active objects and update lighting of them
-       */
-
-       g_profiler->avg("CEnv: num of objects", m_active_objects.size());
-       bool update_lighting = m_active_object_light_update_interval.step(dtime, 0.21);
-       for (auto &ao_it : m_active_objects) {
-               ClientActiveObject* obj = ao_it.second;
-               // Step object
-               obj->step(dtime, this);
-
-               if (update_lighting) {
-                       // Update lighting
-                       u8 light = 0;
-                       bool pos_ok;
-
-                       // Get node at head
-                       v3s16 p = obj->getLightPosition();
-                       MapNode n = m_map->getNodeNoEx(p, &pos_ok);
-                       if (pos_ok)
-                               light = n.getLightBlend(day_night_ratio, m_client->ndef());
-                       else
-                               light = blend_light(day_night_ratio, LIGHT_SUN, 0);
-
-                       obj->updateLight(light);
-               }
-       }
-
-       /*
-               Step and handle simple objects
-       */
-       g_profiler->avg("CEnv: num of simple objects", m_simple_objects.size());
-       for (auto i = m_simple_objects.begin(); i != m_simple_objects.end();) {
-               auto cur = i;
-               ClientSimpleObject *simple = *cur;
-
-               simple->step(dtime);
-               if(simple->m_to_be_removed) {
-                       delete simple;
-                       i = m_simple_objects.erase(cur);
-               }
-               else {
-                       ++i;
-               }
-       }
-}
-
-void ClientEnvironment::addSimpleObject(ClientSimpleObject *simple)
-{
-       m_simple_objects.push_back(simple);
-}
-
-GenericCAO* ClientEnvironment::getGenericCAO(u16 id)
-{
-       ClientActiveObject *obj = getActiveObject(id);
-       if (obj && obj->getType() == ACTIVEOBJECT_TYPE_GENERIC)
-               return (GenericCAO*) obj;
-
-       return NULL;
-}
-
-ClientActiveObject* ClientEnvironment::getActiveObject(u16 id)
-{
-       auto n = m_active_objects.find(id);
-       if (n == m_active_objects.end())
-               return NULL;
-       return n->second;
-}
-
-bool isFreeClientActiveObjectId(const u16 id,
-       ClientActiveObjectMap &objects)
-{
-       return id != 0 && objects.find(id) == objects.end();
-
-}
-
-u16 getFreeClientActiveObjectId(ClientActiveObjectMap &objects)
-{
-       //try to reuse id's as late as possible
-       static u16 last_used_id = 0;
-       u16 startid = last_used_id;
-       for(;;) {
-               last_used_id ++;
-               if (isFreeClientActiveObjectId(last_used_id, objects))
-                       return last_used_id;
-
-               if (last_used_id == startid)
-                       return 0;
-       }
-}
-
-u16 ClientEnvironment::addActiveObject(ClientActiveObject *object)
-{
-       assert(object); // Pre-condition
-       if(object->getId() == 0)
-       {
-               u16 new_id = getFreeClientActiveObjectId(m_active_objects);
-               if(new_id == 0)
-               {
-                       infostream<<"ClientEnvironment::addActiveObject(): "
-                               <<"no free ids available"<<std::endl;
-                       delete object;
-                       return 0;
-               }
-               object->setId(new_id);
-       }
-       if (!isFreeClientActiveObjectId(object->getId(), m_active_objects)) {
-               infostream<<"ClientEnvironment::addActiveObject(): "
-                       <<"id is not free ("<<object->getId()<<")"<<std::endl;
-               delete object;
-               return 0;
-       }
-       infostream<<"ClientEnvironment::addActiveObject(): "
-               <<"added (id="<<object->getId()<<")"<<std::endl;
-       m_active_objects[object->getId()] = object;
-       object->addToScene(m_texturesource);
-       { // Update lighting immediately
-               u8 light = 0;
-               bool pos_ok;
-
-               // Get node at head
-               v3s16 p = object->getLightPosition();
-               MapNode n = m_map->getNodeNoEx(p, &pos_ok);
-               if (pos_ok)
-                       light = n.getLightBlend(getDayNightRatio(), m_client->ndef());
-               else
-                       light = blend_light(getDayNightRatio(), LIGHT_SUN, 0);
-
-               object->updateLight(light);
-       }
-       return object->getId();
-}
-
-void ClientEnvironment::addActiveObject(u16 id, u8 type,
-       const std::string &init_data)
-{
-       ClientActiveObject* obj =
-               ClientActiveObject::create((ActiveObjectType) type, m_client, this);
-       if(obj == NULL)
-       {
-               infostream<<"ClientEnvironment::addActiveObject(): "
-                       <<"id="<<id<<" type="<<type<<": Couldn't create object"
-                       <<std::endl;
-               return;
-       }
-
-       obj->setId(id);
-
-       try
-       {
-               obj->initialize(init_data);
-       }
-       catch(SerializationError &e)
-       {
-               errorstream<<"ClientEnvironment::addActiveObject():"
-                       <<" id="<<id<<" type="<<type
-                       <<": SerializationError in initialize(): "
-                       <<e.what()
-                       <<": init_data="<<serializeJsonString(init_data)
-                       <<std::endl;
-       }
-
-       addActiveObject(obj);
-}
-
-void ClientEnvironment::removeActiveObject(u16 id)
-{
-       verbosestream<<"ClientEnvironment::removeActiveObject(): "
-               <<"id="<<id<<std::endl;
-       ClientActiveObject* obj = getActiveObject(id);
-       if (obj == NULL) {
-               infostream<<"ClientEnvironment::removeActiveObject(): "
-                       <<"id="<<id<<" not found"<<std::endl;
-               return;
-       }
-       obj->removeFromScene(true);
-       delete obj;
-       m_active_objects.erase(id);
-}
-
-void ClientEnvironment::processActiveObjectMessage(u16 id, const std::string &data)
-{
-       ClientActiveObject *obj = getActiveObject(id);
-       if (obj == NULL) {
-               infostream << "ClientEnvironment::processActiveObjectMessage():"
-                       << " got message for id=" << id << ", which doesn't exist."
-                       << std::endl;
-               return;
-       }
-
-       try {
-               obj->processMessage(data);
-       } catch (SerializationError &e) {
-               errorstream<<"ClientEnvironment::processActiveObjectMessage():"
-                       << " id=" << id << " type=" << obj->getType()
-                       << " SerializationError in processMessage(): " << e.what()
-                       << std::endl;
-       }
-}
-
-/*
-       Callbacks for activeobjects
-*/
-
-void ClientEnvironment::damageLocalPlayer(u8 damage, bool handle_hp)
-{
-       LocalPlayer *lplayer = getLocalPlayer();
-       assert(lplayer);
-
-       if (handle_hp) {
-               if (lplayer->hp > damage)
-                       lplayer->hp -= damage;
-               else
-                       lplayer->hp = 0;
-       }
-
-       ClientEnvEvent event;
-       event.type = CEE_PLAYER_DAMAGE;
-       event.player_damage.amount = damage;
-       event.player_damage.send_to_server = handle_hp;
-       m_client_event_queue.push(event);
-}
-
-/*
-       Client likes to call these
-*/
-
-void ClientEnvironment::getActiveObjects(v3f origin, f32 max_d,
-       std::vector<DistanceSortedActiveObject> &dest)
-{
-       for (auto &ao_it : m_active_objects) {
-               ClientActiveObject* obj = ao_it.second;
-
-               f32 d = (obj->getPosition() - origin).getLength();
-
-               if (d > max_d)
-                       continue;
-
-               dest.emplace_back(obj, d);
-       }
-}
-
-ClientEnvEvent ClientEnvironment::getClientEnvEvent()
-{
-       FATAL_ERROR_IF(m_client_event_queue.empty(),
-                       "ClientEnvironment::getClientEnvEvent(): queue is empty");
-
-       ClientEnvEvent event = m_client_event_queue.front();
-       m_client_event_queue.pop();
-       return event;
-}
-
-void ClientEnvironment::getSelectedActiveObjects(
-       const core::line3d<f32> &shootline_on_map,
-       std::vector<PointedThing> &objects)
-{
-       std::vector<DistanceSortedActiveObject> allObjects;
-       getActiveObjects(shootline_on_map.start,
-               shootline_on_map.getLength() + 10.0f, allObjects);
-       const v3f line_vector = shootline_on_map.getVector();
-
-       for (const auto &allObject : allObjects) {
-               ClientActiveObject *obj = allObject.obj;
-               aabb3f selection_box;
-               if (!obj->getSelectionBox(&selection_box))
-                       continue;
-
-               const v3f &pos = obj->getPosition();
-               aabb3f offsetted_box(selection_box.MinEdge + pos,
-                       selection_box.MaxEdge + pos);
-
-               v3f current_intersection;
-               v3s16 current_normal;
-               if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
-                               &current_intersection, &current_normal)) {
-                       objects.emplace_back((s16) obj->getId(), current_intersection, current_normal,
-                               (current_intersection - shootline_on_map.start).getLengthSQ());
-               }
-       }
-}
diff --git a/src/clientenvironment.h b/src/clientenvironment.h
deleted file mode 100644 (file)
index 606070e..0000000
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2017 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include "environment.h"
-#include <ISceneManager.h>
-#include "clientobject.h"
-#include "util/numeric.h"
-
-class ClientSimpleObject;
-class ClientMap;
-class ClientScripting;
-class ClientActiveObject;
-class GenericCAO;
-class LocalPlayer;
-
-/*
-       The client-side environment.
-
-       This is not thread-safe.
-       Must be called from main (irrlicht) thread (uses the SceneManager)
-       Client uses an environment mutex.
-*/
-
-enum ClientEnvEventType
-{
-       CEE_NONE,
-       CEE_PLAYER_DAMAGE
-};
-
-struct ClientEnvEvent
-{
-       ClientEnvEventType type;
-       union {
-               //struct{
-               //} none;
-               struct{
-                       u8 amount;
-                       bool send_to_server;
-               } player_damage;
-       };
-};
-
-typedef std::unordered_map<u16, ClientActiveObject*> ClientActiveObjectMap;
-class ClientEnvironment : public Environment
-{
-public:
-       ClientEnvironment(ClientMap *map, ITextureSource *texturesource, Client *client);
-       ~ClientEnvironment();
-
-       Map & getMap();
-       ClientMap & getClientMap();
-
-       Client *getGameDef() { return m_client; }
-       void setScript(ClientScripting *script) { m_script = script; }
-
-       void step(f32 dtime);
-
-       virtual void setLocalPlayer(LocalPlayer *player);
-       LocalPlayer *getLocalPlayer() const { return m_local_player; }
-
-       /*
-               ClientSimpleObjects
-       */
-
-       void addSimpleObject(ClientSimpleObject *simple);
-
-       /*
-               ActiveObjects
-       */
-
-       GenericCAO* getGenericCAO(u16 id);
-       ClientActiveObject* getActiveObject(u16 id);
-
-       /*
-               Adds an active object to the environment.
-               Environment handles deletion of object.
-               Object may be deleted by environment immediately.
-               If id of object is 0, assigns a free id to it.
-               Returns the id of the object.
-               Returns 0 if not added and thus deleted.
-       */
-       u16 addActiveObject(ClientActiveObject *object);
-
-       void addActiveObject(u16 id, u8 type, const std::string &init_data);
-       void removeActiveObject(u16 id);
-
-       void processActiveObjectMessage(u16 id, const std::string &data);
-
-       /*
-               Callbacks for activeobjects
-       */
-
-       void damageLocalPlayer(u8 damage, bool handle_hp=true);
-
-       /*
-               Client likes to call these
-       */
-
-       // Get all nearby objects
-       void getActiveObjects(v3f origin, f32 max_d,
-               std::vector<DistanceSortedActiveObject> &dest);
-
-       bool hasClientEnvEvents() const { return !m_client_event_queue.empty(); }
-
-       // Get event from queue. If queue is empty, it triggers an assertion failure.
-       ClientEnvEvent getClientEnvEvent();
-
-       virtual void getSelectedActiveObjects(
-               const core::line3d<f32> &shootline_on_map,
-               std::vector<PointedThing> &objects
-       );
-
-       u16 attachement_parent_ids[USHRT_MAX + 1];
-
-       const std::list<std::string> &getPlayerNames() { return m_player_names; }
-       void addPlayerName(const std::string &name) { m_player_names.push_back(name); }
-       void removePlayerName(const std::string &name) { m_player_names.remove(name); }
-       void updateCameraOffset(const v3s16 &camera_offset)
-       { m_camera_offset = camera_offset; }
-       v3s16 getCameraOffset() const { return m_camera_offset; }
-private:
-       ClientMap *m_map;
-       LocalPlayer *m_local_player = nullptr;
-       ITextureSource *m_texturesource;
-       Client *m_client;
-       ClientScripting *m_script = nullptr;
-       ClientActiveObjectMap m_active_objects;
-       std::vector<ClientSimpleObject*> m_simple_objects;
-       std::queue<ClientEnvEvent> m_client_event_queue;
-       IntervalLimiter m_active_object_light_update_interval;
-       std::list<std::string> m_player_names;
-       v3s16 m_camera_offset;
-};
diff --git a/src/clientmap.cpp b/src/clientmap.cpp
deleted file mode 100644 (file)
index 969c555..0000000
+++ /dev/null
@@ -1,671 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 "clientmap.h"
-#include "client.h"
-#include "mapblock_mesh.h"
-#include <IMaterialRenderer.h>
-#include <matrix4.h>
-#include "mapsector.h"
-#include "mapblock.h"
-#include "profiler.h"
-#include "settings.h"
-#include "camera.h"               // CameraModes
-#include "util/basic_macros.h"
-#include <algorithm>
-#include "client/renderingengine.h"
-
-ClientMap::ClientMap(
-               Client *client,
-               MapDrawControl &control,
-               s32 id
-):
-       Map(dout_client, client),
-       scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(),
-               RenderingEngine::get_scene_manager(), id),
-       m_client(client),
-       m_control(control)
-{
-       m_box = aabb3f(-BS*1000000,-BS*1000000,-BS*1000000,
-                       BS*1000000,BS*1000000,BS*1000000);
-
-       /* TODO: Add a callback function so these can be updated when a setting
-        *       changes.  At this point in time it doesn't matter (e.g. /set
-        *       is documented to change server settings only)
-        *
-        * 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.
-        */
-       m_cache_trilinear_filter  = g_settings->getBool("trilinear_filter");
-       m_cache_bilinear_filter   = g_settings->getBool("bilinear_filter");
-       m_cache_anistropic_filter = g_settings->getBool("anisotropic_filter");
-
-}
-
-MapSector * ClientMap::emergeSector(v2s16 p2d)
-{
-       // Check that it doesn't exist already
-       try {
-               return getSectorNoGenerate(p2d);
-       } catch(InvalidPositionException &e) {
-       }
-
-       // Create a sector
-       MapSector *sector = new MapSector(this, p2d, m_gamedef);
-       m_sectors[p2d] = sector;
-
-       return sector;
-}
-
-void ClientMap::OnRegisterSceneNode()
-{
-       if(IsVisible)
-       {
-               SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID);
-               SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT);
-       }
-
-       ISceneNode::OnRegisterSceneNode();
-}
-
-void ClientMap::getBlocksInViewRange(v3s16 cam_pos_nodes,
-               v3s16 *p_blocks_min, v3s16 *p_blocks_max)
-{
-       v3s16 box_nodes_d = m_control.wanted_range * v3s16(1, 1, 1);
-       // Define p_nodes_min/max as v3s32 because 'cam_pos_nodes -/+ box_nodes_d'
-       // can exceed the range of v3s16 when a large view range is used near the
-       // world edges.
-       v3s32 p_nodes_min(
-               cam_pos_nodes.X - box_nodes_d.X,
-               cam_pos_nodes.Y - box_nodes_d.Y,
-               cam_pos_nodes.Z - box_nodes_d.Z);
-       v3s32 p_nodes_max(
-               cam_pos_nodes.X + box_nodes_d.X,
-               cam_pos_nodes.Y + box_nodes_d.Y,
-               cam_pos_nodes.Z + box_nodes_d.Z);
-       // Take a fair amount as we will be dropping more out later
-       // Umm... these additions are a bit strange but they are needed.
-       *p_blocks_min = v3s16(
-                       p_nodes_min.X / MAP_BLOCKSIZE - 3,
-                       p_nodes_min.Y / MAP_BLOCKSIZE - 3,
-                       p_nodes_min.Z / MAP_BLOCKSIZE - 3);
-       *p_blocks_max = v3s16(
-                       p_nodes_max.X / MAP_BLOCKSIZE + 1,
-                       p_nodes_max.Y / MAP_BLOCKSIZE + 1,
-                       p_nodes_max.Z / MAP_BLOCKSIZE + 1);
-}
-
-void ClientMap::updateDrawList()
-{
-       ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG);
-       g_profiler->add("CM::updateDrawList() count", 1);
-
-       for (auto &i : m_drawlist) {
-               MapBlock *block = i.second;
-               block->refDrop();
-       }
-       m_drawlist.clear();
-
-       v3f camera_position = m_camera_position;
-       v3f camera_direction = m_camera_direction;
-       f32 camera_fov = m_camera_fov;
-
-       // Use a higher fov to accomodate faster camera movements.
-       // Blocks are cropped better when they are drawn.
-       // Or maybe they aren't? Well whatever.
-       camera_fov *= 1.2;
-
-       v3s16 cam_pos_nodes = floatToInt(camera_position, BS);
-       v3s16 p_blocks_min;
-       v3s16 p_blocks_max;
-       getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max);
-
-       // Number of blocks in rendering range
-       u32 blocks_in_range = 0;
-       // Number of blocks occlusion culled
-       u32 blocks_occlusion_culled = 0;
-       // Number of blocks in rendering range but don't have a mesh
-       u32 blocks_in_range_without_mesh = 0;
-       // Blocks that had mesh that would have been drawn according to
-       // rendering range (if max blocks limit didn't kick in)
-       u32 blocks_would_have_drawn = 0;
-       // Blocks that were drawn and had a mesh
-       u32 blocks_drawn = 0;
-       // Blocks which had a corresponding meshbuffer for this pass
-       //u32 blocks_had_pass_meshbuf = 0;
-       // Blocks from which stuff was actually drawn
-       //u32 blocks_without_stuff = 0;
-       // Distance to farthest drawn block
-       float farthest_drawn = 0;
-
-       // No occlusion culling when free_move is on and camera is
-       // inside ground
-       bool occlusion_culling_enabled = true;
-       if (g_settings->getBool("free_move")) {
-               MapNode n = getNodeNoEx(cam_pos_nodes);
-               if (n.getContent() == CONTENT_IGNORE ||
-                               m_nodedef->get(n).solidness == 2)
-                       occlusion_culling_enabled = false;
-       }
-
-       for (const auto &sector_it : m_sectors) {
-               MapSector *sector = sector_it.second;
-               v2s16 sp = sector->getPos();
-
-               if (!m_control.range_all) {
-                       if (sp.X < p_blocks_min.X || sp.X > p_blocks_max.X ||
-                                       sp.Y < p_blocks_min.Z || sp.Y > p_blocks_max.Z)
-                               continue;
-               }
-
-               MapBlockVect sectorblocks;
-               sector->getBlocks(sectorblocks);
-
-               /*
-                       Loop through blocks in sector
-               */
-
-               u32 sector_blocks_drawn = 0;
-
-               for (auto block : sectorblocks) {
-                       /*
-                       Compare block position to camera position, skip
-                       if not seen on display
-               */
-
-                       if (block->mesh)
-                               block->mesh->updateCameraOffset(m_camera_offset);
-
-                       float range = 100000 * BS;
-                       if (!m_control.range_all)
-                               range = m_control.wanted_range * BS;
-
-                       float d = 0.0;
-                       if (!isBlockInSight(block->getPos(), camera_position,
-                                       camera_direction, camera_fov, range, &d))
-                               continue;
-
-                       blocks_in_range++;
-
-                       /*
-                               Ignore if mesh doesn't exist
-                       */
-                       if (!block->mesh) {
-                               blocks_in_range_without_mesh++;
-                               continue;
-                       }
-
-                       /*
-                               Occlusion culling
-                       */
-                       if (occlusion_culling_enabled && isBlockOccluded(block, cam_pos_nodes)) {
-                               blocks_occlusion_culled++;
-                               continue;
-                       }
-
-                       // This block is in range. Reset usage timer.
-                       block->resetUsageTimer();
-
-                       // Limit block count in case of a sudden increase
-                       blocks_would_have_drawn++;
-                       if (blocks_drawn >= m_control.wanted_max_blocks &&
-                                       !m_control.range_all &&
-                                       d > m_control.wanted_range * BS)
-                               continue;
-
-                       // Add to set
-                       block->refGrab();
-                       m_drawlist[block->getPos()] = block;
-
-                       sector_blocks_drawn++;
-                       blocks_drawn++;
-                       if (d / BS > farthest_drawn)
-                               farthest_drawn = d / BS;
-
-               } // foreach sectorblocks
-
-               if (sector_blocks_drawn != 0)
-                       m_last_drawn_sectors.insert(sp);
-       }
-
-       g_profiler->avg("CM: blocks in range", blocks_in_range);
-       g_profiler->avg("CM: blocks occlusion culled", blocks_occlusion_culled);
-       if (blocks_in_range != 0)
-               g_profiler->avg("CM: blocks in range without mesh (frac)",
-                               (float)blocks_in_range_without_mesh / blocks_in_range);
-       g_profiler->avg("CM: blocks drawn", blocks_drawn);
-       g_profiler->avg("CM: farthest drawn", farthest_drawn);
-       g_profiler->avg("CM: wanted max blocks", m_control.wanted_max_blocks);
-}
-
-struct MeshBufList
-{
-       video::SMaterial m;
-       std::vector<scene::IMeshBuffer*> bufs;
-};
-
-struct MeshBufListList
-{
-       /*!
-        * Stores the mesh buffers of the world.
-        * The array index is the material's layer.
-        * The vector part groups vertices by material.
-        */
-       std::vector<MeshBufList> lists[MAX_TILE_LAYERS];
-
-       void clear()
-       {
-               for (auto &list : lists)
-                       list.clear();
-       }
-
-       void add(scene::IMeshBuffer *buf, u8 layer)
-       {
-               // Append to the correct layer
-               std::vector<MeshBufList> &list = lists[layer];
-               const video::SMaterial &m = buf->getMaterial();
-               for (MeshBufList &l : list) {
-                       // comparing a full material is quite expensive so we don't do it if
-                       // not even first texture is equal
-                       if (l.m.TextureLayer[0].Texture != m.TextureLayer[0].Texture)
-                               continue;
-
-                       if (l.m == m) {
-                               l.bufs.push_back(buf);
-                               return;
-                       }
-               }
-               MeshBufList l;
-               l.m = m;
-               l.bufs.push_back(buf);
-               list.push_back(l);
-       }
-};
-
-void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
-{
-       bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
-
-       std::string prefix;
-       if (pass == scene::ESNRP_SOLID)
-               prefix = "CM: solid: ";
-       else
-               prefix = "CM: transparent: ";
-
-       /*
-               This is called two times per frame, reset on the non-transparent one
-       */
-       if (pass == scene::ESNRP_SOLID)
-               m_last_drawn_sectors.clear();
-
-       /*
-               Get time for measuring timeout.
-
-               Measuring time is very useful for long delays when the
-               machine is swapping a lot.
-       */
-       std::time_t time1 = time(0);
-
-       /*
-               Get animation parameters
-       */
-       float animation_time = m_client->getAnimationTime();
-       int crack = m_client->getCrackLevel();
-       u32 daynight_ratio = m_client->getEnv().getDayNightRatio();
-
-       v3f camera_position = m_camera_position;
-       v3f camera_direction = m_camera_direction;
-       f32 camera_fov = m_camera_fov;
-
-       /*
-               Get all blocks and draw all visible ones
-       */
-
-       u32 vertex_count = 0;
-       u32 meshbuffer_count = 0;
-
-       // For limiting number of mesh animations per frame
-       u32 mesh_animate_count = 0;
-       u32 mesh_animate_count_far = 0;
-
-       // Blocks that were drawn and had a mesh
-       u32 blocks_drawn = 0;
-       // Blocks which had a corresponding meshbuffer for this pass
-       u32 blocks_had_pass_meshbuf = 0;
-       // Blocks from which stuff was actually drawn
-       u32 blocks_without_stuff = 0;
-
-       /*
-               Draw the selected MapBlocks
-       */
-
-       {
-       ScopeProfiler sp(g_profiler, prefix + "drawing blocks", SPT_AVG);
-
-       MeshBufListList drawbufs;
-
-       for (auto &i : m_drawlist) {
-               MapBlock *block = i.second;
-
-               // If the mesh of the block happened to get deleted, ignore it
-               if (!block->mesh)
-                       continue;
-
-               float d = 0.0;
-               if (!isBlockInSight(block->getPos(), camera_position,
-                               camera_direction, camera_fov, 100000 * BS, &d))
-                       continue;
-
-               // Mesh animation
-               if (pass == scene::ESNRP_SOLID) {
-                       //MutexAutoLock lock(block->mesh_mutex);
-                       MapBlockMesh *mapBlockMesh = block->mesh;
-                       assert(mapBlockMesh);
-                       // Pretty random but this should work somewhat nicely
-                       bool faraway = d >= BS * 50;
-                       //bool faraway = d >= m_control.wanted_range * BS;
-                       if (mapBlockMesh->isAnimationForced() || !faraway ||
-                                       mesh_animate_count_far < (m_control.range_all ? 200 : 50)) {
-                               bool animated = mapBlockMesh->animate(faraway, animation_time,
-                                       crack, daynight_ratio);
-                               if (animated)
-                                       mesh_animate_count++;
-                               if (animated && faraway)
-                                       mesh_animate_count_far++;
-                       } else {
-                               mapBlockMesh->decreaseAnimationForceTimer();
-                       }
-               }
-
-               /*
-                       Get the meshbuffers of the block
-               */
-               {
-                       //MutexAutoLock lock(block->mesh_mutex);
-
-                       MapBlockMesh *mapBlockMesh = block->mesh;
-                       assert(mapBlockMesh);
-
-                       for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
-                               scene::IMesh *mesh = mapBlockMesh->getMesh(layer);
-                               assert(mesh);
-
-                               u32 c = mesh->getMeshBufferCount();
-                               for (u32 i = 0; i < c; i++) {
-                                       scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
-
-                                       video::SMaterial& material = buf->getMaterial();
-                                       video::IMaterialRenderer* rnd =
-                                               driver->getMaterialRenderer(material.MaterialType);
-                                       bool transparent = (rnd && rnd->isTransparent());
-                                       if (transparent == is_transparent_pass) {
-                                               if (buf->getVertexCount() == 0)
-                                                       errorstream << "Block [" << analyze_block(block)
-                                                               << "] contains an empty meshbuf" << std::endl;
-
-                                               material.setFlag(video::EMF_TRILINEAR_FILTER,
-                                                       m_cache_trilinear_filter);
-                                               material.setFlag(video::EMF_BILINEAR_FILTER,
-                                                       m_cache_bilinear_filter);
-                                               material.setFlag(video::EMF_ANISOTROPIC_FILTER,
-                                                       m_cache_anistropic_filter);
-                                               material.setFlag(video::EMF_WIREFRAME,
-                                                       m_control.show_wireframe);
-
-                                               drawbufs.add(buf, layer);
-                                       }
-                               }
-                       }
-               }
-       }
-
-       // Render all layers in order
-       for (auto &lists : drawbufs.lists) {
-               int timecheck_counter = 0;
-               for (MeshBufList &list : lists) {
-                       timecheck_counter++;
-                       if (timecheck_counter > 50) {
-                               timecheck_counter = 0;
-                               std::time_t time2 = time(0);
-                               if (time2 > time1 + 4) {
-                                       infostream << "ClientMap::renderMap(): "
-                                               "Rendering takes ages, returning."
-                                               << std::endl;
-                                       return;
-                               }
-                       }
-
-                       driver->setMaterial(list.m);
-
-                       for (scene::IMeshBuffer *buf : list.bufs) {
-                               driver->drawMeshBuffer(buf);
-                               vertex_count += buf->getVertexCount();
-                               meshbuffer_count++;
-                       }
-               }
-       }
-       } // ScopeProfiler
-
-       // Log only on solid pass because values are the same
-       if (pass == scene::ESNRP_SOLID) {
-               g_profiler->avg("CM: animated meshes", mesh_animate_count);
-               g_profiler->avg("CM: animated meshes (far)", mesh_animate_count_far);
-       }
-
-       g_profiler->avg(prefix + "vertices drawn", vertex_count);
-       if (blocks_had_pass_meshbuf != 0)
-               g_profiler->avg(prefix + "meshbuffers per block",
-                       (float)meshbuffer_count / (float)blocks_had_pass_meshbuf);
-       if (blocks_drawn != 0)
-               g_profiler->avg(prefix + "empty blocks (frac)",
-                       (float)blocks_without_stuff / blocks_drawn);
-}
-
-static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step,
-       float step_multiplier, float start_distance, float end_distance,
-       const NodeDefManager *ndef, u32 daylight_factor, float sunlight_min_d,
-       int *result, bool *sunlight_seen)
-{
-       int brightness_sum = 0;
-       int brightness_count = 0;
-       float distance = start_distance;
-       dir.normalize();
-       v3f pf = p0;
-       pf += dir * distance;
-       int noncount = 0;
-       bool nonlight_seen = false;
-       bool allow_allowing_non_sunlight_propagates = false;
-       bool allow_non_sunlight_propagates = false;
-       // Check content nearly at camera position
-       {
-               v3s16 p = floatToInt(p0 /*+ dir * 3*BS*/, BS);
-               MapNode n = map->getNodeNoEx(p);
-               if(ndef->get(n).param_type == CPT_LIGHT &&
-                               !ndef->get(n).sunlight_propagates)
-                       allow_allowing_non_sunlight_propagates = true;
-       }
-       // If would start at CONTENT_IGNORE, start closer
-       {
-               v3s16 p = floatToInt(pf, BS);
-               MapNode n = map->getNodeNoEx(p);
-               if(n.getContent() == CONTENT_IGNORE){
-                       float newd = 2*BS;
-                       pf = p0 + dir * 2*newd;
-                       distance = newd;
-                       sunlight_min_d = 0;
-               }
-       }
-       for (int i=0; distance < end_distance; i++) {
-               pf += dir * step;
-               distance += step;
-               step *= step_multiplier;
-
-               v3s16 p = floatToInt(pf, BS);
-               MapNode n = map->getNodeNoEx(p);
-               if (allow_allowing_non_sunlight_propagates && i == 0 &&
-                               ndef->get(n).param_type == CPT_LIGHT &&
-                               !ndef->get(n).sunlight_propagates) {
-                       allow_non_sunlight_propagates = true;
-               }
-
-               if (ndef->get(n).param_type != CPT_LIGHT ||
-                               (!ndef->get(n).sunlight_propagates &&
-                                       !allow_non_sunlight_propagates)){
-                       nonlight_seen = true;
-                       noncount++;
-                       if(noncount >= 4)
-                               break;
-                       continue;
-               }
-
-               if (distance >= sunlight_min_d && !*sunlight_seen && !nonlight_seen)
-                       if (n.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN)
-                               *sunlight_seen = true;
-               noncount = 0;
-               brightness_sum += decode_light(n.getLightBlend(daylight_factor, ndef));
-               brightness_count++;
-       }
-       *result = 0;
-       if(brightness_count == 0)
-               return false;
-       *result = brightness_sum / brightness_count;
-       /*std::cerr<<"Sampled "<<brightness_count<<" points; result="
-                       <<(*result)<<std::endl;*/
-       return true;
-}
-
-int ClientMap::getBackgroundBrightness(float max_d, u32 daylight_factor,
-               int oldvalue, bool *sunlight_seen_result)
-{
-       static v3f z_directions[50] = {
-               v3f(-100, 0, 0)
-       };
-       static f32 z_offsets[sizeof(z_directions)/sizeof(*z_directions)] = {
-               -1000,
-       };
-
-       if(z_directions[0].X < -99){
-               for(u32 i=0; i<sizeof(z_directions)/sizeof(*z_directions); i++){
-                       // Assumes FOV of 72 and 16/9 aspect ratio
-                       z_directions[i] = v3f(
-                               0.02 * myrand_range(-100, 100),
-                               1.0,
-                               0.01 * myrand_range(-100, 100)
-                       ).normalize();
-                       z_offsets[i] = 0.01 * myrand_range(0,100);
-               }
-       }
-
-       int sunlight_seen_count = 0;
-       float sunlight_min_d = max_d*0.8;
-       if(sunlight_min_d > 35*BS)
-               sunlight_min_d = 35*BS;
-       std::vector<int> values;
-       for(u32 i=0; i<sizeof(z_directions)/sizeof(*z_directions); i++){
-               v3f z_dir = z_directions[i];
-               core::CMatrix4<f32> a;
-               a.buildRotateFromTo(v3f(0,1,0), z_dir);
-               v3f dir = m_camera_direction;
-               a.rotateVect(dir);
-               int br = 0;
-               float step = BS*1.5;
-               if(max_d > 35*BS)
-                       step = max_d / 35 * 1.5;
-               float off = step * z_offsets[i];
-               bool sunlight_seen_now = false;
-               bool ok = getVisibleBrightness(this, m_camera_position, dir,
-                               step, 1.0, max_d*0.6+off, max_d, m_nodedef, daylight_factor,
-                               sunlight_min_d,
-                               &br, &sunlight_seen_now);
-               if(sunlight_seen_now)
-                       sunlight_seen_count++;
-               if(!ok)
-                       continue;
-               values.push_back(br);
-               // Don't try too much if being in the sun is clear
-               if(sunlight_seen_count >= 20)
-                       break;
-       }
-       int brightness_sum = 0;
-       int brightness_count = 0;
-       std::sort(values.begin(), values.end());
-       u32 num_values_to_use = values.size();
-       if(num_values_to_use >= 10)
-               num_values_to_use -= num_values_to_use/2;
-       else if(num_values_to_use >= 7)
-               num_values_to_use -= num_values_to_use/3;
-       u32 first_value_i = (values.size() - num_values_to_use) / 2;
-
-       for (u32 i=first_value_i; i < first_value_i + num_values_to_use; i++) {
-               brightness_sum += values[i];
-               brightness_count++;
-       }
-
-       int ret = 0;
-       if(brightness_count == 0){
-               MapNode n = getNodeNoEx(floatToInt(m_camera_position, BS));
-               if(m_nodedef->get(n).param_type == CPT_LIGHT){
-                       ret = decode_light(n.getLightBlend(daylight_factor, m_nodedef));
-               } else {
-                       ret = oldvalue;
-               }
-       } else {
-               ret = brightness_sum / brightness_count;
-       }
-
-       *sunlight_seen_result = (sunlight_seen_count > 0);
-       return ret;
-}
-
-void ClientMap::renderPostFx(CameraMode cam_mode)
-{
-       // Sadly ISceneManager has no "post effects" render pass, in that case we
-       // could just register for that and handle it in renderMap().
-
-       MapNode n = getNodeNoEx(floatToInt(m_camera_position, BS));
-
-       // - If the player is in a solid node, make everything black.
-       // - If the player is in liquid, draw a semi-transparent overlay.
-       // - Do not if player is in third person mode
-       const ContentFeatures& features = m_nodedef->get(n);
-       video::SColor post_effect_color = features.post_effect_color;
-       if(features.solidness == 2 && !(g_settings->getBool("noclip") &&
-                       m_client->checkLocalPrivilege("noclip")) &&
-                       cam_mode == CAMERA_MODE_FIRST)
-       {
-               post_effect_color = video::SColor(255, 0, 0, 0);
-       }
-       if (post_effect_color.getAlpha() != 0)
-       {
-               // Draw a full-screen rectangle
-               video::IVideoDriver* driver = SceneManager->getVideoDriver();
-               v2u32 ss = driver->getScreenSize();
-               core::rect<s32> rect(0,0, ss.X, ss.Y);
-               driver->draw2DRectangle(post_effect_color, rect);
-       }
-}
-
-void ClientMap::PrintInfo(std::ostream &out)
-{
-       out<<"ClientMap: ";
-}
-
-
diff --git a/src/clientmap.h b/src/clientmap.h
deleted file mode 100644 (file)
index 8402bb0..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include "map.h"
-#include "camera.h"
-#include <set>
-#include <map>
-
-struct MapDrawControl
-{
-       // Overrides limits by drawing everything
-       bool range_all = false;
-       // Wanted drawing range
-       float wanted_range = 0.0f;
-       // Maximum number of blocks to draw
-       u32 wanted_max_blocks = 0;
-       // show a wire frame for debugging
-       bool show_wireframe = false;
-};
-
-class Client;
-class ITextureSource;
-
-/*
-       ClientMap
-
-       This is the only map class that is able to render itself on screen.
-*/
-
-class ClientMap : public Map, public scene::ISceneNode
-{
-public:
-       ClientMap(
-                       Client *client,
-                       MapDrawControl &control,
-                       s32 id
-       );
-
-       virtual ~ClientMap() = default;
-
-       s32 mapType() const
-       {
-               return MAPTYPE_CLIENT;
-       }
-
-       void drop()
-       {
-               ISceneNode::drop();
-       }
-
-       void updateCamera(const v3f &pos, const v3f &dir, f32 fov, const v3s16 &offset)
-       {
-               m_camera_position = pos;
-               m_camera_direction = dir;
-               m_camera_fov = fov;
-               m_camera_offset = offset;
-       }
-
-       /*
-               Forcefully get a sector from somewhere
-       */
-       MapSector * emergeSector(v2s16 p);
-
-       //void deSerializeSector(v2s16 p2d, std::istream &is);
-
-       /*
-               ISceneNode methods
-       */
-
-       virtual void OnRegisterSceneNode();
-
-       virtual void render()
-       {
-               video::IVideoDriver* driver = SceneManager->getVideoDriver();
-               driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
-               renderMap(driver, SceneManager->getSceneNodeRenderPass());
-       }
-
-       virtual const aabb3f &getBoundingBox() const
-       {
-               return m_box;
-       }
-
-       void getBlocksInViewRange(v3s16 cam_pos_nodes,
-               v3s16 *p_blocks_min, v3s16 *p_blocks_max);
-       void updateDrawList();
-       void renderMap(video::IVideoDriver* driver, s32 pass);
-
-       int getBackgroundBrightness(float max_d, u32 daylight_factor,
-                       int oldvalue, bool *sunlight_seen_result);
-
-       void renderPostFx(CameraMode cam_mode);
-
-       // For debug printing
-       virtual void PrintInfo(std::ostream &out);
-
-       const MapDrawControl & getControl() const { return m_control; }
-       f32 getCameraFov() const { return m_camera_fov; }
-private:
-       Client *m_client;
-
-       aabb3f m_box = aabb3f(-BS * 1000000, -BS * 1000000, -BS * 1000000,
-               BS * 1000000, BS * 1000000, BS * 1000000);
-
-       MapDrawControl &m_control;
-
-       v3f m_camera_position = v3f(0,0,0);
-       v3f m_camera_direction = v3f(0,0,1);
-       f32 m_camera_fov = M_PI;
-       v3s16 m_camera_offset;
-
-       std::map<v3s16, MapBlock*> m_drawlist;
-
-       std::set<v2s16> m_last_drawn_sectors;
-
-       bool m_cache_trilinear_filter;
-       bool m_cache_bilinear_filter;
-       bool m_cache_anistropic_filter;
-};
diff --git a/src/clientmedia.cpp b/src/clientmedia.cpp
deleted file mode 100644 (file)
index 97931ee..0000000
+++ /dev/null
@@ -1,639 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 "clientmedia.h"
-#include "httpfetch.h"
-#include "client.h"
-#include "filecache.h"
-#include "filesys.h"
-#include "log.h"
-#include "porting.h"
-#include "settings.h"
-#include "util/hex.h"
-#include "util/serialize.h"
-#include "util/sha1.h"
-#include "util/string.h"
-
-static std::string getMediaCacheDir()
-{
-       return porting::path_cache + DIR_DELIM + "media";
-}
-
-/*
-       ClientMediaDownloader
-*/
-
-ClientMediaDownloader::ClientMediaDownloader():
-       m_media_cache(getMediaCacheDir()),
-       m_httpfetch_caller(HTTPFETCH_DISCARD)
-{
-}
-
-ClientMediaDownloader::~ClientMediaDownloader()
-{
-       if (m_httpfetch_caller != HTTPFETCH_DISCARD)
-               httpfetch_caller_free(m_httpfetch_caller);
-
-       for (auto &file_it : m_files)
-               delete file_it.second;
-
-       for (auto &remote : m_remotes)
-               delete remote;
-}
-
-void ClientMediaDownloader::addFile(const std::string &name, const std::string &sha1)
-{
-       assert(!m_initial_step_done); // pre-condition
-
-       // if name was already announced, ignore the new announcement
-       if (m_files.count(name) != 0) {
-               errorstream << "Client: ignoring duplicate media announcement "
-                               << "sent by server: \"" << name << "\""
-                               << std::endl;
-               return;
-       }
-
-       // if name is empty or contains illegal characters, ignore the file
-       if (name.empty() || !string_allowed(name, TEXTURENAME_ALLOWED_CHARS)) {
-               errorstream << "Client: ignoring illegal file name "
-                               << "sent by server: \"" << name << "\""
-                               << std::endl;
-               return;
-       }
-
-       // length of sha1 must be exactly 20 (160 bits), else ignore the file
-       if (sha1.size() != 20) {
-               errorstream << "Client: ignoring illegal SHA1 sent by server: "
-                               << hex_encode(sha1) << " \"" << name << "\""
-                               << std::endl;
-               return;
-       }
-
-       FileStatus *filestatus = new FileStatus();
-       filestatus->received = false;
-       filestatus->sha1 = sha1;
-       filestatus->current_remote = -1;
-       m_files.insert(std::make_pair(name, filestatus));
-}
-
-void ClientMediaDownloader::addRemoteServer(const std::string &baseurl)
-{
-       assert(!m_initial_step_done);   // pre-condition
-
-       #ifdef USE_CURL
-
-       if (g_settings->getBool("enable_remote_media_server")) {
-               infostream << "Client: Adding remote server \""
-                       << baseurl << "\" for media download" << std::endl;
-
-               RemoteServerStatus *remote = new RemoteServerStatus();
-               remote->baseurl = baseurl;
-               remote->active_count = 0;
-               remote->request_by_filename = false;
-               m_remotes.push_back(remote);
-       }
-
-       #else
-
-       infostream << "Client: Ignoring remote server \""
-               << baseurl << "\" because cURL support is not compiled in"
-               << std::endl;
-
-       #endif
-}
-
-void ClientMediaDownloader::step(Client *client)
-{
-       if (!m_initial_step_done) {
-               initialStep(client);
-               m_initial_step_done = true;
-       }
-
-       // Remote media: check for completion of fetches
-       if (m_httpfetch_active) {
-               bool fetched_something = false;
-               HTTPFetchResult fetch_result;
-
-               while (httpfetch_async_get(m_httpfetch_caller, fetch_result)) {
-                       m_httpfetch_active--;
-                       fetched_something = true;
-
-                       // Is this a hashset (index.mth) or a media file?
-                       if (fetch_result.request_id < m_remotes.size())
-                               remoteHashSetReceived(fetch_result);
-                       else
-                               remoteMediaReceived(fetch_result, client);
-               }
-
-               if (fetched_something)
-                       startRemoteMediaTransfers();
-
-               // Did all remote transfers end and no new ones can be started?
-               // If so, request still missing files from the minetest server
-               // (Or report that we have all files.)
-               if (m_httpfetch_active == 0) {
-                       if (m_uncached_received_count < m_uncached_count) {
-                               infostream << "Client: Failed to remote-fetch "
-                                       << (m_uncached_count-m_uncached_received_count)
-                                       << " files. Requesting them"
-                                       << " the usual way." << std::endl;
-                       }
-                       startConventionalTransfers(client);
-               }
-       }
-}
-
-void ClientMediaDownloader::initialStep(Client *client)
-{
-       // Check media cache
-       m_uncached_count = m_files.size();
-       for (auto &file_it : m_files) {
-               std::string name = file_it.first;
-               FileStatus *filestatus = file_it.second;
-               const std::string &sha1 = filestatus->sha1;
-
-               std::ostringstream tmp_os(std::ios_base::binary);
-               bool found_in_cache = m_media_cache.load(hex_encode(sha1), tmp_os);
-
-               // If found in cache, try to load it from there
-               if (found_in_cache) {
-                       bool success = checkAndLoad(name, sha1,
-                                       tmp_os.str(), true, client);
-                       if (success) {
-                               filestatus->received = true;
-                               m_uncached_count--;
-                       }
-               }
-       }
-
-       assert(m_uncached_received_count == 0);
-
-       // Create the media cache dir if we are likely to write to it
-       if (m_uncached_count != 0) {
-               bool did = fs::CreateAllDirs(getMediaCacheDir());
-               if (!did) {
-                       errorstream << "Client: "
-                               << "Could not create media cache directory: "
-                               << getMediaCacheDir()
-                               << std::endl;
-               }
-       }
-
-       // If we found all files in the cache, report this fact to the server.
-       // If the server reported no remote servers, immediately start
-       // conventional transfers. Note: if cURL support is not compiled in,
-       // m_remotes is always empty, so "!USE_CURL" is redundant but may
-       // reduce the size of the compiled code
-       if (!USE_CURL || m_uncached_count == 0 || m_remotes.empty()) {
-               startConventionalTransfers(client);
-       }
-       else {
-               // Otherwise start off by requesting each server's sha1 set
-
-               // This is the first time we use httpfetch, so alloc a caller ID
-               m_httpfetch_caller = httpfetch_caller_alloc();
-               m_httpfetch_timeout = g_settings->getS32("curl_timeout");
-
-               // Set the active fetch limit to curl_parallel_limit or 84,
-               // whichever is greater. This gives us some leeway so that
-               // inefficiencies in communicating with the httpfetch thread
-               // don't slow down fetches too much. (We still want some limit
-               // so that when the first remote server returns its hash set,
-               // not all files are requested from that server immediately.)
-               // One such inefficiency is that ClientMediaDownloader::step()
-               // is only called a couple times per second, while httpfetch
-               // might return responses much faster than that.
-               // Note that httpfetch strictly enforces curl_parallel_limit
-               // but at no inter-thread communication cost. This however
-               // doesn't help with the aforementioned inefficiencies.
-               // The signifance of 84 is that it is 2*6*9 in base 13.
-               m_httpfetch_active_limit = g_settings->getS32("curl_parallel_limit");
-               m_httpfetch_active_limit = MYMAX(m_httpfetch_active_limit, 84);
-
-               // Write a list of hashes that we need. This will be POSTed
-               // to the server using Content-Type: application/octet-stream
-               std::string required_hash_set = serializeRequiredHashSet();
-
-               // minor fixme: this loop ignores m_httpfetch_active_limit
-
-               // another minor fixme, unlikely to matter in normal usage:
-               // these index.mth fetches do (however) count against
-               // m_httpfetch_active_limit when starting actual media file
-               // requests, so if there are lots of remote servers that are
-               // not responding, those will stall new media file transfers.
-
-               for (u32 i = 0; i < m_remotes.size(); ++i) {
-                       assert(m_httpfetch_next_id == i);
-
-                       RemoteServerStatus *remote = m_remotes[i];
-                       actionstream << "Client: Contacting remote server \""
-                               << remote->baseurl << "\"" << std::endl;
-
-                       HTTPFetchRequest fetch_request;
-                       fetch_request.url =
-                               remote->baseurl + MTHASHSET_FILE_NAME;
-                       fetch_request.caller = m_httpfetch_caller;
-                       fetch_request.request_id = m_httpfetch_next_id; // == i
-                       fetch_request.timeout = m_httpfetch_timeout;
-                       fetch_request.connect_timeout = m_httpfetch_timeout;
-                       fetch_request.post_data = required_hash_set;
-                       fetch_request.extra_headers.emplace_back(
-                               "Content-Type: application/octet-stream");
-                       httpfetch_async(fetch_request);
-
-                       m_httpfetch_active++;
-                       m_httpfetch_next_id++;
-                       m_outstanding_hash_sets++;
-               }
-       }
-}
-
-void ClientMediaDownloader::remoteHashSetReceived(
-               const HTTPFetchResult &fetch_result)
-{
-       u32 remote_id = fetch_result.request_id;
-       assert(remote_id < m_remotes.size());
-       RemoteServerStatus *remote = m_remotes[remote_id];
-
-       m_outstanding_hash_sets--;
-
-       if (fetch_result.succeeded) {
-               try {
-                       // Server sent a list of file hashes that are
-                       // available on it, try to parse the list
-
-                       std::set<std::string> sha1_set;
-                       deSerializeHashSet(fetch_result.data, sha1_set);
-
-                       // Parsing succeeded: For every file that is
-                       // available on this server, add this server
-                       // to the available_remotes array
-
-                       for(std::map<std::string, FileStatus*>::iterator
-                                       it = m_files.upper_bound(m_name_bound);
-                                       it != m_files.end(); ++it) {
-                               FileStatus *f = it->second;
-                               if (!f->received && sha1_set.count(f->sha1))
-                                       f->available_remotes.push_back(remote_id);
-                       }
-               }
-               catch (SerializationError &e) {
-                       infostream << "Client: Remote server \""
-                               << remote->baseurl << "\" sent invalid hash set: "
-                               << e.what() << std::endl;
-               }
-       }
-
-       // For compatibility: If index.mth is not found, assume that the
-       // server contains files named like the original files (not their sha1)
-
-       // Do NOT check for any particular response code (e.g. 404) here,
-       // because different servers respond differently
-
-       if (!fetch_result.succeeded && !fetch_result.timeout) {
-               infostream << "Client: Enabling compatibility mode for remote "
-                       << "server \"" << remote->baseurl << "\"" << std::endl;
-               remote->request_by_filename = true;
-
-               // Assume every file is available on this server
-
-               for(std::map<std::string, FileStatus*>::iterator
-                               it = m_files.upper_bound(m_name_bound);
-                               it != m_files.end(); ++it) {
-                       FileStatus *f = it->second;
-                       if (!f->received)
-                               f->available_remotes.push_back(remote_id);
-               }
-       }
-}
-
-void ClientMediaDownloader::remoteMediaReceived(
-               const HTTPFetchResult &fetch_result,
-               Client *client)
-{
-       // Some remote server sent us a file.
-       // -> decrement number of active fetches
-       // -> mark file as received if fetch succeeded
-       // -> try to load media
-
-       std::string name;
-       {
-               std::unordered_map<unsigned long, std::string>::iterator it =
-                       m_remote_file_transfers.find(fetch_result.request_id);
-               assert(it != m_remote_file_transfers.end());
-               name = it->second;
-               m_remote_file_transfers.erase(it);
-       }
-
-       sanity_check(m_files.count(name) != 0);
-
-       FileStatus *filestatus = m_files[name];
-       sanity_check(!filestatus->received);
-       sanity_check(filestatus->current_remote >= 0);
-
-       RemoteServerStatus *remote = m_remotes[filestatus->current_remote];
-
-       filestatus->current_remote = -1;
-       remote->active_count--;
-
-       // If fetch succeeded, try to load media file
-
-       if (fetch_result.succeeded) {
-               bool success = checkAndLoad(name, filestatus->sha1,
-                               fetch_result.data, false, client);
-               if (success) {
-                       filestatus->received = true;
-                       assert(m_uncached_received_count < m_uncached_count);
-                       m_uncached_received_count++;
-               }
-       }
-}
-
-s32 ClientMediaDownloader::selectRemoteServer(FileStatus *filestatus)
-{
-       // Pre-conditions
-       assert(filestatus != NULL);
-       assert(!filestatus->received);
-       assert(filestatus->current_remote < 0);
-
-       if (filestatus->available_remotes.empty())
-               return -1;
-
-       // Of all servers that claim to provide the file (and haven't
-       // been unsuccessfully tried before), find the one with the
-       // smallest number of currently active transfers
-
-       s32 best = 0;
-       s32 best_remote_id = filestatus->available_remotes[best];
-       s32 best_active_count = m_remotes[best_remote_id]->active_count;
-
-       for (u32 i = 1; i < filestatus->available_remotes.size(); ++i) {
-               s32 remote_id = filestatus->available_remotes[i];
-               s32 active_count = m_remotes[remote_id]->active_count;
-               if (active_count < best_active_count) {
-                       best = i;
-                       best_remote_id = remote_id;
-                       best_active_count = active_count;
-               }
-       }
-
-       filestatus->available_remotes.erase(
-                       filestatus->available_remotes.begin() + best);
-
-       return best_remote_id;
-
-}
-
-void ClientMediaDownloader::startRemoteMediaTransfers()
-{
-       bool changing_name_bound = true;
-
-       for (std::map<std::string, FileStatus*>::iterator
-                       files_iter = m_files.upper_bound(m_name_bound);
-                       files_iter != m_files.end(); ++files_iter) {
-
-               // Abort if active fetch limit is exceeded
-               if (m_httpfetch_active >= m_httpfetch_active_limit)
-                       break;
-
-               const std::string &name = files_iter->first;
-               FileStatus *filestatus = files_iter->second;
-
-               if (!filestatus->received && filestatus->current_remote < 0) {
-                       // File has not been received yet and is not currently
-                       // being transferred. Choose a server for it.
-                       s32 remote_id = selectRemoteServer(filestatus);
-                       if (remote_id >= 0) {
-                               // Found a server, so start fetching
-                               RemoteServerStatus *remote =
-                                       m_remotes[remote_id];
-
-                               std::string url = remote->baseurl +
-                                       (remote->request_by_filename ? name :
-                                       hex_encode(filestatus->sha1));
-                               verbosestream << "Client: "
-                                       << "Requesting remote media file "
-                                       << "\"" << name << "\" "
-                                       << "\"" << url << "\"" << std::endl;
-
-                               HTTPFetchRequest fetch_request;
-                               fetch_request.url = url;
-                               fetch_request.caller = m_httpfetch_caller;
-                               fetch_request.request_id = m_httpfetch_next_id;
-                               fetch_request.timeout = 0; // no data timeout!
-                               fetch_request.connect_timeout =
-                                       m_httpfetch_timeout;
-                               httpfetch_async(fetch_request);
-
-                               m_remote_file_transfers.insert(std::make_pair(
-                                                       m_httpfetch_next_id,
-                                                       name));
-
-                               filestatus->current_remote = remote_id;
-                               remote->active_count++;
-                               m_httpfetch_active++;
-                               m_httpfetch_next_id++;
-                       }
-               }
-
-               if (filestatus->received ||
-                               (filestatus->current_remote < 0 &&
-                                !m_outstanding_hash_sets)) {
-                       // If we arrive here, we conclusively know that we
-                       // won't fetch this file from a remote server in the
-                       // future. So update the name bound if possible.
-                       if (changing_name_bound)
-                               m_name_bound = name;
-               }
-               else
-                       changing_name_bound = false;
-       }
-
-}
-
-void ClientMediaDownloader::startConventionalTransfers(Client *client)
-{
-       assert(m_httpfetch_active == 0);        // pre-condition
-
-       if (m_uncached_received_count != m_uncached_count) {
-               // Some media files have not been received yet, use the
-               // conventional slow method (minetest protocol) to get them
-               std::vector<std::string> file_requests;
-               for (auto &file : m_files) {
-                       if (!file.second->received)
-                               file_requests.push_back(file.first);
-               }
-               assert((s32) file_requests.size() ==
-                               m_uncached_count - m_uncached_received_count);
-               client->request_media(file_requests);
-       }
-}
-
-void ClientMediaDownloader::conventionalTransferDone(
-               const std::string &name,
-               const std::string &data,
-               Client *client)
-{
-       // Check that file was announced
-       std::map<std::string, FileStatus*>::iterator
-               file_iter = m_files.find(name);
-       if (file_iter == m_files.end()) {
-               errorstream << "Client: server sent media file that was"
-                       << "not announced, ignoring it: \"" << name << "\""
-                       << std::endl;
-               return;
-       }
-       FileStatus *filestatus = file_iter->second;
-       assert(filestatus != NULL);
-
-       // Check that file hasn't already been received
-       if (filestatus->received) {
-               errorstream << "Client: server sent media file that we already"
-                       << "received, ignoring it: \"" << name << "\""
-                       << std::endl;
-               return;
-       }
-
-       // Mark file as received, regardless of whether loading it works and
-       // whether the checksum matches (because at this point there is no
-       // other server that could send a replacement)
-       filestatus->received = true;
-       assert(m_uncached_received_count < m_uncached_count);
-       m_uncached_received_count++;
-
-       // Check that received file matches announced checksum
-       // If so, load it
-       checkAndLoad(name, filestatus->sha1, data, false, client);
-}
-
-bool ClientMediaDownloader::checkAndLoad(
-               const std::string &name, const std::string &sha1,
-               const std::string &data, bool is_from_cache, Client *client)
-{
-       const char *cached_or_received = is_from_cache ? "cached" : "received";
-       const char *cached_or_received_uc = is_from_cache ? "Cached" : "Received";
-       std::string sha1_hex = hex_encode(sha1);
-
-       // Compute actual checksum of data
-       std::string data_sha1;
-       {
-               SHA1 data_sha1_calculator;
-               data_sha1_calculator.addBytes(data.c_str(), data.size());
-               unsigned char *data_tmpdigest = data_sha1_calculator.getDigest();
-               data_sha1.assign((char*) data_tmpdigest, 20);
-               free(data_tmpdigest);
-       }
-
-       // Check that received file matches announced checksum
-       if (data_sha1 != sha1) {
-               std::string data_sha1_hex = hex_encode(data_sha1);
-               infostream << "Client: "
-                       << cached_or_received_uc << " media file "
-                       << sha1_hex << " \"" << name << "\" "
-                       << "mismatches actual checksum " << data_sha1_hex
-                       << std::endl;
-               return false;
-       }
-
-       // Checksum is ok, try loading the file
-       bool success = client->loadMedia(data, name);
-       if (!success) {
-               infostream << "Client: "
-                       << "Failed to load " << cached_or_received << " media: "
-                       << sha1_hex << " \"" << name << "\""
-                       << std::endl;
-               return false;
-       }
-
-       verbosestream << "Client: "
-               << "Loaded " << cached_or_received << " media: "
-               << sha1_hex << " \"" << name << "\""
-               << std::endl;
-
-       // Update cache (unless we just loaded the file from the cache)
-       if (!is_from_cache)
-               m_media_cache.update(sha1_hex, data);
-
-       return true;
-}
-
-
-/*
-       Minetest Hashset File Format
-
-       All values are stored in big-endian byte order.
-       [u32] signature: 'MTHS'
-       [u16] version: 1
-       For each hash in set:
-               [u8*20] SHA1 hash
-
-       Version changes:
-       1 - Initial version
-*/
-
-std::string ClientMediaDownloader::serializeRequiredHashSet()
-{
-       std::ostringstream os(std::ios::binary);
-
-       writeU32(os, MTHASHSET_FILE_SIGNATURE); // signature
-       writeU16(os, 1);                        // version
-
-       // Write list of hashes of files that have not been
-       // received (found in cache) yet
-       for (std::map<std::string, FileStatus*>::iterator
-                       it = m_files.begin();
-                       it != m_files.end(); ++it) {
-               if (!it->second->received) {
-                       FATAL_ERROR_IF(it->second->sha1.size() != 20, "Invalid SHA1 size");
-                       os << it->second->sha1;
-               }
-       }
-
-       return os.str();
-}
-
-void ClientMediaDownloader::deSerializeHashSet(const std::string &data,
-               std::set<std::string> &result)
-{
-       if (data.size() < 6 || data.size() % 20 != 6) {
-               throw SerializationError(
-                               "ClientMediaDownloader::deSerializeHashSet: "
-                               "invalid hash set file size");
-       }
-
-       const u8 *data_cstr = (const u8*) data.c_str();
-
-       u32 signature = readU32(&data_cstr[0]);
-       if (signature != MTHASHSET_FILE_SIGNATURE) {
-               throw SerializationError(
-                               "ClientMediaDownloader::deSerializeHashSet: "
-                               "invalid hash set file signature");
-       }
-
-       u16 version = readU16(&data_cstr[4]);
-       if (version != 1) {
-               throw SerializationError(
-                               "ClientMediaDownloader::deSerializeHashSet: "
-                               "unsupported hash set file version");
-       }
-
-       for (u32 pos = 6; pos < data.size(); pos += 20) {
-               result.insert(data.substr(pos, 20));
-       }
-}
diff --git a/src/clientmedia.h b/src/clientmedia.h
deleted file mode 100644 (file)
index b08b83e..0000000
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include "irrlichttypes.h"
-#include "filecache.h"
-#include <ostream>
-#include <map>
-#include <set>
-#include <vector>
-#include <unordered_map>
-
-class Client;
-struct HTTPFetchResult;
-
-#define MTHASHSET_FILE_SIGNATURE 0x4d544853 // 'MTHS'
-#define MTHASHSET_FILE_NAME "index.mth"
-
-class ClientMediaDownloader
-{
-public:
-       ClientMediaDownloader();
-       ~ClientMediaDownloader();
-
-       float getProgress() const {
-               if (m_uncached_count >= 1)
-                       return 1.0f * m_uncached_received_count /
-                               m_uncached_count;
-
-               return 0.0f;
-       }
-
-       bool isStarted() const {
-               return m_initial_step_done;
-       }
-
-       // If this returns true, the downloader is done and can be deleted
-       bool isDone() const {
-               return m_initial_step_done &&
-                       m_uncached_received_count == m_uncached_count;
-       }
-
-       // Add a file to the list of required file (but don't fetch it yet)
-       void addFile(const std::string &name, const std::string &sha1);
-
-       // Add a remote server to the list; ignored if not built with cURL
-       void addRemoteServer(const std::string &baseurl);
-
-       // Steps the media downloader:
-       // - May load media into client by calling client->loadMedia()
-       // - May check media cache for files
-       // - May add files to media cache
-       // - May start remote transfers by calling httpfetch_async
-       // - May check for completion of current remote transfers
-       // - May start conventional transfers by calling client->request_media()
-       // - May inform server that all media has been loaded
-       //   by calling client->received_media()
-       // After step has been called once, don't call addFile/addRemoteServer.
-       void step(Client *client);
-
-       // Must be called for each file received through TOCLIENT_MEDIA
-       void conventionalTransferDone(
-                       const std::string &name,
-                       const std::string &data,
-                       Client *client);
-
-private:
-       struct FileStatus {
-               bool received;
-               std::string sha1;
-               s32 current_remote;
-               std::vector<s32> available_remotes;
-       };
-
-       struct RemoteServerStatus {
-               std::string baseurl;
-               s32 active_count;
-               bool request_by_filename;
-       };
-
-       void initialStep(Client *client);
-       void remoteHashSetReceived(const HTTPFetchResult &fetch_result);
-       void remoteMediaReceived(const HTTPFetchResult &fetch_result,
-                       Client *client);
-       s32 selectRemoteServer(FileStatus *filestatus);
-       void startRemoteMediaTransfers();
-       void startConventionalTransfers(Client *client);
-
-       bool checkAndLoad(const std::string &name, const std::string &sha1,
-                       const std::string &data, bool is_from_cache,
-                       Client *client);
-
-       std::string serializeRequiredHashSet();
-       static void deSerializeHashSet(const std::string &data,
-                       std::set<std::string> &result);
-
-       // Maps filename to file status
-       std::map<std::string, FileStatus*> m_files;
-
-       // Array of remote media servers
-       std::vector<RemoteServerStatus*> m_remotes;
-
-       // Filesystem-based media cache
-       FileCache m_media_cache;
-
-       // Has an attempt been made to load media files from the file cache?
-       // Have hash sets been requested from remote servers?
-       bool m_initial_step_done = false;
-
-       // Total number of media files to load
-       s32 m_uncached_count = 0;
-
-       // Number of media files that have been received
-       s32 m_uncached_received_count = 0;
-
-       // Status of remote transfers
-       unsigned long m_httpfetch_caller;
-       unsigned long m_httpfetch_next_id = 0;
-       long m_httpfetch_timeout = 0;
-       s32 m_httpfetch_active = 0;
-       s32 m_httpfetch_active_limit = 0;
-       s32 m_outstanding_hash_sets = 0;
-       std::unordered_map<unsigned long, std::string> m_remote_file_transfers;
-
-       // All files up to this name have either been received from a
-       // remote server or failed on all remote servers, so those files
-       // don't need to be looked at again
-       // (use m_files.upper_bound(m_name_bound) to get an iterator)
-       std::string m_name_bound = "";
-
-};
diff --git a/src/clientobject.cpp b/src/clientobject.cpp
deleted file mode 100644 (file)
index f4b6920..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 "clientobject.h"
-#include "debug.h"
-#include "porting.h"
-
-/*
-       ClientActiveObject
-*/
-
-ClientActiveObject::ClientActiveObject(u16 id, Client *client,
-               ClientEnvironment *env):
-       ActiveObject(id),
-       m_client(client),
-       m_env(env)
-{
-}
-
-ClientActiveObject::~ClientActiveObject()
-{
-       removeFromScene(true);
-}
-
-ClientActiveObject* ClientActiveObject::create(ActiveObjectType type,
-               Client *client, ClientEnvironment *env)
-{
-       // Find factory function
-       auto n = m_types.find(type);
-       if (n == m_types.end()) {
-               // If factory is not found, just return.
-               warningstream << "ClientActiveObject: No factory for type="
-                               << (int)type << std::endl;
-               return NULL;
-       }
-
-       Factory f = n->second;
-       ClientActiveObject *object = (*f)(client, env);
-       return object;
-}
-
-void ClientActiveObject::registerType(u16 type, Factory f)
-{
-       auto n = m_types.find(type);
-       if (n != m_types.end())
-               return;
-       m_types[type] = f;
-}
-
-
diff --git a/src/clientobject.h b/src/clientobject.h
deleted file mode 100644 (file)
index 9377d1e..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include "activeobject.h"
-#include <unordered_map>
-
-class ClientEnvironment;
-class ITextureSource;
-class Client;
-class IGameDef;
-class LocalPlayer;
-struct ItemStack;
-class WieldMeshSceneNode;
-
-class ClientActiveObject : public ActiveObject
-{
-public:
-       ClientActiveObject(u16 id, Client *client, ClientEnvironment *env);
-       virtual ~ClientActiveObject();
-
-       virtual void addToScene(ITextureSource *tsrc) {};
-       virtual void removeFromScene(bool permanent) {}
-       // 0 <= light_at_pos <= LIGHT_SUN
-       virtual void updateLight(u8 light_at_pos){}
-       virtual void updateLightNoCheck(u8 light_at_pos){}
-       virtual v3s16 getLightPosition(){return v3s16(0,0,0);}
-       virtual bool getCollisionBox(aabb3f *toset) const { return false; }
-       virtual bool getSelectionBox(aabb3f *toset) const { return false; }
-       virtual bool collideWithObjects() const { return false; }
-       virtual v3f getPosition(){ return v3f(0,0,0); }
-       virtual float getYaw() const { return 0; }
-       virtual scene::ISceneNode *getSceneNode() { return NULL; }
-       virtual scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() { return NULL; }
-       virtual bool isLocalPlayer() const {return false;}
-       virtual ClientActiveObject *getParent() const { return nullptr; };
-       virtual void setAttachments() {}
-       virtual bool doShowSelectionBox(){return true;}
-
-       // Step object in time
-       virtual void step(float dtime, ClientEnvironment *env){}
-
-       // Process a message sent by the server side object
-       virtual void processMessage(const std::string &data){}
-
-       virtual std::string infoText() {return "";}
-       virtual std::string debugInfoText() {return "";}
-
-       /*
-               This takes the return value of
-               ServerActiveObject::getClientInitializationData
-       */
-       virtual void initialize(const std::string &data){}
-
-       // Create a certain type of ClientActiveObject
-       static ClientActiveObject* create(ActiveObjectType type, Client *client,
-                       ClientEnvironment *env);
-
-       // If returns true, punch will not be sent to the server
-       virtual bool directReportPunch(v3f dir, const ItemStack *punchitem=NULL,
-                       float time_from_last_punch=1000000)
-       { return false; }
-
-protected:
-       // Used for creating objects based on type
-       typedef ClientActiveObject* (*Factory)(Client *client, ClientEnvironment *env);
-       static void registerType(u16 type, Factory f);
-       Client *m_client;
-       ClientEnvironment *m_env;
-private:
-       // Used for creating objects based on type
-       static std::unordered_map<u16, Factory> m_types;
-};
-
-struct DistanceSortedActiveObject
-{
-       ClientActiveObject *obj;
-       f32 d;
-
-       DistanceSortedActiveObject(ClientActiveObject *a_obj, f32 a_d)
-       {
-               obj = a_obj;
-               d = a_d;
-       }
-
-       bool operator < (const DistanceSortedActiveObject &other) const
-       {
-               return d < other.d;
-       }
-};
diff --git a/src/clouds.cpp b/src/clouds.cpp
deleted file mode 100644 (file)
index 13051f3..0000000
+++ /dev/null
@@ -1,386 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 "client/renderingengine.h"
-#include "clouds.h"
-#include "noise.h"
-#include "constants.h"
-#include "debug.h"
-#include "profiler.h"
-#include "settings.h"
-#include <cmath>
-
-
-// Menu clouds are created later
-class Clouds;
-Clouds *g_menuclouds = NULL;
-irr::scene::ISceneManager *g_menucloudsmgr = NULL;
-
-// Constant for now
-static constexpr const float cloud_size = BS * 64.0f;
-
-static void cloud_3d_setting_changed(const std::string &settingname, void *data)
-{
-       ((Clouds *)data)->readSettings();
-}
-
-Clouds::Clouds(scene::ISceneManager* mgr,
-               s32 id,
-               u32 seed
-):
-       scene::ISceneNode(mgr->getRootSceneNode(), mgr, id),
-       m_seed(seed)
-{
-       m_material.setFlag(video::EMF_LIGHTING, false);
-       //m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
-       m_material.setFlag(video::EMF_BACK_FACE_CULLING, true);
-       m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
-       m_material.setFlag(video::EMF_FOG_ENABLE, true);
-       m_material.setFlag(video::EMF_ANTI_ALIASING, true);
-       //m_material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
-       m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
-
-       m_params.height        = 120;
-       m_params.density       = 0.4f;
-       m_params.thickness     = 16.0f;
-       m_params.color_bright  = video::SColor(229, 240, 240, 255);
-       m_params.color_ambient = video::SColor(255, 0, 0, 0);
-       m_params.speed         = v2f(0.0f, -2.0f);
-
-       readSettings();
-       g_settings->registerChangedCallback("enable_3d_clouds",
-               &cloud_3d_setting_changed, this);
-
-       updateBox();
-}
-
-Clouds::~Clouds()
-{
-       g_settings->deregisterChangedCallback("enable_3d_clouds",
-               &cloud_3d_setting_changed, this);
-}
-
-void Clouds::OnRegisterSceneNode()
-{
-       if(IsVisible)
-       {
-               SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT);
-               //SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID);
-       }
-
-       ISceneNode::OnRegisterSceneNode();
-}
-
-void Clouds::render()
-{
-
-       if (m_params.density <= 0.0f)
-               return; // no need to do anything
-
-       video::IVideoDriver* driver = SceneManager->getVideoDriver();
-
-       if(SceneManager->getSceneNodeRenderPass() != scene::ESNRP_TRANSPARENT)
-       //if(SceneManager->getSceneNodeRenderPass() != scene::ESNRP_SOLID)
-               return;
-
-       ScopeProfiler sp(g_profiler, "Rendering of clouds, avg", SPT_AVG);
-
-       int num_faces_to_draw = m_enable_3d ? 6 : 1;
-
-       m_material.setFlag(video::EMF_BACK_FACE_CULLING, m_enable_3d);
-
-       driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
-       driver->setMaterial(m_material);
-
-       /*
-               Clouds move from Z+ towards Z-
-       */
-
-       const float cloud_full_radius = cloud_size * m_cloud_radius_i;
-
-       v2f camera_pos_2d(m_camera_pos.X, m_camera_pos.Z);
-       // Position of cloud noise origin from the camera
-       v2f cloud_origin_from_camera_f = m_origin - camera_pos_2d;
-       // The center point of drawing in the noise
-       v2f center_of_drawing_in_noise_f = -cloud_origin_from_camera_f;
-       // The integer center point of drawing in the noise
-       v2s16 center_of_drawing_in_noise_i(
-               std::floor(center_of_drawing_in_noise_f.X / cloud_size),
-               std::floor(center_of_drawing_in_noise_f.Y / cloud_size)
-       );
-
-       // The world position of the integer center point of drawing in the noise
-       v2f world_center_of_drawing_in_noise_f = v2f(
-               center_of_drawing_in_noise_i.X * cloud_size,
-               center_of_drawing_in_noise_i.Y * cloud_size
-       ) + m_origin;
-
-       /*video::SColor c_top(128,b*240,b*240,b*255);
-       video::SColor c_side_1(128,b*230,b*230,b*255);
-       video::SColor c_side_2(128,b*220,b*220,b*245);
-       video::SColor c_bottom(128,b*205,b*205,b*230);*/
-       video::SColorf c_top_f(m_color);
-       video::SColorf c_side_1_f(m_color);
-       video::SColorf c_side_2_f(m_color);
-       video::SColorf c_bottom_f(m_color);
-       c_side_1_f.r *= 0.95;
-       c_side_1_f.g *= 0.95;
-       c_side_1_f.b *= 0.95;
-       c_side_2_f.r *= 0.90;
-       c_side_2_f.g *= 0.90;
-       c_side_2_f.b *= 0.90;
-       c_bottom_f.r *= 0.80;
-       c_bottom_f.g *= 0.80;
-       c_bottom_f.b *= 0.80;
-       video::SColor c_top = c_top_f.toSColor();
-       video::SColor c_side_1 = c_side_1_f.toSColor();
-       video::SColor c_side_2 = c_side_2_f.toSColor();
-       video::SColor c_bottom = c_bottom_f.toSColor();
-
-       // Get fog parameters for setting them back later
-       video::SColor fog_color(0,0,0,0);
-       video::E_FOG_TYPE fog_type = video::EFT_FOG_LINEAR;
-       f32 fog_start = 0;
-       f32 fog_end = 0;
-       f32 fog_density = 0;
-       bool fog_pixelfog = false;
-       bool fog_rangefog = false;
-       driver->getFog(fog_color, fog_type, fog_start, fog_end, fog_density,
-                       fog_pixelfog, fog_rangefog);
-
-       // Set our own fog
-       driver->setFog(fog_color, fog_type, cloud_full_radius * 0.5,
-                       cloud_full_radius*1.2, fog_density, fog_pixelfog, fog_rangefog);
-
-       // Read noise
-
-       bool *grid = new bool[m_cloud_radius_i * 2 * m_cloud_radius_i * 2];
-
-
-       for(s16 zi = -m_cloud_radius_i; zi < m_cloud_radius_i; zi++) {
-               u32 si = (zi + m_cloud_radius_i) * m_cloud_radius_i * 2 + m_cloud_radius_i;
-
-               for (s16 xi = -m_cloud_radius_i; xi < m_cloud_radius_i; xi++) {
-                       u32 i = si + xi;
-
-                       grid[i] = gridFilled(
-                               xi + center_of_drawing_in_noise_i.X,
-                               zi + center_of_drawing_in_noise_i.Y
-                       );
-               }
-       }
-
-#define GETINDEX(x, z, radius) (((z)+(radius))*(radius)*2 + (x)+(radius))
-#define INAREA(x, z, radius) \
-       ((x) >= -(radius) && (x) < (radius) && (z) >= -(radius) && (z) < (radius))
-
-       for (s16 zi0= -m_cloud_radius_i; zi0 < m_cloud_radius_i; zi0++)
-       for (s16 xi0= -m_cloud_radius_i; xi0 < m_cloud_radius_i; xi0++)
-       {
-               s16 zi = zi0;
-               s16 xi = xi0;
-               // Draw from front to back (needed for transparency)
-               /*if(zi <= 0)
-                       zi = -m_cloud_radius_i - zi;
-               if(xi <= 0)
-                       xi = -m_cloud_radius_i - xi;*/
-               // Draw from back to front
-               if(zi >= 0)
-                       zi = m_cloud_radius_i - zi - 1;
-               if(xi >= 0)
-                       xi = m_cloud_radius_i - xi - 1;
-
-               u32 i = GETINDEX(xi, zi, m_cloud_radius_i);
-
-               if (!grid[i])
-                       continue;
-
-               v2f p0 = v2f(xi,zi)*cloud_size + world_center_of_drawing_in_noise_f;
-
-               video::S3DVertex v[4] = {
-                       video::S3DVertex(0,0,0, 0,0,0, c_top, 0, 1),
-                       video::S3DVertex(0,0,0, 0,0,0, c_top, 1, 1),
-                       video::S3DVertex(0,0,0, 0,0,0, c_top, 1, 0),
-                       video::S3DVertex(0,0,0, 0,0,0, c_top, 0, 0)
-               };
-
-               /*if(zi <= 0 && xi <= 0){
-                       v[0].Color.setBlue(255);
-                       v[1].Color.setBlue(255);
-                       v[2].Color.setBlue(255);
-                       v[3].Color.setBlue(255);
-               }*/
-
-               f32 rx = cloud_size / 2.0f;
-               // if clouds are flat, the top layer should be at the given height
-               f32 ry = m_enable_3d ? m_params.thickness * BS : 0.0f;
-               f32 rz = cloud_size / 2;
-
-               for(int i=0; i<num_faces_to_draw; i++)
-               {
-                       switch(i)
-                       {
-                       case 0: // top
-                               for (video::S3DVertex &vertex : v) {
-                                       vertex.Normal.set(0,1,0);
-                               }
-                               v[0].Pos.set(-rx, ry,-rz);
-                               v[1].Pos.set(-rx, ry, rz);
-                               v[2].Pos.set( rx, ry, rz);
-                               v[3].Pos.set( rx, ry,-rz);
-                               break;
-                       case 1: // back
-                               if (INAREA(xi, zi - 1, m_cloud_radius_i)) {
-                                       u32 j = GETINDEX(xi, zi - 1, m_cloud_radius_i);
-                                       if(grid[j])
-                                               continue;
-                               }
-                               for (video::S3DVertex &vertex : v) {
-                                       vertex.Color = c_side_1;
-                                       vertex.Normal.set(0,0,-1);
-                               }
-                               v[0].Pos.set(-rx, ry,-rz);
-                               v[1].Pos.set( rx, ry,-rz);
-                               v[2].Pos.set( rx,  0,-rz);
-                               v[3].Pos.set(-rx,  0,-rz);
-                               break;
-                       case 2: //right
-                               if (INAREA(xi + 1, zi, m_cloud_radius_i)) {
-                                       u32 j = GETINDEX(xi+1, zi, m_cloud_radius_i);
-                                       if(grid[j])
-                                               continue;
-                               }
-                               for (video::S3DVertex &vertex : v) {
-                                       vertex.Color = c_side_2;
-                                       vertex.Normal.set(1,0,0);
-                               }
-                               v[0].Pos.set( rx, ry,-rz);
-                               v[1].Pos.set( rx, ry, rz);
-                               v[2].Pos.set( rx,  0, rz);
-                               v[3].Pos.set( rx,  0,-rz);
-                               break;
-                       case 3: // front
-                               if (INAREA(xi, zi + 1, m_cloud_radius_i)) {
-                                       u32 j = GETINDEX(xi, zi + 1, m_cloud_radius_i);
-                                       if(grid[j])
-                                               continue;
-                               }
-                               for (video::S3DVertex &vertex : v) {
-                                       vertex.Color = c_side_1;
-                                       vertex.Normal.set(0,0,-1);
-                               }
-                               v[0].Pos.set( rx, ry, rz);
-                               v[1].Pos.set(-rx, ry, rz);
-                               v[2].Pos.set(-rx,  0, rz);
-                               v[3].Pos.set( rx,  0, rz);
-                               break;
-                       case 4: // left
-                               if (INAREA(xi-1, zi, m_cloud_radius_i)) {
-                                       u32 j = GETINDEX(xi-1, zi, m_cloud_radius_i);
-                                       if(grid[j])
-                                               continue;
-                               }
-                               for (video::S3DVertex &vertex : v) {
-                                       vertex.Color = c_side_2;
-                                       vertex.Normal.set(-1,0,0);
-                               }
-                               v[0].Pos.set(-rx, ry, rz);
-                               v[1].Pos.set(-rx, ry,-rz);
-                               v[2].Pos.set(-rx,  0,-rz);
-                               v[3].Pos.set(-rx,  0, rz);
-                               break;
-                       case 5: // bottom
-                               for (video::S3DVertex &vertex : v) {
-                                       vertex.Color = c_bottom;
-                                       vertex.Normal.set(0,-1,0);
-                               }
-                               v[0].Pos.set( rx,  0, rz);
-                               v[1].Pos.set(-rx,  0, rz);
-                               v[2].Pos.set(-rx,  0,-rz);
-                               v[3].Pos.set( rx,  0,-rz);
-                               break;
-                       }
-
-                       v3f pos(p0.X, m_params.height * BS, p0.Y);
-                       pos -= intToFloat(m_camera_offset, BS);
-
-                       for (video::S3DVertex &vertex : v)
-                               vertex.Pos += pos;
-                       u16 indices[] = {0,1,2,2,3,0};
-                       driver->drawVertexPrimitiveList(v, 4, indices, 2,
-                                       video::EVT_STANDARD, scene::EPT_TRIANGLES, video::EIT_16BIT);
-               }
-       }
-
-       delete[] grid;
-
-       // Restore fog settings
-       driver->setFog(fog_color, fog_type, fog_start, fog_end, fog_density,
-                       fog_pixelfog, fog_rangefog);
-}
-
-void Clouds::step(float dtime)
-{
-       m_origin = m_origin + dtime * BS * m_params.speed;
-}
-
-void Clouds::update(const v3f &camera_p, const video::SColorf &color_diffuse)
-{
-       m_camera_pos = camera_p;
-       m_color.r = MYMIN(MYMAX(color_diffuse.r * m_params.color_bright.getRed(),
-                       m_params.color_ambient.getRed()), 255) / 255.0f;
-       m_color.g = MYMIN(MYMAX(color_diffuse.g * m_params.color_bright.getGreen(),
-                       m_params.color_ambient.getGreen()), 255) / 255.0f;
-       m_color.b = MYMIN(MYMAX(color_diffuse.b * m_params.color_bright.getBlue(),
-                       m_params.color_ambient.getBlue()), 255) / 255.0f;
-       m_color.a = m_params.color_bright.getAlpha() / 255.0f;
-
-       // is the camera inside the cloud mesh?
-       m_camera_inside_cloud = false; // default
-       if (m_enable_3d) {
-               float camera_height = camera_p.Y;
-               if (camera_height >= m_box.MinEdge.Y &&
-                               camera_height <= m_box.MaxEdge.Y) {
-                       v2f camera_in_noise;
-                       camera_in_noise.X = floor((camera_p.X - m_origin.X) / cloud_size + 0.5);
-                       camera_in_noise.Y = floor((camera_p.Z - m_origin.Y) / cloud_size + 0.5);
-                       bool filled = gridFilled(camera_in_noise.X, camera_in_noise.Y);
-                       m_camera_inside_cloud = filled;
-               }
-       }
-}
-
-void Clouds::readSettings()
-{
-       m_cloud_radius_i = g_settings->getU16("cloud_radius");
-       m_enable_3d = g_settings->getBool("enable_3d_clouds");
-}
-
-bool Clouds::gridFilled(int x, int y) const
-{
-       float cloud_size_noise = cloud_size / (BS * 200.f);
-       float noise = noise2d_perlin(
-                       (float)x * cloud_size_noise,
-                       (float)y * cloud_size_noise,
-                       m_seed, 3, 0.5);
-       // normalize to 0..1 (given 3 octaves)
-       static constexpr const float noise_bound = 1.0f + 0.5f + 0.25f;
-       float density = noise / noise_bound * 0.5f + 0.5f;
-       return (density < m_params.density);
-}
diff --git a/src/clouds.h b/src/clouds.h
deleted file mode 100644 (file)
index a4d810f..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include <iostream>
-#include "constants.h"
-#include "cloudparams.h"
-
-// Menu clouds
-class Clouds;
-extern Clouds *g_menuclouds;
-
-// Scene manager used for menu clouds
-namespace irr{namespace scene{class ISceneManager;}}
-extern irr::scene::ISceneManager *g_menucloudsmgr;
-
-class Clouds : public scene::ISceneNode
-{
-public:
-       Clouds(scene::ISceneManager* mgr,
-                       s32 id,
-                       u32 seed
-       );
-
-       ~Clouds();
-
-       /*
-               ISceneNode methods
-       */
-
-       virtual void OnRegisterSceneNode();
-
-       virtual void render();
-
-       virtual const aabb3f &getBoundingBox() const
-       {
-               return m_box;
-       }
-
-       virtual u32 getMaterialCount() const
-       {
-               return 1;
-       }
-
-       virtual video::SMaterial& getMaterial(u32 i)
-       {
-               return m_material;
-       }
-
-       /*
-               Other stuff
-       */
-
-       void step(float dtime);
-
-       void update(const v3f &camera_p, const video::SColorf &color);
-
-       void updateCameraOffset(const v3s16 &camera_offset)
-       {
-               m_camera_offset = camera_offset;
-               updateBox();
-       }
-
-       void readSettings();
-
-       void setDensity(float density)
-       {
-               m_params.density = density;
-               // currently does not need bounding
-       }
-
-       void setColorBright(const video::SColor &color_bright)
-       {
-               m_params.color_bright = color_bright;
-       }
-
-       void setColorAmbient(const video::SColor &color_ambient)
-       {
-               m_params.color_ambient = color_ambient;
-       }
-
-       void setHeight(float height)
-       {
-               m_params.height = height; // add bounding when necessary
-               updateBox();
-       }
-
-       void setSpeed(v2f speed)
-       {
-               m_params.speed = speed;
-       }
-
-       void setThickness(float thickness)
-       {
-               m_params.thickness = thickness;
-               updateBox();
-       }
-
-       bool isCameraInsideCloud() const { return m_camera_inside_cloud; }
-
-       const video::SColor getColor() const { return m_color.toSColor(); }
-
-private:
-       void updateBox()
-       {
-               float height_bs    = m_params.height    * BS;
-               float thickness_bs = m_params.thickness * BS;
-               m_box = aabb3f(-BS * 1000000.0f, height_bs - BS * m_camera_offset.Y, -BS * 1000000.0f,
-                               BS * 1000000.0f, height_bs + thickness_bs - BS * m_camera_offset.Y, BS * 1000000.0f);
-       }
-
-       bool gridFilled(int x, int y) const;
-
-       video::SMaterial m_material;
-       aabb3f m_box;
-       u16 m_cloud_radius_i;
-       bool m_enable_3d;
-       u32 m_seed;
-       v3f m_camera_pos;
-       v2f m_origin;
-       v3s16 m_camera_offset;
-       video::SColorf m_color = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f);
-       CloudParams m_params;
-       bool m_camera_inside_cloud = false;
-
-};
index 9626221a0c973104f2ae650953962030a3512f00..a07899e652c7ec0d976ca79c30ab3923976a9f4e 100644 (file)
@@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "nodedef.h"
 #include "gamedef.h"
 #ifndef SERVER
-#include "clientenvironment.h"
+#include "client/clientenvironment.h"
 #endif
 #include "serverenvironment.h"
 #include "serverobject.h"
diff --git a/src/content_cao.cpp b/src/content_cao.cpp
deleted file mode 100644 (file)
index db59ae5..0000000
+++ /dev/null
@@ -1,1611 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 <ICameraSceneNode.h>
-#include <ITextSceneNode.h>
-#include <IBillboardSceneNode.h>
-#include <IMeshManipulator.h>
-#include <IAnimatedMeshSceneNode.h>
-#include "content_cao.h"
-#include "util/numeric.h" // For IntervalLimiter
-#include "util/serialize.h"
-#include "util/basic_macros.h"
-#include "client/sound.h"
-#include "client/tile.h"
-#include "environment.h"
-#include "collision.h"
-#include "settings.h"
-#include "serialization.h" // For decompressZlib
-#include "clientobject.h"
-#include "mesh.h"
-#include "itemdef.h"
-#include "tool.h"
-#include "content_cso.h"
-#include "sound.h"
-#include "nodedef.h"
-#include "localplayer.h"
-#include "map.h"
-#include "camera.h" // CameraModes
-#include "client.h"
-#include "wieldmesh.h"
-#include <algorithm>
-#include <cmath>
-#include "client/renderingengine.h"
-
-class Settings;
-struct ToolCapabilities;
-
-std::unordered_map<u16, ClientActiveObject::Factory> ClientActiveObject::m_types;
-
-template<typename T>
-void SmoothTranslator<T>::init(T current)
-{
-       val_old = current;
-       val_current = current;
-       val_target = current;
-       anim_time = 0;
-       anim_time_counter = 0;
-       aim_is_end = true;
-}
-
-template<typename T>
-void SmoothTranslator<T>::update(T new_target, bool is_end_position, float update_interval)
-{
-       aim_is_end = is_end_position;
-       val_old = val_current;
-       val_target = new_target;
-       if (update_interval > 0) {
-               anim_time = update_interval;
-       } else {
-               if (anim_time < 0.001 || anim_time > 1.0)
-                       anim_time = anim_time_counter;
-               else
-                       anim_time = anim_time * 0.9 + anim_time_counter * 0.1;
-       }
-       anim_time_counter = 0;
-}
-
-template<typename T>
-void SmoothTranslator<T>::translate(f32 dtime)
-{
-       anim_time_counter = anim_time_counter + dtime;
-       T val_diff = val_target - val_old;
-       f32 moveratio = 1.0;
-       if (anim_time > 0.001)
-               moveratio = anim_time_counter / anim_time;
-       f32 move_end = aim_is_end ? 1.0 : 1.5;
-
-       // Move a bit less than should, to avoid oscillation
-       moveratio = std::min(moveratio * 0.8f, move_end);
-       val_current = val_old + val_diff * moveratio;
-}
-
-void SmoothTranslatorWrapped::translate(f32 dtime)
-{
-       anim_time_counter = anim_time_counter + dtime;
-       f32 val_diff = std::abs(val_target - val_old);
-       if (val_diff > 180.f)
-               val_diff = 360.f - val_diff;
-
-       f32 moveratio = 1.0;
-       if (anim_time > 0.001)
-               moveratio = anim_time_counter / anim_time;
-       f32 move_end = aim_is_end ? 1.0 : 1.5;
-
-       // Move a bit less than should, to avoid oscillation
-       moveratio = std::min(moveratio * 0.8f, move_end);
-       wrappedApproachShortest(val_current, val_target,
-               val_diff * moveratio, 360.f);
-}
-
-void SmoothTranslatorWrappedv3f::translate(f32 dtime)
-{
-       anim_time_counter = anim_time_counter + dtime;
-
-       v3f val_diff_v3f;
-       val_diff_v3f.X = std::abs(val_target.X - val_old.X);
-       val_diff_v3f.Y = std::abs(val_target.Y - val_old.Y);
-       val_diff_v3f.Z = std::abs(val_target.Z - val_old.Z);
-
-       if (val_diff_v3f.X > 180.f)
-               val_diff_v3f.X = 360.f - val_diff_v3f.X;
-
-       if (val_diff_v3f.Y > 180.f)
-               val_diff_v3f.Y = 360.f - val_diff_v3f.Y;
-
-       if (val_diff_v3f.Z > 180.f)
-               val_diff_v3f.Z = 360.f - val_diff_v3f.Z;
-
-       f32 moveratio = 1.0;
-       if (anim_time > 0.001)
-               moveratio = anim_time_counter / anim_time;
-       f32 move_end = aim_is_end ? 1.0 : 1.5;
-
-       // Move a bit less than should, to avoid oscillation
-       moveratio = std::min(moveratio * 0.8f, move_end);
-       wrappedApproachShortest(val_current.X, val_target.X,
-               val_diff_v3f.X * moveratio, 360.f);
-
-       wrappedApproachShortest(val_current.Y, val_target.Y,
-               val_diff_v3f.Y * moveratio, 360.f);
-
-       wrappedApproachShortest(val_current.Z, val_target.Z,
-               val_diff_v3f.Z * moveratio, 360.f);
-}
-
-/*
-       Other stuff
-*/
-
-static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill,
-               float txs, float tys, int col, int row)
-{
-       video::SMaterial& material = bill->getMaterial(0);
-       core::matrix4& matrix = material.getTextureMatrix(0);
-       matrix.setTextureTranslate(txs*col, tys*row);
-       matrix.setTextureScale(txs, tys);
-}
-
-/*
-       TestCAO
-*/
-
-class TestCAO : public ClientActiveObject
-{
-public:
-       TestCAO(Client *client, ClientEnvironment *env);
-       virtual ~TestCAO() = default;
-
-       ActiveObjectType getType() const
-       {
-               return ACTIVEOBJECT_TYPE_TEST;
-       }
-
-       static ClientActiveObject* create(Client *client, ClientEnvironment *env);
-
-       void addToScene(ITextureSource *tsrc);
-       void removeFromScene(bool permanent);
-       void updateLight(u8 light_at_pos);
-       v3s16 getLightPosition();
-       void updateNodePos();
-
-       void step(float dtime, ClientEnvironment *env);
-
-       void processMessage(const std::string &data);
-
-       bool getCollisionBox(aabb3f *toset) const { return false; }
-private:
-       scene::IMeshSceneNode *m_node;
-       v3f m_position;
-};
-
-// Prototype
-TestCAO proto_TestCAO(NULL, NULL);
-
-TestCAO::TestCAO(Client *client, ClientEnvironment *env):
-       ClientActiveObject(0, client, env),
-       m_node(NULL),
-       m_position(v3f(0,10*BS,0))
-{
-       ClientActiveObject::registerType(getType(), create);
-}
-
-ClientActiveObject* TestCAO::create(Client *client, ClientEnvironment *env)
-{
-       return new TestCAO(client, env);
-}
-
-void TestCAO::addToScene(ITextureSource *tsrc)
-{
-       if(m_node != NULL)
-               return;
-
-       //video::IVideoDriver* driver = smgr->getVideoDriver();
-
-       scene::SMesh *mesh = new scene::SMesh();
-       scene::IMeshBuffer *buf = new scene::SMeshBuffer();
-       video::SColor c(255,255,255,255);
-       video::S3DVertex vertices[4] =
-       {
-               video::S3DVertex(-BS/2,-BS/4,0, 0,0,0, c, 0,1),
-               video::S3DVertex(BS/2,-BS/4,0, 0,0,0, c, 1,1),
-               video::S3DVertex(BS/2,BS/4,0, 0,0,0, c, 1,0),
-               video::S3DVertex(-BS/2,BS/4,0, 0,0,0, c, 0,0),
-       };
-       u16 indices[] = {0,1,2,2,3,0};
-       buf->append(vertices, 4, indices, 6);
-       // Set material
-       buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
-       buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false);
-       buf->getMaterial().setTexture(0, tsrc->getTextureForMesh("rat.png"));
-       buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
-       buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true);
-       buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
-       // Add to mesh
-       mesh->addMeshBuffer(buf);
-       buf->drop();
-       m_node = RenderingEngine::get_scene_manager()->addMeshSceneNode(mesh, NULL);
-       mesh->drop();
-       updateNodePos();
-}
-
-void TestCAO::removeFromScene(bool permanent)
-{
-       if (!m_node)
-               return;
-
-       m_node->remove();
-       m_node = NULL;
-}
-
-void TestCAO::updateLight(u8 light_at_pos)
-{
-}
-
-v3s16 TestCAO::getLightPosition()
-{
-       return floatToInt(m_position, BS);
-}
-
-void TestCAO::updateNodePos()
-{
-       if (!m_node)
-               return;
-
-       m_node->setPosition(m_position);
-       //m_node->setRotation(v3f(0, 45, 0));
-}
-
-void TestCAO::step(float dtime, ClientEnvironment *env)
-{
-       if(m_node)
-       {
-               v3f rot = m_node->getRotation();
-               //infostream<<"dtime="<<dtime<<", rot.Y="<<rot.Y<<std::endl;
-               rot.Y += dtime * 180;
-               m_node->setRotation(rot);
-       }
-}
-
-void TestCAO::processMessage(const std::string &data)
-{
-       infostream<<"TestCAO: Got data: "<<data<<std::endl;
-       std::istringstream is(data, std::ios::binary);
-       u16 cmd;
-       is>>cmd;
-       if(cmd == 0)
-       {
-               v3f newpos;
-               is>>newpos.X;
-               is>>newpos.Y;
-               is>>newpos.Z;
-               m_position = newpos;
-               updateNodePos();
-       }
-}
-
-/*
-       GenericCAO
-*/
-
-#include "genericobject.h"
-
-GenericCAO::GenericCAO(Client *client, ClientEnvironment *env):
-               ClientActiveObject(0, client, env)
-{
-       if (client == NULL) {
-               ClientActiveObject::registerType(getType(), create);
-       } else {
-               m_client = client;
-       }
-}
-
-bool GenericCAO::getCollisionBox(aabb3f *toset) const
-{
-       if (m_prop.physical)
-       {
-               //update collision box
-               toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
-               toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
-
-               toset->MinEdge += m_position;
-               toset->MaxEdge += m_position;
-
-               return true;
-       }
-
-       return false;
-}
-
-bool GenericCAO::collideWithObjects() const
-{
-       return m_prop.collideWithObjects;
-}
-
-void GenericCAO::initialize(const std::string &data)
-{
-       infostream<<"GenericCAO: Got init data"<<std::endl;
-       processInitData(data);
-
-       if (m_is_player) {
-               // Check if it's the current player
-               LocalPlayer *player = m_env->getLocalPlayer();
-               if (player && strcmp(player->getName(), m_name.c_str()) == 0) {
-                       m_is_local_player = true;
-                       m_is_visible = false;
-                       player->setCAO(this);
-               }
-       }
-}
-
-void GenericCAO::processInitData(const std::string &data)
-{
-       std::istringstream is(data, std::ios::binary);
-       int num_messages = 0;
-       // version
-       u8 version = readU8(is);
-       // check version
-       if (version == 1) { // In PROTOCOL_VERSION 14
-               m_name = deSerializeString(is);
-               m_is_player = readU8(is);
-               m_id = readU16(is);
-               m_position = readV3F1000(is);
-               m_rotation = readV3F1000(is);
-               m_hp = readS16(is);
-               num_messages = readU8(is);
-       } else {
-               errorstream<<"GenericCAO: Unsupported init data version"
-                               <<std::endl;
-               return;
-       }
-
-       for (int i = 0; i < num_messages; i++) {
-               std::string message = deSerializeLongString(is);
-               processMessage(message);
-       }
-
-       m_rotation = wrapDegrees_0_360_v3f(m_rotation);
-       pos_translator.init(m_position);
-       rot_translator.init(m_rotation);
-       updateNodePos();
-}
-
-GenericCAO::~GenericCAO()
-{
-       removeFromScene(true);
-}
-
-bool GenericCAO::getSelectionBox(aabb3f *toset) const
-{
-       if (!m_prop.is_visible || !m_is_visible || m_is_local_player
-                       || !m_prop.pointable) {
-               return false;
-       }
-       *toset = m_selection_box;
-       return true;
-}
-
-v3f GenericCAO::getPosition()
-{
-       if (getParent() != NULL) {
-               scene::ISceneNode *node = getSceneNode();
-               if (node)
-                       return node->getAbsolutePosition();
-
-               return m_position;
-       }
-       return pos_translator.val_current;
-}
-
-const bool GenericCAO::isImmortal()
-{
-       return itemgroup_get(getGroups(), "immortal");
-}
-
-scene::ISceneNode* GenericCAO::getSceneNode()
-{
-       if (m_meshnode) {
-               return m_meshnode;
-       }
-
-       if (m_animated_meshnode) {
-               return m_animated_meshnode;
-       }
-
-       if (m_wield_meshnode) {
-               return m_wield_meshnode;
-       }
-
-       if (m_spritenode) {
-               return m_spritenode;
-       }
-       return NULL;
-}
-
-scene::IAnimatedMeshSceneNode* GenericCAO::getAnimatedMeshSceneNode()
-{
-       return m_animated_meshnode;
-}
-
-void GenericCAO::setChildrenVisible(bool toset)
-{
-       for (u16 cao_id : m_children) {
-               GenericCAO *obj = m_env->getGenericCAO(cao_id);
-               if (obj) {
-                       obj->setVisible(toset);
-               }
-       }
-}
-
-void GenericCAO::setAttachments()
-{
-       updateAttachments();
-}
-
-ClientActiveObject* GenericCAO::getParent() const
-{
-       ClientActiveObject *obj = NULL;
-
-       u16 attached_id = m_env->attachement_parent_ids[getId()];
-
-       if ((attached_id != 0) &&
-                       (attached_id != getId())) {
-               obj = m_env->getActiveObject(attached_id);
-       }
-       return obj;
-}
-
-void GenericCAO::removeFromScene(bool permanent)
-{
-       // Should be true when removing the object permanently and false when refreshing (eg: updating visuals)
-       if((m_env != NULL) && (permanent))
-       {
-               for (u16 ci : m_children) {
-                       if (m_env->attachement_parent_ids[ci] == getId()) {
-                               m_env->attachement_parent_ids[ci] = 0;
-                       }
-               }
-               m_children.clear();
-
-               m_env->attachement_parent_ids[getId()] = 0;
-
-               LocalPlayer* player = m_env->getLocalPlayer();
-               if (this == player->parent) {
-                       player->parent = NULL;
-                       player->isAttached = false;
-               }
-       }
-
-       if (m_meshnode) {
-               m_meshnode->remove();
-               m_meshnode->drop();
-               m_meshnode = NULL;
-       } else if (m_animated_meshnode) {
-               m_animated_meshnode->remove();
-               m_animated_meshnode->drop();
-               m_animated_meshnode = NULL;
-       } else if (m_wield_meshnode) {
-               m_wield_meshnode->remove();
-               m_wield_meshnode->drop();
-               m_wield_meshnode = NULL;
-       } else if (m_spritenode) {
-               m_spritenode->remove();
-               m_spritenode->drop();
-               m_spritenode = NULL;
-       }
-
-       if (m_nametag) {
-               m_client->getCamera()->removeNametag(m_nametag);
-               m_nametag = NULL;
-       }
-}
-
-void GenericCAO::addToScene(ITextureSource *tsrc)
-{
-       m_smgr = RenderingEngine::get_scene_manager();
-
-       if (getSceneNode() != NULL) {
-               return;
-       }
-
-       m_visuals_expired = false;
-
-       if (!m_prop.is_visible) {
-               return;
-       }
-
-       video::E_MATERIAL_TYPE material_type = (m_prop.use_texture_alpha) ?
-               video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
-
-       if (m_prop.visual == "sprite") {
-               infostream<<"GenericCAO::addToScene(): single_sprite"<<std::endl;
-               m_spritenode = RenderingEngine::get_scene_manager()->addBillboardSceneNode(
-                               NULL, v2f(1, 1), v3f(0,0,0), -1);
-               m_spritenode->grab();
-               m_spritenode->setMaterialTexture(0,
-                               tsrc->getTextureForMesh("unknown_node.png"));
-               m_spritenode->setMaterialFlag(video::EMF_LIGHTING, false);
-               m_spritenode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
-               m_spritenode->setMaterialType(material_type);
-               m_spritenode->setMaterialFlag(video::EMF_FOG_ENABLE, true);
-               u8 li = m_last_light;
-               m_spritenode->setColor(video::SColor(255,li,li,li));
-               m_spritenode->setSize(m_prop.visual_size*BS);
-               {
-                       const float txs = 1.0 / 1;
-                       const float tys = 1.0 / 1;
-                       setBillboardTextureMatrix(m_spritenode,
-                                       txs, tys, 0, 0);
-               }
-       } else if (m_prop.visual == "upright_sprite") {
-               scene::SMesh *mesh = new scene::SMesh();
-               double dx = BS * m_prop.visual_size.X / 2;
-               double dy = BS * m_prop.visual_size.Y / 2;
-               u8 li = m_last_light;
-               video::SColor c(255, li, li, li);
-
-               { // Front
-                       scene::IMeshBuffer *buf = new scene::SMeshBuffer();
-                       video::S3DVertex vertices[4] = {
-                               video::S3DVertex(-dx, -dy, 0, 0,0,0, c, 1,1),
-                               video::S3DVertex( dx, -dy, 0, 0,0,0, c, 0,1),
-                               video::S3DVertex( dx,  dy, 0, 0,0,0, c, 0,0),
-                               video::S3DVertex(-dx,  dy, 0, 0,0,0, c, 1,0),
-                       };
-                       if (m_is_player) {
-                               // Move minimal Y position to 0 (feet position)
-                               for (video::S3DVertex &vertex : vertices)
-                                       vertex.Pos.Y += dy;
-                       }
-                       u16 indices[] = {0,1,2,2,3,0};
-                       buf->append(vertices, 4, indices, 6);
-                       // Set material
-                       buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
-                       buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
-                       buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true);
-                       buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
-                       // Add to mesh
-                       mesh->addMeshBuffer(buf);
-                       buf->drop();
-               }
-               { // Back
-                       scene::IMeshBuffer *buf = new scene::SMeshBuffer();
-                       video::S3DVertex vertices[4] = {
-                               video::S3DVertex( dx,-dy, 0, 0,0,0, c, 1,1),
-                               video::S3DVertex(-dx,-dy, 0, 0,0,0, c, 0,1),
-                               video::S3DVertex(-dx, dy, 0, 0,0,0, c, 0,0),
-                               video::S3DVertex( dx, dy, 0, 0,0,0, c, 1,0),
-                       };
-                       if (m_is_player) {
-                               // Move minimal Y position to 0 (feet position)
-                               for (video::S3DVertex &vertex : vertices)
-                                       vertex.Pos.Y += dy;
-                       }
-                       u16 indices[] = {0,1,2,2,3,0};
-                       buf->append(vertices, 4, indices, 6);
-                       // Set material
-                       buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
-                       buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
-                       buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true);
-                       buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
-                       // Add to mesh
-                       mesh->addMeshBuffer(buf);
-                       buf->drop();
-               }
-               m_meshnode = RenderingEngine::get_scene_manager()->addMeshSceneNode(mesh, NULL);
-               m_meshnode->grab();
-               mesh->drop();
-               // Set it to use the materials of the meshbuffers directly.
-               // This is needed for changing the texture in the future
-               m_meshnode->setReadOnlyMaterials(true);
-       }
-       else if(m_prop.visual == "cube") {
-               infostream<<"GenericCAO::addToScene(): cube"<<std::endl;
-               scene::IMesh *mesh = createCubeMesh(v3f(BS,BS,BS));
-               m_meshnode = RenderingEngine::get_scene_manager()->addMeshSceneNode(mesh, NULL);
-               m_meshnode->grab();
-               mesh->drop();
-
-               m_meshnode->setScale(v3f(m_prop.visual_size.X,
-                               m_prop.visual_size.Y,
-                               m_prop.visual_size.X));
-               u8 li = m_last_light;
-               setMeshColor(m_meshnode->getMesh(), video::SColor(255,li,li,li));
-
-               m_meshnode->setMaterialFlag(video::EMF_LIGHTING, false);
-               m_meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
-               m_meshnode->setMaterialType(material_type);
-               m_meshnode->setMaterialFlag(video::EMF_FOG_ENABLE, true);
-       }
-       else if(m_prop.visual == "mesh") {
-               infostream<<"GenericCAO::addToScene(): mesh"<<std::endl;
-               scene::IAnimatedMesh *mesh = m_client->getMesh(m_prop.mesh, true);
-               if(mesh)
-               {
-                       m_animated_meshnode = RenderingEngine::get_scene_manager()->
-                               addAnimatedMeshSceneNode(mesh, NULL);
-                       m_animated_meshnode->grab();
-                       mesh->drop(); // The scene node took hold of it
-                       m_animated_meshnode->animateJoints(); // Needed for some animations
-                       m_animated_meshnode->setScale(v3f(m_prop.visual_size.X,
-                                       m_prop.visual_size.Y,
-                                       m_prop.visual_size.X));
-                       u8 li = m_last_light;
-
-                       // set vertex colors to ensure alpha is set
-                       setMeshColor(m_animated_meshnode->getMesh(), video::SColor(255,li,li,li));
-
-                       setAnimatedMeshColor(m_animated_meshnode, video::SColor(255,li,li,li));
-
-                       m_animated_meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
-                       m_animated_meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
-                       m_animated_meshnode->setMaterialType(material_type);
-                       m_animated_meshnode->setMaterialFlag(video::EMF_FOG_ENABLE, true);
-                       m_animated_meshnode->setMaterialFlag(video::EMF_BACK_FACE_CULLING,
-                               m_prop.backface_culling);
-               }
-               else
-                       errorstream<<"GenericCAO::addToScene(): Could not load mesh "<<m_prop.mesh<<std::endl;
-       } else if (m_prop.visual == "wielditem") {
-               ItemStack item;
-               infostream << "GenericCAO::addToScene(): wielditem" << std::endl;
-               if (m_prop.wield_item.empty()) {
-                       // Old format, only textures are specified.
-                       infostream << "textures: " << m_prop.textures.size() << std::endl;
-                       if (!m_prop.textures.empty()) {
-                               infostream << "textures[0]: " << m_prop.textures[0]
-                                       << std::endl;
-                               IItemDefManager *idef = m_client->idef();
-                               item = ItemStack(m_prop.textures[0], 1, 0, idef);
-                       }
-               } else {
-                       infostream << "serialized form: " << m_prop.wield_item << std::endl;
-                       item.deSerialize(m_prop.wield_item, m_client->idef());
-               }
-               m_wield_meshnode = new WieldMeshSceneNode(
-                       RenderingEngine::get_scene_manager(), -1);
-               m_wield_meshnode->setItem(item, m_client);
-
-               m_wield_meshnode->setScale(
-                       v3f(m_prop.visual_size.X / 2, m_prop.visual_size.Y / 2,
-                               m_prop.visual_size.X / 2));
-               u8 li = m_last_light;
-               m_wield_meshnode->setColor(video::SColor(255, li, li, li));
-       } else {
-               infostream<<"GenericCAO::addToScene(): \""<<m_prop.visual
-                               <<"\" not supported"<<std::endl;
-       }
-
-       /* don't update while punch texture modifier is active */
-       if (m_reset_textures_timer < 0)
-               updateTextures(m_current_texture_modifier);
-
-       scene::ISceneNode *node = getSceneNode();
-       if (node && !m_prop.nametag.empty() && !m_is_local_player) {
-               // Add nametag
-               v3f pos;
-               pos.Y = m_prop.selectionbox.MaxEdge.Y + 0.3f;
-               m_nametag = m_client->getCamera()->addNametag(node,
-                       m_prop.nametag, m_prop.nametag_color,
-                       pos);
-       }
-
-       updateNodePos();
-       updateAnimation();
-       updateBonePosition();
-       updateAttachments();
-}
-
-void GenericCAO::updateLight(u8 light_at_pos)
-{
-       // Don't update light of attached one
-       if (getParent() != NULL) {
-               return;
-       }
-
-       updateLightNoCheck(light_at_pos);
-
-       // Update light of all children
-       for (u16 i : m_children) {
-               ClientActiveObject *obj = m_env->getActiveObject(i);
-               if (obj) {
-                       obj->updateLightNoCheck(light_at_pos);
-               }
-       }
-}
-
-void GenericCAO::updateLightNoCheck(u8 light_at_pos)
-{
-       if (m_glow < 0)
-               return;
-
-       u8 li = decode_light(light_at_pos + m_glow);
-       if (li != m_last_light) {
-               m_last_light = li;
-               video::SColor color(255,li,li,li);
-               if (m_meshnode) {
-                       setMeshColor(m_meshnode->getMesh(), color);
-               } else if (m_animated_meshnode) {
-                       setAnimatedMeshColor(m_animated_meshnode, color);
-               } else if (m_wield_meshnode) {
-                       m_wield_meshnode->setColor(color);
-               } else if (m_spritenode) {
-                       m_spritenode->setColor(color);
-               }
-       }
-}
-
-v3s16 GenericCAO::getLightPosition()
-{
-       if (m_is_player)
-               return floatToInt(m_position + v3f(0, 0.5 * BS, 0), BS);
-
-       return floatToInt(m_position, BS);
-}
-
-void GenericCAO::updateNodePos()
-{
-       if (getParent() != NULL)
-               return;
-
-       scene::ISceneNode *node = getSceneNode();
-
-       if (node) {
-               v3s16 camera_offset = m_env->getCameraOffset();
-               node->setPosition(pos_translator.val_current - intToFloat(camera_offset, BS));
-               if (node != m_spritenode) { // rotate if not a sprite
-                       v3f rot = m_is_local_player ? -m_rotation : -rot_translator.val_current;
-                       node->setRotation(rot);
-               }
-       }
-}
-
-void GenericCAO::step(float dtime, ClientEnvironment *env)
-{
-       // Handel model of local player instantly to prevent lags
-       if (m_is_local_player) {
-               LocalPlayer *player = m_env->getLocalPlayer();
-               if (m_is_visible) {
-                       int old_anim = player->last_animation;
-                       float old_anim_speed = player->last_animation_speed;
-                       m_position = player->getPosition();
-                       m_rotation.Y = wrapDegrees_0_360(player->getYaw());
-                       m_velocity = v3f(0,0,0);
-                       m_acceleration = v3f(0,0,0);
-                       pos_translator.val_current = m_position;
-                       rot_translator.val_current = m_rotation;
-                       const PlayerControl &controls = player->getPlayerControl();
-
-                       bool walking = false;
-                       if (controls.up || controls.down || controls.left || controls.right ||
-                                       controls.forw_move_joystick_axis != 0.f ||
-                                       controls.sidew_move_joystick_axis != 0.f)
-                               walking = true;
-
-                       f32 new_speed = player->local_animation_speed;
-                       v2s32 new_anim = v2s32(0,0);
-                       bool allow_update = false;
-
-                       // increase speed if using fast or flying fast
-                       if((g_settings->getBool("fast_move") &&
-                                       m_client->checkLocalPrivilege("fast")) &&
-                                       (controls.aux1 ||
-                                       (!player->touching_ground &&
-                                       g_settings->getBool("free_move") &&
-                                       m_client->checkLocalPrivilege("fly"))))
-                                       new_speed *= 1.5;
-                       // slowdown speed if sneeking
-                       if (controls.sneak && walking)
-                               new_speed /= 2;
-
-                       if (walking && (controls.LMB || controls.RMB)) {
-                               new_anim = player->local_animations[3];
-                               player->last_animation = WD_ANIM;
-                       } else if(walking) {
-                               new_anim = player->local_animations[1];
-                               player->last_animation = WALK_ANIM;
-                       } else if(controls.LMB || controls.RMB) {
-                               new_anim = player->local_animations[2];
-                               player->last_animation = DIG_ANIM;
-                       }
-
-                       // Apply animations if input detected and not attached
-                       // or set idle animation
-                       if ((new_anim.X + new_anim.Y) > 0 && !player->isAttached) {
-                               allow_update = true;
-                               m_animation_range = new_anim;
-                               m_animation_speed = new_speed;
-                               player->last_animation_speed = m_animation_speed;
-                       } else {
-                               player->last_animation = NO_ANIM;
-
-                               if (old_anim != NO_ANIM) {
-                                       m_animation_range = player->local_animations[0];
-                                       updateAnimation();
-                               }
-                       }
-
-                       // Update local player animations
-                       if ((player->last_animation != old_anim ||
-                               m_animation_speed != old_anim_speed) &&
-                               player->last_animation != NO_ANIM && allow_update)
-                                       updateAnimation();
-
-               }
-       }
-
-       if (m_visuals_expired && m_smgr) {
-               m_visuals_expired = false;
-
-               // Attachments, part 1: All attached objects must be unparented first,
-               // or Irrlicht causes a segmentation fault
-               for (auto ci = m_children.begin(); ci != m_children.end();) {
-                       if (m_env->attachement_parent_ids[*ci] != getId()) {
-                               ci = m_children.erase(ci);
-                               continue;
-                       }
-                       ClientActiveObject *obj = m_env->getActiveObject(*ci);
-                       if (obj) {
-                               scene::ISceneNode *child_node = obj->getSceneNode();
-                               if (child_node)
-                                       child_node->setParent(m_smgr->getRootSceneNode());
-                       }
-                       ++ci;
-               }
-
-               removeFromScene(false);
-               addToScene(m_client->tsrc());
-
-               // Attachments, part 2: Now that the parent has been refreshed, put its attachments back
-               for (u16 cao_id : m_children) {
-                       // Get the object of the child
-                       ClientActiveObject *obj = m_env->getActiveObject(cao_id);
-                       if (obj)
-                               obj->setAttachments();
-               }
-       }
-
-       // Make sure m_is_visible is always applied
-       scene::ISceneNode *node = getSceneNode();
-       if (node)
-               node->setVisible(m_is_visible);
-
-       if(getParent() != NULL) // Attachments should be glued to their parent by Irrlicht
-       {
-               // Set these for later
-               m_position = getPosition();
-               m_velocity = v3f(0,0,0);
-               m_acceleration = v3f(0,0,0);
-               pos_translator.val_current = m_position;
-
-               if(m_is_local_player) // Update local player attachment position
-               {
-                       LocalPlayer *player = m_env->getLocalPlayer();
-                       player->overridePosition = getParent()->getPosition();
-                       m_env->getLocalPlayer()->parent = getParent();
-               }
-       } else {
-               rot_translator.translate(dtime);
-               v3f lastpos = pos_translator.val_current;
-
-               if(m_prop.physical)
-               {
-                       aabb3f box = m_prop.collisionbox;
-                       box.MinEdge *= BS;
-                       box.MaxEdge *= BS;
-                       collisionMoveResult moveresult;
-                       f32 pos_max_d = BS*0.125; // Distance per iteration
-                       v3f p_pos = m_position;
-                       v3f p_velocity = m_velocity;
-                       moveresult = collisionMoveSimple(env,env->getGameDef(),
-                                       pos_max_d, box, m_prop.stepheight, dtime,
-                                       &p_pos, &p_velocity, m_acceleration,
-                                       this, m_prop.collideWithObjects);
-                       // Apply results
-                       m_position = p_pos;
-                       m_velocity = p_velocity;
-
-                       bool is_end_position = moveresult.collides;
-                       pos_translator.update(m_position, is_end_position, dtime);
-                       pos_translator.translate(dtime);
-                       updateNodePos();
-               } else {
-                       m_position += dtime * m_velocity + 0.5 * dtime * dtime * m_acceleration;
-                       m_velocity += dtime * m_acceleration;
-                       pos_translator.update(m_position, pos_translator.aim_is_end,
-                                       pos_translator.anim_time);
-                       pos_translator.translate(dtime);
-                       updateNodePos();
-               }
-
-               float moved = lastpos.getDistanceFrom(pos_translator.val_current);
-               m_step_distance_counter += moved;
-               if (m_step_distance_counter > 1.5f * BS) {
-                       m_step_distance_counter = 0.0f;
-                       if (!m_is_local_player && m_prop.makes_footstep_sound) {
-                               const NodeDefManager *ndef = m_client->ndef();
-                               v3s16 p = floatToInt(getPosition() +
-                                       v3f(0.0f, (m_prop.collisionbox.MinEdge.Y - 0.5f) * BS, 0.0f), BS);
-                               MapNode n = m_env->getMap().getNodeNoEx(p);
-                               SimpleSoundSpec spec = ndef->get(n).sound_footstep;
-                               // Reduce footstep gain, as non-local-player footsteps are
-                               // somehow louder.
-                               spec.gain *= 0.6f;
-                               m_client->sound()->playSoundAt(spec, false, getPosition());
-                       }
-               }
-       }
-
-       m_anim_timer += dtime;
-       if(m_anim_timer >= m_anim_framelength)
-       {
-               m_anim_timer -= m_anim_framelength;
-               m_anim_frame++;
-               if(m_anim_frame >= m_anim_num_frames)
-                       m_anim_frame = 0;
-       }
-
-       updateTexturePos();
-
-       if(m_reset_textures_timer >= 0)
-       {
-               m_reset_textures_timer -= dtime;
-               if(m_reset_textures_timer <= 0) {
-                       m_reset_textures_timer = -1;
-                       updateTextures(m_previous_texture_modifier);
-               }
-       }
-       if (!getParent() && std::fabs(m_prop.automatic_rotate) > 0.001) {
-               m_rotation.Y += dtime * m_prop.automatic_rotate * 180 / M_PI;
-               rot_translator.val_current = m_rotation;
-               updateNodePos();
-       }
-
-       if (!getParent() && m_prop.automatic_face_movement_dir &&
-                       (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)) {
-
-               float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI
-                               + m_prop.automatic_face_movement_dir_offset;
-               float max_rotation_delta =
-                               dtime * m_prop.automatic_face_movement_max_rotation_per_sec;
-
-               wrappedApproachShortest(m_rotation.Y, target_yaw, max_rotation_delta, 360.f);
-               rot_translator.val_current = m_rotation;
-
-               updateNodePos();
-       }
-}
-
-void GenericCAO::updateTexturePos()
-{
-       if(m_spritenode)
-       {
-               scene::ICameraSceneNode* camera =
-                               m_spritenode->getSceneManager()->getActiveCamera();
-               if(!camera)
-                       return;
-               v3f cam_to_entity = m_spritenode->getAbsolutePosition()
-                               - camera->getAbsolutePosition();
-               cam_to_entity.normalize();
-
-               int row = m_tx_basepos.Y;
-               int col = m_tx_basepos.X;
-
-               if (m_tx_select_horiz_by_yawpitch) {
-                       if (cam_to_entity.Y > 0.75)
-                               col += 5;
-                       else if (cam_to_entity.Y < -0.75)
-                               col += 4;
-                       else {
-                               float mob_dir =
-                                               atan2(cam_to_entity.Z, cam_to_entity.X) / M_PI * 180.;
-                               float dir = mob_dir - m_rotation.Y;
-                               dir = wrapDegrees_180(dir);
-                               if (std::fabs(wrapDegrees_180(dir - 0)) <= 45.1f)
-                                       col += 2;
-                               else if(std::fabs(wrapDegrees_180(dir - 90)) <= 45.1f)
-                                       col += 3;
-                               else if(std::fabs(wrapDegrees_180(dir - 180)) <= 45.1f)
-                                       col += 0;
-                               else if(std::fabs(wrapDegrees_180(dir + 90)) <= 45.1f)
-                                       col += 1;
-                               else
-                                       col += 4;
-                       }
-               }
-
-               // Animation goes downwards
-               row += m_anim_frame;
-
-               float txs = m_tx_size.X;
-               float tys = m_tx_size.Y;
-               setBillboardTextureMatrix(m_spritenode, txs, tys, col, row);
-       }
-}
-
-void GenericCAO::updateTextures(std::string mod)
-{
-       ITextureSource *tsrc = m_client->tsrc();
-
-       bool use_trilinear_filter = g_settings->getBool("trilinear_filter");
-       bool use_bilinear_filter = g_settings->getBool("bilinear_filter");
-       bool use_anisotropic_filter = g_settings->getBool("anisotropic_filter");
-
-       m_previous_texture_modifier = m_current_texture_modifier;
-       m_current_texture_modifier = mod;
-       m_glow = m_prop.glow;
-
-       video::E_MATERIAL_TYPE material_type = (m_prop.use_texture_alpha) ?
-               video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
-
-       if (m_spritenode) {
-               if (m_prop.visual == "sprite") {
-                       std::string texturestring = "unknown_node.png";
-                       if (!m_prop.textures.empty())
-                               texturestring = m_prop.textures[0];
-                       texturestring += mod;
-                       m_spritenode->getMaterial(0).MaterialType = material_type;
-                       m_spritenode->setMaterialTexture(0,
-                                       tsrc->getTextureForMesh(texturestring));
-
-                       // This allows setting per-material colors. However, until a real lighting
-                       // system is added, the code below will have no effect. Once MineTest
-                       // has directional lighting, it should work automatically.
-                       if (!m_prop.colors.empty()) {
-                               m_spritenode->getMaterial(0).AmbientColor = m_prop.colors[0];
-                               m_spritenode->getMaterial(0).DiffuseColor = m_prop.colors[0];
-                               m_spritenode->getMaterial(0).SpecularColor = m_prop.colors[0];
-                       }
-
-                       m_spritenode->getMaterial(0).setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
-                       m_spritenode->getMaterial(0).setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
-                       m_spritenode->getMaterial(0).setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
-               }
-       }
-
-       if (m_animated_meshnode) {
-               if (m_prop.visual == "mesh") {
-                       for (u32 i = 0; i < m_prop.textures.size() &&
-                                       i < m_animated_meshnode->getMaterialCount(); ++i) {
-                               std::string texturestring = m_prop.textures[i];
-                               if (texturestring.empty())
-                                       continue; // Empty texture string means don't modify that material
-                               texturestring += mod;
-                               video::ITexture* texture = tsrc->getTextureForMesh(texturestring);
-                               if (!texture) {
-                                       errorstream<<"GenericCAO::updateTextures(): Could not load texture "<<texturestring<<std::endl;
-                                       continue;
-                               }
-
-                               // Set material flags and texture
-                               video::SMaterial& material = m_animated_meshnode->getMaterial(i);
-                               material.MaterialType = material_type;
-                               material.TextureLayer[0].Texture = texture;
-                               material.setFlag(video::EMF_LIGHTING, true);
-                               material.setFlag(video::EMF_BILINEAR_FILTER, false);
-                               material.setFlag(video::EMF_BACK_FACE_CULLING, m_prop.backface_culling);
-
-                               // don't filter low-res textures, makes them look blurry
-                               // player models have a res of 64
-                               const core::dimension2d<u32> &size = texture->getOriginalSize();
-                               const u32 res = std::min(size.Height, size.Width);
-                               use_trilinear_filter &= res > 64;
-                               use_bilinear_filter &= res > 64;
-
-                               m_animated_meshnode->getMaterial(i)
-                                               .setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
-                               m_animated_meshnode->getMaterial(i)
-                                               .setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
-                               m_animated_meshnode->getMaterial(i)
-                                               .setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
-                       }
-                       for (u32 i = 0; i < m_prop.colors.size() &&
-                       i < m_animated_meshnode->getMaterialCount(); ++i)
-                       {
-                               // This allows setting per-material colors. However, until a real lighting
-                               // system is added, the code below will have no effect. Once MineTest
-                               // has directional lighting, it should work automatically.
-                               m_animated_meshnode->getMaterial(i).AmbientColor = m_prop.colors[i];
-                               m_animated_meshnode->getMaterial(i).DiffuseColor = m_prop.colors[i];
-                               m_animated_meshnode->getMaterial(i).SpecularColor = m_prop.colors[i];
-                       }
-               }
-       }
-       if(m_meshnode)
-       {
-               if(m_prop.visual == "cube")
-               {
-                       for (u32 i = 0; i < 6; ++i)
-                       {
-                               std::string texturestring = "unknown_node.png";
-                               if(m_prop.textures.size() > i)
-                                       texturestring = m_prop.textures[i];
-                               texturestring += mod;
-
-
-                               // Set material flags and texture
-                               video::SMaterial& material = m_meshnode->getMaterial(i);
-                               material.MaterialType = material_type;
-                               material.setFlag(video::EMF_LIGHTING, false);
-                               material.setFlag(video::EMF_BILINEAR_FILTER, false);
-                               material.setTexture(0,
-                                               tsrc->getTextureForMesh(texturestring));
-                               material.getTextureMatrix(0).makeIdentity();
-
-                               // This allows setting per-material colors. However, until a real lighting
-                               // system is added, the code below will have no effect. Once MineTest
-                               // has directional lighting, it should work automatically.
-                               if(m_prop.colors.size() > i)
-                               {
-                                       m_meshnode->getMaterial(i).AmbientColor = m_prop.colors[i];
-                                       m_meshnode->getMaterial(i).DiffuseColor = m_prop.colors[i];
-                                       m_meshnode->getMaterial(i).SpecularColor = m_prop.colors[i];
-                               }
-
-                               m_meshnode->getMaterial(i).setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
-                               m_meshnode->getMaterial(i).setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
-                               m_meshnode->getMaterial(i).setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
-                       }
-               } else if (m_prop.visual == "upright_sprite") {
-                       scene::IMesh *mesh = m_meshnode->getMesh();
-                       {
-                               std::string tname = "unknown_object.png";
-                               if (!m_prop.textures.empty())
-                                       tname = m_prop.textures[0];
-                               tname += mod;
-                               scene::IMeshBuffer *buf = mesh->getMeshBuffer(0);
-                               buf->getMaterial().setTexture(0,
-                                               tsrc->getTextureForMesh(tname));
-
-                               // This allows setting per-material colors. However, until a real lighting
-                               // system is added, the code below will have no effect. Once MineTest
-                               // has directional lighting, it should work automatically.
-                               if(!m_prop.colors.empty()) {
-                                       buf->getMaterial().AmbientColor = m_prop.colors[0];
-                                       buf->getMaterial().DiffuseColor = m_prop.colors[0];
-                                       buf->getMaterial().SpecularColor = m_prop.colors[0];
-                               }
-
-                               buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
-                               buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
-                               buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
-                       }
-                       {
-                               std::string tname = "unknown_object.png";
-                               if (m_prop.textures.size() >= 2)
-                                       tname = m_prop.textures[1];
-                               else if (!m_prop.textures.empty())
-                                       tname = m_prop.textures[0];
-                               tname += mod;
-                               scene::IMeshBuffer *buf = mesh->getMeshBuffer(1);
-                               buf->getMaterial().setTexture(0,
-                                               tsrc->getTextureForMesh(tname));
-
-                               // This allows setting per-material colors. However, until a real lighting
-                               // system is added, the code below will have no effect. Once MineTest
-                               // has directional lighting, it should work automatically.
-                               if (m_prop.colors.size() >= 2) {
-                                       buf->getMaterial().AmbientColor = m_prop.colors[1];
-                                       buf->getMaterial().DiffuseColor = m_prop.colors[1];
-                                       buf->getMaterial().SpecularColor = m_prop.colors[1];
-                                       setMeshColor(mesh, m_prop.colors[1]);
-                               } else if (!m_prop.colors.empty()) {
-                                       buf->getMaterial().AmbientColor = m_prop.colors[0];
-                                       buf->getMaterial().DiffuseColor = m_prop.colors[0];
-                                       buf->getMaterial().SpecularColor = m_prop.colors[0];
-                                       setMeshColor(mesh, m_prop.colors[0]);
-                               }
-
-                               buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
-                               buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
-                               buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
-                       }
-               }
-       }
-}
-
-void GenericCAO::updateAnimation()
-{
-       if (!m_animated_meshnode)
-               return;
-
-       if (m_animated_meshnode->getStartFrame() != m_animation_range.X ||
-               m_animated_meshnode->getEndFrame() != m_animation_range.Y)
-                       m_animated_meshnode->setFrameLoop(m_animation_range.X, m_animation_range.Y);
-       if (m_animated_meshnode->getAnimationSpeed() != m_animation_speed)
-               m_animated_meshnode->setAnimationSpeed(m_animation_speed);
-       m_animated_meshnode->setTransitionTime(m_animation_blend);
-// Requires Irrlicht 1.8 or greater
-#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR > 1
-       if (m_animated_meshnode->getLoopMode() != m_animation_loop)
-               m_animated_meshnode->setLoopMode(m_animation_loop);
-#endif
-}
-
-void GenericCAO::updateAnimationSpeed()
-{
-       if (!m_animated_meshnode)
-               return;
-
-       m_animated_meshnode->setAnimationSpeed(m_animation_speed);
-}
-
-void GenericCAO::updateBonePosition()
-{
-       if (m_bone_position.empty() || !m_animated_meshnode)
-               return;
-
-       m_animated_meshnode->setJointMode(irr::scene::EJUOR_CONTROL); // To write positions to the mesh on render
-       for(std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
-                       ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
-               std::string bone_name = (*ii).first;
-               v3f bone_pos = (*ii).second.X;
-               v3f bone_rot = (*ii).second.Y;
-               irr::scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str());
-               if(bone)
-               {
-                       bone->setPosition(bone_pos);
-                       bone->setRotation(bone_rot);
-               }
-       }
-}
-
-void GenericCAO::updateAttachments()
-{
-
-       if (!getParent()) { // Detach or don't attach
-               scene::ISceneNode *node = getSceneNode();
-               if (node) {
-                       v3f old_position = node->getAbsolutePosition();
-                       v3f old_rotation = node->getRotation();
-                       node->setParent(m_smgr->getRootSceneNode());
-                       node->setPosition(old_position);
-                       node->setRotation(old_rotation);
-                       node->updateAbsolutePosition();
-               }
-               if (m_is_local_player) {
-                       LocalPlayer *player = m_env->getLocalPlayer();
-                       player->isAttached = false;
-               }
-       }
-       else // Attach
-       {
-               scene::ISceneNode *my_node = getSceneNode();
-
-               scene::ISceneNode *parent_node = getParent()->getSceneNode();
-               scene::IAnimatedMeshSceneNode *parent_animated_mesh_node =
-                               getParent()->getAnimatedMeshSceneNode();
-               if (parent_animated_mesh_node && !m_attachment_bone.empty()) {
-                       parent_node = parent_animated_mesh_node->getJointNode(m_attachment_bone.c_str());
-               }
-
-               if (my_node && parent_node) {
-                       my_node->setParent(parent_node);
-                       my_node->setPosition(m_attachment_position);
-                       my_node->setRotation(m_attachment_rotation);
-                       my_node->updateAbsolutePosition();
-               }
-               if (m_is_local_player) {
-                       LocalPlayer *player = m_env->getLocalPlayer();
-                       player->isAttached = true;
-               }
-       }
-}
-
-void GenericCAO::processMessage(const std::string &data)
-{
-       //infostream<<"GenericCAO: Got message"<<std::endl;
-       std::istringstream is(data, std::ios::binary);
-       // command
-       u8 cmd = readU8(is);
-       if (cmd == GENERIC_CMD_SET_PROPERTIES) {
-               m_prop = gob_read_set_properties(is);
-
-               m_selection_box = m_prop.selectionbox;
-               m_selection_box.MinEdge *= BS;
-               m_selection_box.MaxEdge *= BS;
-
-               m_tx_size.X = 1.0 / m_prop.spritediv.X;
-               m_tx_size.Y = 1.0 / m_prop.spritediv.Y;
-
-               if(!m_initial_tx_basepos_set){
-                       m_initial_tx_basepos_set = true;
-                       m_tx_basepos = m_prop.initial_sprite_basepos;
-               }
-               if (m_is_local_player) {
-                       LocalPlayer *player = m_env->getLocalPlayer();
-                       player->makes_footstep_sound = m_prop.makes_footstep_sound;
-                       aabb3f collision_box = m_prop.collisionbox;
-                       collision_box.MinEdge *= BS;
-                       collision_box.MaxEdge *= BS;
-                       player->setCollisionbox(collision_box);
-                       player->setEyeHeight(m_prop.eye_height);
-                       player->setZoomFOV(m_prop.zoom_fov);
-               }
-
-               if ((m_is_player && !m_is_local_player) && m_prop.nametag.empty())
-                       m_prop.nametag = m_name;
-
-               expireVisuals();
-       } else if (cmd == GENERIC_CMD_UPDATE_POSITION) {
-               // Not sent by the server if this object is an attachment.
-               // We might however get here if the server notices the object being detached before the client.
-               m_position = readV3F1000(is);
-               m_velocity = readV3F1000(is);
-               m_acceleration = readV3F1000(is);
-
-               if (std::fabs(m_prop.automatic_rotate) < 0.001f)
-                       m_rotation = readV3F1000(is);
-               else
-                       readV3F1000(is);
-
-               m_rotation = wrapDegrees_0_360_v3f(m_rotation);
-               bool do_interpolate = readU8(is);
-               bool is_end_position = readU8(is);
-               float update_interval = readF1000(is);
-
-               // Place us a bit higher if we're physical, to not sink into
-               // the ground due to sucky collision detection...
-               if(m_prop.physical)
-                       m_position += v3f(0,0.002,0);
-
-               if(getParent() != NULL) // Just in case
-                       return;
-
-               if(do_interpolate)
-               {
-                       if(!m_prop.physical)
-                               pos_translator.update(m_position, is_end_position, update_interval);
-               } else {
-                       pos_translator.init(m_position);
-               }
-               rot_translator.update(m_rotation, false, update_interval);
-               updateNodePos();
-       } else if (cmd == GENERIC_CMD_SET_TEXTURE_MOD) {
-               std::string mod = deSerializeString(is);
-
-               // immediatly reset a engine issued texture modifier if a mod sends a different one
-               if (m_reset_textures_timer > 0) {
-                       m_reset_textures_timer = -1;
-                       updateTextures(m_previous_texture_modifier);
-               }
-               updateTextures(mod);
-       } else if (cmd == GENERIC_CMD_SET_SPRITE) {
-               v2s16 p = readV2S16(is);
-               int num_frames = readU16(is);
-               float framelength = readF1000(is);
-               bool select_horiz_by_yawpitch = readU8(is);
-
-               m_tx_basepos = p;
-               m_anim_num_frames = num_frames;
-               m_anim_framelength = framelength;
-               m_tx_select_horiz_by_yawpitch = select_horiz_by_yawpitch;
-
-               updateTexturePos();
-       } else if (cmd == GENERIC_CMD_SET_PHYSICS_OVERRIDE) {
-               float override_speed = readF1000(is);
-               float override_jump = readF1000(is);
-               float override_gravity = readF1000(is);
-               // these are sent inverted so we get true when the server sends nothing
-               bool sneak = !readU8(is);
-               bool sneak_glitch = !readU8(is);
-               bool new_move = !readU8(is);
-
-
-               if(m_is_local_player)
-               {
-                       LocalPlayer *player = m_env->getLocalPlayer();
-                       player->physics_override_speed = override_speed;
-                       player->physics_override_jump = override_jump;
-                       player->physics_override_gravity = override_gravity;
-                       player->physics_override_sneak = sneak;
-                       player->physics_override_sneak_glitch = sneak_glitch;
-                       player->physics_override_new_move = new_move;
-               }
-       } else if (cmd == GENERIC_CMD_SET_ANIMATION) {
-               // TODO: change frames send as v2s32 value
-               v2f range = readV2F1000(is);
-               if (!m_is_local_player) {
-                       m_animation_range = v2s32((s32)range.X, (s32)range.Y);
-                       m_animation_speed = readF1000(is);
-                       m_animation_blend = readF1000(is);
-                       // these are sent inverted so we get true when the server sends nothing
-                       m_animation_loop = !readU8(is);
-                       updateAnimation();
-               } else {
-                       LocalPlayer *player = m_env->getLocalPlayer();
-                       if(player->last_animation == NO_ANIM)
-                       {
-                               m_animation_range = v2s32((s32)range.X, (s32)range.Y);
-                               m_animation_speed = readF1000(is);
-                               m_animation_blend = readF1000(is);
-                               // these are sent inverted so we get true when the server sends nothing
-                               m_animation_loop = !readU8(is);
-                       }
-                       // update animation only if local animations present
-                       // and received animation is unknown (except idle animation)
-                       bool is_known = false;
-                       for (int i = 1;i<4;i++)
-                       {
-                               if(m_animation_range.Y == player->local_animations[i].Y)
-                                       is_known = true;
-                       }
-                       if(!is_known ||
-                                       (player->local_animations[1].Y + player->local_animations[2].Y < 1))
-                       {
-                                       updateAnimation();
-                       }
-               }
-       } else if (cmd == GENERIC_CMD_SET_ANIMATION_SPEED) {
-               m_animation_speed = readF1000(is);
-               updateAnimationSpeed();
-       } else if (cmd == GENERIC_CMD_SET_BONE_POSITION) {
-               std::string bone = deSerializeString(is);
-               v3f position = readV3F1000(is);
-               v3f rotation = readV3F1000(is);
-               m_bone_position[bone] = core::vector2d<v3f>(position, rotation);
-
-               updateBonePosition();
-       } else if (cmd == GENERIC_CMD_ATTACH_TO) {
-               u16 parent_id = readS16(is);
-               u16 &old_parent_id = m_env->attachement_parent_ids[getId()];
-               if (parent_id != old_parent_id) {
-                       if (GenericCAO *old_parent = m_env->getGenericCAO(old_parent_id)) {
-                               old_parent->m_children.erase(std::remove(
-                                       m_children.begin(), m_children.end(),
-                                       getId()), m_children.end());
-                       }
-                       if (GenericCAO *new_parent = m_env->getGenericCAO(parent_id))
-                               new_parent->m_children.push_back(getId());
-
-                       old_parent_id = parent_id;
-               }
-
-               m_attachment_bone = deSerializeString(is);
-               m_attachment_position = readV3F1000(is);
-               m_attachment_rotation = readV3F1000(is);
-
-               // localplayer itself can't be attached to localplayer
-               if (!m_is_local_player) {
-                       m_attached_to_local = getParent() != NULL && getParent()->isLocalPlayer();
-                       // Objects attached to the local player should be hidden by default
-                       m_is_visible = !m_attached_to_local;
-               }
-
-               updateAttachments();
-       } else if (cmd == GENERIC_CMD_PUNCHED) {
-               /*s16 damage =*/ readS16(is);
-               s16 result_hp = readS16(is);
-
-               // Use this instead of the send damage to not interfere with prediction
-               s16 damage = m_hp - result_hp;
-
-               m_hp = result_hp;
-
-               if (damage > 0)
-               {
-                       if (m_hp <= 0)
-                       {
-                               // TODO: Execute defined fast response
-                               // As there is no definition, make a smoke puff
-                               ClientSimpleObject *simple = createSmokePuff(
-                                               m_smgr, m_env, m_position,
-                                               m_prop.visual_size * BS);
-                               m_env->addSimpleObject(simple);
-                       } else if (m_reset_textures_timer < 0) {
-                               // TODO: Execute defined fast response
-                               // Flashing shall suffice as there is no definition
-                               m_reset_textures_timer = 0.05;
-                               if(damage >= 2)
-                                       m_reset_textures_timer += 0.05 * damage;
-                               updateTextures(m_current_texture_modifier + "^[brighten");
-                       }
-               }
-       } else if (cmd == GENERIC_CMD_UPDATE_ARMOR_GROUPS) {
-               m_armor_groups.clear();
-               int armor_groups_size = readU16(is);
-               for(int i=0; i<armor_groups_size; i++)
-               {
-                       std::string name = deSerializeString(is);
-                       int rating = readS16(is);
-                       m_armor_groups[name] = rating;
-               }
-       } else if (cmd == GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) {
-               // Deprecated, for backwards compatibility only.
-               readU8(is); // version
-               m_prop.nametag_color = readARGB8(is);
-               if (m_nametag != NULL) {
-                       m_nametag->nametag_color = m_prop.nametag_color;
-                       v3f pos;
-                       pos.Y = m_prop.collisionbox.MaxEdge.Y + 0.3f;
-                       m_nametag->nametag_pos = pos;
-               }
-       } else if (cmd == GENERIC_CMD_SPAWN_INFANT) {
-               u16 child_id = readU16(is);
-               u8 type = readU8(is);
-
-               if (GenericCAO *childobj = m_env->getGenericCAO(child_id)) {
-                       childobj->processInitData(deSerializeLongString(is));
-               } else {
-                       m_env->addActiveObject(child_id, type, deSerializeLongString(is));
-               }
-       } else {
-               warningstream << FUNCTION_NAME
-                       << ": unknown command or outdated client \""
-                       << +cmd << "\"" << std::endl;
-       }
-}
-
-/* \pre punchitem != NULL
- */
-bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem,
-               float time_from_last_punch)
-{
-       assert(punchitem);      // pre-condition
-       const ToolCapabilities *toolcap =
-                       &punchitem->getToolCapabilities(m_client->idef());
-       PunchDamageResult result = getPunchDamage(
-                       m_armor_groups,
-                       toolcap,
-                       punchitem,
-                       time_from_last_punch);
-
-       if(result.did_punch && result.damage != 0)
-       {
-               if(result.damage < m_hp)
-               {
-                       m_hp -= result.damage;
-               } else {
-                       m_hp = 0;
-                       // TODO: Execute defined fast response
-                       // As there is no definition, make a smoke puff
-                       ClientSimpleObject *simple = createSmokePuff(
-                                       m_smgr, m_env, m_position,
-                                       m_prop.visual_size * BS);
-                       m_env->addSimpleObject(simple);
-               }
-               // TODO: Execute defined fast response
-               // Flashing shall suffice as there is no definition
-               if (m_reset_textures_timer < 0) {
-                       m_reset_textures_timer = 0.05;
-                       if (result.damage >= 2)
-                               m_reset_textures_timer += 0.05 * result.damage;
-                       updateTextures(m_current_texture_modifier + "^[brighten");
-               }
-       }
-
-       return false;
-}
-
-std::string GenericCAO::debugInfoText()
-{
-       std::ostringstream os(std::ios::binary);
-       os<<"GenericCAO hp="<<m_hp<<"\n";
-       os<<"armor={";
-       for(ItemGroupList::const_iterator i = m_armor_groups.begin();
-                       i != m_armor_groups.end(); ++i)
-       {
-               os<<i->first<<"="<<i->second<<", ";
-       }
-       os<<"}";
-       return os.str();
-}
-
-// Prototype
-GenericCAO proto_GenericCAO(NULL, NULL);
diff --git a/src/content_cao.h b/src/content_cao.h
deleted file mode 100644 (file)
index 9893213..0000000
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include <map>
-#include "irrlichttypes_extrabloated.h"
-#include "clientobject.h"
-#include "object_properties.h"
-#include "itemgroup.h"
-#include "constants.h"
-
-class Camera;
-class Client;
-struct Nametag;
-
-/*
-       SmoothTranslator
-*/
-
-template<typename T>
-struct SmoothTranslator
-{
-       T val_old;
-       T val_current;
-       T val_target;
-       f32 anim_time = 0;
-       f32 anim_time_counter = 0;
-       bool aim_is_end = true;
-
-       SmoothTranslator() = default;
-
-       void init(T current);
-
-       void update(T new_target, bool is_end_position = false,
-               float update_interval = -1);
-
-       void translate(f32 dtime);
-};
-
-struct SmoothTranslatorWrapped : SmoothTranslator<f32>
-{
-       void translate(f32 dtime);
-};
-
-struct SmoothTranslatorWrappedv3f : SmoothTranslator<v3f>
-{
-       void translate(f32 dtime);
-};
-
-class GenericCAO : public ClientActiveObject
-{
-private:
-       // Only set at initialization
-       std::string m_name = "";
-       bool m_is_player = false;
-       bool m_is_local_player = false;
-       // Property-ish things
-       ObjectProperties m_prop;
-       //
-       scene::ISceneManager *m_smgr = nullptr;
-       Client *m_client = nullptr;
-       aabb3f m_selection_box = aabb3f(-BS/3.,-BS/3.,-BS/3., BS/3.,BS/3.,BS/3.);
-       scene::IMeshSceneNode *m_meshnode = nullptr;
-       scene::IAnimatedMeshSceneNode *m_animated_meshnode = nullptr;
-       WieldMeshSceneNode *m_wield_meshnode = nullptr;
-       scene::IBillboardSceneNode *m_spritenode = nullptr;
-       Nametag *m_nametag = nullptr;
-       v3f m_position = v3f(0.0f, 10.0f * BS, 0);
-       v3f m_velocity;
-       v3f m_acceleration;
-       v3f m_rotation;
-       s16 m_hp = 1;
-       SmoothTranslator<v3f> pos_translator;
-       SmoothTranslatorWrappedv3f rot_translator;
-       // Spritesheet/animation stuff
-       v2f m_tx_size = v2f(1,1);
-       v2s16 m_tx_basepos;
-       bool m_initial_tx_basepos_set = false;
-       bool m_tx_select_horiz_by_yawpitch = false;
-       v2s32 m_animation_range;
-       float m_animation_speed = 15.0f;
-       float m_animation_blend = 0.0f;
-       bool m_animation_loop = true;
-       // stores position and rotation for each bone name
-       std::unordered_map<std::string, core::vector2d<v3f>> m_bone_position;
-       std::string m_attachment_bone = "";
-       v3f m_attachment_position;
-       v3f m_attachment_rotation;
-       bool m_attached_to_local = false;
-       int m_anim_frame = 0;
-       int m_anim_num_frames = 1;
-       float m_anim_framelength = 0.2f;
-       float m_anim_timer = 0.0f;
-       ItemGroupList m_armor_groups;
-       float m_reset_textures_timer = -1.0f;
-       // stores texture modifier before punch update
-       std::string m_previous_texture_modifier = "";
-       // last applied texture modifier
-       std::string m_current_texture_modifier = "";
-       bool m_visuals_expired = false;
-       float m_step_distance_counter = 0.0f;
-       u8 m_last_light = 255;
-       bool m_is_visible = false;
-       s8 m_glow = 0;
-
-       std::vector<u16> m_children;
-
-public:
-       GenericCAO(Client *client, ClientEnvironment *env);
-
-       ~GenericCAO();
-
-       static ClientActiveObject* create(Client *client, ClientEnvironment *env)
-       {
-               return new GenericCAO(client, env);
-       }
-
-       inline ActiveObjectType getType() const
-       {
-               return ACTIVEOBJECT_TYPE_GENERIC;
-       }
-       inline const ItemGroupList &getGroups() const
-       {
-               return m_armor_groups;
-       }
-       void initialize(const std::string &data);
-
-       void processInitData(const std::string &data);
-
-       bool getCollisionBox(aabb3f *toset) const;
-
-       bool collideWithObjects() const;
-
-       virtual bool getSelectionBox(aabb3f *toset) const;
-
-       v3f getPosition();
-
-       inline const v3f &getRotation()
-       {
-               return m_rotation;
-       }
-
-       const bool isImmortal();
-
-       scene::ISceneNode *getSceneNode();
-
-       scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode();
-
-       inline f32 getStepHeight() const
-       {
-               return m_prop.stepheight;
-       }
-
-       inline bool isLocalPlayer() const
-       {
-               return m_is_local_player;
-       }
-
-       inline bool isVisible() const
-       {
-               return m_is_visible;
-       }
-
-       inline void setVisible(bool toset)
-       {
-               m_is_visible = toset;
-       }
-
-       void setChildrenVisible(bool toset);
-
-       ClientActiveObject *getParent() const;
-
-       void setAttachments();
-
-       void removeFromScene(bool permanent);
-
-       void addToScene(ITextureSource *tsrc);
-
-       inline void expireVisuals()
-       {
-               m_visuals_expired = true;
-       }
-
-       void updateLight(u8 light_at_pos);
-
-       void updateLightNoCheck(u8 light_at_pos);
-
-       v3s16 getLightPosition();
-
-       void updateNodePos();
-
-       void step(float dtime, ClientEnvironment *env);
-
-       void updateTexturePos();
-
-       // std::string copy is mandatory as mod can be a class member and there is a swap
-       // on those class members... do NOT pass by reference
-       void updateTextures(std::string mod);
-
-       void updateAnimation();
-
-       void updateAnimationSpeed();
-
-       void updateBonePosition();
-
-       void updateAttachments();
-
-       void processMessage(const std::string &data);
-
-       bool directReportPunch(v3f dir, const ItemStack *punchitem=NULL,
-                       float time_from_last_punch=1000000);
-
-       std::string debugInfoText();
-
-       std::string infoText()
-       {
-               return m_prop.infotext;
-       }
-};
diff --git a/src/content_cso.cpp b/src/content_cso.cpp
deleted file mode 100644 (file)
index 04c503f..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 "content_cso.h"
-#include <IBillboardSceneNode.h>
-#include "client/tile.h"
-#include "clientenvironment.h"
-#include "client.h"
-#include "map.h"
-
-class SmokePuffCSO: public ClientSimpleObject
-{
-       float m_age = 0.0f;
-       scene::IBillboardSceneNode *m_spritenode = nullptr;
-public:
-       SmokePuffCSO(scene::ISceneManager *smgr,
-                       ClientEnvironment *env, const v3f &pos, const v2f &size)
-       {
-               infostream<<"SmokePuffCSO: constructing"<<std::endl;
-               m_spritenode = smgr->addBillboardSceneNode(
-                               NULL, v2f(1,1), pos, -1);
-               m_spritenode->setMaterialTexture(0,
-                               env->getGameDef()->tsrc()->getTextureForMesh("smoke_puff.png"));
-               m_spritenode->setMaterialFlag(video::EMF_LIGHTING, false);
-               m_spritenode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
-               //m_spritenode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF);
-               m_spritenode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL);
-               m_spritenode->setMaterialFlag(video::EMF_FOG_ENABLE, true);
-               m_spritenode->setColor(video::SColor(255,0,0,0));
-               m_spritenode->setVisible(true);
-               m_spritenode->setSize(size);
-               /* Update brightness */
-               u8 light;
-               bool pos_ok;
-               MapNode n = env->getMap().getNodeNoEx(floatToInt(pos, BS), &pos_ok);
-               light = pos_ok ? decode_light(n.getLightBlend(env->getDayNightRatio(),
-                                                       env->getGameDef()->ndef()))
-                              : 64;
-               video::SColor color(255,light,light,light);
-               m_spritenode->setColor(color);
-       }
-       virtual ~SmokePuffCSO()
-       {
-               infostream<<"SmokePuffCSO: destructing"<<std::endl;
-               m_spritenode->remove();
-       }
-       void step(float dtime)
-       {
-               m_age += dtime;
-               if(m_age > 1.0){
-                       m_to_be_removed = true;
-               }
-       }
-};
-
-ClientSimpleObject* createSmokePuff(scene::ISceneManager *smgr,
-               ClientEnvironment *env, v3f pos, v2f size)
-{
-       return new SmokePuffCSO(smgr, env, pos, size);
-}
-
diff --git a/src/content_cso.h b/src/content_cso.h
deleted file mode 100644 (file)
index cc92131..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include "clientsimpleobject.h"
-
-ClientSimpleObject* createSmokePuff(scene::ISceneManager *smgr,
-               ClientEnvironment *env, v3f pos, v2f size);
diff --git a/src/content_mapblock.cpp b/src/content_mapblock.cpp
deleted file mode 100644 (file)
index 4a0df61..0000000
+++ /dev/null
@@ -1,1430 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 "content_mapblock.h"
-#include "util/numeric.h"
-#include "util/directiontables.h"
-#include "mapblock_mesh.h"
-#include "settings.h"
-#include "nodedef.h"
-#include "client/tile.h"
-#include "mesh.h"
-#include <IMeshManipulator.h>
-#include "client/meshgen/collector.h"
-#include "client/renderingengine.h"
-#include "client.h"
-#include "noise.h"
-
-// Distance of light extrapolation (for oversized nodes)
-// After this distance, it gives up and considers light level constant
-#define SMOOTH_LIGHTING_OVERSIZE 1.0
-
-// Node edge count (for glasslike-framed)
-#define FRAMED_EDGE_COUNT 12
-
-// Node neighbor count, including edge-connected, but not vertex-connected
-// (for glasslike-framed)
-// Corresponding offsets are listed in g_27dirs
-#define FRAMED_NEIGHBOR_COUNT 18
-
-static const v3s16 light_dirs[8] = {
-       v3s16(-1, -1, -1),
-       v3s16(-1, -1,  1),
-       v3s16(-1,  1, -1),
-       v3s16(-1,  1,  1),
-       v3s16( 1, -1, -1),
-       v3s16( 1, -1,  1),
-       v3s16( 1,  1, -1),
-       v3s16( 1,  1,  1),
-};
-
-// Standard index set to make a quad on 4 vertices
-static constexpr u16 quad_indices[] = {0, 1, 2, 2, 3, 0};
-
-const std::string MapblockMeshGenerator::raillike_groupname = "connect_to_raillike";
-
-MapblockMeshGenerator::MapblockMeshGenerator(MeshMakeData *input, MeshCollector *output)
-{
-       data      = input;
-       collector = output;
-
-       nodedef   = data->m_client->ndef();
-       meshmanip = RenderingEngine::get_scene_manager()->getMeshManipulator();
-
-       enable_mesh_cache = g_settings->getBool("enable_mesh_cache") &&
-               !data->m_smooth_lighting; // Mesh cache is not supported with smooth lighting
-
-       blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE;
-}
-
-void MapblockMeshGenerator::useTile(int index, u8 set_flags, u8 reset_flags, bool special)
-{
-       if (special)
-               getSpecialTile(index, &tile, p == data->m_crack_pos_relative);
-       else
-               getTile(index, &tile);
-       if (!data->m_smooth_lighting)
-               color = encode_light(light, f->light_source);
-
-       for (auto &layer : tile.layers) {
-               layer.material_flags |= set_flags;
-               layer.material_flags &= ~reset_flags;
-       }
-}
-
-// Returns a tile, ready for use, non-rotated.
-void MapblockMeshGenerator::getTile(int index, TileSpec *tile)
-{
-       getNodeTileN(n, p, index, data, *tile);
-}
-
-// Returns a tile, ready for use, rotated according to the node facedir.
-void MapblockMeshGenerator::getTile(v3s16 direction, TileSpec *tile)
-{
-       getNodeTile(n, p, direction, data, *tile);
-}
-
-// Returns a special tile, ready for use, non-rotated.
-void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile, bool apply_crack)
-{
-       *tile = f->special_tiles[index];
-       TileLayer *top_layer = nullptr;
-
-       for (auto &layernum : tile->layers) {
-               TileLayer *layer = &layernum;
-               if (layer->texture_id == 0)
-                       continue;
-               top_layer = layer;
-               if (!layer->has_color)
-                       n.getColor(*f, &layer->color);
-       }
-
-       if (apply_crack)
-               top_layer->material_flags |= MATERIAL_FLAG_CRACK;
-}
-
-void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal,
-       float vertical_tiling)
-{
-       const v2f tcoords[4] = {v2f(0.0, 0.0), v2f(1.0, 0.0),
-               v2f(1.0, vertical_tiling), v2f(0.0, vertical_tiling)};
-       video::S3DVertex vertices[4];
-       bool shade_face = !f->light_source && (normal != v3s16(0, 0, 0));
-       v3f normal2(normal.X, normal.Y, normal.Z);
-       for (int j = 0; j < 4; j++) {
-               vertices[j].Pos = coords[j] + origin;
-               vertices[j].Normal = normal2;
-               if (data->m_smooth_lighting)
-                       vertices[j].Color = blendLightColor(coords[j]);
-               else
-                       vertices[j].Color = color;
-               if (shade_face)
-                       applyFacesShading(vertices[j].Color, normal2);
-               vertices[j].TCoords = tcoords[j];
-       }
-       collector->append(tile, vertices, 4, quad_indices, 6);
-}
-
-// Create a cuboid.
-//  tiles     - the tiles (materials) to use (for all 6 faces)
-//  tilecount - number of entries in tiles, 1<=tilecount<=6
-//  lights    - vertex light levels. The order is the same as in light_dirs.
-//              NULL may be passed if smooth lighting is disabled.
-//  txc       - texture coordinates - this is a list of texture coordinates
-//              for the opposite corners of each face - therefore, there
-//              should be (2+2)*6=24 values in the list. The order of
-//              the faces in the list is up-down-right-left-back-front
-//              (compatible with ContentFeatures).
-void MapblockMeshGenerator::drawCuboid(const aabb3f &box,
-       TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc)
-{
-       assert(tilecount >= 1 && tilecount <= 6); // pre-condition
-
-       v3f min = box.MinEdge;
-       v3f max = box.MaxEdge;
-
-       video::SColor colors[6];
-       if (!data->m_smooth_lighting) {
-               for (int face = 0; face != 6; ++face) {
-                       colors[face] = encode_light(light, f->light_source);
-               }
-               if (!f->light_source) {
-                       applyFacesShading(colors[0], v3f(0, 1, 0));
-                       applyFacesShading(colors[1], v3f(0, -1, 0));
-                       applyFacesShading(colors[2], v3f(1, 0, 0));
-                       applyFacesShading(colors[3], v3f(-1, 0, 0));
-                       applyFacesShading(colors[4], v3f(0, 0, 1));
-                       applyFacesShading(colors[5], v3f(0, 0, -1));
-               }
-       }
-
-       video::S3DVertex vertices[24] = {
-               // top
-               video::S3DVertex(min.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[0], txc[1]),
-               video::S3DVertex(max.X, max.Y, max.Z, 0, 1, 0, colors[0], txc[2], txc[1]),
-               video::S3DVertex(max.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[2], txc[3]),
-               video::S3DVertex(min.X, max.Y, min.Z, 0, 1, 0, colors[0], txc[0], txc[3]),
-               // bottom
-               video::S3DVertex(min.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[4], txc[5]),
-               video::S3DVertex(max.X, min.Y, min.Z, 0, -1, 0, colors[1], txc[6], txc[5]),
-               video::S3DVertex(max.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[6], txc[7]),
-               video::S3DVertex(min.X, min.Y, max.Z, 0, -1, 0, colors[1], txc[4], txc[7]),
-               // right
-               video::S3DVertex(max.X, max.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[9]),
-               video::S3DVertex(max.X, max.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[9]),
-               video::S3DVertex(max.X, min.Y, max.Z, 1, 0, 0, colors[2], txc[10], txc[11]),
-               video::S3DVertex(max.X, min.Y, min.Z, 1, 0, 0, colors[2], txc[ 8], txc[11]),
-               // left
-               video::S3DVertex(min.X, max.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[13]),
-               video::S3DVertex(min.X, max.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[13]),
-               video::S3DVertex(min.X, min.Y, min.Z, -1, 0, 0, colors[3], txc[14], txc[15]),
-               video::S3DVertex(min.X, min.Y, max.Z, -1, 0, 0, colors[3], txc[12], txc[15]),
-               // back
-               video::S3DVertex(max.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[17]),
-               video::S3DVertex(min.X, max.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[17]),
-               video::S3DVertex(min.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[18], txc[19]),
-               video::S3DVertex(max.X, min.Y, max.Z, 0, 0, 1, colors[4], txc[16], txc[19]),
-               // front
-               video::S3DVertex(min.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[21]),
-               video::S3DVertex(max.X, max.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[21]),
-               video::S3DVertex(max.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[22], txc[23]),
-               video::S3DVertex(min.X, min.Y, min.Z, 0, 0, -1, colors[5], txc[20], txc[23]),
-       };
-
-       static const u8 light_indices[24] = {
-               3, 7, 6, 2,
-               0, 4, 5, 1,
-               6, 7, 5, 4,
-               3, 2, 0, 1,
-               7, 3, 1, 5,
-               2, 6, 4, 0
-       };
-
-       for (int face = 0; face < 6; face++) {
-               int tileindex = MYMIN(face, tilecount - 1);
-               const TileSpec &tile = tiles[tileindex];
-               for (int j = 0; j < 4; j++) {
-                       video::S3DVertex &vertex = vertices[face * 4 + j];
-                       v2f &tcoords = vertex.TCoords;
-                       switch (tile.rotation) {
-                       case 0:
-                               break;
-                       case 1: // R90
-                               tcoords.rotateBy(90, irr::core::vector2df(0, 0));
-                               break;
-                       case 2: // R180
-                               tcoords.rotateBy(180, irr::core::vector2df(0, 0));
-                               break;
-                       case 3: // R270
-                               tcoords.rotateBy(270, irr::core::vector2df(0, 0));
-                               break;
-                       case 4: // FXR90
-                               tcoords.X = 1.0 - tcoords.X;
-                               tcoords.rotateBy(90, irr::core::vector2df(0, 0));
-                               break;
-                       case 5: // FXR270
-                               tcoords.X = 1.0 - tcoords.X;
-                               tcoords.rotateBy(270, irr::core::vector2df(0, 0));
-                               break;
-                       case 6: // FYR90
-                               tcoords.Y = 1.0 - tcoords.Y;
-                               tcoords.rotateBy(90, irr::core::vector2df(0, 0));
-                               break;
-                       case 7: // FYR270
-                               tcoords.Y = 1.0 - tcoords.Y;
-                               tcoords.rotateBy(270, irr::core::vector2df(0, 0));
-                               break;
-                       case 8: // FX
-                               tcoords.X = 1.0 - tcoords.X;
-                               break;
-                       case 9: // FY
-                               tcoords.Y = 1.0 - tcoords.Y;
-                               break;
-                       default:
-                               break;
-                       }
-               }
-       }
-
-       if (data->m_smooth_lighting) {
-               for (int j = 0; j < 24; ++j) {
-                       video::S3DVertex &vertex = vertices[j];
-                       vertex.Color = encode_light(
-                               lights[light_indices[j]].getPair(MYMAX(0.0f, vertex.Normal.Y)),
-                               f->light_source);
-                       if (!f->light_source)
-                               applyFacesShading(vertex.Color, vertex.Normal);
-               }
-       }
-
-       // Add to mesh collector
-       for (int k = 0; k < 6; ++k) {
-               int tileindex = MYMIN(k, tilecount - 1);
-               collector->append(tiles[tileindex], vertices + 4 * k, 4, quad_indices, 6);
-       }
-}
-
-// Gets the base lighting values for a node
-void MapblockMeshGenerator::getSmoothLightFrame()
-{
-       for (int k = 0; k < 8; ++k)
-               frame.sunlight[k] = false;
-       for (int k = 0; k < 8; ++k) {
-               LightPair light(getSmoothLightTransparent(blockpos_nodes + p, light_dirs[k], data));
-               frame.lightsDay[k] = light.lightDay;
-               frame.lightsNight[k] = light.lightNight;
-               // If there is direct sunlight and no ambient occlusion at some corner,
-               // mark the vertical edge (top and bottom corners) containing it.
-               if (light.lightDay == 255) {
-                       frame.sunlight[k] = true;
-                       frame.sunlight[k ^ 2] = true;
-               }
-       }
-}
-
-// Calculates vertex light level
-//  vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so)
-LightInfo MapblockMeshGenerator::blendLight(const v3f &vertex_pos)
-{
-       // Light levels at (logical) node corners are known. Here,
-       // trilinear interpolation is used to calculate light level
-       // at a given point in the node.
-       f32 x = core::clamp(vertex_pos.X / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
-       f32 y = core::clamp(vertex_pos.Y / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
-       f32 z = core::clamp(vertex_pos.Z / BS + 0.5, 0.0 - SMOOTH_LIGHTING_OVERSIZE, 1.0 + SMOOTH_LIGHTING_OVERSIZE);
-       f32 lightDay = 0.0; // daylight
-       f32 lightNight = 0.0;
-       f32 lightBoosted = 0.0; // daylight + direct sunlight, if any
-       for (int k = 0; k < 8; ++k) {
-               f32 dx = (k & 4) ? x : 1 - x;
-               f32 dy = (k & 2) ? y : 1 - y;
-               f32 dz = (k & 1) ? z : 1 - z;
-               // Use direct sunlight (255), if any; use daylight otherwise.
-               f32 light_boosted = frame.sunlight[k] ? 255 : frame.lightsDay[k];
-               lightDay += dx * dy * dz * frame.lightsDay[k];
-               lightNight += dx * dy * dz * frame.lightsNight[k];
-               lightBoosted += dx * dy * dz * light_boosted;
-       }
-       return LightInfo{lightDay, lightNight, lightBoosted};
-}
-
-// Calculates vertex color to be used in mapblock mesh
-//  vertex_pos - vertex position in the node (coordinates are clamped to [0.0, 1.0] or so)
-//  tile_color - node's tile color
-video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos)
-{
-       LightInfo light = blendLight(vertex_pos);
-       return encode_light(light.getPair(), f->light_source);
-}
-
-video::SColor MapblockMeshGenerator::blendLightColor(const v3f &vertex_pos,
-       const v3f &vertex_normal)
-{
-       LightInfo light = blendLight(vertex_pos);
-       video::SColor color = encode_light(light.getPair(MYMAX(0.0f, vertex_normal.Y)), f->light_source);
-       if (!f->light_source)
-               applyFacesShading(color, vertex_normal);
-       return color;
-}
-
-void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 *coords)
-{
-       f32 tx1 = (box.MinEdge.X / BS) + 0.5;
-       f32 ty1 = (box.MinEdge.Y / BS) + 0.5;
-       f32 tz1 = (box.MinEdge.Z / BS) + 0.5;
-       f32 tx2 = (box.MaxEdge.X / BS) + 0.5;
-       f32 ty2 = (box.MaxEdge.Y / BS) + 0.5;
-       f32 tz2 = (box.MaxEdge.Z / BS) + 0.5;
-       f32 txc[24] = {
-                   tx1, 1 - tz2,     tx2, 1 - tz1, // up
-                   tx1,     tz1,     tx2,     tz2, // down
-                   tz1, 1 - ty2,     tz2, 1 - ty1, // right
-               1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1, // left
-               1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1, // back
-                   tx1, 1 - ty2,     tx2, 1 - ty1, // front
-       };
-       for (int i = 0; i != 24; ++i)
-               coords[i] = txc[i];
-}
-
-void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
-       TileSpec *tiles, int tile_count)
-{
-       f32 texture_coord_buf[24];
-       f32 dx1 = box.MinEdge.X;
-       f32 dy1 = box.MinEdge.Y;
-       f32 dz1 = box.MinEdge.Z;
-       f32 dx2 = box.MaxEdge.X;
-       f32 dy2 = box.MaxEdge.Y;
-       f32 dz2 = box.MaxEdge.Z;
-       box.MinEdge += origin;
-       box.MaxEdge += origin;
-       if (!txc) {
-               generateCuboidTextureCoords(box, texture_coord_buf);
-               txc = texture_coord_buf;
-       }
-       if (!tiles) {
-               tiles = &tile;
-               tile_count = 1;
-       }
-       if (data->m_smooth_lighting) {
-               LightInfo lights[8];
-               for (int j = 0; j < 8; ++j) {
-                       v3f d;
-                       d.X = (j & 4) ? dx2 : dx1;
-                       d.Y = (j & 2) ? dy2 : dy1;
-                       d.Z = (j & 1) ? dz2 : dz1;
-                       lights[j] = blendLight(d);
-               }
-               drawCuboid(box, tiles, tile_count, lights, txc);
-       } else {
-               drawCuboid(box, tiles, tile_count, nullptr, txc);
-       }
-}
-
-void MapblockMeshGenerator::prepareLiquidNodeDrawing()
-{
-       getSpecialTile(0, &tile_liquid_top);
-       getSpecialTile(1, &tile_liquid);
-
-       MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y + 1, p.Z));
-       MapNode nbottom = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y - 1, p.Z));
-       c_flowing = nodedef->getId(f->liquid_alternative_flowing);
-       c_source = nodedef->getId(f->liquid_alternative_source);
-       top_is_same_liquid = (ntop.getContent() == c_flowing) || (ntop.getContent() == c_source);
-       draw_liquid_bottom = (nbottom.getContent() != c_flowing) && (nbottom.getContent() != c_source);
-       if (draw_liquid_bottom) {
-               const ContentFeatures &f2 = nodedef->get(nbottom.getContent());
-               if (f2.solidness > 1)
-                       draw_liquid_bottom = false;
-       }
-
-       if (data->m_smooth_lighting)
-               return; // don't need to pre-compute anything in this case
-
-       if (f->light_source != 0) {
-               // If this liquid emits light and doesn't contain light, draw
-               // it at what it emits, for an increased effect
-               u8 e = decode_light(f->light_source);
-               light = LightPair(std::max(e, light.lightDay), std::max(e, light.lightNight));
-       } else if (nodedef->get(ntop).param_type == CPT_LIGHT) {
-               // Otherwise, use the light of the node on top if possible
-               light = LightPair(getInteriorLight(ntop, 0, nodedef));
-       }
-
-       color_liquid_top = encode_light(light, f->light_source);
-       color = encode_light(light, f->light_source);
-}
-
-void MapblockMeshGenerator::getLiquidNeighborhood()
-{
-       u8 range = rangelim(nodedef->get(c_flowing).liquid_range, 1, 8);
-
-       for (int w = -1; w <= 1; w++)
-       for (int u = -1; u <= 1; u++) {
-               NeighborData &neighbor = liquid_neighbors[w + 1][u + 1];
-               v3s16 p2 = p + v3s16(u, 0, w);
-               MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
-               neighbor.content = n2.getContent();
-               neighbor.level = -0.5 * BS;
-               neighbor.is_same_liquid = false;
-               neighbor.top_is_same_liquid = false;
-
-               if (neighbor.content == CONTENT_IGNORE)
-                       continue;
-
-               if (neighbor.content == c_source) {
-                       neighbor.is_same_liquid = true;
-                       neighbor.level = 0.5 * BS;
-               } else if (neighbor.content == c_flowing) {
-                       neighbor.is_same_liquid = true;
-                       u8 liquid_level = (n2.param2 & LIQUID_LEVEL_MASK);
-                       if (liquid_level <= LIQUID_LEVEL_MAX + 1 - range)
-                               liquid_level = 0;
-                       else
-                               liquid_level -= (LIQUID_LEVEL_MAX + 1 - range);
-                       neighbor.level = (-0.5 + (liquid_level + 0.5) / range) * BS;
-               }
-
-               // Check node above neighbor.
-               // NOTE: This doesn't get executed if neighbor
-               //       doesn't exist
-               p2.Y++;
-               n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
-               if (n2.getContent() == c_source || n2.getContent() == c_flowing)
-                       neighbor.top_is_same_liquid = true;
-       }
-}
-
-void MapblockMeshGenerator::calculateCornerLevels()
-{
-       for (int k = 0; k < 2; k++)
-       for (int i = 0; i < 2; i++)
-               corner_levels[k][i] = getCornerLevel(i, k);
-}
-
-f32 MapblockMeshGenerator::getCornerLevel(int i, int k)
-{
-       float sum = 0;
-       int count = 0;
-       int air_count = 0;
-       for (int dk = 0; dk < 2; dk++)
-       for (int di = 0; di < 2; di++) {
-               NeighborData &neighbor_data = liquid_neighbors[k + dk][i + di];
-               content_t content = neighbor_data.content;
-
-               // If top is liquid, draw starting from top of node
-               if (neighbor_data.top_is_same_liquid)
-                       return 0.5 * BS;
-
-               // Source always has the full height
-               if (content == c_source)
-                       return 0.5 * BS;
-
-               // Flowing liquid has level information
-               if (content == c_flowing) {
-                       sum += neighbor_data.level;
-                       count++;
-               } else if (content == CONTENT_AIR) {
-                       air_count++;
-                       if (air_count >= 2)
-                               return -0.5 * BS + 0.2;
-               }
-       }
-       if (count > 0)
-               return sum / count;
-       return 0;
-}
-
-void MapblockMeshGenerator::drawLiquidSides()
-{
-       struct LiquidFaceDesc {
-               v3s16 dir; // XZ
-               v3s16 p[2]; // XZ only; 1 means +, 0 means -
-       };
-       struct UV {
-               int u, v;
-       };
-       static const LiquidFaceDesc base_faces[4] = {
-               {v3s16( 1, 0,  0), {v3s16(1, 0, 1), v3s16(1, 0, 0)}},
-               {v3s16(-1, 0,  0), {v3s16(0, 0, 0), v3s16(0, 0, 1)}},
-               {v3s16( 0, 0,  1), {v3s16(0, 0, 1), v3s16(1, 0, 1)}},
-               {v3s16( 0, 0, -1), {v3s16(1, 0, 0), v3s16(0, 0, 0)}},
-       };
-       static const UV base_vertices[4] = {
-               {0, 1},
-               {1, 1},
-               {1, 0},
-               {0, 0}
-       };
-
-       for (const auto &face : base_faces) {
-               const NeighborData &neighbor = liquid_neighbors[face.dir.Z + 1][face.dir.X + 1];
-
-               // No face between nodes of the same liquid, unless there is node
-               // at the top to which it should be connected. Again, unless the face
-               // there would be inside the liquid
-               if (neighbor.is_same_liquid) {
-                       if (!top_is_same_liquid)
-                               continue;
-                       if (neighbor.top_is_same_liquid)
-                               continue;
-               }
-
-               const ContentFeatures &neighbor_features = nodedef->get(neighbor.content);
-               // Don't draw face if neighbor is blocking the view
-               if (neighbor_features.solidness == 2)
-                       continue;
-
-               video::S3DVertex vertices[4];
-               for (int j = 0; j < 4; j++) {
-                       const UV &vertex = base_vertices[j];
-                       const v3s16 &base = face.p[vertex.u];
-                       v3f pos;
-                       pos.X = (base.X - 0.5) * BS;
-                       pos.Z = (base.Z - 0.5) * BS;
-                       if (vertex.v)
-                               pos.Y = neighbor.is_same_liquid ? corner_levels[base.Z][base.X] : -0.5 * BS;
-                       else
-                               pos.Y =     !top_is_same_liquid ? corner_levels[base.Z][base.X] :  0.5 * BS;
-                       if (data->m_smooth_lighting)
-                               color = blendLightColor(pos);
-                       pos += origin;
-                       vertices[j] = video::S3DVertex(pos.X, pos.Y, pos.Z, 0, 0, 0, color, vertex.u, vertex.v);
-               };
-               collector->append(tile_liquid, vertices, 4, quad_indices, 6);
-       }
-}
-
-void MapblockMeshGenerator::drawLiquidTop()
-{
-       // To get backface culling right, the vertices need to go
-       // clockwise around the front of the face. And we happened to
-       // calculate corner levels in exact reverse order.
-       static const int corner_resolve[4][2] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}};
-
-       video::S3DVertex vertices[4] = {
-               video::S3DVertex(-BS / 2, 0,  BS / 2, 0, 0, 0, color_liquid_top, 0, 1),
-               video::S3DVertex( BS / 2, 0,  BS / 2, 0, 0, 0, color_liquid_top, 1, 1),
-               video::S3DVertex( BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0),
-               video::S3DVertex(-BS / 2, 0, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0),
-       };
-
-       for (int i = 0; i < 4; i++) {
-               int u = corner_resolve[i][0];
-               int w = corner_resolve[i][1];
-               vertices[i].Pos.Y += corner_levels[w][u];
-               if (data->m_smooth_lighting)
-                       vertices[i].Color = blendLightColor(vertices[i].Pos);
-               vertices[i].Pos += origin;
-       }
-
-       // Default downwards-flowing texture animation goes from
-       // -Z towards +Z, thus the direction is +Z.
-       // Rotate texture to make animation go in flow direction
-       // Positive if liquid moves towards +Z
-       f32 dz = (corner_levels[0][0] + corner_levels[0][1]) -
-                (corner_levels[1][0] + corner_levels[1][1]);
-       // Positive if liquid moves towards +X
-       f32 dx = (corner_levels[0][0] + corner_levels[1][0]) -
-                (corner_levels[0][1] + corner_levels[1][1]);
-       f32 tcoord_angle = atan2(dz, dx) * core::RADTODEG;
-       v2f tcoord_center(0.5, 0.5);
-       v2f tcoord_translate(blockpos_nodes.Z + p.Z, blockpos_nodes.X + p.X);
-       tcoord_translate.rotateBy(tcoord_angle);
-       tcoord_translate.X -= floor(tcoord_translate.X);
-       tcoord_translate.Y -= floor(tcoord_translate.Y);
-
-       for (video::S3DVertex &vertex : vertices) {
-               vertex.TCoords.rotateBy(tcoord_angle, tcoord_center);
-               vertex.TCoords += tcoord_translate;
-       }
-
-       std::swap(vertices[0].TCoords, vertices[2].TCoords);
-
-       collector->append(tile_liquid_top, vertices, 4, quad_indices, 6);
-}
-
-void MapblockMeshGenerator::drawLiquidBottom()
-{
-       video::S3DVertex vertices[4] = {
-               video::S3DVertex(-BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 0, 0),
-               video::S3DVertex( BS / 2, -BS / 2, -BS / 2, 0, 0, 0, color_liquid_top, 1, 0),
-               video::S3DVertex( BS / 2, -BS / 2,  BS / 2, 0, 0, 0, color_liquid_top, 1, 1),
-               video::S3DVertex(-BS / 2, -BS / 2,  BS / 2, 0, 0, 0, color_liquid_top, 0, 1),
-       };
-
-       for (int i = 0; i < 4; i++) {
-               if (data->m_smooth_lighting)
-                       vertices[i].Color = blendLightColor(vertices[i].Pos);
-               vertices[i].Pos += origin;
-       }
-
-       collector->append(tile_liquid_top, vertices, 4, quad_indices, 6);
-}
-
-void MapblockMeshGenerator::drawLiquidNode()
-{
-       prepareLiquidNodeDrawing();
-       getLiquidNeighborhood();
-       calculateCornerLevels();
-       drawLiquidSides();
-       if (!top_is_same_liquid)
-               drawLiquidTop();
-       if (draw_liquid_bottom)
-               drawLiquidBottom();
-}
-
-void MapblockMeshGenerator::drawGlasslikeNode()
-{
-       useTile(0, 0, 0);
-
-       for (int face = 0; face < 6; face++) {
-               // Check this neighbor
-               v3s16 dir = g_6dirs[face];
-               v3s16 neighbor_pos = blockpos_nodes + p + dir;
-               MapNode neighbor = data->m_vmanip.getNodeNoExNoEmerge(neighbor_pos);
-               // Don't make face if neighbor is of same type
-               if (neighbor.getContent() == n.getContent())
-                       continue;
-               // Face at Z-
-               v3f vertices[4] = {
-                       v3f(-BS / 2,  BS / 2, -BS / 2),
-                       v3f( BS / 2,  BS / 2, -BS / 2),
-                       v3f( BS / 2, -BS / 2, -BS / 2),
-                       v3f(-BS / 2, -BS / 2, -BS / 2),
-               };
-
-               for (v3f &vertex : vertices) {
-                       switch (face) {
-                               case D6D_ZP:
-                                       vertex.rotateXZBy(180); break;
-                               case D6D_YP:
-                                       vertex.rotateYZBy( 90); break;
-                               case D6D_XP:
-                                       vertex.rotateXZBy( 90); break;
-                               case D6D_ZN:
-                                       vertex.rotateXZBy(  0); break;
-                               case D6D_YN:
-                                       vertex.rotateYZBy(-90); break;
-                               case D6D_XN:
-                                       vertex.rotateXZBy(-90); break;
-                       }
-               }
-               drawQuad(vertices, dir);
-       }
-}
-
-void MapblockMeshGenerator::drawGlasslikeFramedNode()
-{
-       TileSpec tiles[6];
-       for (int face = 0; face < 6; face++)
-               getTile(g_6dirs[face], &tiles[face]);
-
-       if (!data->m_smooth_lighting)
-               color = encode_light(light, f->light_source);
-
-       TileSpec glass_tiles[6];
-       for (auto &glass_tile : glass_tiles)
-               glass_tile = tiles[4];
-
-       u8 param2 = n.getParam2();
-       bool H_merge = !(param2 & 128);
-       bool V_merge = !(param2 & 64);
-       param2 &= 63;
-
-       static const float a = BS / 2.0f;
-       static const float g = a - 0.03f;
-       static const float b = 0.876f * (BS / 2.0f);
-
-       static const aabb3f frame_edges[FRAMED_EDGE_COUNT] = {
-               aabb3f( b,  b, -a,  a,  a,  a), // y+
-               aabb3f(-a,  b, -a, -b,  a,  a), // y+
-               aabb3f( b, -a, -a,  a, -b,  a), // y-
-               aabb3f(-a, -a, -a, -b, -b,  a), // y-
-               aabb3f( b, -a,  b,  a,  a,  a), // x+
-               aabb3f( b, -a, -a,  a,  a, -b), // x+
-               aabb3f(-a, -a,  b, -b,  a,  a), // x-
-               aabb3f(-a, -a, -a, -b,  a, -b), // x-
-               aabb3f(-a,  b,  b,  a,  a,  a), // z+
-               aabb3f(-a, -a,  b,  a, -b,  a), // z+
-               aabb3f(-a, -a, -a,  a, -b, -b), // z-
-               aabb3f(-a,  b, -a,  a,  a, -b), // z-
-       };
-
-       // tables of neighbour (connect if same type and merge allowed),
-       // checked with g_26dirs
-
-       // 1 = connect, 0 = face visible
-       bool nb[FRAMED_NEIGHBOR_COUNT] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
-
-       // 1 = check
-       static const bool check_nb_vertical [FRAMED_NEIGHBOR_COUNT] =
-               {0,1,0,0,1,0, 0,0,0,0, 0,0,0,0, 0,0,0,0};
-       static const bool check_nb_horizontal [FRAMED_NEIGHBOR_COUNT] =
-               {1,0,1,1,0,1, 0,0,0,0, 1,1,1,1, 0,0,0,0};
-       static const bool check_nb_all [FRAMED_NEIGHBOR_COUNT] =
-               {1,1,1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1};
-       const bool *check_nb = check_nb_all;
-
-       // neighbours checks for frames visibility
-       if (H_merge || V_merge) {
-               if (!H_merge)
-                       check_nb = check_nb_vertical; // vertical-only merge
-               if (!V_merge)
-                       check_nb = check_nb_horizontal; // horizontal-only merge
-               content_t current = n.getContent();
-               for (int i = 0; i < FRAMED_NEIGHBOR_COUNT; i++) {
-                       if (!check_nb[i])
-                               continue;
-                       v3s16 n2p = blockpos_nodes + p + g_26dirs[i];
-                       MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
-                       content_t n2c = n2.getContent();
-                       if (n2c == current)
-                               nb[i] = 1;
-               }
-       }
-
-       // edge visibility
-
-       static const u8 nb_triplet[FRAMED_EDGE_COUNT][3] = {
-               {1, 2,  7}, {1, 5,  6}, {4, 2, 15}, {4, 5, 14},
-               {2, 0, 11}, {2, 3, 13}, {5, 0, 10}, {5, 3, 12},
-               {0, 1,  8}, {0, 4, 16}, {3, 4, 17}, {3, 1,  9},
-       };
-
-       tile = tiles[1];
-       for (int edge = 0; edge < FRAMED_EDGE_COUNT; edge++) {
-               bool edge_invisible;
-               if (nb[nb_triplet[edge][2]])
-                       edge_invisible = nb[nb_triplet[edge][0]] & nb[nb_triplet[edge][1]];
-               else
-                       edge_invisible = nb[nb_triplet[edge][0]] ^ nb[nb_triplet[edge][1]];
-               if (edge_invisible)
-                       continue;
-               drawAutoLightedCuboid(frame_edges[edge]);
-       }
-
-       for (int face = 0; face < 6; face++) {
-               if (nb[face])
-                       continue;
-
-               tile = glass_tiles[face];
-               // Face at Z-
-               v3f vertices[4] = {
-                       v3f(-a,  a, -g),
-                       v3f( a,  a, -g),
-                       v3f( a, -a, -g),
-                       v3f(-a, -a, -g),
-               };
-
-               for (v3f &vertex : vertices) {
-                       switch (face) {
-                               case D6D_ZP:
-                                       vertex.rotateXZBy(180); break;
-                               case D6D_YP:
-                                       vertex.rotateYZBy( 90); break;
-                               case D6D_XP:
-                                       vertex.rotateXZBy( 90); break;
-                               case D6D_ZN:
-                                       vertex.rotateXZBy(  0); break;
-                               case D6D_YN:
-                                       vertex.rotateYZBy(-90); break;
-                               case D6D_XN:
-                                       vertex.rotateXZBy(-90); break;
-                       }
-               }
-               v3s16 dir = g_6dirs[face];
-               drawQuad(vertices, dir);
-       }
-
-       // Optionally render internal liquid level defined by param2
-       // Liquid is textured with 1 tile defined in nodedef 'special_tiles'
-       if (param2 > 0 && f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL &&
-                       f->special_tiles[0].layers[0].texture) {
-               // Internal liquid level has param2 range 0 .. 63,
-               // convert it to -0.5 .. 0.5
-               float vlev = (param2 / 63.0f) * 2.0f - 1.0f;
-               getSpecialTile(0, &tile);
-               drawAutoLightedCuboid(aabb3f(-(nb[5] ? g : b),
-                                            -(nb[4] ? g : b),
-                                            -(nb[3] ? g : b),
-                                             (nb[2] ? g : b),
-                                             (nb[1] ? g : b) * vlev,
-                                             (nb[0] ? g : b)));
-       }
-}
-
-void MapblockMeshGenerator::drawAllfacesNode()
-{
-       static const aabb3f box(-BS / 2, -BS / 2, -BS / 2, BS / 2, BS / 2, BS / 2);
-       useTile(0, 0, 0);
-       drawAutoLightedCuboid(box);
-}
-
-void MapblockMeshGenerator::drawTorchlikeNode()
-{
-       u8 wall = n.getWallMounted(nodedef);
-       u8 tileindex = 0;
-       switch (wall) {
-               case DWM_YP: tileindex = 1; break; // ceiling
-               case DWM_YN: tileindex = 0; break; // floor
-               default:     tileindex = 2; // side (or invalid—should we care?)
-       }
-       useTile(tileindex, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
-
-       float size = BS / 2 * f->visual_scale;
-       v3f vertices[4] = {
-               v3f(-size,  size, 0),
-               v3f( size,  size, 0),
-               v3f( size, -size, 0),
-               v3f(-size, -size, 0),
-       };
-
-       for (v3f &vertex : vertices) {
-               switch (wall) {
-                       case DWM_YP:
-                               vertex.rotateXZBy(-45); break;
-                       case DWM_YN:
-                               vertex.rotateXZBy( 45); break;
-                       case DWM_XP:
-                               vertex.rotateXZBy(  0); break;
-                       case DWM_XN:
-                               vertex.rotateXZBy(180); break;
-                       case DWM_ZP:
-                               vertex.rotateXZBy( 90); break;
-                       case DWM_ZN:
-                               vertex.rotateXZBy(-90); break;
-               }
-       }
-       drawQuad(vertices);
-}
-
-void MapblockMeshGenerator::drawSignlikeNode()
-{
-       u8 wall = n.getWallMounted(nodedef);
-       useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
-       static const float offset = BS / 16;
-       float size = BS / 2 * f->visual_scale;
-       // Wall at X+ of node
-       v3f vertices[4] = {
-               v3f(BS / 2 - offset,  size,  size),
-               v3f(BS / 2 - offset,  size, -size),
-               v3f(BS / 2 - offset, -size, -size),
-               v3f(BS / 2 - offset, -size,  size),
-       };
-
-       for (v3f &vertex : vertices) {
-               switch (wall) {
-                       case DWM_YP:
-                               vertex.rotateXYBy( 90); break;
-                       case DWM_YN:
-                               vertex.rotateXYBy(-90); break;
-                       case DWM_XP:
-                               vertex.rotateXZBy(  0); break;
-                       case DWM_XN:
-                               vertex.rotateXZBy(180); break;
-                       case DWM_ZP:
-                               vertex.rotateXZBy( 90); break;
-                       case DWM_ZN:
-                               vertex.rotateXZBy(-90); break;
-               }
-       }
-       drawQuad(vertices);
-}
-
-void MapblockMeshGenerator::drawPlantlikeQuad(float rotation, float quad_offset,
-       bool offset_top_only)
-{
-       v3f vertices[4] = {
-               v3f(-scale, -BS / 2 + 2.0 * scale * plant_height, 0),
-               v3f( scale, -BS / 2 + 2.0 * scale * plant_height, 0),
-               v3f( scale, -BS / 2, 0),
-               v3f(-scale, -BS / 2, 0),
-       };
-       if (random_offset_Y) {
-               PseudoRandom yrng(face_num++ | p.X << 16 | p.Z << 8 | p.Y << 24);
-               offset.Y = -BS * ((yrng.next() % 16 / 16.0) * 0.125);
-       }
-       int offset_count = offset_top_only ? 2 : 4;
-       for (int i = 0; i < offset_count; i++)
-               vertices[i].Z += quad_offset;
-
-       for (v3f &vertex : vertices) {
-               vertex.rotateXZBy(rotation + rotate_degree);
-               vertex += offset;
-       }
-       drawQuad(vertices, v3s16(0, 0, 0), plant_height);
-}
-
-void MapblockMeshGenerator::drawPlantlike()
-{
-       draw_style = PLANT_STYLE_CROSS;
-       scale = BS / 2 * f->visual_scale;
-       offset = v3f(0, 0, 0);
-       rotate_degree = 0;
-       random_offset_Y = false;
-       face_num = 0;
-       plant_height = 1.0;
-
-       switch (f->param_type_2) {
-       case CPT2_MESHOPTIONS:
-               draw_style = PlantlikeStyle(n.param2 & MO_MASK_STYLE);
-               if (n.param2 & MO_BIT_SCALE_SQRT2)
-                       scale *= 1.41421;
-               if (n.param2 & MO_BIT_RANDOM_OFFSET) {
-                       PseudoRandom rng(p.X << 8 | p.Z | p.Y << 16);
-                       offset.X = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145);
-                       offset.Z = BS * ((rng.next() % 16 / 16.0) * 0.29 - 0.145);
-               }
-               if (n.param2 & MO_BIT_RANDOM_OFFSET_Y)
-                       random_offset_Y = true;
-               break;
-
-       case CPT2_DEGROTATE:
-               rotate_degree = n.param2 * 2;
-               break;
-
-       case CPT2_LEVELED:
-               plant_height = n.param2 / 16.0;
-               break;
-
-       default:
-               break;
-       }
-
-       switch (draw_style) {
-       case PLANT_STYLE_CROSS:
-               drawPlantlikeQuad(46);
-               drawPlantlikeQuad(-44);
-               break;
-
-       case PLANT_STYLE_CROSS2:
-               drawPlantlikeQuad(91);
-               drawPlantlikeQuad(1);
-               break;
-
-       case PLANT_STYLE_STAR:
-               drawPlantlikeQuad(121);
-               drawPlantlikeQuad(241);
-               drawPlantlikeQuad(1);
-               break;
-
-       case PLANT_STYLE_HASH:
-               drawPlantlikeQuad(  1, BS / 4);
-               drawPlantlikeQuad( 91, BS / 4);
-               drawPlantlikeQuad(181, BS / 4);
-               drawPlantlikeQuad(271, BS / 4);
-               break;
-
-       case PLANT_STYLE_HASH2:
-               drawPlantlikeQuad(  1, -BS / 2, true);
-               drawPlantlikeQuad( 91, -BS / 2, true);
-               drawPlantlikeQuad(181, -BS / 2, true);
-               drawPlantlikeQuad(271, -BS / 2, true);
-               break;
-       }
-}
-
-void MapblockMeshGenerator::drawPlantlikeNode()
-{
-       useTile();
-       drawPlantlike();
-}
-
-void MapblockMeshGenerator::drawPlantlikeRootedNode()
-{
-       useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, 0, true);
-       origin += v3f(0.0, BS, 0.0);
-       p.Y++;
-       if (data->m_smooth_lighting) {
-               getSmoothLightFrame();
-       } else {
-               MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
-               light = LightPair(getInteriorLight(ntop, 1, nodedef));
-       }
-       drawPlantlike();
-       p.Y--;
-}
-
-void MapblockMeshGenerator::drawFirelikeQuad(float rotation, float opening_angle,
-       float offset_h, float offset_v)
-{
-       v3f vertices[4] = {
-               v3f(-scale, -BS / 2 + scale * 2, 0),
-               v3f( scale, -BS / 2 + scale * 2, 0),
-               v3f( scale, -BS / 2, 0),
-               v3f(-scale, -BS / 2, 0),
-       };
-
-       for (v3f &vertex : vertices) {
-               vertex.rotateYZBy(opening_angle);
-               vertex.Z += offset_h;
-               vertex.rotateXZBy(rotation);
-               vertex.Y += offset_v;
-       }
-       drawQuad(vertices);
-}
-
-void MapblockMeshGenerator::drawFirelikeNode()
-{
-       useTile();
-       scale = BS / 2 * f->visual_scale;
-
-       // Check for adjacent nodes
-       bool neighbors = false;
-       bool neighbor[6] = {0, 0, 0, 0, 0, 0};
-       content_t current = n.getContent();
-       for (int i = 0; i < 6; i++) {
-               v3s16 n2p = blockpos_nodes + p + g_6dirs[i];
-               MapNode n2 = data->m_vmanip.getNodeNoEx(n2p);
-               content_t n2c = n2.getContent();
-               if (n2c != CONTENT_IGNORE && n2c != CONTENT_AIR && n2c != current) {
-                       neighbor[i] = true;
-                       neighbors = true;
-               }
-       }
-       bool drawBasicFire = neighbor[D6D_YN] || !neighbors;
-       bool drawBottomFire = neighbor[D6D_YP];
-
-       if (drawBasicFire || neighbor[D6D_ZP])
-               drawFirelikeQuad(0, -10, 0.4 * BS);
-       else if (drawBottomFire)
-               drawFirelikeQuad(0, 70, 0.47 * BS, 0.484 * BS);
-
-       if (drawBasicFire || neighbor[D6D_XN])
-               drawFirelikeQuad(90, -10, 0.4 * BS);
-       else if (drawBottomFire)
-               drawFirelikeQuad(90, 70, 0.47 * BS, 0.484 * BS);
-
-       if (drawBasicFire || neighbor[D6D_ZN])
-               drawFirelikeQuad(180, -10, 0.4 * BS);
-       else if (drawBottomFire)
-               drawFirelikeQuad(180, 70, 0.47 * BS, 0.484 * BS);
-
-       if (drawBasicFire || neighbor[D6D_XP])
-               drawFirelikeQuad(270, -10, 0.4 * BS);
-       else if (drawBottomFire)
-               drawFirelikeQuad(270, 70, 0.47 * BS, 0.484 * BS);
-
-       if (drawBasicFire) {
-               drawFirelikeQuad(45, 0, 0.0);
-               drawFirelikeQuad(-45, 0, 0.0);
-       }
-}
-
-void MapblockMeshGenerator::drawFencelikeNode()
-{
-       useTile(0, 0, 0);
-       TileSpec tile_nocrack = tile;
-
-       for (auto &layer : tile_nocrack.layers)
-               layer.material_flags &= ~MATERIAL_FLAG_CRACK;
-
-       // Put wood the right way around in the posts
-       TileSpec tile_rot = tile;
-       tile_rot.rotation = 1;
-
-       static const f32 post_rad = BS / 8;
-       static const f32 bar_rad  = BS / 16;
-       static const f32 bar_len  = BS / 2 - post_rad;
-
-       // The post - always present
-       static const aabb3f post(-post_rad, -BS / 2, -post_rad,
-                                 post_rad,  BS / 2,  post_rad);
-       static const f32 postuv[24] = {
-               0.375, 0.375, 0.625, 0.625,
-               0.375, 0.375, 0.625, 0.625,
-               0.000, 0.000, 0.250, 1.000,
-               0.250, 0.000, 0.500, 1.000,
-               0.500, 0.000, 0.750, 1.000,
-               0.750, 0.000, 1.000, 1.000,
-       };
-       tile = tile_rot;
-       drawAutoLightedCuboid(post, postuv);
-
-       tile = tile_nocrack;
-
-       // Now a section of fence, +X, if there's a post there
-       v3s16 p2 = p;
-       p2.X++;
-       MapNode n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
-       const ContentFeatures *f2 = &nodedef->get(n2);
-       if (f2->drawtype == NDT_FENCELIKE) {
-               static const aabb3f bar_x1(BS / 2 - bar_len,  BS / 4 - bar_rad, -bar_rad,
-                                          BS / 2 + bar_len,  BS / 4 + bar_rad,  bar_rad);
-               static const aabb3f bar_x2(BS / 2 - bar_len, -BS / 4 - bar_rad, -bar_rad,
-                                          BS / 2 + bar_len, -BS / 4 + bar_rad,  bar_rad);
-               static const f32 xrailuv[24] = {
-                       0.000, 0.125, 1.000, 0.250,
-                       0.000, 0.250, 1.000, 0.375,
-                       0.375, 0.375, 0.500, 0.500,
-                       0.625, 0.625, 0.750, 0.750,
-                       0.000, 0.500, 1.000, 0.625,
-                       0.000, 0.875, 1.000, 1.000,
-               };
-               drawAutoLightedCuboid(bar_x1, xrailuv);
-               drawAutoLightedCuboid(bar_x2, xrailuv);
-       }
-
-       // Now a section of fence, +Z, if there's a post there
-       p2 = p;
-       p2.Z++;
-       n2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p2);
-       f2 = &nodedef->get(n2);
-       if (f2->drawtype == NDT_FENCELIKE) {
-               static const aabb3f bar_z1(-bar_rad,  BS / 4 - bar_rad, BS / 2 - bar_len,
-                                           bar_rad,  BS / 4 + bar_rad, BS / 2 + bar_len);
-               static const aabb3f bar_z2(-bar_rad, -BS / 4 - bar_rad, BS / 2 - bar_len,
-                                           bar_rad, -BS / 4 + bar_rad, BS / 2 + bar_len);
-               static const f32 zrailuv[24] = {
-                       0.1875, 0.0625, 0.3125, 0.3125, // cannot rotate; stretch
-                       0.2500, 0.0625, 0.3750, 0.3125, // for wood texture instead
-                       0.0000, 0.5625, 1.0000, 0.6875,
-                       0.0000, 0.3750, 1.0000, 0.5000,
-                       0.3750, 0.3750, 0.5000, 0.5000,
-                       0.6250, 0.6250, 0.7500, 0.7500,
-               };
-               drawAutoLightedCuboid(bar_z1, zrailuv);
-               drawAutoLightedCuboid(bar_z2, zrailuv);
-       }
-}
-
-bool MapblockMeshGenerator::isSameRail(v3s16 dir)
-{
-       MapNode node2 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p + dir);
-       if (node2.getContent() == n.getContent())
-               return true;
-       const ContentFeatures &def2 = nodedef->get(node2);
-       return ((def2.drawtype == NDT_RAILLIKE) &&
-               (def2.getGroup(raillike_groupname) == raillike_group));
-}
-
-void MapblockMeshGenerator::drawRaillikeNode()
-{
-       static const v3s16 direction[4] = {
-               v3s16( 0, 0,  1),
-               v3s16( 0, 0, -1),
-               v3s16(-1, 0,  0),
-               v3s16( 1, 0,  0),
-       };
-       static const int slope_angle[4] = {0, 180, 90, -90};
-
-       enum RailTile {
-               straight,
-               curved,
-               junction,
-               cross,
-       };
-       struct RailDesc {
-               int tile_index;
-               int angle;
-       };
-       static const RailDesc rail_kinds[16] = {
-                                  // +x -x -z +z
-                                  //-------------
-               {straight,   0}, //  .  .  .  .
-               {straight,   0}, //  .  .  . +Z
-               {straight,   0}, //  .  . -Z  .
-               {straight,   0}, //  .  . -Z +Z
-               {straight,  90}, //  . -X  .  .
-               {  curved, 180}, //  . -X  . +Z
-               {  curved, 270}, //  . -X -Z  .
-               {junction, 180}, //  . -X -Z +Z
-               {straight,  90}, // +X  .  .  .
-               {  curved,  90}, // +X  .  . +Z
-               {  curved,   0}, // +X  . -Z  .
-               {junction,   0}, // +X  . -Z +Z
-               {straight,  90}, // +X -X  .  .
-               {junction,  90}, // +X -X  . +Z
-               {junction, 270}, // +X -X -Z  .
-               {   cross,   0}, // +X -X -Z +Z
-       };
-
-       raillike_group = nodedef->get(n).getGroup(raillike_groupname);
-
-       int code = 0;
-       int angle;
-       int tile_index;
-       bool sloped = false;
-       for (int dir = 0; dir < 4; dir++) {
-               bool rail_above = isSameRail(direction[dir] + v3s16(0, 1, 0));
-               if (rail_above) {
-                       sloped = true;
-                       angle = slope_angle[dir];
-               }
-               if (rail_above ||
-                               isSameRail(direction[dir]) ||
-                               isSameRail(direction[dir] + v3s16(0, -1, 0)))
-                       code |= 1 << dir;
-       }
-
-       if (sloped) {
-               tile_index = straight;
-       } else {
-               tile_index = rail_kinds[code].tile_index;
-               angle = rail_kinds[code].angle;
-       }
-
-       useTile(tile_index, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
-
-       static const float offset = BS / 64;
-       static const float size   = BS / 2;
-       float y2 = sloped ? size : -size;
-       v3f vertices[4] = {
-               v3f(-size,    y2 + offset,  size),
-               v3f( size,    y2 + offset,  size),
-               v3f( size, -size + offset, -size),
-               v3f(-size, -size + offset, -size),
-       };
-       if (angle)
-               for (v3f &vertex : vertices)
-                       vertex.rotateXZBy(angle);
-       drawQuad(vertices);
-}
-
-void MapblockMeshGenerator::drawNodeboxNode()
-{
-       static const v3s16 tile_dirs[6] = {
-               v3s16(0, 1, 0),
-               v3s16(0, -1, 0),
-               v3s16(1, 0, 0),
-               v3s16(-1, 0, 0),
-               v3s16(0, 0, 1),
-               v3s16(0, 0, -1)
-       };
-
-       // we have this order for some reason...
-       static const v3s16 connection_dirs[6] = {
-               v3s16( 0,  1,  0), // top
-               v3s16( 0, -1,  0), // bottom
-               v3s16( 0,  0, -1), // front
-               v3s16(-1,  0,  0), // left
-               v3s16( 0,  0,  1), // back
-               v3s16( 1,  0,  0), // right
-       };
-
-       TileSpec tiles[6];
-       for (int face = 0; face < 6; face++) {
-               // Handles facedir rotation for textures
-               getTile(tile_dirs[face], &tiles[face]);
-       }
-
-       // locate possible neighboring nodes to connect to
-       int neighbors_set = 0;
-       if (f->node_box.type == NODEBOX_CONNECTED) {
-               for (int dir = 0; dir != 6; dir++) {
-                       int flag = 1 << dir;
-                       v3s16 p2 = blockpos_nodes + p + connection_dirs[dir];
-                       MapNode n2 = data->m_vmanip.getNodeNoEx(p2);
-                       if (nodedef->nodeboxConnects(n, n2, flag))
-                               neighbors_set |= flag;
-               }
-       }
-
-       std::vector<aabb3f> boxes;
-       n.getNodeBoxes(nodedef, &boxes, neighbors_set);
-       for (const auto &box : boxes)
-               drawAutoLightedCuboid(box, nullptr, tiles, 6);
-}
-
-void MapblockMeshGenerator::drawMeshNode()
-{
-       u8 facedir = 0;
-       scene::IMesh* mesh;
-       bool private_mesh; // as a grab/drop pair is not thread-safe
-
-       if (f->param_type_2 == CPT2_FACEDIR ||
-                       f->param_type_2 == CPT2_COLORED_FACEDIR) {
-               facedir = n.getFaceDir(nodedef);
-       } else if (f->param_type_2 == CPT2_WALLMOUNTED ||
-                       f->param_type_2 == CPT2_COLORED_WALLMOUNTED) {
-               // Convert wallmounted to 6dfacedir.
-               // When cache enabled, it is already converted.
-               facedir = n.getWallMounted(nodedef);
-               if (!enable_mesh_cache)
-                       facedir = wallmounted_to_facedir[facedir];
-       }
-
-       if (!data->m_smooth_lighting && f->mesh_ptr[facedir]) {
-               // use cached meshes
-               private_mesh = false;
-               mesh = f->mesh_ptr[facedir];
-       } else if (f->mesh_ptr[0]) {
-               // no cache, clone and rotate mesh
-               private_mesh = true;
-               mesh = cloneMesh(f->mesh_ptr[0]);
-               rotateMeshBy6dFacedir(mesh, facedir);
-               recalculateBoundingBox(mesh);
-               meshmanip->recalculateNormals(mesh, true, false);
-       } else
-               return;
-
-       int mesh_buffer_count = mesh->getMeshBufferCount();
-       for (int j = 0; j < mesh_buffer_count; j++) {
-               useTile(j);
-               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
-               video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices();
-               int vertex_count = buf->getVertexCount();
-
-               if (data->m_smooth_lighting) {
-                       // Mesh is always private here. So the lighting is applied to each
-                       // vertex right here.
-                       for (int k = 0; k < vertex_count; k++) {
-                               video::S3DVertex &vertex = vertices[k];
-                               vertex.Color = blendLightColor(vertex.Pos, vertex.Normal);
-                               vertex.Pos += origin;
-                       }
-                       collector->append(tile, vertices, vertex_count,
-                               buf->getIndices(), buf->getIndexCount());
-               } else {
-                       // Don't modify the mesh, it may not be private here.
-                       // Instead, let the collector process colors, etc.
-                       collector->append(tile, vertices, vertex_count,
-                               buf->getIndices(), buf->getIndexCount(), origin,
-                               color, f->light_source);
-               }
-       }
-       if (private_mesh)
-               mesh->drop();
-}
-
-// also called when the drawtype is known but should have been pre-converted
-void MapblockMeshGenerator::errorUnknownDrawtype()
-{
-       infostream << "Got drawtype " << f->drawtype << std::endl;
-       FATAL_ERROR("Unknown drawtype");
-}
-
-void MapblockMeshGenerator::drawNode()
-{
-       // skip some drawtypes early
-       switch (f->drawtype) {
-               case NDT_NORMAL:   // Drawn by MapBlockMesh
-               case NDT_AIRLIKE:  // Not drawn at all
-               case NDT_LIQUID:   // Drawn by MapBlockMesh
-                       return;
-               default:
-                       break;
-       }
-       origin = intToFloat(p, BS);
-       if (data->m_smooth_lighting)
-               getSmoothLightFrame();
-       else
-               light = LightPair(getInteriorLight(n, 1, nodedef));
-       switch (f->drawtype) {
-               case NDT_FLOWINGLIQUID:     drawLiquidNode(); break;
-               case NDT_GLASSLIKE:         drawGlasslikeNode(); break;
-               case NDT_GLASSLIKE_FRAMED:  drawGlasslikeFramedNode(); break;
-               case NDT_ALLFACES:          drawAllfacesNode(); break;
-               case NDT_TORCHLIKE:         drawTorchlikeNode(); break;
-               case NDT_SIGNLIKE:          drawSignlikeNode(); break;
-               case NDT_PLANTLIKE:         drawPlantlikeNode(); break;
-               case NDT_PLANTLIKE_ROOTED:  drawPlantlikeRootedNode(); break;
-               case NDT_FIRELIKE:          drawFirelikeNode(); break;
-               case NDT_FENCELIKE:         drawFencelikeNode(); break;
-               case NDT_RAILLIKE:          drawRaillikeNode(); break;
-               case NDT_NODEBOX:           drawNodeboxNode(); break;
-               case NDT_MESH:              drawMeshNode(); break;
-               default:                    errorUnknownDrawtype(); break;
-       }
-}
-
-/*
-       TODO: Fix alpha blending for special nodes
-       Currently only the last element rendered is blended correct
-*/
-void MapblockMeshGenerator::generate()
-{
-       for (p.Z = 0; p.Z < MAP_BLOCKSIZE; p.Z++)
-       for (p.Y = 0; p.Y < MAP_BLOCKSIZE; p.Y++)
-       for (p.X = 0; p.X < MAP_BLOCKSIZE; p.X++) {
-               n = data->m_vmanip.getNodeNoEx(blockpos_nodes + p);
-               f = &nodedef->get(n);
-               drawNode();
-       }
-}
-
-void MapblockMeshGenerator::renderSingle(content_t node)
-{
-       p = {0, 0, 0};
-       n = MapNode(node, 0xff, 0x00);
-       f = &nodedef->get(n);
-       drawNode();
-}
diff --git a/src/content_mapblock.h b/src/content_mapblock.h
deleted file mode 100644 (file)
index 97947cd..0000000
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include "nodedef.h"
-#include <IMeshManipulator.h>
-
-struct MeshMakeData;
-struct MeshCollector;
-
-struct LightPair {
-       u8 lightDay;
-       u8 lightNight;
-
-       LightPair() = default;
-       explicit LightPair(u16 value) : lightDay(value & 0xff), lightNight(value >> 8) {}
-       LightPair(u8 valueA, u8 valueB) : lightDay(valueA), lightNight(valueB) {}
-       LightPair(float valueA, float valueB) :
-               lightDay(core::clamp(core::round32(valueA), 0, 255)),
-               lightNight(core::clamp(core::round32(valueB), 0, 255)) {}
-       operator u16() const { return lightDay | lightNight << 8; }
-};
-
-struct LightInfo {
-       float light_day;
-       float light_night;
-       float light_boosted;
-
-       LightPair getPair(float sunlight_boost = 0.0) const
-       {
-               return LightPair(
-                       (1 - sunlight_boost) * light_day
-                       + sunlight_boost * light_boosted,
-                       light_night);
-       }
-};
-
-struct LightFrame {
-       f32 lightsDay[8];
-       f32 lightsNight[8];
-       bool sunlight[8];
-};
-
-class MapblockMeshGenerator
-{
-public:
-       MeshMakeData *data;
-       MeshCollector *collector;
-
-       const NodeDefManager *nodedef;
-       scene::IMeshManipulator *meshmanip;
-
-// options
-       bool enable_mesh_cache;
-
-// current node
-       v3s16 blockpos_nodes;
-       v3s16 p;
-       v3f origin;
-       MapNode n;
-       const ContentFeatures *f;
-       LightPair light;
-       LightFrame frame;
-       video::SColor color;
-       TileSpec tile;
-       float scale;
-
-// lighting
-       void getSmoothLightFrame();
-       LightInfo blendLight(const v3f &vertex_pos);
-       video::SColor blendLightColor(const v3f &vertex_pos);
-       video::SColor blendLightColor(const v3f &vertex_pos, const v3f &vertex_normal);
-
-       void useTile(int index = 0, u8 set_flags = MATERIAL_FLAG_CRACK_OVERLAY,
-               u8 reset_flags = 0, bool special = false);
-       void getTile(int index, TileSpec *tile);
-       void getTile(v3s16 direction, TileSpec *tile);
-       void getSpecialTile(int index, TileSpec *tile, bool apply_crack = false);
-
-// face drawing
-       void drawQuad(v3f *vertices, const v3s16 &normal = v3s16(0, 0, 0),
-               float vertical_tiling = 1.0);
-
-// cuboid drawing!
-       void drawCuboid(const aabb3f &box, TileSpec *tiles, int tilecount,
-               const LightInfo *lights , const f32 *txc);
-       void generateCuboidTextureCoords(aabb3f const &box, f32 *coords);
-       void drawAutoLightedCuboid(aabb3f box, const f32 *txc = NULL,
-               TileSpec *tiles = NULL, int tile_count = 0);
-
-// liquid-specific
-       bool top_is_same_liquid;
-       bool draw_liquid_bottom;
-       TileSpec tile_liquid;
-       TileSpec tile_liquid_top;
-       content_t c_flowing;
-       content_t c_source;
-       video::SColor color_liquid_top;
-       struct NeighborData {
-               f32 level;
-               content_t content;
-               bool is_same_liquid;
-               bool top_is_same_liquid;
-       };
-       NeighborData liquid_neighbors[3][3];
-       f32 corner_levels[2][2];
-
-       void prepareLiquidNodeDrawing();
-       void getLiquidNeighborhood();
-       void calculateCornerLevels();
-       f32 getCornerLevel(int i, int k);
-       void drawLiquidSides();
-       void drawLiquidTop();
-       void drawLiquidBottom();
-
-// raillike-specific
-       // name of the group that enables connecting to raillike nodes of different kind
-       static const std::string raillike_groupname;
-       int raillike_group;
-       bool isSameRail(v3s16 dir);
-
-// plantlike-specific
-       PlantlikeStyle draw_style;
-       v3f offset;
-       int rotate_degree;
-       bool random_offset_Y;
-       int face_num;
-       float plant_height;
-
-       void drawPlantlikeQuad(float rotation, float quad_offset = 0,
-               bool offset_top_only = false);
-       void drawPlantlike();
-
-// firelike-specific
-       void drawFirelikeQuad(float rotation, float opening_angle,
-               float offset_h, float offset_v = 0.0);
-
-// drawtypes
-       void drawLiquidNode();
-       void drawGlasslikeNode();
-       void drawGlasslikeFramedNode();
-       void drawAllfacesNode();
-       void drawTorchlikeNode();
-       void drawSignlikeNode();
-       void drawPlantlikeNode();
-       void drawPlantlikeRootedNode();
-       void drawFirelikeNode();
-       void drawFencelikeNode();
-       void drawRaillikeNode();
-       void drawNodeboxNode();
-       void drawMeshNode();
-
-// common
-       void errorUnknownDrawtype();
-       void drawNode();
-
-public:
-       MapblockMeshGenerator(MeshMakeData *input, MeshCollector *output);
-       void generate();
-       void renderSingle(content_t node);
-};
diff --git a/src/filecache.cpp b/src/filecache.cpp
deleted file mode 100644 (file)
index 3d1b302..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2013 Jonathan Neuschäfer <j.neuschaefer@gmx.net>
-
-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 "filecache.h"
-
-#include "network/networkprotocol.h"
-#include "log.h"
-#include "filesys.h"
-#include <string>
-#include <iostream>
-#include <fstream>
-#include <cstdlib>
-
-bool FileCache::loadByPath(const std::string &path, std::ostream &os)
-{
-       std::ifstream fis(path.c_str(), std::ios_base::binary);
-
-       if(!fis.good()){
-               verbosestream<<"FileCache: File not found in cache: "
-                               <<path<<std::endl;
-               return false;
-       }
-
-       bool bad = false;
-       for(;;){
-               char buf[1024];
-               fis.read(buf, 1024);
-               std::streamsize len = fis.gcount();
-               os.write(buf, len);
-               if(fis.eof())
-                       break;
-               if(!fis.good()){
-                       bad = true;
-                       break;
-               }
-       }
-       if(bad){
-               errorstream<<"FileCache: Failed to read file from cache: \""
-                               <<path<<"\""<<std::endl;
-       }
-
-       return !bad;
-}
-
-bool FileCache::updateByPath(const std::string &path, const std::string &data)
-{
-       std::ofstream file(path.c_str(), std::ios_base::binary |
-                       std::ios_base::trunc);
-
-       if(!file.good())
-       {
-               errorstream<<"FileCache: Can't write to file at "
-                               <<path<<std::endl;
-               return false;
-       }
-
-       file.write(data.c_str(), data.length());
-       file.close();
-
-       return !file.fail();
-}
-
-bool FileCache::update(const std::string &name, const std::string &data)
-{
-       std::string path = m_dir + DIR_DELIM + name;
-       return updateByPath(path, data);
-}
-bool FileCache::load(const std::string &name, std::ostream &os)
-{
-       std::string path = m_dir + DIR_DELIM + name;
-       return loadByPath(path, os);
-}
diff --git a/src/filecache.h b/src/filecache.h
deleted file mode 100644 (file)
index 96e4c8b..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2013 Jonathan Neuschäfer <j.neuschaefer@gmx.net>
-
-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.
-*/
-
-#pragma once
-
-#include <iostream>
-#include <string>
-
-class FileCache
-{
-public:
-       /*
-               'dir' is the file cache directory to use.
-       */
-       FileCache(const std::string &dir) : m_dir(dir) {}
-
-       bool update(const std::string &name, const std::string &data);
-       bool load(const std::string &name, std::ostream &os);
-
-private:
-       std::string m_dir;
-
-       bool loadByPath(const std::string &path, std::ostream &os);
-       bool updateByPath(const std::string &path, const std::string &data);
-};
diff --git a/src/fontengine.cpp b/src/fontengine.cpp
deleted file mode 100644 (file)
index dc98fb1..0000000
+++ /dev/null
@@ -1,505 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2014 sapier <sapier at gmx dot net>
-
-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 "fontengine.h"
-#include <cmath>
-#include "client/renderingengine.h"
-#include "config.h"
-#include "porting.h"
-#include "filesys.h"
-
-#if USE_FREETYPE
-#include "gettext.h"
-#include "irrlicht_changes/CGUITTFont.h"
-#endif
-
-/** maximum size distance for getting a "similar" font size */
-#define MAX_FONT_SIZE_OFFSET 10
-
-/** reference to access font engine, has to be initialized by main */
-FontEngine* g_fontengine = NULL;
-
-/** callback to be used on change of font size setting */
-static void font_setting_changed(const std::string &name, void *userdata)
-{
-       g_fontengine->readSettings();
-}
-
-/******************************************************************************/
-FontEngine::FontEngine(Settings* main_settings, gui::IGUIEnvironment* env) :
-       m_settings(main_settings),
-       m_env(env)
-{
-
-       for (u32 &i : m_default_size) {
-               i = (FontMode) FONT_SIZE_UNSPECIFIED;
-       }
-
-       assert(m_settings != NULL); // pre-condition
-       assert(m_env != NULL); // pre-condition
-       assert(m_env->getSkin() != NULL); // pre-condition
-
-       m_currentMode = FM_Simple;
-
-#if USE_FREETYPE
-       if (g_settings->getBool("freetype")) {
-               m_default_size[FM_Standard] = m_settings->getU16("font_size");
-               m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size");
-               m_default_size[FM_Mono]     = m_settings->getU16("mono_font_size");
-
-               if (is_yes(gettext("needs_fallback_font"))) {
-                       m_currentMode = FM_Fallback;
-               }
-               else {
-                       m_currentMode = FM_Standard;
-               }
-       }
-
-       // having freetype but not using it is quite a strange case so we need to do
-       // special handling for it
-       if (m_currentMode == FM_Simple) {
-               std::stringstream fontsize;
-               fontsize << DEFAULT_FONT_SIZE;
-               m_settings->setDefault("font_size", fontsize.str());
-               m_settings->setDefault("mono_font_size", fontsize.str());
-       }
-#endif
-
-       m_default_size[FM_Simple]       = m_settings->getU16("font_size");
-       m_default_size[FM_SimpleMono]   = m_settings->getU16("mono_font_size");
-
-       updateSkin();
-
-       if (m_currentMode == FM_Standard) {
-               m_settings->registerChangedCallback("font_size", font_setting_changed, NULL);
-               m_settings->registerChangedCallback("font_path", font_setting_changed, NULL);
-               m_settings->registerChangedCallback("font_shadow", font_setting_changed, NULL);
-               m_settings->registerChangedCallback("font_shadow_alpha", font_setting_changed, NULL);
-       }
-       else if (m_currentMode == FM_Fallback) {
-               m_settings->registerChangedCallback("fallback_font_size", font_setting_changed, NULL);
-               m_settings->registerChangedCallback("fallback_font_path", font_setting_changed, NULL);
-               m_settings->registerChangedCallback("fallback_font_shadow", font_setting_changed, NULL);
-               m_settings->registerChangedCallback("fallback_font_shadow_alpha", font_setting_changed, NULL);
-       }
-
-       m_settings->registerChangedCallback("mono_font_path", font_setting_changed, NULL);
-       m_settings->registerChangedCallback("mono_font_size", font_setting_changed, NULL);
-       m_settings->registerChangedCallback("screen_dpi", font_setting_changed, NULL);
-       m_settings->registerChangedCallback("gui_scaling", font_setting_changed, NULL);
-}
-
-/******************************************************************************/
-FontEngine::~FontEngine()
-{
-       cleanCache();
-}
-
-/******************************************************************************/
-void FontEngine::cleanCache()
-{
-       for (auto &font_cache_it : m_font_cache) {
-
-               for (auto &font_it : font_cache_it) {
-                       font_it.second->drop();
-                       font_it.second = NULL;
-               }
-               font_cache_it.clear();
-       }
-}
-
-/******************************************************************************/
-irr::gui::IGUIFont* FontEngine::getFont(unsigned int font_size, FontMode mode)
-{
-       if (mode == FM_Unspecified) {
-               mode = m_currentMode;
-       }
-       else if ((mode == FM_Mono) && (m_currentMode == FM_Simple)) {
-               mode = FM_SimpleMono;
-       }
-
-       if (font_size == FONT_SIZE_UNSPECIFIED) {
-               font_size = m_default_size[mode];
-       }
-
-       if ((font_size == m_lastSize) && (mode == m_lastMode)) {
-               return m_lastFont;
-       }
-
-       if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) {
-               initFont(font_size, mode);
-       }
-
-       if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) {
-               return NULL;
-       }
-
-       m_lastSize = font_size;
-       m_lastMode = mode;
-       m_lastFont = m_font_cache[mode][font_size];
-
-       return m_font_cache[mode][font_size];
-}
-
-/******************************************************************************/
-unsigned int FontEngine::getTextHeight(unsigned int font_size, FontMode mode)
-{
-       irr::gui::IGUIFont* font = getFont(font_size, mode);
-
-       // use current skin font as fallback
-       if (font == NULL) {
-               font = m_env->getSkin()->getFont();
-       }
-       FATAL_ERROR_IF(font == NULL, "Could not get skin font");
-
-       return font->getDimension(L"Some unimportant example String").Height;
-}
-
-/******************************************************************************/
-unsigned int FontEngine::getTextWidth(const std::wstring& text,
-               unsigned int font_size, FontMode mode)
-{
-       irr::gui::IGUIFont* font = getFont(font_size, mode);
-
-       // use current skin font as fallback
-       if (font == NULL) {
-               font = m_env->getSkin()->getFont();
-       }
-       FATAL_ERROR_IF(font == NULL, "Could not get font");
-
-       return font->getDimension(text.c_str()).Width;
-}
-
-
-/** get line height for a specific font (including empty room between lines) */
-unsigned int FontEngine::getLineHeight(unsigned int font_size, FontMode mode)
-{
-       irr::gui::IGUIFont* font = getFont(font_size, mode);
-
-       // use current skin font as fallback
-       if (font == NULL) {
-               font = m_env->getSkin()->getFont();
-       }
-       FATAL_ERROR_IF(font == NULL, "Could not get font");
-
-       return font->getDimension(L"Some unimportant example String").Height
-                       + font->getKerningHeight();
-}
-
-/******************************************************************************/
-unsigned int FontEngine::getDefaultFontSize()
-{
-       return m_default_size[m_currentMode];
-}
-
-/******************************************************************************/
-void FontEngine::readSettings()
-{
-#if USE_FREETYPE
-       if (g_settings->getBool("freetype")) {
-               m_default_size[FM_Standard] = m_settings->getU16("font_size");
-               m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size");
-               m_default_size[FM_Mono]     = m_settings->getU16("mono_font_size");
-
-               if (is_yes(gettext("needs_fallback_font"))) {
-                       m_currentMode = FM_Fallback;
-               }
-               else {
-                       m_currentMode = FM_Standard;
-               }
-       }
-#endif
-       m_default_size[FM_Simple]       = m_settings->getU16("font_size");
-       m_default_size[FM_SimpleMono]   = m_settings->getU16("mono_font_size");
-
-       cleanCache();
-       updateFontCache();
-       updateSkin();
-}
-
-/******************************************************************************/
-void FontEngine::updateSkin()
-{
-       gui::IGUIFont *font = getFont();
-
-       if (font)
-               m_env->getSkin()->setFont(font);
-       else
-               errorstream << "FontEngine: Default font file: " <<
-                               "\n\t\"" << m_settings->get("font_path") << "\"" <<
-                               "\n\trequired for current screen configuration was not found" <<
-                               " or was invalid file format." <<
-                               "\n\tUsing irrlicht default font." << std::endl;
-
-       // If we did fail to create a font our own make irrlicht find a default one
-       font = m_env->getSkin()->getFont();
-       FATAL_ERROR_IF(font == NULL, "Could not create/get font");
-
-       u32 text_height = font->getDimension(L"Hello, world!").Height;
-       infostream << "text_height=" << text_height << std::endl;
-}
-
-/******************************************************************************/
-void FontEngine::updateFontCache()
-{
-       /* the only font to be initialized is default one,
-        * all others are re-initialized on demand */
-       initFont(m_default_size[m_currentMode], m_currentMode);
-
-       /* reset font quick access */
-       m_lastMode = FM_Unspecified;
-       m_lastSize = 0;
-       m_lastFont = NULL;
-}
-
-/******************************************************************************/
-void FontEngine::initFont(unsigned int basesize, FontMode mode)
-{
-
-       std::string font_config_prefix;
-
-       if (mode == FM_Unspecified) {
-               mode = m_currentMode;
-       }
-
-       switch (mode) {
-
-               case FM_Standard:
-                       font_config_prefix = "";
-                       break;
-
-               case FM_Fallback:
-                       font_config_prefix = "fallback_";
-                       break;
-
-               case FM_Mono:
-                       font_config_prefix = "mono_";
-                       if (m_currentMode == FM_Simple)
-                               mode = FM_SimpleMono;
-                       break;
-
-               case FM_Simple: /* Fallthrough */
-               case FM_SimpleMono: /* Fallthrough */
-               default:
-                       font_config_prefix = "";
-
-       }
-
-       if (m_font_cache[mode].find(basesize) != m_font_cache[mode].end())
-               return;
-
-       if ((mode == FM_Simple) || (mode == FM_SimpleMono)) {
-               initSimpleFont(basesize, mode);
-               return;
-       }
-#if USE_FREETYPE
-       else {
-               if (!is_yes(m_settings->get("freetype"))) {
-                       return;
-               }
-               u32 size = std::floor(RenderingEngine::getDisplayDensity() *
-                               m_settings->getFloat("gui_scaling") * basesize);
-               u32 font_shadow       = 0;
-               u32 font_shadow_alpha = 0;
-
-               try {
-                       font_shadow =
-                                       g_settings->getU16(font_config_prefix + "font_shadow");
-               } catch (SettingNotFoundException&) {}
-               try {
-                       font_shadow_alpha =
-                                       g_settings->getU16(font_config_prefix + "font_shadow_alpha");
-               } catch (SettingNotFoundException&) {}
-
-               std::string font_path = g_settings->get(font_config_prefix + "font_path");
-
-               irr::gui::IGUIFont* font = gui::CGUITTFont::createTTFont(m_env,
-                               font_path.c_str(), size, true, true, font_shadow,
-                               font_shadow_alpha);
-
-               if (font) {
-                       m_font_cache[mode][basesize] = font;
-                       return;
-               }
-
-               if (font_config_prefix == "mono_") {
-                       const std::string &mono_font_path = m_settings->getDefault("mono_font_path");
-
-                       if (font_path != mono_font_path) {
-                               // try original mono font
-                               errorstream << "FontEngine: failed to load custom mono "
-                                               "font: " << font_path << ", trying to fall back to "
-                                               "original mono font" << std::endl;
-
-                               font = gui::CGUITTFont::createTTFont(m_env,
-                                       mono_font_path.c_str(), size, true, true,
-                                       font_shadow, font_shadow_alpha);
-
-                               if (font) {
-                                       m_font_cache[mode][basesize] = font;
-                                       return;
-                               }
-                       }
-               } else {
-                       // try fallback font
-                       errorstream << "FontEngine: failed to load: " << font_path <<
-                                       ", trying to fall back to fallback font" << std::endl;
-
-                       font_path = g_settings->get(font_config_prefix + "fallback_font_path");
-
-                       font = gui::CGUITTFont::createTTFont(m_env,
-                               font_path.c_str(), size, true, true, font_shadow,
-                               font_shadow_alpha);
-
-                       if (font) {
-                               m_font_cache[mode][basesize] = font;
-                               return;
-                       }
-
-                       const std::string &fallback_font_path = m_settings->getDefault("fallback_font_path");
-
-                       if (font_path != fallback_font_path) {
-                               // try original fallback font
-                               errorstream << "FontEngine: failed to load custom fallback "
-                                               "font: " << font_path << ", trying to fall back to "
-                                               "original fallback font" << std::endl;
-
-                               font = gui::CGUITTFont::createTTFont(m_env,
-                                       fallback_font_path.c_str(), size, true, true,
-                                       font_shadow, font_shadow_alpha);
-
-                               if (font) {
-                                       m_font_cache[mode][basesize] = font;
-                                       return;
-                               }
-                       }
-               }
-
-               // give up
-               errorstream << "FontEngine: failed to load freetype font: "
-                               << font_path << std::endl;
-               errorstream << "minetest can not continue without a valid font. "
-                               "Please correct the 'font_path' setting or install the font "
-                               "file in the proper location" << std::endl;
-               abort();
-       }
-#endif
-}
-
-/** initialize a font without freetype */
-void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode)
-{
-       assert(mode == FM_Simple || mode == FM_SimpleMono); // pre-condition
-
-       std::string font_path;
-       if (mode == FM_Simple) {
-               font_path = m_settings->get("font_path");
-       } else {
-               font_path = m_settings->get("mono_font_path");
-       }
-       std::string basename = font_path;
-       std::string ending = font_path.substr(font_path.length() -4);
-
-       if (ending == ".ttf") {
-               errorstream << "FontEngine: Not trying to open \"" << font_path
-                               << "\" which seems to be a truetype font." << std::endl;
-               return;
-       }
-
-       if ((ending == ".xml") || (ending == ".png")) {
-               basename = font_path.substr(0,font_path.length()-4);
-       }
-
-       if (basesize == FONT_SIZE_UNSPECIFIED)
-               basesize = DEFAULT_FONT_SIZE;
-
-       u32 size = std::floor(
-                       RenderingEngine::getDisplayDensity() *
-                       m_settings->getFloat("gui_scaling") *
-                       basesize);
-
-       irr::gui::IGUIFont* font = NULL;
-
-       for(unsigned int offset = 0; offset < MAX_FONT_SIZE_OFFSET; offset++) {
-
-               // try opening positive offset
-               std::stringstream fontsize_plus_png;
-               fontsize_plus_png << basename << "_" << (size + offset) << ".png";
-
-               if (fs::PathExists(fontsize_plus_png.str())) {
-                       font = m_env->getFont(fontsize_plus_png.str().c_str());
-
-                       if (font) {
-                               verbosestream << "FontEngine: found font: " << fontsize_plus_png.str() << std::endl;
-                               break;
-                       }
-               }
-
-               std::stringstream fontsize_plus_xml;
-               fontsize_plus_xml << basename << "_" << (size + offset) << ".xml";
-
-               if (fs::PathExists(fontsize_plus_xml.str())) {
-                       font = m_env->getFont(fontsize_plus_xml.str().c_str());
-
-                       if (font) {
-                               verbosestream << "FontEngine: found font: " << fontsize_plus_xml.str() << std::endl;
-                               break;
-                       }
-               }
-
-               // try negative offset
-               std::stringstream fontsize_minus_png;
-               fontsize_minus_png << basename << "_" << (size - offset) << ".png";
-
-               if (fs::PathExists(fontsize_minus_png.str())) {
-                       font = m_env->getFont(fontsize_minus_png.str().c_str());
-
-                       if (font) {
-                               verbosestream << "FontEngine: found font: " << fontsize_minus_png.str() << std::endl;
-                               break;
-                       }
-               }
-
-               std::stringstream fontsize_minus_xml;
-               fontsize_minus_xml << basename << "_" << (size - offset) << ".xml";
-
-               if (fs::PathExists(fontsize_minus_xml.str())) {
-                       font = m_env->getFont(fontsize_minus_xml.str().c_str());
-
-                       if (font) {
-                               verbosestream << "FontEngine: found font: " << fontsize_minus_xml.str() << std::endl;
-                               break;
-                       }
-               }
-       }
-
-       // try name direct
-       if (font == NULL) {
-               if (fs::PathExists(font_path)) {
-                       font = m_env->getFont(font_path.c_str());
-                       if (font)
-                               verbosestream << "FontEngine: found font: " << font_path << std::endl;
-               }
-       }
-
-       if (font) {
-               font->grab();
-               m_font_cache[mode][basesize] = font;
-       }
-}
diff --git a/src/fontengine.h b/src/fontengine.h
deleted file mode 100644 (file)
index a75618f..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2014 sapier <sapier at gmx dot net>
-
-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.
-*/
-
-#pragma once
-
-#include <map>
-#include <vector>
-#include "util/basic_macros.h"
-#include <IGUIFont.h>
-#include <IGUISkin.h>
-#include <IGUIEnvironment.h>
-#include "settings.h"
-
-#define FONT_SIZE_UNSPECIFIED 0xFFFFFFFF
-
-enum FontMode {
-       FM_Standard = 0,
-       FM_Mono,
-       FM_Fallback,
-       FM_Simple,
-       FM_SimpleMono,
-       FM_MaxMode,
-       FM_Unspecified
-};
-
-class FontEngine
-{
-public:
-
-       FontEngine(Settings* main_settings, gui::IGUIEnvironment* env);
-
-       ~FontEngine();
-
-       /** get Font */
-       irr::gui::IGUIFont* getFont(unsigned int font_size=FONT_SIZE_UNSPECIFIED,
-                       FontMode mode=FM_Unspecified);
-
-       /** get text height for a specific font */
-       unsigned int getTextHeight(unsigned int font_size=FONT_SIZE_UNSPECIFIED,
-                       FontMode mode=FM_Unspecified);
-
-       /** get text width if a text for a specific font */
-       unsigned int getTextWidth(const std::string& text,
-                       unsigned int font_size=FONT_SIZE_UNSPECIFIED,
-                       FontMode mode=FM_Unspecified)
-       {
-               return getTextWidth(utf8_to_wide(text));
-       }
-
-       /** get text width if a text for a specific font */
-       unsigned int getTextWidth(const std::wstring& text,
-                       unsigned int font_size=FONT_SIZE_UNSPECIFIED,
-                       FontMode mode=FM_Unspecified);
-
-       /** get line height for a specific font (including empty room between lines) */
-       unsigned int getLineHeight(unsigned int font_size=FONT_SIZE_UNSPECIFIED,
-                       FontMode mode=FM_Unspecified);
-
-       /** get default font size */
-       unsigned int getDefaultFontSize();
-
-       /** initialize font engine */
-       void initialize(Settings* main_settings, gui::IGUIEnvironment* env);
-
-       /** update internal parameters from settings */
-       void readSettings();
-
-private:
-       /** update content of font cache in case of a setting change made it invalid */
-       void updateFontCache();
-
-       /** initialize a new font */
-       void initFont(unsigned int basesize, FontMode mode=FM_Unspecified);
-
-       /** initialize a font without freetype */
-       void initSimpleFont(unsigned int basesize, FontMode mode);
-
-       /** update current minetest skin with font changes */
-       void updateSkin();
-
-       /** clean cache */
-       void cleanCache();
-
-       /** pointer to settings for registering callbacks or reading config */
-       Settings* m_settings = nullptr;
-
-       /** pointer to irrlicht gui environment */
-       gui::IGUIEnvironment* m_env = nullptr;
-
-       /** internal storage for caching fonts of different size */
-       std::map<unsigned int, irr::gui::IGUIFont*> m_font_cache[FM_MaxMode];
-
-       /** default font size to use */
-       unsigned int m_default_size[FM_MaxMode];
-
-       /** current font engine mode */
-       FontMode m_currentMode = FM_Standard;
-
-       /** font mode of last request */
-       FontMode m_lastMode;
-
-       /** size of last request */
-       unsigned int m_lastSize = 0;
-
-       /** last font returned */
-       irr::gui::IGUIFont* m_lastFont = nullptr;
-
-       DISABLE_CLASS_COPY(FontEngine);
-};
-
-/** interface to access main font engine*/
-extern FontEngine* g_fontengine;
diff --git a/src/game.cpp b/src/game.cpp
deleted file mode 100644 (file)
index 6cf6723..0000000
+++ /dev/null
@@ -1,4218 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 "game.h"
-
-#include <iomanip>
-#include <cmath>
-#include "client/renderingengine.h"
-#include "camera.h"
-#include "client.h"
-#include "client/clientevent.h"
-#include "client/gameui.h"
-#include "client/inputhandler.h"
-#include "client/tile.h"     // For TextureSource
-#include "client/keys.h"
-#include "client/joystick_controller.h"
-#include "clientmap.h"
-#include "clouds.h"
-#include "config.h"
-#include "content_cao.h"
-#include "client/event_manager.h"
-#include "fontengine.h"
-#include "itemdef.h"
-#include "log.h"
-#include "filesys.h"
-#include "gettext.h"
-#include "gui/guiChatConsole.h"
-#include "gui/guiConfirmRegistration.h"
-#include "gui/guiFormSpecMenu.h"
-#include "gui/guiKeyChangeMenu.h"
-#include "gui/guiPasswordChange.h"
-#include "gui/guiVolumeChange.h"
-#include "gui/mainmenumanager.h"
-#include "gui/profilergraph.h"
-#include "mapblock.h"
-#include "minimap.h"
-#include "nodedef.h"         // Needed for determining pointing to nodes
-#include "nodemetadata.h"
-#include "particles.h"
-#include "porting.h"
-#include "profiler.h"
-#include "quicktune_shortcutter.h"
-#include "raycast.h"
-#include "server.h"
-#include "settings.h"
-#include "shader.h"
-#include "sky.h"
-#include "translation.h"
-#include "util/basic_macros.h"
-#include "util/directiontables.h"
-#include "util/pointedthing.h"
-#include "irrlicht_changes/static_text.h"
-#include "version.h"
-#include "script/scripting_client.h"
-
-#if USE_SOUND
-       #include "client/sound_openal.h"
-#else
-       #include "client/sound.h"
-#endif
-/*
-       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;
-                       }
-
-                       if (fields.find("quit") != fields.end()) {
-                               return;
-                       }
-
-                       if (fields.find("btn_continue") != fields.end()) {
-                               return;
-                       }
-               }
-
-               if (m_formname == "MT_DEATH_SCREEN") {
-                       assert(m_client != 0);
-                       m_client->sendRespawn();
-                       return;
-               }
-
-               if (m_client && m_client->moddingEnabled())
-                       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;
-
-       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)
-       {
-       }
-
-       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);
-               }
-       }
-
-       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;
-       }
-
-       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;
-       }
-};
-
-// 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);
-       }
-};
-
-
-// before 1.8 there isn't a "integer interface", only float
-#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
-typedef f32 SamplerLayer_t;
-#else
-typedef s32 SamplerLayer_t;
-#endif
-
-
-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, 3> m_eye_position_pixel;
-       CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
-       CachedPixelShaderSetting<float, 3> m_minimap_yaw;
-       CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
-       CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture;
-       CachedPixelShaderSetting<SamplerLayer_t> m_texture_flags;
-       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_eye_position_pixel("eyePosition"),
-               m_eye_position_vertex("eyePosition"),
-               m_minimap_yaw("yawVec"),
-               m_base_texture("baseTexture"),
-               m_normal_texture("normalTexture"),
-               m_texture_flags("textureFlags"),
-               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);
-       }
-
-       virtual void onSetConstants(video::IMaterialRendererServices *services,
-                       bool is_highlevel)
-       {
-               if (!is_highlevel)
-                       return;
-
-               // 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);
-
-               u32 animation_timer = porting::getTimeMs() % 100000;
-               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();
-#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
-               eye_position_array[0] = epos.X;
-               eye_position_array[1] = epos.Y;
-               eye_position_array[2] = epos.Z;
-#else
-               epos.getAs3Values(eye_position_array);
-#endif
-               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();
-#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
-                       minimap_yaw_array[0] = minimap_yaw.X;
-                       minimap_yaw_array[1] = minimap_yaw.Y;
-                       minimap_yaw_array[2] = minimap_yaw.Z;
-#else
-                       minimap_yaw.getAs3Values(minimap_yaw_array);
-#endif
-                       m_minimap_yaw.set(minimap_yaw_array, services);
-               }
-
-               SamplerLayer_t base_tex = 0,
-                               normal_tex = 1,
-                               flags_tex = 2;
-               m_base_texture.set(&base_tex, services);
-               m_normal_texture.set(&normal_tex, services);
-               m_texture_flags.set(&flags_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()
-       {
-               GameGlobalShaderConstantSetter *scs = new GameGlobalShaderConstantSetter(
-                               m_sky, m_force_fog_off, m_fog_range, m_client);
-               if (!m_sky)
-                       created_nosky.push_back(scs);
-               return scs;
-       }
-};
-
-#ifdef __ANDROID__
-#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 {
-       u32 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 ldown_for_dig;
-       bool dig_instantly;
-       bool digging_blocked;
-       bool left_punch;
-       bool update_wielded_item_trigger;
-       bool reset_jump_timer;
-       float nodig_delay_timer;
-       float dig_time;
-       float dig_time_complete;
-       float repeat_rightclick_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 *);
-};
-
-/****************************************************************************
- THE GAME
- ****************************************************************************/
-
-/* This is not intended to be a public class. If a public class becomes
- * desirable then it may be better to create another 'wrapper' class that
- * hides most of the stuff in this class (nothing in this class is required
- * by any other file) but exposes the public methods/data only.
- */
-class Game {
-public:
-       Game();
-       ~Game();
-
-       bool startup(bool *kill,
-                       bool random_input,
-                       InputHandler *input,
-                       const std::string &map_dir,
-                       const std::string &playername,
-                       const std::string &password,
-                       // If address is "", local server is used and address is updated
-                       std::string *address,
-                       u16 port,
-                       std::string &error_message,
-                       bool *reconnect,
-                       ChatBackend *chat_backend,
-                       const SubgameSpec &gamespec,    // Used for local game
-                       bool simple_singleplayer_mode);
-
-       void run();
-       void shutdown();
-
-protected:
-
-       void extendedResourceCleanup();
-
-       // Basic initialisation
-       bool init(const std::string &map_dir, std::string *address,
-                       u16 port,
-                       const SubgameSpec &gamespec);
-       bool initSound();
-       bool createSingleplayerServer(const std::string &map_dir,
-                       const SubgameSpec &gamespec, u16 port, std::string *address);
-
-       // Client creation
-       bool createClient(const std::string &playername,
-                       const std::string &password, std::string *address, u16 port);
-       bool initGui();
-
-       // Client connection
-       bool connectToServer(const std::string &playername,
-                       const std::string &password, std::string *address, u16 port,
-                       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 addProfilerGraphs(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
-       void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
-
-       // Input related
-       void processUserInput(f32 dtime);
-       void processKeyInput();
-       void processItemSelection(u16 *new_playeritem);
-
-       void dropSelectedItem(bool single_item = false);
-       void openInventory();
-       void openConsole(float scale, const wchar_t *line=NULL);
-       void toggleFreeMove();
-       void toggleFreeMoveAlt();
-       void toggleFast();
-       void toggleNoClip();
-       void toggleCinematic();
-       void toggleAutoforward();
-
-       void toggleMinimap(bool shift_pressed);
-       void toggleFog();
-       void toggleDebug();
-       void toggleUpdateCamera();
-
-       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 updateCamera(u32 busy_time, f32 dtime);
-       void updateSound(f32 dtime);
-       void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug);
-       /*!
-        * 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 ItemDefinition &playeritem_def, const ItemStack &playeritem,
-               const ToolCapabilities &playeritem_toolcap, 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 ToolCapabilities &playeritem_toolcap, f32 dtime);
-       void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
-                       const CameraOrientation &cam);
-       void updateProfilerGraphs(ProfilerGraph *graph);
-
-       // Misc
-       void limitFps(FpsControl *fps_timings, f32 *dtime);
-
-       void showOverlayMessage(const char *msg, float dtime, int percent,
-                       bool draw_clouds = true);
-
-       static void settingChangedCallback(const std::string &setting_name, void *data);
-       void readSettings();
-
-       inline bool isKeyDown(GameKeyType k)
-       {
-               return input->isKeyDown(k);
-       }
-       inline bool wasKeyDown(GameKeyType k)
-       {
-               return input->wasKeyDown(k);
-       }
-
-#ifdef __ANDROID__
-       void handleAndroidChatInput();
-#endif
-
-private:
-       struct Flags {
-               bool force_fog_off = false;
-               bool disable_camera_update = false;
-       };
-
-       void showDeathFormspec();
-       void showPauseMenu();
-
-       // 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_OverrideDayNigthRatio(ClientEvent *event,
-               CameraOrientation *cam);
-       void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
-
-       void updateChat(f32 dtime, const v2u32 &screensize);
-
-       bool nodePlacementPrediction(const ItemDefinition &playeritem_def,
-               const ItemStack &playeritem, const v3s16 &nodepos, const v3s16 &neighbourpos);
-       static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
-
-       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;
-
-       GUIFormSpecMenu *current_formspec = nullptr;
-       //default: "". If other than "", empty show_formspec packets will only close the formspec when the formname matches
-       std::string cur_formname;
-
-       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()
-       MapDrawControl *draw_control = nullptr;
-       Camera *camera = nullptr;
-       Clouds *clouds = nullptr;                         // Free using ->Drop()
-       Sky *sky = nullptr;                         // Free using ->Drop()
-       Inventory *local_inventory = nullptr;
-       Hud *hud = nullptr;
-       Minimap *mapper = nullptr;
-
-       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;
-       video::IVideoDriver *driver;
-       scene::ISceneManager *smgr;
-       bool *kill;
-       std::string *error_message;
-       bool *reconnect_requested;
-       scene::ISceneNode *skybox;
-
-       bool random_input;
-       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_right_click_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__
-       bool m_cache_hold_aux1;
-       bool m_android_chat_open;
-#endif
-};
-
-Game::Game() :
-       m_game_ui(new GameUI())
-{
-       g_settings->registerChangedCallback("doubletap_jump",
-               &settingChangedCallback, this);
-       g_settings->registerChangedCallback("enable_clouds",
-               &settingChangedCallback, this);
-       g_settings->registerChangedCallback("doubletap_joysticks",
-               &settingChangedCallback, this);
-       g_settings->registerChangedCallback("enable_particles",
-               &settingChangedCallback, this);
-       g_settings->registerChangedCallback("enable_fog",
-               &settingChangedCallback, this);
-       g_settings->registerChangedCallback("mouse_sensitivity",
-               &settingChangedCallback, this);
-       g_settings->registerChangedCallback("joystick_frustum_sensitivity",
-               &settingChangedCallback, this);
-       g_settings->registerChangedCallback("repeat_rightclick_time",
-               &settingChangedCallback, this);
-       g_settings->registerChangedCallback("noclip",
-               &settingChangedCallback, this);
-       g_settings->registerChangedCallback("free_move",
-               &settingChangedCallback, this);
-       g_settings->registerChangedCallback("cinematic",
-               &settingChangedCallback, this);
-       g_settings->registerChangedCallback("cinematic_camera_smoothing",
-               &settingChangedCallback, this);
-       g_settings->registerChangedCallback("camera_smoothing",
-               &settingChangedCallback, this);
-
-       readSettings();
-
-#ifdef __ANDROID__
-       m_cache_hold_aux1 = false;      // This is initialised properly later
-#endif
-
-}
-
-
-
-/****************************************************************************
- MinetestApp Public
- ****************************************************************************/
-
-Game::~Game()
-{
-       delete client;
-       delete soundmaker;
-       if (!sound_is_dummy)
-               delete sound;
-
-       delete server; // deleted first to stop all server threads
-
-       delete hud;
-       delete local_inventory;
-       delete camera;
-       delete quicktune;
-       delete eventmgr;
-       delete texture_src;
-       delete shader_src;
-       delete nodedef_manager;
-       delete itemdef_manager;
-       delete draw_control;
-
-       extendedResourceCleanup();
-
-       g_settings->deregisterChangedCallback("doubletap_jump",
-               &settingChangedCallback, this);
-       g_settings->deregisterChangedCallback("enable_clouds",
-               &settingChangedCallback, this);
-       g_settings->deregisterChangedCallback("enable_particles",
-               &settingChangedCallback, this);
-       g_settings->deregisterChangedCallback("enable_fog",
-               &settingChangedCallback, this);
-       g_settings->deregisterChangedCallback("mouse_sensitivity",
-               &settingChangedCallback, this);
-       g_settings->deregisterChangedCallback("repeat_rightclick_time",
-               &settingChangedCallback, this);
-       g_settings->deregisterChangedCallback("noclip",
-               &settingChangedCallback, this);
-       g_settings->deregisterChangedCallback("free_move",
-               &settingChangedCallback, this);
-       g_settings->deregisterChangedCallback("cinematic",
-               &settingChangedCallback, this);
-       g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
-               &settingChangedCallback, this);
-       g_settings->deregisterChangedCallback("camera_smoothing",
-               &settingChangedCallback, this);
-}
-
-bool Game::startup(bool *kill,
-               bool random_input,
-               InputHandler *input,
-               const std::string &map_dir,
-               const std::string &playername,
-               const std::string &password,
-               std::string *address,     // can change if simple_singleplayer_mode
-               u16 port,
-               std::string &error_message,
-               bool *reconnect,
-               ChatBackend *chat_backend,
-               const SubgameSpec &gamespec,
-               bool simple_singleplayer_mode)
-{
-       // "cache"
-       this->device              = RenderingEngine::get_raw_device();
-       this->kill                = kill;
-       this->error_message       = &error_message;
-       this->reconnect_requested = reconnect;
-       this->random_input        = random_input;
-       this->input               = input;
-       this->chat_backend        = chat_backend;
-       this->simple_singleplayer_mode = simple_singleplayer_mode;
-
-       input->keycache.populate();
-
-       driver = device->getVideoDriver();
-       smgr = RenderingEngine::get_scene_manager();
-
-       RenderingEngine::get_scene_manager()->getParameters()->
-               setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
-
-       // Reinit runData
-       runData = GameRunData();
-       runData.time_from_last_punch = 10.0;
-       runData.update_wielded_item_trigger = true;
-
-       m_game_ui->initFlags();
-
-       m_invert_mouse = g_settings->getBool("invert_mouse");
-       m_first_loop_after_window_activation = true;
-
-       g_translations->clear();
-
-       if (!init(map_dir, address, port, gamespec))
-               return false;
-
-       if (!createClient(playername, password, address, port))
-               return false;
-
-       RenderingEngine::initialize(client, hud);
-
-       return true;
-}
-
-
-void Game::run()
-{
-       ProfilerGraph graph;
-       RunStats stats              = { 0 };
-       CameraOrientation cam_view_target  = { 0 };
-       CameraOrientation cam_view  = { 0 };
-       FpsControl draw_times       = { 0 };
-       f32 dtime; // in seconds
-
-       /* Clear the profiler */
-       Profiler::GraphValues dummyvalues;
-       g_profiler->graphGet(dummyvalues);
-
-       draw_times.last_time = RenderingEngine::get_timer_time();
-
-       set_light_table(g_settings->getFloat("display_gamma"));
-
-#ifdef __ANDROID__
-       m_cache_hold_aux1 = g_settings->getBool("fast_move")
-                       && client->checkPrivilege("fast");
-#endif
-
-       irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
-               g_settings->getU16("screen_h"));
-
-       while (RenderingEngine::run()
-                       && !(*kill || g_gamecallback->shutdown_requested
-                       || (server && server->isShutdownRequested()))) {
-
-               const irr::core::dimension2d<u32> &current_screen_size =
-                       RenderingEngine::get_video_driver()->getScreenSize();
-               // Verify if window size has changed and save it if it's the case
-               // Ensure evaluating settings->getBool after verifying screensize
-               // First condition is cheaper
-               if (previous_screen_size != current_screen_size &&
-                               current_screen_size != irr::core::dimension2d<u32>(0,0) &&
-                               g_settings->getBool("autosave_screensize")) {
-                       g_settings->setU16("screen_w", current_screen_size.Width);
-                       g_settings->setU16("screen_h", current_screen_size.Height);
-                       previous_screen_size = current_screen_size;
-               }
-
-               /* Must be called immediately after a device->run() call because it
-                * uses device->getTimer()->getTime()
-                */
-               limitFps(&draw_times, &dtime);
-
-               updateStats(&stats, draw_times, dtime);
-               updateInteractTimers(dtime);
-
-               if (!checkConnection())
-                       break;
-               if (!handleCallbacks())
-                       break;
-
-               processQueues();
-
-               m_game_ui->clearInfoText();
-               hud->resizeHotbar();
-
-               updateProfilers(stats, draw_times, dtime);
-               processUserInput(dtime);
-               // Update camera before player movement to avoid camera lag of one frame
-               updateCameraDirection(&cam_view_target, dtime);
-               cam_view.camera_yaw += (cam_view_target.camera_yaw -
-                               cam_view.camera_yaw) * m_cache_cam_smoothing;
-               cam_view.camera_pitch += (cam_view_target.camera_pitch -
-                               cam_view.camera_pitch) * m_cache_cam_smoothing;
-               updatePlayerControl(cam_view);
-               step(&dtime);
-               processClientEvents(&cam_view_target);
-               updateCamera(draw_times.busy_time, dtime);
-               updateSound(dtime);
-               processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
-                       m_game_ui->m_flags.show_debug);
-               updateFrame(&graph, &stats, dtime, cam_view);
-               updateProfilerGraphs(&graph);
-
-               // Update if minimap has been disabled by the server
-               m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
-
-               if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
-                       showPauseMenu();
-               }
-       }
-}
-
-
-void Game::shutdown()
-{
-       RenderingEngine::finalize();
-#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
-       if (g_settings->get("3d_mode") == "pageflip") {
-               driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
-       }
-#endif
-       if (current_formspec)
-               current_formspec->quitMenu();
-
-       showOverlayMessage("Shutting down...", 0, 0, false);
-
-       if (clouds)
-               clouds->drop();
-
-       if (gui_chat_console)
-               gui_chat_console->drop();
-
-       if (sky)
-               sky->drop();
-
-       /* cleanup menus */
-       while (g_menumgr.menuCount() > 0) {
-               g_menumgr.m_stack.front()->setVisible(false);
-               g_menumgr.deletingMenu(g_menumgr.m_stack.front());
-       }
-
-       if (current_formspec) {
-               current_formspec->drop();
-               current_formspec = NULL;
-       }
-
-       chat_backend->addMessage(L"", L"# Disconnected.");
-       chat_backend->addMessage(L"", L"");
-
-       if (client) {
-               client->Stop();
-               while (!client->isShutdown()) {
-                       assert(texture_src != NULL);
-                       assert(shader_src != NULL);
-                       texture_src->processQueue();
-                       shader_src->processQueue();
-                       sleep_ms(100);
-               }
-       }
-}
-
-
-/****************************************************************************/
-/****************************************************************************
- Startup
- ****************************************************************************/
-/****************************************************************************/
-
-bool Game::init(
-               const std::string &map_dir,
-               std::string *address,
-               u16 port,
-               const SubgameSpec &gamespec)
-{
-       texture_src = createTextureSource();
-
-       showOverlayMessage("Loading...", 0, 0);
-
-       shader_src = createShaderSource();
-
-       itemdef_manager = createItemDefManager();
-       nodedef_manager = createNodeDefManager();
-
-       eventmgr = new EventManager();
-       quicktune = new QuicktuneShortcutter();
-
-       if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
-                       && eventmgr && quicktune))
-               return false;
-
-       if (!initSound())
-               return false;
-
-       // Create a server if not connecting to an existing one
-       if (address->empty()) {
-               if (!createSingleplayerServer(map_dir, gamespec, port, address))
-                       return false;
-       }
-
-       return true;
-}
-
-bool Game::initSound()
-{
-#if USE_SOUND
-       if (g_settings->getBool("enable_sound")) {
-               infostream << "Attempting to use OpenAL audio" << std::endl;
-               sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
-               if (!sound)
-                       infostream << "Failed to initialize OpenAL audio" << std::endl;
-       } else
-               infostream << "Sound disabled." << std::endl;
-#endif
-
-       if (!sound) {
-               infostream << "Using dummy audio." << std::endl;
-               sound = &dummySoundManager;
-               sound_is_dummy = true;
-       }
-
-       soundmaker = new SoundMaker(sound, nodedef_manager);
-       if (!soundmaker)
-               return false;
-
-       soundmaker->registerReceiver(eventmgr);
-
-       return true;
-}
-
-bool Game::createSingleplayerServer(const std::string &map_dir,
-               const SubgameSpec &gamespec, u16 port, std::string *address)
-{
-       showOverlayMessage("Creating server...", 0, 5);
-
-       std::string bind_str = g_settings->get("bind_address");
-       Address bind_addr(0, 0, 0, 0, port);
-
-       if (g_settings->getBool("ipv6_server")) {
-               bind_addr.setAddress((IPv6AddressBytes *) NULL);
-       }
-
-       try {
-               bind_addr.Resolve(bind_str.c_str());
-       } catch (ResolveError &e) {
-               infostream << "Resolving bind address \"" << bind_str
-                          << "\" failed: " << e.what()
-                          << " -- Listening on all addresses." << std::endl;
-       }
-
-       if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
-               *error_message = "Unable to listen on " +
-                               bind_addr.serializeString() +
-                               " because IPv6 is disabled";
-               errorstream << *error_message << std::endl;
-               return false;
-       }
-
-       server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr, false);
-       server->init();
-       server->start();
-
-       return true;
-}
-
-bool Game::createClient(const std::string &playername,
-               const std::string &password, std::string *address, u16 port)
-{
-       showOverlayMessage("Creating client...", 0, 10);
-
-       draw_control = new MapDrawControl;
-       if (!draw_control)
-               return false;
-
-       bool could_connect, connect_aborted;
-#ifdef HAVE_TOUCHSCREENGUI
-       if (g_touchscreengui) {
-               g_touchscreengui->init(texture_src);
-               g_touchscreengui->hide();
-       }
-#endif
-       if (!connectToServer(playername, password, address, port,
-                       &could_connect, &connect_aborted))
-               return false;
-
-       if (!could_connect) {
-               if (error_message->empty() && !connect_aborted) {
-                       // Should not happen if error messages are set properly
-                       *error_message = "Connection failed for unknown reason";
-                       errorstream << *error_message << std::endl;
-               }
-               return false;
-       }
-
-       if (!getServerContent(&connect_aborted)) {
-               if (error_message->empty() && !connect_aborted) {
-                       // Should not happen if error messages are set properly
-                       *error_message = "Connection failed for unknown reason";
-                       errorstream << *error_message << std::endl;
-               }
-               return false;
-       }
-
-       GameGlobalShaderConstantSetterFactory *scsf = new GameGlobalShaderConstantSetterFactory(
-                       &m_flags.force_fog_off, &runData.fog_range, client);
-       shader_src->addShaderConstantSetterFactory(scsf);
-
-       // Update cached textures, meshes and materials
-       client->afterContentReceived();
-
-       /* Camera
-        */
-       camera = new Camera(*draw_control, client);
-       if (!camera || !camera->successfullyCreated(*error_message))
-               return false;
-       client->setCamera(camera);
-
-       /* Clouds
-        */
-       if (m_cache_enable_clouds) {
-               clouds = new Clouds(smgr, -1, time(0));
-               if (!clouds) {
-                       *error_message = "Memory allocation error (clouds)";
-                       errorstream << *error_message << std::endl;
-                       return false;
-               }
-       }
-
-       /* Skybox
-        */
-       sky = new Sky(-1, texture_src);
-       scsf->setSky(sky);
-       skybox = NULL;  // This is used/set later on in the main run loop
-
-       local_inventory = new Inventory(itemdef_manager);
-
-       if (!(sky && local_inventory)) {
-               *error_message = "Memory allocation error (sky or local inventory)";
-               errorstream << *error_message << std::endl;
-               return false;
-       }
-
-       /* Pre-calculated values
-        */
-       video::ITexture *t = texture_src->getTexture("crack_anylength.png");
-       if (t) {
-               v2u32 size = t->getOriginalSize();
-               crack_animation_length = size.Y / size.X;
-       } else {
-               crack_animation_length = 5;
-       }
-
-       if (!initGui())
-               return false;
-
-       /* Set window caption
-        */
-       std::wstring str = utf8_to_wide(PROJECT_NAME_C);
-       str += L" ";
-       str += utf8_to_wide(g_version_hash);
-       str += L" [";
-       str += driver->getName();
-       str += L"]";
-       device->setWindowCaption(str.c_str());
-
-       LocalPlayer *player = client->getEnv().getLocalPlayer();
-       player->hurt_tilt_timer = 0;
-       player->hurt_tilt_strength = 0;
-
-       hud = new Hud(guienv, client, player, local_inventory);
-
-       if (!hud) {
-               *error_message = "Memory error: could not create HUD";
-               errorstream << *error_message << std::endl;
-               return false;
-       }
-
-       mapper = client->getMinimap();
-       if (mapper)
-               mapper->setMinimapMode(MINIMAP_MODE_OFF);
-
-       return true;
-}
-
-bool Game::initGui()
-{
-       m_game_ui->init();
-
-       // Remove stale "recent" chat messages from previous connections
-       chat_backend->clearRecentChat();
-
-       // Make sure the size of the recent messages buffer is right
-       chat_backend->applySettings();
-
-       // Chat backend and console
-       gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
-                       -1, chat_backend, client, &g_menumgr);
-       if (!gui_chat_console) {
-               *error_message = "Could not allocate memory for chat console";
-               errorstream << *error_message << std::endl;
-               return false;
-       }
-
-#ifdef HAVE_TOUCHSCREENGUI
-
-       if (g_touchscreengui)
-               g_touchscreengui->show();
-
-#endif
-
-       return true;
-}
-
-bool Game::connectToServer(const std::string &playername,
-               const std::string &password, std::string *address, u16 port,
-               bool *connect_ok, bool *connection_aborted)
-{
-       *connect_ok = false;    // Let's not be overly optimistic
-       *connection_aborted = false;
-       bool local_server_mode = false;
-
-       showOverlayMessage("Resolving address...", 0, 15);
-
-       Address connect_address(0, 0, 0, 0, port);
-
-       try {
-               connect_address.Resolve(address->c_str());
-
-               if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
-                       //connect_address.Resolve("localhost");
-                       if (connect_address.isIPv6()) {
-                               IPv6AddressBytes addr_bytes;
-                               addr_bytes.bytes[15] = 1;
-                               connect_address.setAddress(&addr_bytes);
-                       } else {
-                               connect_address.setAddress(127, 0, 0, 1);
-                       }
-                       local_server_mode = true;
-               }
-       } catch (ResolveError &e) {
-               *error_message = std::string("Couldn't resolve address: ") + e.what();
-               errorstream << *error_message << std::endl;
-               return false;
-       }
-
-       if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
-               *error_message = "Unable to connect to " +
-                               connect_address.serializeString() +
-                               " because IPv6 is disabled";
-               errorstream << *error_message << std::endl;
-               return false;
-       }
-
-       client = new Client(playername.c_str(), password, *address,
-                       *draw_control, texture_src, shader_src,
-                       itemdef_manager, nodedef_manager, sound, eventmgr,
-                       connect_address.isIPv6(), m_game_ui.get());
-
-       if (!client)
-               return false;
-
-       client->m_simple_singleplayer_mode = simple_singleplayer_mode;
-
-       infostream << "Connecting to server at ";
-       connect_address.print(&infostream);
-       infostream << std::endl;
-
-       client->connect(connect_address,
-               simple_singleplayer_mode || local_server_mode);
-
-       /*
-               Wait for server to accept connection
-       */
-
-       try {
-               input->clear();
-
-               FpsControl fps_control = { 0 };
-               f32 dtime;
-               f32 wait_time = 0; // in seconds
-
-               fps_control.last_time = RenderingEngine::get_timer_time();
-
-               while (RenderingEngine::run()) {
-
-                       limitFps(&fps_control, &dtime);
-
-                       // Update client and server
-                       client->step(dtime);
-
-                       if (server != NULL)
-                               server->step(dtime);
-
-                       // End condition
-                       if (client->getState() == LC_Init) {
-                               *connect_ok = true;
-                               break;
-                       }
-
-                       // Break conditions
-                       if (*connection_aborted)
-                               break;
-
-                       if (client->accessDenied()) {
-                               *error_message = "Access denied. Reason: "
-                                               + client->accessDeniedReason();
-                               *reconnect_requested = client->reconnectRequested();
-                               errorstream << *error_message << std::endl;
-                               break;
-                       }
-
-                       if (input->cancelPressed()) {
-                               *connection_aborted = true;
-                               infostream << "Connect aborted [Escape]" << std::endl;
-                               break;
-                       }
-
-                       if (client->m_is_registration_confirmation_state) {
-                               if (registration_confirmation_shown) {
-                                       // Keep drawing the GUI
-                                       RenderingEngine::draw_menu_scene(guienv, dtime, true);
-                               } else {
-                                       registration_confirmation_shown = true;
-                                       (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
-                                                  &g_menumgr, client, playername, password, *address, connection_aborted))->drop();
-                               }
-                       } else {
-                               wait_time += dtime;
-                               // Only time out if we aren't waiting for the server we started
-                               if (!address->empty() && wait_time > 10) {
-                                       *error_message = "Connection timed out.";
-                                       errorstream << *error_message << std::endl;
-                                       break;
-                               }
-
-                               // Update status
-                               showOverlayMessage("Connecting to server...", dtime, 20);
-                       }
-               }
-       } catch (con::PeerNotFoundException &e) {
-               // TODO: Should something be done here? At least an info/error
-               // message?
-               return false;
-       }
-
-       return true;
-}
-
-bool Game::getServerContent(bool *aborted)
-{
-       input->clear();
-
-       FpsControl fps_control = { 0 };
-       f32 dtime; // in seconds
-
-       fps_control.last_time = RenderingEngine::get_timer_time();
-
-       while (RenderingEngine::run()) {
-
-               limitFps(&fps_control, &dtime);
-
-               // Update client and server
-               client->step(dtime);
-
-               if (server != NULL)
-                       server->step(dtime);
-
-               // End condition
-               if (client->mediaReceived() && client->itemdefReceived() &&
-                               client->nodedefReceived()) {
-                       break;
-               }
-
-               // Error conditions
-               if (!checkConnection())
-                       return false;
-
-               if (client->getState() < LC_Init) {
-                       *error_message = "Client disconnected";
-                       errorstream << *error_message << std::endl;
-                       return false;
-               }
-
-               if (input->cancelPressed()) {
-                       *aborted = true;
-                       infostream << "Connect aborted [Escape]" << std::endl;
-                       return false;
-               }
-
-               // Display status
-               int progress = 25;
-
-               if (!client->itemdefReceived()) {
-                       const wchar_t *text = wgettext("Item definitions...");
-                       progress = 25;
-                       RenderingEngine::draw_load_screen(text, guienv, texture_src,
-                               dtime, progress);
-                       delete[] text;
-               } else if (!client->nodedefReceived()) {
-                       const wchar_t *text = wgettext("Node definitions...");
-                       progress = 30;
-                       RenderingEngine::draw_load_screen(text, guienv, texture_src,
-                               dtime, progress);
-                       delete[] text;
-               } else {
-                       std::stringstream message;
-                       std::fixed(message);
-                       message.precision(0);
-                       message << gettext("Media...") << " " << (client->mediaReceiveProgress()*100) << "%";
-                       message.precision(2);
-
-                       if ((USE_CURL == 0) ||
-                                       (!g_settings->getBool("enable_remote_media_server"))) {
-                               float cur = client->getCurRate();
-                               std::string cur_unit = gettext("KiB/s");
-
-                               if (cur > 900) {
-                                       cur /= 1024.0;
-                                       cur_unit = gettext("MiB/s");
-                               }
-
-                               message << " (" << cur << ' ' << cur_unit << ")";
-                       }
-
-                       progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
-                       RenderingEngine::draw_load_screen(utf8_to_wide(message.str()), guienv,
-                               texture_src, dtime, progress);
-               }
-       }
-
-       return true;
-}
-
-
-/****************************************************************************/
-/****************************************************************************
- Run
- ****************************************************************************/
-/****************************************************************************/
-
-inline void Game::updateInteractTimers(f32 dtime)
-{
-       if (runData.nodig_delay_timer >= 0)
-               runData.nodig_delay_timer -= dtime;
-
-       if (runData.object_hit_delay_timer >= 0)
-               runData.object_hit_delay_timer -= dtime;
-
-       runData.time_from_last_punch += dtime;
-}
-
-
-/* returns false if game should exit, otherwise true
- */
-inline bool Game::checkConnection()
-{
-       if (client->accessDenied()) {
-               *error_message = "Access denied. Reason: "
-                               + client->accessDeniedReason();
-               *reconnect_requested = client->reconnectRequested();
-               errorstream << *error_message << std::endl;
-               return false;
-       }
-
-       return true;
-}
-
-
-/* returns false if game should exit, otherwise true
- */
-inline bool Game::handleCallbacks()
-{
-       if (g_gamecallback->disconnect_requested) {
-               g_gamecallback->disconnect_requested = false;
-               return false;
-       }
-
-       if (g_gamecallback->changepassword_requested) {
-               (new GUIPasswordChange(guienv, guiroot, -1,
-                                      &g_menumgr, client))->drop();
-               g_gamecallback->changepassword_requested = false;
-       }
-
-       if (g_gamecallback->changevolume_requested) {
-               (new GUIVolumeChange(guienv, guiroot, -1,
-                                    &g_menumgr))->drop();
-               g_gamecallback->changevolume_requested = false;
-       }
-
-       if (g_gamecallback->keyconfig_requested) {
-               (new GUIKeyChangeMenu(guienv, guiroot, -1,
-                                     &g_menumgr))->drop();
-               g_gamecallback->keyconfig_requested = false;
-       }
-
-       if (g_gamecallback->keyconfig_changed) {
-               input->keycache.populate(); // update the cache with new settings
-               g_gamecallback->keyconfig_changed = false;
-       }
-
-       return true;
-}
-
-
-void Game::processQueues()
-{
-       texture_src->processQueue();
-       itemdef_manager->processQueue(client);
-       shader_src->processQueue();
-}
-
-
-void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime)
-{
-       float profiler_print_interval =
-                       g_settings->getFloat("profiler_print_interval");
-       bool print_to_log = true;
-
-       if (profiler_print_interval == 0) {
-               print_to_log = false;
-               profiler_print_interval = 5;
-       }
-
-       if (profiler_interval.step(dtime, profiler_print_interval)) {
-               if (print_to_log) {
-                       infostream << "Profiler:" << std::endl;
-                       g_profiler->print(infostream);
-               }
-
-               m_game_ui->updateProfiler();
-               g_profiler->clear();
-       }
-
-       addProfilerGraphs(stats, draw_times, dtime);
-}
-
-
-void Game::addProfilerGraphs(const RunStats &stats,
-               const FpsControl &draw_times, f32 dtime)
-{
-       g_profiler->graphAdd("mainloop_other",
-                       draw_times.busy_time / 1000.0f - stats.drawtime / 1000.0f);
-
-       if (draw_times.sleep_time != 0)
-               g_profiler->graphAdd("mainloop_sleep", draw_times.sleep_time / 1000.0f);
-       g_profiler->graphAdd("mainloop_dtime", dtime);
-
-       g_profiler->add("Elapsed time", dtime);
-       g_profiler->avg("FPS", 1. / dtime);
-}
-
-
-void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
-               f32 dtime)
-{
-
-       f32 jitter;
-       Jitter *jp;
-
-       /* Time average and jitter calculation
-        */
-       jp = &stats->dtime_jitter;
-       jp->avg = jp->avg * 0.96 + dtime * 0.04;
-
-       jitter = dtime - jp->avg;
-
-       if (jitter > jp->max)
-               jp->max = jitter;
-
-       jp->counter += dtime;
-
-       if (jp->counter > 0.0) {
-               jp->counter -= 3.0;
-               jp->max_sample = jp->max;
-               jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
-               jp->max = 0.0;
-       }
-
-       /* Busytime average and jitter calculation
-        */
-       jp = &stats->busy_time_jitter;
-       jp->avg = jp->avg + draw_times.busy_time * 0.02;
-
-       jitter = draw_times.busy_time - jp->avg;
-
-       if (jitter > jp->max)
-               jp->max = jitter;
-       if (jitter < jp->min)
-               jp->min = jitter;
-
-       jp->counter += dtime;
-
-       if (jp->counter > 0.0) {
-               jp->counter -= 3.0;
-               jp->max_sample = jp->max;
-               jp->min_sample = jp->min;
-               jp->max = 0.0;
-               jp->min = 0.0;
-       }
-}
-
-
-
-/****************************************************************************
- Input handling
- ****************************************************************************/
-
-void Game::processUserInput(f32 dtime)
-{
-       // Reset input if window not active or some menu is active
-       if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
-               input->clear();
-#ifdef HAVE_TOUCHSCREENGUI
-               g_touchscreengui->hide();
-#endif
-       }
-#ifdef HAVE_TOUCHSCREENGUI
-       else if (g_touchscreengui) {
-               /* on touchscreengui step may generate own input events which ain't
-                * what we want in case we just did clear them */
-               g_touchscreengui->step(dtime);
-       }
-#endif
-
-       if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
-               gui_chat_console->closeConsoleAtOnce();
-       }
-
-       // Input handler step() (used by the random input generator)
-       input->step(dtime);
-
-#ifdef __ANDROID__
-       if (current_formspec != NULL)
-               current_formspec->getAndroidUIInput();
-       else
-               handleAndroidChatInput();
-#endif
-
-       // Increase timer for double tap of "keymap_jump"
-       if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
-               runData.jump_timer += dtime;
-
-       processKeyInput();
-       processItemSelection(&runData.new_playeritem);
-}
-
-
-void Game::processKeyInput()
-{
-       if (wasKeyDown(KeyType::DROP)) {
-               dropSelectedItem(isKeyDown(KeyType::SNEAK));
-       } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
-               toggleAutoforward();
-       } else if (wasKeyDown(KeyType::BACKWARD)) {
-               if (g_settings->getBool("continuous_forward"))
-                       toggleAutoforward();
-       } else if (wasKeyDown(KeyType::INVENTORY)) {
-               openInventory();
-       } else if (input->cancelPressed()) {
-               if (!gui_chat_console->isOpenInhibited()) {
-                       showPauseMenu();
-               }
-       } else if (wasKeyDown(KeyType::CHAT)) {
-               openConsole(0.2, L"");
-       } else if (wasKeyDown(KeyType::CMD)) {
-               openConsole(0.2, L"/");
-       } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
-               if (client->moddingEnabled())
-                       openConsole(0.2, L".");
-               else
-                       m_game_ui->showStatusText(wgettext("CSM is disabled"));
-       } else if (wasKeyDown(KeyType::CONSOLE)) {
-               openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
-       } else if (wasKeyDown(KeyType::FREEMOVE)) {
-               toggleFreeMove();
-       } else if (wasKeyDown(KeyType::JUMP)) {
-               toggleFreeMoveAlt();
-       } else if (wasKeyDown(KeyType::FASTMOVE)) {
-               toggleFast();
-       } else if (wasKeyDown(KeyType::NOCLIP)) {
-               toggleNoClip();
-       } else if (wasKeyDown(KeyType::MUTE)) {
-               bool new_mute_sound = !g_settings->getBool("mute_sound");
-               g_settings->setBool("mute_sound", new_mute_sound);
-               if (new_mute_sound)
-                       m_game_ui->showTranslatedStatusText("Sound muted");
-               else
-                       m_game_ui->showTranslatedStatusText("Sound unmuted");
-       } else if (wasKeyDown(KeyType::INC_VOLUME)) {
-               float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
-               wchar_t buf[100];
-               g_settings->setFloat("sound_volume", new_volume);
-               const wchar_t *str = wgettext("Volume changed to %d%%");
-               swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
-               delete[] str;
-               m_game_ui->showStatusText(buf);
-       } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
-               float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
-               wchar_t buf[100];
-               g_settings->setFloat("sound_volume", new_volume);
-               const wchar_t *str = wgettext("Volume changed to %d%%");
-               swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
-               delete[] str;
-               m_game_ui->showStatusText(buf);
-       } else if (wasKeyDown(KeyType::CINEMATIC)) {
-               toggleCinematic();
-       } else if (wasKeyDown(KeyType::SCREENSHOT)) {
-               client->makeScreenshot();
-       } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
-               m_game_ui->toggleHud();
-       } else if (wasKeyDown(KeyType::MINIMAP)) {
-               toggleMinimap(isKeyDown(KeyType::SNEAK));
-       } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
-               m_game_ui->toggleChat();
-       } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
-               toggleFog();
-       } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
-               toggleUpdateCamera();
-       } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
-               toggleDebug();
-       } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
-               m_game_ui->toggleProfiler();
-       } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
-               increaseViewRange();
-       } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
-               decreaseViewRange();
-       } else if (wasKeyDown(KeyType::RANGESELECT)) {
-               toggleFullViewRange();
-       } else if (wasKeyDown(KeyType::ZOOM)) {
-               checkZoomEnabled();
-       } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
-               quicktune->next();
-       } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
-               quicktune->prev();
-       } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
-               quicktune->inc();
-       } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
-               quicktune->dec();
-       }
-
-       if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
-               runData.reset_jump_timer = false;
-               runData.jump_timer = 0.0f;
-       }
-
-       if (quicktune->hasMessage()) {
-               m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
-       }
-}
-
-void Game::processItemSelection(u16 *new_playeritem)
-{
-       LocalPlayer *player = client->getEnv().getLocalPlayer();
-
-       /* Item selection using mouse wheel
-        */
-       *new_playeritem = client->getPlayerItem();
-
-       s32 wheel = input->getMouseWheel();
-       u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
-                   player->hud_hotbar_itemcount - 1);
-
-       s32 dir = wheel;
-
-       if (input->joystick.wasKeyDown(KeyType::SCROLL_DOWN) ||
-                       wasKeyDown(KeyType::HOTBAR_NEXT)) {
-               dir = -1;
-       }
-
-       if (input->joystick.wasKeyDown(KeyType::SCROLL_UP) ||
-                       wasKeyDown(KeyType::HOTBAR_PREV)) {
-               dir = 1;
-       }
-
-       if (dir < 0)
-               *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
-       else if (dir > 0)
-               *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
-       // else dir == 0
-
-       /* Item selection using hotbar slot keys
-        */
-       for (u16 i = 0; i < 23; i++) {
-               if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
-                       if (i < PLAYER_INVENTORY_SIZE && i < player->hud_hotbar_itemcount) {
-                               *new_playeritem = i;
-                               infostream << "Selected item: " << new_playeritem << std::endl;
-                       }
-                       break;
-               }
-       }
-}
-
-
-void Game::dropSelectedItem(bool single_item)
-{
-       IDropAction *a = new IDropAction();
-       a->count = single_item ? 1 : 0;
-       a->from_inv.setCurrentPlayer();
-       a->from_list = "main";
-       a->from_i = client->getPlayerItem();
-       client->inventoryAction(a);
-}
-
-
-void Game::openInventory()
-{
-       /*
-        * Don't permit to open inventory is CAO or player doesn't exists.
-        * This prevent showing an empty inventory at player load
-        */
-
-       LocalPlayer *player = client->getEnv().getLocalPlayer();
-       if (!player || !player->getCAO())
-               return;
-
-       infostream << "the_game: " << "Launching inventory" << std::endl;
-
-       PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
-
-       InventoryLocation inventoryloc;
-       inventoryloc.setCurrentPlayer();
-
-       if (!client->moddingEnabled()
-                       || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
-               TextDest *txt_dst = new TextDestPlayerInventory(client);
-               GUIFormSpecMenu::create(current_formspec, client, &input->joystick, fs_src,
-                       txt_dst, client->getFormspecPrepend());
-               cur_formname = "";
-               current_formspec->setFormSpec(fs_src->getForm(), inventoryloc);
-       }
-}
-
-
-void Game::openConsole(float scale, const wchar_t *line)
-{
-       assert(scale > 0.0f && scale <= 1.0f);
-
-#ifdef __ANDROID__
-       porting::showInputDialog(gettext("ok"), "", "", 2);
-       m_android_chat_open = true;
-#else
-       if (gui_chat_console->isOpenInhibited())
-               return;
-       gui_chat_console->openConsole(scale);
-       if (line) {
-               gui_chat_console->setCloseOnEnter(true);
-               gui_chat_console->replaceAndAddToHistory(line);
-       }
-#endif
-}
-
-#ifdef __ANDROID__
-void Game::handleAndroidChatInput()
-{
-       if (m_android_chat_open && porting::getInputDialogState() == 0) {
-               std::string text = porting::getInputDialogValue();
-               client->typeChatMessage(utf8_to_wide(text));
-       }
-}
-#endif
-
-
-void Game::toggleFreeMove()
-{
-       bool free_move = !g_settings->getBool("free_move");
-       g_settings->set("free_move", bool_to_cstr(free_move));
-
-       if (free_move) {
-               if (client->checkPrivilege("fly")) {
-                       m_game_ui->showTranslatedStatusText("Fly mode enabled");
-               } else {
-                       m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
-               }
-       } else {
-               m_game_ui->showTranslatedStatusText("Fly mode disabled");
-       }
-}
-
-void Game::toggleFreeMoveAlt()
-{
-       if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
-               toggleFreeMove();
-
-       runData.reset_jump_timer = true;
-}
-
-
-void Game::toggleFast()
-{
-       bool fast_move = !g_settings->getBool("fast_move");
-       bool has_fast_privs = client->checkPrivilege("fast");
-       g_settings->set("fast_move", bool_to_cstr(fast_move));
-
-       if (fast_move) {
-               if (has_fast_privs) {
-                       m_game_ui->showTranslatedStatusText("Fast mode enabled");
-               } else {
-                       m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
-               }
-       } else {
-               m_game_ui->showTranslatedStatusText("Fast mode disabled");
-       }
-
-#ifdef __ANDROID__
-       m_cache_hold_aux1 = fast_move && has_fast_privs;
-#endif
-}
-
-
-void Game::toggleNoClip()
-{
-       bool noclip = !g_settings->getBool("noclip");
-       g_settings->set("noclip", bool_to_cstr(noclip));
-
-       if (noclip) {
-               if (client->checkPrivilege("noclip")) {
-                       m_game_ui->showTranslatedStatusText("Noclip mode enabled");
-               } else {
-                       m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
-               }
-       } else {
-               m_game_ui->showTranslatedStatusText("Noclip mode disabled");
-       }
-}
-
-void Game::toggleCinematic()
-{
-       bool cinematic = !g_settings->getBool("cinematic");
-       g_settings->set("cinematic", bool_to_cstr(cinematic));
-
-       if (cinematic)
-               m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
-       else
-               m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
-}
-
-// Autoforward by toggling continuous forward.
-void Game::toggleAutoforward()
-{
-       bool autorun_enabled = !g_settings->getBool("continuous_forward");
-       g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
-
-       if (autorun_enabled)
-               m_game_ui->showTranslatedStatusText("Automatic forwards enabled");
-       else
-               m_game_ui->showTranslatedStatusText("Automatic forwards disabled");
-}
-
-void Game::toggleMinimap(bool shift_pressed)
-{
-       if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
-               return;
-
-       if (shift_pressed) {
-               mapper->toggleMinimapShape();
-               return;
-       }
-
-       u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
-
-       MinimapMode mode = MINIMAP_MODE_OFF;
-       if (hud_flags & HUD_FLAG_MINIMAP_VISIBLE) {
-               mode = mapper->getMinimapMode();
-               mode = (MinimapMode)((int)mode + 1);
-               // If radar is disabled and in, or switching to, radar mode
-               if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE) && mode > 3)
-                       mode = MINIMAP_MODE_OFF;
-       }
-
-       m_game_ui->m_flags.show_minimap = true;
-       switch (mode) {
-               case MINIMAP_MODE_SURFACEx1:
-                       m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x1");
-                       break;
-               case MINIMAP_MODE_SURFACEx2:
-                       m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x2");
-                       break;
-               case MINIMAP_MODE_SURFACEx4:
-                       m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x4");
-                       break;
-               case MINIMAP_MODE_RADARx1:
-                       m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x1");
-                       break;
-               case MINIMAP_MODE_RADARx2:
-                       m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x2");
-                       break;
-               case MINIMAP_MODE_RADARx4:
-                       m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x4");
-                       break;
-               default:
-                       mode = MINIMAP_MODE_OFF;
-                       m_game_ui->m_flags.show_minimap = false;
-                       if (hud_flags & HUD_FLAG_MINIMAP_VISIBLE)
-                               m_game_ui->showTranslatedStatusText("Minimap hidden");
-                       else
-                               m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
-       }
-
-       mapper->setMinimapMode(mode);
-}
-
-void Game::toggleFog()
-{
-       bool fog_enabled = g_settings->getBool("enable_fog");
-       g_settings->setBool("enable_fog", !fog_enabled);
-       if (fog_enabled)
-               m_game_ui->showTranslatedStatusText("Fog disabled");
-       else
-               m_game_ui->showTranslatedStatusText("Fog enabled");
-}
-
-
-void Game::toggleDebug()
-{
-       // Initial / 4x toggle: Chat only
-       // 1x toggle: Debug text with chat
-       // 2x toggle: Debug text with profiler graph
-       // 3x toggle: Debug text and wireframe
-       if (!m_game_ui->m_flags.show_debug) {
-               m_game_ui->m_flags.show_debug = true;
-               m_game_ui->m_flags.show_profiler_graph = false;
-               draw_control->show_wireframe = false;
-               m_game_ui->showTranslatedStatusText("Debug info shown");
-       } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
-               m_game_ui->m_flags.show_profiler_graph = true;
-               m_game_ui->showTranslatedStatusText("Profiler graph shown");
-       } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
-               m_game_ui->m_flags.show_profiler_graph = false;
-               draw_control->show_wireframe = true;
-               m_game_ui->showTranslatedStatusText("Wireframe shown");
-       } else {
-               m_game_ui->m_flags.show_debug = false;
-               m_game_ui->m_flags.show_profiler_graph = false;
-               draw_control->show_wireframe = false;
-               if (client->checkPrivilege("debug")) {
-                       m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
-               } else {
-                       m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
-               }
-       }
-}
-
-
-void Game::toggleUpdateCamera()
-{
-       m_flags.disable_camera_update = !m_flags.disable_camera_update;
-       if (m_flags.disable_camera_update)
-               m_game_ui->showTranslatedStatusText("Camera update disabled");
-       else
-               m_game_ui->showTranslatedStatusText("Camera update enabled");
-}
-
-
-void Game::increaseViewRange()
-{
-       s16 range = g_settings->getS16("viewing_range");
-       s16 range_new = range + 10;
-
-       wchar_t buf[255];
-       const wchar_t *str;
-       if (range_new > 4000) {
-               range_new = 4000;
-               str = wgettext("Viewing range is at maximum: %d");
-               swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
-               delete[] str;
-               m_game_ui->showStatusText(buf);
-
-       } else {
-               str = wgettext("Viewing range changed to %d");
-               swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
-               delete[] str;
-               m_game_ui->showStatusText(buf);
-       }
-       g_settings->set("viewing_range", itos(range_new));
-}
-
-
-void Game::decreaseViewRange()
-{
-       s16 range = g_settings->getS16("viewing_range");
-       s16 range_new = range - 10;
-
-       wchar_t buf[255];
-       const wchar_t *str;
-       if (range_new < 20) {
-               range_new = 20;
-               str = wgettext("Viewing range is at minimum: %d");
-               swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
-               delete[] str;
-               m_game_ui->showStatusText(buf);
-       } else {
-               str = wgettext("Viewing range changed to %d");
-               swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
-               delete[] str;
-               m_game_ui->showStatusText(buf);
-       }
-       g_settings->set("viewing_range", itos(range_new));
-}
-
-
-void Game::toggleFullViewRange()
-{
-       draw_control->range_all = !draw_control->range_all;
-       if (draw_control->range_all)
-               m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
-       else
-               m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
-}
-
-
-void Game::checkZoomEnabled()
-{
-       LocalPlayer *player = client->getEnv().getLocalPlayer();
-       if (player->getZoomFOV() < 0.001f)
-               m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
-}
-
-
-void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
-{
-       if ((device->isWindowActive() && device->isWindowFocused()
-                       && !isMenuActive()) || random_input) {
-
-#ifndef __ANDROID__
-               if (!random_input) {
-                       // Mac OSX gets upset if this is set every frame
-                       if (device->getCursorControl()->isVisible())
-                               device->getCursorControl()->setVisible(false);
-               }
-#endif
-
-               if (m_first_loop_after_window_activation) {
-                       m_first_loop_after_window_activation = false;
-
-                       input->setMousePos(driver->getScreenSize().Width / 2,
-                               driver->getScreenSize().Height / 2);
-               } else {
-                       updateCameraOrientation(cam, dtime);
-               }
-
-       } else {
-
-#ifndef ANDROID
-               // Mac OSX gets upset if this is set every frame
-               if (!device->getCursorControl()->isVisible())
-                       device->getCursorControl()->setVisible(true);
-#endif
-
-               m_first_loop_after_window_activation = true;
-
-       }
-}
-
-void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
-{
-#ifdef HAVE_TOUCHSCREENGUI
-       if (g_touchscreengui) {
-               cam->camera_yaw   += g_touchscreengui->getYawChange();
-               cam->camera_pitch  = g_touchscreengui->getPitch();
-       } else {
-#endif
-               v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
-               v2s32 dist = input->getMousePos() - center;
-
-               if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
-                       dist.Y = -dist.Y;
-               }
-
-               cam->camera_yaw   -= dist.X * m_cache_mouse_sensitivity;
-               cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity;
-
-               if (dist.X != 0 || dist.Y != 0)
-                       input->setMousePos(center.X, center.Y);
-#ifdef HAVE_TOUCHSCREENGUI
-       }
-#endif
-
-       if (m_cache_enable_joysticks) {
-               f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime;
-               cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
-               cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
-       }
-
-       cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
-}
-
-
-void Game::updatePlayerControl(const CameraOrientation &cam)
-{
-       //TimeTaker tt("update player control", NULL, PRECISION_NANO);
-
-       // DO NOT use the isKeyDown method for the forward, backward, left, right
-       // buttons, as the code that uses the controls needs to be able to
-       // distinguish between the two in order to know when to use joysticks.
-
-       PlayerControl control(
-               input->isKeyDown(KeyType::FORWARD),
-               input->isKeyDown(KeyType::BACKWARD),
-               input->isKeyDown(KeyType::LEFT),
-               input->isKeyDown(KeyType::RIGHT),
-               isKeyDown(KeyType::JUMP),
-               isKeyDown(KeyType::SPECIAL1),
-               isKeyDown(KeyType::SNEAK),
-               isKeyDown(KeyType::ZOOM),
-               input->getLeftState(),
-               input->getRightState(),
-               cam.camera_pitch,
-               cam.camera_yaw,
-               input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE),
-               input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE)
-       );
-
-       u32 keypress_bits =
-                       ( (u32)(isKeyDown(KeyType::FORWARD)                       & 0x1) << 0) |
-                       ( (u32)(isKeyDown(KeyType::BACKWARD)                      & 0x1) << 1) |
-                       ( (u32)(isKeyDown(KeyType::LEFT)                          & 0x1) << 2) |
-                       ( (u32)(isKeyDown(KeyType::RIGHT)                         & 0x1) << 3) |
-                       ( (u32)(isKeyDown(KeyType::JUMP)                          & 0x1) << 4) |
-                       ( (u32)(isKeyDown(KeyType::SPECIAL1)                      & 0x1) << 5) |
-                       ( (u32)(isKeyDown(KeyType::SNEAK)                         & 0x1) << 6) |
-                       ( (u32)(input->getLeftState()                             & 0x1) << 7) |
-                       ( (u32)(input->getRightState()                            & 0x1) << 8
-               );
-
-#ifdef ANDROID
-       /* For Android, simulate holding down AUX1 (fast move) if the user has
-        * the fast_move setting toggled on. If there is an aux1 key defined for
-        * Android then its meaning is inverted (i.e. holding aux1 means walk and
-        * not fast)
-        */
-       if (m_cache_hold_aux1) {
-               control.aux1 = control.aux1 ^ true;
-               keypress_bits ^= ((u32)(1U << 5));
-       }
-#endif
-
-       LocalPlayer *player = client->getEnv().getLocalPlayer();
-
-       // autojump if set: simulate "jump" key
-       if (player->getAutojump()) {
-               control.jump = true;
-               keypress_bits |= 1U << 4;
-       }
-
-       client->setPlayerControl(control);
-       player->keyPressed = keypress_bits;
-
-       //tt.stop();
-}
-
-
-inline void Game::step(f32 *dtime)
-{
-       bool can_be_and_is_paused =
-                       (simple_singleplayer_mode && g_menumgr.pausesGame());
-
-       if (can_be_and_is_paused) {     // This is for a singleplayer server
-               *dtime = 0;             // No time passes
-       } else {
-               if (server)
-                       server->step(*dtime);
-
-               client->step(*dtime);
-       }
-}
-
-const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
-       {&Game::handleClientEvent_None},
-       {&Game::handleClientEvent_PlayerDamage},
-       {&Game::handleClientEvent_PlayerForceMove},
-       {&Game::handleClientEvent_Deathscreen},
-       {&Game::handleClientEvent_ShowFormSpec},
-       {&Game::handleClientEvent_ShowLocalFormSpec},
-       {&Game::handleClientEvent_HandleParticleEvent},
-       {&Game::handleClientEvent_HandleParticleEvent},
-       {&Game::handleClientEvent_HandleParticleEvent},
-       {&Game::handleClientEvent_HudAdd},
-       {&Game::handleClientEvent_HudRemove},
-       {&Game::handleClientEvent_HudChange},
-       {&Game::handleClientEvent_SetSky},
-       {&Game::handleClientEvent_OverrideDayNigthRatio},
-       {&Game::handleClientEvent_CloudParams},
-};
-
-void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
-{
-       FATAL_ERROR("ClientEvent type None received");
-}
-
-void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
-{
-       if (client->moddingEnabled()) {
-               client->getScript()->on_damage_taken(event->player_damage.amount);
-       }
-
-       // Damage flash and hurt tilt are not used at death
-       if (client->getHP() > 0) {
-               runData.damage_flash += 95.0f + 3.2f * event->player_damage.amount;
-               runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
-
-               LocalPlayer *player = client->getEnv().getLocalPlayer();
-
-               player->hurt_tilt_timer = 1.5f;
-               player->hurt_tilt_strength =
-                       rangelim(event->player_damage.amount / 4.0f, 1.0f, 4.0f);
-       }
-
-       // Play damage sound
-       client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
-}
-
-void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
-{
-       cam->camera_yaw = event->player_force_move.yaw;
-       cam->camera_pitch = event->player_force_move.pitch;
-}
-
-void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
-{
-       // If CSM enabled, deathscreen is handled by CSM code in
-       // builtin/client/init.lua
-       if (client->moddingEnabled())
-               client->getScript()->on_death();
-       else
-               showDeathFormspec();
-
-       /* Handle visualization */
-       LocalPlayer *player = client->getEnv().getLocalPlayer();
-       runData.damage_flash = 0;
-       player->hurt_tilt_timer = 0;
-       player->hurt_tilt_strength = 0;
-}
-
-void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
-{
-       if (event->show_formspec.formspec->empty()) {
-               if (current_formspec && (event->show_formspec.formname->empty()
-                       || *(event->show_formspec.formname) == cur_formname)) {
-                       current_formspec->quitMenu();
-               }
-       } else {
-               FormspecFormSource *fs_src =
-                       new FormspecFormSource(*(event->show_formspec.formspec));
-               TextDestPlayerInventory *txt_dst =
-                       new TextDestPlayerInventory(client, *(event->show_formspec.formname));
-
-               GUIFormSpecMenu::create(current_formspec, client, &input->joystick,
-                       fs_src, txt_dst, client->getFormspecPrepend());
-               cur_formname = *(event->show_formspec.formname);
-       }
-
-       delete event->show_formspec.formspec;
-       delete event->show_formspec.formname;
-}
-
-void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
-{
-       FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
-       LocalFormspecHandler *txt_dst =
-               new LocalFormspecHandler(*event->show_formspec.formname, client);
-       GUIFormSpecMenu::create(current_formspec, client, &input->joystick,
-                       fs_src, txt_dst, client->getFormspecPrepend());
-
-       delete event->show_formspec.formspec;
-       delete event->show_formspec.formname;
-}
-
-void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
-               CameraOrientation *cam)
-{
-       LocalPlayer *player = client->getEnv().getLocalPlayer();
-       client->getParticleManager()->handleParticleEvent(event, client, player);
-}
-
-void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
-{
-       LocalPlayer *player = client->getEnv().getLocalPlayer();
-       auto &hud_server_to_client = client->getHUDTranslationMap();
-
-       u32 server_id = event->hudadd.server_id;
-       // ignore if we already have a HUD with that ID
-       auto i = hud_server_to_client.find(server_id);
-       if (i != hud_server_to_client.end()) {
-               delete event->hudadd.pos;
-               delete event->hudadd.name;
-               delete event->hudadd.scale;
-               delete event->hudadd.text;
-               delete event->hudadd.align;
-               delete event->hudadd.offset;
-               delete event->hudadd.world_pos;
-               delete event->hudadd.size;
-               return;
-       }
-
-       HudElement *e = new HudElement;
-       e->type   = (HudElementType)event->hudadd.type;
-       e->pos    = *event->hudadd.pos;
-       e->name   = *event->hudadd.name;
-       e->scale  = *event->hudadd.scale;
-       e->text   = *event->hudadd.text;
-       e->number = event->hudadd.number;
-       e->item   = event->hudadd.item;
-       e->dir    = event->hudadd.dir;
-       e->align  = *event->hudadd.align;
-       e->offset = *event->hudadd.offset;
-       e->world_pos = *event->hudadd.world_pos;
-       e->size = *event->hudadd.size;
-       hud_server_to_client[server_id] = player->addHud(e);
-
-       delete event->hudadd.pos;
-       delete event->hudadd.name;
-       delete event->hudadd.scale;
-       delete event->hudadd.text;
-       delete event->hudadd.align;
-       delete event->hudadd.offset;
-       delete event->hudadd.world_pos;
-       delete event->hudadd.size;
-}
-
-void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
-{
-       LocalPlayer *player = client->getEnv().getLocalPlayer();
-       HudElement *e = player->removeHud(event->hudrm.id);
-       delete e;
-}
-
-void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
-{
-       LocalPlayer *player = client->getEnv().getLocalPlayer();
-
-       u32 id = event->hudchange.id;
-       HudElement *e = player->getHud(id);
-
-       if (e == NULL) {
-               delete event->hudchange.v3fdata;
-               delete event->hudchange.v2fdata;
-               delete event->hudchange.sdata;
-               delete event->hudchange.v2s32data;
-               return;
-       }
-
-       switch (event->hudchange.stat) {
-               case HUD_STAT_POS:
-                       e->pos = *event->hudchange.v2fdata;
-                       break;
-
-               case HUD_STAT_NAME:
-                       e->name = *event->hudchange.sdata;
-                       break;
-
-               case HUD_STAT_SCALE:
-                       e->scale = *event->hudchange.v2fdata;
-                       break;
-
-               case HUD_STAT_TEXT:
-                       e->text = *event->hudchange.sdata;
-                       break;
-
-               case HUD_STAT_NUMBER:
-                       e->number = event->hudchange.data;
-                       break;
-
-               case HUD_STAT_ITEM:
-                       e->item = event->hudchange.data;
-                       break;
-
-               case HUD_STAT_DIR:
-                       e->dir = event->hudchange.data;
-                       break;
-
-               case HUD_STAT_ALIGN:
-                       e->align = *event->hudchange.v2fdata;
-                       break;
-
-               case HUD_STAT_OFFSET:
-                       e->offset = *event->hudchange.v2fdata;
-                       break;
-
-               case HUD_STAT_WORLD_POS:
-                       e->world_pos = *event->hudchange.v3fdata;
-                       break;
-
-               case HUD_STAT_SIZE:
-                       e->size = *event->hudchange.v2s32data;
-                       break;
-       }
-
-       delete event->hudchange.v3fdata;
-       delete event->hudchange.v2fdata;
-       delete event->hudchange.sdata;
-       delete event->hudchange.v2s32data;
-}
-
-void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
-{
-       sky->setVisible(false);
-       // Whether clouds are visible in front of a custom skybox
-       sky->setCloudsEnabled(event->set_sky.clouds);
-
-       if (skybox) {
-               skybox->remove();
-               skybox = NULL;
-       }
-
-       // Handle according to type
-       if (*event->set_sky.type == "regular") {
-               sky->setVisible(true);
-               sky->setCloudsEnabled(true);
-       } else if (*event->set_sky.type == "skybox" &&
-               event->set_sky.params->size() == 6) {
-               sky->setFallbackBgColor(*event->set_sky.bgcolor);
-               skybox = RenderingEngine::get_scene_manager()->addSkyBoxSceneNode(
-                       texture_src->getTextureForMesh((*event->set_sky.params)[0]),
-                       texture_src->getTextureForMesh((*event->set_sky.params)[1]),
-                       texture_src->getTextureForMesh((*event->set_sky.params)[2]),
-                       texture_src->getTextureForMesh((*event->set_sky.params)[3]),
-                       texture_src->getTextureForMesh((*event->set_sky.params)[4]),
-                       texture_src->getTextureForMesh((*event->set_sky.params)[5]));
-       }
-               // Handle everything else as plain color
-       else {
-               if (*event->set_sky.type != "plain")
-                       infostream << "Unknown sky type: "
-                               << (*event->set_sky.type) << std::endl;
-
-               sky->setFallbackBgColor(*event->set_sky.bgcolor);
-       }
-
-       delete event->set_sky.bgcolor;
-       delete event->set_sky.type;
-       delete event->set_sky.params;
-}
-
-void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
-               CameraOrientation *cam)
-{
-       client->getEnv().setDayNightRatioOverride(
-               event->override_day_night_ratio.do_override,
-               event->override_day_night_ratio.ratio_f * 1000.0f);
-}
-
-void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
-{
-       if (!clouds)
-               return;
-
-       clouds->setDensity(event->cloud_params.density);
-       clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
-       clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
-       clouds->setHeight(event->cloud_params.height);
-       clouds->setThickness(event->cloud_params.thickness);
-       clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
-}
-
-void Game::processClientEvents(CameraOrientation *cam)
-{
-       while (client->hasClientEvents()) {
-               std::unique_ptr<ClientEvent> event(client->getClientEvent());
-               FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
-               const ClientEventHandler& evHandler = clientEventHandler[event->type];
-               (this->*evHandler.handler)(event.get(), cam);
-       }
-}
-
-void Game::updateChat(f32 dtime, const v2u32 &screensize)
-{
-       // Add chat log output for errors to be shown in chat
-       static LogOutputBuffer chat_log_error_buf(g_logger, LL_ERROR);
-
-       // Get new messages from error log buffer
-       while (!chat_log_error_buf.empty()) {
-               std::wstring error_message = utf8_to_wide(chat_log_error_buf.get());
-               if (!g_settings->getBool("disable_escape_sequences")) {
-                       error_message.insert(0, L"\x1b(c@red)");
-                       error_message.append(L"\x1b(c@white)");
-               }
-               chat_backend->addMessage(L"", error_message);
-       }
-
-       // Get new messages from client
-       std::wstring message;
-       while (client->getChatMessage(message)) {
-               chat_backend->addUnparsedMessage(message);
-       }
-
-       // Remove old messages
-       chat_backend->step(dtime);
-
-       // Display all messages in a static text element
-       m_game_ui->setChatText(chat_backend->getRecentChat(),
-               chat_backend->getRecentBuffer().getLineCount());
-}
-
-void Game::updateCamera(u32 busy_time, f32 dtime)
-{
-       LocalPlayer *player = client->getEnv().getLocalPlayer();
-
-       /*
-               For interaction purposes, get info about the held item
-               - What item is it?
-               - Is it a usable item?
-               - Can it point to liquids?
-       */
-       ItemStack playeritem;
-       {
-               InventoryList *mlist = local_inventory->getList("main");
-
-               if (mlist && client->getPlayerItem() < mlist->getSize())
-                       playeritem = mlist->getItem(client->getPlayerItem());
-       }
-
-       if (playeritem.getDefinition(itemdef_manager).name.empty()) { // override the hand
-               InventoryList *hlist = local_inventory->getList("hand");
-               if (hlist)
-                       playeritem = hlist->getItem(0);
-       }
-
-
-       ToolCapabilities playeritem_toolcap =
-               playeritem.getToolCapabilities(itemdef_manager);
-
-       v3s16 old_camera_offset = camera->getOffset();
-
-       if (wasKeyDown(KeyType::CAMERA_MODE)) {
-               GenericCAO *playercao = player->getCAO();
-
-               // If playercao not loaded, don't change camera
-               if (!playercao)
-                       return;
-
-               camera->toggleCameraMode();
-
-               playercao->setVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
-               playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
-       }
-
-       float full_punch_interval = playeritem_toolcap.full_punch_interval;
-       float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
-
-       tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
-       camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
-       camera->step(dtime);
-
-       v3f camera_position = camera->getPosition();
-       v3f camera_direction = camera->getDirection();
-       f32 camera_fov = camera->getFovMax();
-       v3s16 camera_offset = camera->getOffset();
-
-       m_camera_offset_changed = (camera_offset != old_camera_offset);
-
-       if (!m_flags.disable_camera_update) {
-               client->getEnv().getClientMap().updateCamera(camera_position,
-                               camera_direction, camera_fov, camera_offset);
-
-               if (m_camera_offset_changed) {
-                       client->updateCameraOffset(camera_offset);
-                       client->getEnv().updateCameraOffset(camera_offset);
-
-                       if (clouds)
-                               clouds->updateCameraOffset(camera_offset);
-               }
-       }
-}
-
-
-void Game::updateSound(f32 dtime)
-{
-       // Update sound listener
-       v3s16 camera_offset = camera->getOffset();
-       sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
-                             v3f(0, 0, 0), // velocity
-                             camera->getDirection(),
-                             camera->getCameraNode()->getUpVector());
-
-       bool mute_sound = g_settings->getBool("mute_sound");
-       if (mute_sound) {
-               sound->setListenerGain(0.0f);
-       } else {
-               // Check if volume is in the proper range, else fix it.
-               float old_volume = g_settings->getFloat("sound_volume");
-               float new_volume = rangelim(old_volume, 0.0f, 1.0f);
-               sound->setListenerGain(new_volume);
-
-               if (old_volume != new_volume) {
-                       g_settings->setFloat("sound_volume", new_volume);
-               }
-       }
-
-       LocalPlayer *player = client->getEnv().getLocalPlayer();
-
-       // Tell the sound maker whether to make footstep sounds
-       soundmaker->makes_footstep_sound = player->makes_footstep_sound;
-
-       //      Update sound maker
-       if (player->makes_footstep_sound)
-               soundmaker->step(dtime);
-
-       ClientMap &map = client->getEnv().getClientMap();
-       MapNode n = map.getNodeNoEx(player->getFootstepNodePos());
-       soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
-}
-
-
-void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
-{
-       LocalPlayer *player = client->getEnv().getLocalPlayer();
-
-       ItemStack playeritem;
-       {
-               InventoryList *mlist = local_inventory->getList("main");
-
-               if (mlist && client->getPlayerItem() < mlist->getSize())
-                       playeritem = mlist->getItem(client->getPlayerItem());
-       }
-
-       const ItemDefinition &playeritem_def =
-                       playeritem.getDefinition(itemdef_manager);
-       InventoryList *hlist = local_inventory->getList("hand");
-       const ItemDefinition &hand_def =
-               hlist ? hlist->getItem(0).getDefinition(itemdef_manager) : itemdef_manager->get("");
-
-       v3f player_position  = player->getPosition();
-       v3f camera_position  = camera->getPosition();
-       v3f camera_direction = camera->getDirection();
-       v3s16 camera_offset  = camera->getOffset();
-
-
-       /*
-               Calculate what block is the crosshair pointing to
-       */
-
-       f32 d = playeritem_def.range; // max. distance
-       f32 d_hand = hand_def.range;
-
-       if (d < 0 && d_hand >= 0)
-               d = d_hand;
-       else if (d < 0)
-               d = 4.0;
-
-       core::line3d<f32> shootline;
-
-       if (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT) {
-               shootline = core::line3d<f32>(camera_position,
-                       camera_position + camera_direction * BS * d);
-       } else {
-           // prevent player pointing anything in front-view
-               shootline = core::line3d<f32>(camera_position,camera_position);
-       }
-
-#ifdef HAVE_TOUCHSCREENGUI
-
-       if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
-               shootline = g_touchscreengui->getShootline();
-               // Scale shootline to the acual distance the player can reach
-               shootline.end = shootline.start
-                       + shootline.getVector().normalize() * BS * d;
-               shootline.start += intToFloat(camera_offset, BS);
-               shootline.end += intToFloat(camera_offset, BS);
-       }
-
-#endif
-
-       PointedThing pointed = updatePointedThing(shootline,
-                       playeritem_def.liquids_pointable,
-                       !runData.ldown_for_dig,
-                       camera_offset);
-
-       if (pointed != runData.pointed_old) {
-               infostream << "Pointing at " << pointed.dump() << std::endl;
-               hud->updateSelectionMesh(camera_offset);
-       }
-
-       if (runData.digging_blocked && !input->getLeftState()) {
-               // allow digging again if button is not pressed
-               runData.digging_blocked = false;
-       }
-
-       /*
-               Stop digging when
-               - releasing left mouse button
-               - pointing away from node
-       */
-       if (runData.digging) {
-               if (input->getLeftReleased()) {
-                       infostream << "Left button released"
-                                  << " (stopped digging)" << std::endl;
-                       runData.digging = false;
-               } else if (pointed != runData.pointed_old) {
-                       if (pointed.type == POINTEDTHING_NODE
-                                       && runData.pointed_old.type == POINTEDTHING_NODE
-                                       && pointed.node_undersurface
-                                                       == runData.pointed_old.node_undersurface) {
-                               // Still pointing to the same node, but a different face.
-                               // Don't reset.
-                       } else {
-                               infostream << "Pointing away from node"
-                                          << " (stopped digging)" << std::endl;
-                               runData.digging = false;
-                               hud->updateSelectionMesh(camera_offset);
-                       }
-               }
-
-               if (!runData.digging) {
-                       client->interact(1, runData.pointed_old);
-                       client->setCrack(-1, v3s16(0, 0, 0));
-                       runData.dig_time = 0.0;
-               }
-       } else if (runData.dig_instantly && input->getLeftReleased()) {
-               // Remove e.g. torches faster when clicking instead of holding LMB
-               runData.nodig_delay_timer = 0;
-               runData.dig_instantly = false;
-       }
-
-       if (!runData.digging && runData.ldown_for_dig && !input->getLeftState()) {
-               runData.ldown_for_dig = false;
-       }
-
-       runData.left_punch = false;
-
-       soundmaker->m_player_leftpunch_sound.name = "";
-
-       // Prepare for repeating, unless we're not supposed to
-       if (input->getRightState() && !g_settings->getBool("safe_dig_and_place"))
-               runData.repeat_rightclick_timer += dtime;
-       else
-               runData.repeat_rightclick_timer = 0;
-
-       if (playeritem_def.usable && input->getLeftState()) {
-               if (input->getLeftClicked() && (!client->moddingEnabled()
-                               || !client->getScript()->on_item_use(playeritem, pointed)))
-                       client->interact(4, pointed);
-       } else if (pointed.type == POINTEDTHING_NODE) {
-               ToolCapabilities playeritem_toolcap =
-                               playeritem.getToolCapabilities(itemdef_manager);
-               if (playeritem.name.empty()) {
-                       const ToolCapabilities *handToolcap = hlist
-                               ? &hlist->getItem(0).getToolCapabilities(itemdef_manager)
-                               : itemdef_manager->get("").tool_capabilities;
-
-                       if (handToolcap != nullptr)
-                               playeritem_toolcap = *handToolcap;
-               }
-               handlePointingAtNode(pointed, playeritem_def, playeritem,
-                       playeritem_toolcap, dtime);
-       } else if (pointed.type == POINTEDTHING_OBJECT) {
-               handlePointingAtObject(pointed, playeritem, player_position, show_debug);
-       } else if (input->getLeftState()) {
-               // When button is held down in air, show continuous animation
-               runData.left_punch = true;
-       } else if (input->getRightClicked()) {
-               handlePointingAtNothing(playeritem);
-       }
-
-       runData.pointed_old = pointed;
-
-       if (runData.left_punch || input->getLeftClicked())
-               camera->setDigging(0); // left click animation
-
-       input->resetLeftClicked();
-       input->resetRightClicked();
-
-       input->resetLeftReleased();
-       input->resetRightReleased();
-}
-
-
-PointedThing Game::updatePointedThing(
-       const core::line3d<f32> &shootline,
-       bool liquids_pointable,
-       bool look_for_object,
-       const v3s16 &camera_offset)
-{
-       std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
-       selectionboxes->clear();
-       hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
-       static thread_local const bool show_entity_selectionbox = g_settings->getBool(
-               "show_entity_selectionbox");
-
-       ClientEnvironment &env = client->getEnv();
-       ClientMap &map = env.getClientMap();
-       const NodeDefManager *nodedef = map.getNodeDefManager();
-
-       runData.selected_object = NULL;
-
-       RaycastState s(shootline, look_for_object, liquids_pointable);
-       PointedThing result;
-       env.continueRaycast(&s, &result);
-       if (result.type == POINTEDTHING_OBJECT) {
-               runData.selected_object = client->getEnv().getActiveObject(result.object_id);
-               aabb3f selection_box;
-               if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
-                               runData.selected_object->getSelectionBox(&selection_box)) {
-                       v3f pos = runData.selected_object->getPosition();
-                       selectionboxes->push_back(aabb3f(selection_box));
-                       hud->setSelectionPos(pos, camera_offset);
-               }
-       } else if (result.type == POINTEDTHING_NODE) {
-               // Update selection boxes
-               MapNode n = map.getNodeNoEx(result.node_undersurface);
-               std::vector<aabb3f> boxes;
-               n.getSelectionBoxes(nodedef, &boxes,
-                       n.getNeighbors(result.node_undersurface, &map));
-
-               f32 d = 0.002 * BS;
-               for (std::vector<aabb3f>::const_iterator i = boxes.begin();
-                       i != boxes.end(); ++i) {
-                       aabb3f box = *i;
-                       box.MinEdge -= v3f(d, d, d);
-                       box.MaxEdge += v3f(d, d, d);
-                       selectionboxes->push_back(box);
-               }
-               hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
-                       camera_offset);
-               hud->setSelectedFaceNormal(v3f(
-                       result.intersection_normal.X,
-                       result.intersection_normal.Y,
-                       result.intersection_normal.Z));
-       }
-
-       // Update selection mesh light level and vertex colors
-       if (!selectionboxes->empty()) {
-               v3f pf = hud->getSelectionPos();
-               v3s16 p = floatToInt(pf, BS);
-
-               // Get selection mesh light level
-               MapNode n = map.getNodeNoEx(p);
-               u16 node_light = getInteriorLight(n, -1, nodedef);
-               u16 light_level = node_light;
-
-               for (const v3s16 &dir : g_6dirs) {
-                       n = map.getNodeNoEx(p + dir);
-                       node_light = getInteriorLight(n, -1, nodedef);
-                       if (node_light > light_level)
-                               light_level = node_light;
-               }
-
-               u32 daynight_ratio = client->getEnv().getDayNightRatio();
-               video::SColor c;
-               final_color_blend(&c, light_level, daynight_ratio);
-
-               // Modify final color a bit with time
-               u32 timer = porting::getTimeMs() % 5000;
-               float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
-               float sin_r = 0.08f * std::sin(timerf);
-               float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
-               float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
-               c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
-               c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
-               c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
-
-               // Set mesh final color
-               hud->setSelectionMeshColor(c);
-       }
-       return result;
-}
-
-
-void Game::handlePointingAtNothing(const ItemStack &playerItem)
-{
-       infostream << "Right Clicked in Air" << std::endl;
-       PointedThing fauxPointed;
-       fauxPointed.type = POINTEDTHING_NOTHING;
-       client->interact(5, fauxPointed);
-}
-
-
-void Game::handlePointingAtNode(const PointedThing &pointed,
-       const ItemDefinition &playeritem_def, const ItemStack &playeritem,
-       const ToolCapabilities &playeritem_toolcap, f32 dtime)
-{
-       v3s16 nodepos = pointed.node_undersurface;
-       v3s16 neighbourpos = pointed.node_abovesurface;
-
-       /*
-               Check information text of node
-       */
-
-       ClientMap &map = client->getEnv().getClientMap();
-
-       if (runData.nodig_delay_timer <= 0.0 && input->getLeftState()
-                       && !runData.digging_blocked
-                       && client->checkPrivilege("interact")) {
-               handleDigging(pointed, nodepos, playeritem_toolcap, dtime);
-       }
-
-       // This should be done after digging handling
-       NodeMetadata *meta = map.getNodeMetadata(nodepos);
-
-       if (meta) {
-               m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
-                       meta->getString("infotext"))));
-       } else {
-               MapNode n = map.getNodeNoEx(nodepos);
-
-               if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
-                       m_game_ui->setInfoText(L"Unknown node: " +
-                               utf8_to_wide(nodedef_manager->get(n).name));
-               }
-       }
-
-       if ((input->getRightClicked() ||
-                       runData.repeat_rightclick_timer >= m_repeat_right_click_time) &&
-                       client->checkPrivilege("interact")) {
-               runData.repeat_rightclick_timer = 0;
-               infostream << "Ground right-clicked" << std::endl;
-
-               if (meta && !meta->getString("formspec").empty() && !random_input
-                               && !isKeyDown(KeyType::SNEAK)) {
-                       // Report right click to server
-                       if (nodedef_manager->get(map.getNodeNoEx(nodepos)).rightclickable) {
-                               client->interact(3, pointed);
-                       }
-
-                       infostream << "Launching custom inventory view" << std::endl;
-
-                       InventoryLocation inventoryloc;
-                       inventoryloc.setNodeMeta(nodepos);
-
-                       NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
-                               &client->getEnv().getClientMap(), nodepos);
-                       TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
-
-                       GUIFormSpecMenu::create(current_formspec, client, &input->joystick, fs_src,
-                               txt_dst, client->getFormspecPrepend());
-                       cur_formname.clear();
-
-                       current_formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
-               } else {
-                       // Report right click to server
-
-                       camera->setDigging(1);  // right click animation (always shown for feedback)
-
-                       // If the wielded item has node placement prediction,
-                       // make that happen
-                       bool placed = nodePlacementPrediction(playeritem_def, playeritem, nodepos,
-                               neighbourpos);
-
-                       if (placed) {
-                               // Report to server
-                               client->interact(3, pointed);
-                               // Read the sound
-                               soundmaker->m_player_rightpunch_sound =
-                                               playeritem_def.sound_place;
-
-                               if (client->moddingEnabled())
-                                       client->getScript()->on_placenode(pointed, playeritem_def);
-                       } else {
-                               soundmaker->m_player_rightpunch_sound =
-                                               SimpleSoundSpec();
-
-                               if (playeritem_def.node_placement_prediction.empty() ||
-                                               nodedef_manager->get(map.getNodeNoEx(nodepos)).rightclickable) {
-                                       client->interact(3, pointed); // Report to server
-                               } else {
-                                       soundmaker->m_player_rightpunch_sound =
-                                               playeritem_def.sound_place_failed;
-                               }
-                       }
-               }
-       }
-}
-
-bool Game::nodePlacementPrediction(const ItemDefinition &playeritem_def,
-       const ItemStack &playeritem, const v3s16 &nodepos, const v3s16 &neighbourpos)
-{
-       std::string prediction = playeritem_def.node_placement_prediction;
-       const NodeDefManager *nodedef = client->ndef();
-       ClientMap &map = client->getEnv().getClientMap();
-       MapNode node;
-       bool is_valid_position;
-
-       node = map.getNodeNoEx(nodepos, &is_valid_position);
-       if (!is_valid_position)
-               return false;
-
-       if (!prediction.empty() && !nodedef->get(node).rightclickable) {
-               verbosestream << "Node placement prediction for "
-                       << playeritem_def.name << " is "
-                       << prediction << std::endl;
-               v3s16 p = neighbourpos;
-
-               // Place inside node itself if buildable_to
-               MapNode n_under = map.getNodeNoEx(nodepos, &is_valid_position);
-               if (is_valid_position)
-               {
-                       if (nodedef->get(n_under).buildable_to)
-                               p = nodepos;
-                       else {
-                               node = map.getNodeNoEx(p, &is_valid_position);
-                               if (is_valid_position &&!nodedef->get(node).buildable_to)
-                                       return false;
-                       }
-               }
-
-               // Find id of predicted node
-               content_t id;
-               bool found = nodedef->getId(prediction, id);
-
-               if (!found) {
-                       errorstream << "Node placement prediction failed for "
-                               << playeritem_def.name << " (places "
-                               << prediction
-                               << ") - Name not known" << std::endl;
-                       return false;
-               }
-
-               const ContentFeatures &predicted_f = nodedef->get(id);
-
-               // Predict param2 for facedir and wallmounted nodes
-               u8 param2 = 0;
-
-               if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
-                       predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
-                       v3s16 dir = nodepos - neighbourpos;
-
-                       if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
-                               param2 = dir.Y < 0 ? 1 : 0;
-                       } else if (abs(dir.X) > abs(dir.Z)) {
-                               param2 = dir.X < 0 ? 3 : 2;
-                       } else {
-                               param2 = dir.Z < 0 ? 5 : 4;
-                       }
-               }
-
-               if (predicted_f.param_type_2 == CPT2_FACEDIR ||
-                       predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
-                       v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
-
-                       if (abs(dir.X) > abs(dir.Z)) {
-                               param2 = dir.X < 0 ? 3 : 1;
-                       } else {
-                               param2 = dir.Z < 0 ? 2 : 0;
-                       }
-               }
-
-               assert(param2 <= 5);
-
-               //Check attachment if node is in group attached_node
-               if (((ItemGroupList) predicted_f.groups)["attached_node"] != 0) {
-                       static v3s16 wallmounted_dirs[8] = {
-                               v3s16(0, 1, 0),
-                               v3s16(0, -1, 0),
-                               v3s16(1, 0, 0),
-                               v3s16(-1, 0, 0),
-                               v3s16(0, 0, 1),
-                               v3s16(0, 0, -1),
-                       };
-                       v3s16 pp;
-
-                       if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
-                               predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
-                               pp = p + wallmounted_dirs[param2];
-                       else
-                               pp = p + v3s16(0, -1, 0);
-
-                       if (!nodedef->get(map.getNodeNoEx(pp)).walkable)
-                               return false;
-               }
-
-               // Apply color
-               if ((predicted_f.param_type_2 == CPT2_COLOR
-                       || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
-                       || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
-                       const std::string &indexstr = playeritem.metadata.getString(
-                               "palette_index", 0);
-                       if (!indexstr.empty()) {
-                               s32 index = mystoi(indexstr);
-                               if (predicted_f.param_type_2 == CPT2_COLOR) {
-                                       param2 = index;
-                               } else if (predicted_f.param_type_2
-                                       == CPT2_COLORED_WALLMOUNTED) {
-                                       // param2 = pure palette index + other
-                                       param2 = (index & 0xf8) | (param2 & 0x07);
-                               } else if (predicted_f.param_type_2
-                                       == CPT2_COLORED_FACEDIR) {
-                                       // param2 = pure palette index + other
-                                       param2 = (index & 0xe0) | (param2 & 0x1f);
-                               }
-                       }
-               }
-
-               // Add node to client map
-               MapNode n(id, 0, param2);
-
-               try {
-                       LocalPlayer *player = client->getEnv().getLocalPlayer();
-
-                       // Dont place node when player would be inside new node
-                       // NOTE: This is to be eventually implemented by a mod as client-side Lua
-                       if (!nodedef->get(n).walkable ||
-                               g_settings->getBool("enable_build_where_you_stand") ||
-                               (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
-                               (nodedef->get(n).walkable &&
-                                       neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
-                                       neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
-
-                               // This triggers the required mesh update too
-                               client->addNode(p, n);
-                               return true;
-                       }
-               } catch (InvalidPositionException &e) {
-                       errorstream << "Node placement prediction failed for "
-                               << playeritem_def.name << " (places "
-                               << prediction
-                               << ") - Position not loaded" << std::endl;
-               }
-       }
-
-       return false;
-}
-
-void Game::handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
-               const v3f &player_position, bool show_debug)
-{
-       std::wstring infotext = unescape_translate(
-               utf8_to_wide(runData.selected_object->infoText()));
-
-       if (show_debug) {
-               if (!infotext.empty()) {
-                       infotext += L"\n";
-               }
-               infotext += utf8_to_wide(runData.selected_object->debugInfoText());
-       }
-
-       m_game_ui->setInfoText(infotext);
-
-       if (input->getLeftState()) {
-               bool do_punch = false;
-               bool do_punch_damage = false;
-
-               if (runData.object_hit_delay_timer <= 0.0) {
-                       do_punch = true;
-                       do_punch_damage = true;
-                       runData.object_hit_delay_timer = object_hit_delay;
-               }
-
-               if (input->getLeftClicked())
-                       do_punch = true;
-
-               if (do_punch) {
-                       infostream << "Left-clicked object" << std::endl;
-                       runData.left_punch = true;
-               }
-
-               if (do_punch_damage) {
-                       // Report direct punch
-                       v3f objpos = runData.selected_object->getPosition();
-                       v3f dir = (objpos - player_position).normalize();
-                       ItemStack item = playeritem;
-                       if (playeritem.name.empty()) {
-                               InventoryList *hlist = local_inventory->getList("hand");
-                               if (hlist) {
-                                       item = hlist->getItem(0);
-                               }
-                       }
-
-                       bool disable_send = runData.selected_object->directReportPunch(
-                                       dir, &item, runData.time_from_last_punch);
-                       runData.time_from_last_punch = 0;
-
-                       if (!disable_send)
-                               client->interact(0, pointed);
-               }
-       } else if (input->getRightClicked()) {
-               infostream << "Right-clicked object" << std::endl;
-               client->interact(3, pointed);  // place
-       }
-}
-
-
-void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
-               const ToolCapabilities &playeritem_toolcap, f32 dtime)
-{
-       LocalPlayer *player = client->getEnv().getLocalPlayer();
-       ClientMap &map = client->getEnv().getClientMap();
-       MapNode n = client->getEnv().getClientMap().getNodeNoEx(nodepos);
-
-       // NOTE: Similar piece of code exists on the server side for
-       // cheat detection.
-       // Get digging parameters
-       DigParams params = getDigParams(nodedef_manager->get(n).groups,
-                       &playeritem_toolcap);
-
-       // If can't dig, try hand
-       if (!params.diggable) {
-               InventoryList *hlist = local_inventory->getList("hand");
-               const ToolCapabilities *tp = hlist
-                       ? &hlist->getItem(0).getToolCapabilities(itemdef_manager)
-                       : itemdef_manager->get("").tool_capabilities;
-
-               if (tp)
-                       params = getDigParams(nodedef_manager->get(n).groups, tp);
-       }
-
-       if (!params.diggable) {
-               // I guess nobody will wait for this long
-               runData.dig_time_complete = 10000000.0;
-       } else {
-               runData.dig_time_complete = params.time;
-
-               if (m_cache_enable_particles) {
-                       const ContentFeatures &features = client->getNodeDefManager()->get(n);
-                       client->getParticleManager()->addNodeParticle(client,
-                                       player, nodepos, n, features);
-               }
-       }
-
-       if (!runData.digging) {
-               infostream << "Started digging" << std::endl;
-               runData.dig_instantly = runData.dig_time_complete == 0;
-               if (client->moddingEnabled() && client->getScript()->on_punchnode(nodepos, n))
-                       return;
-               client->interact(0, pointed);
-               runData.digging = true;
-               runData.ldown_for_dig = true;
-       }
-
-       if (!runData.dig_instantly) {
-               runData.dig_index = (float)crack_animation_length
-                               * runData.dig_time
-                               / runData.dig_time_complete;
-       } else {
-               // This is for e.g. torches
-               runData.dig_index = crack_animation_length;
-       }
-
-       SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
-
-       if (sound_dig.exists() && params.diggable) {
-               if (sound_dig.name == "__group") {
-                       if (!params.main_group.empty()) {
-                               soundmaker->m_player_leftpunch_sound.gain = 0.5;
-                               soundmaker->m_player_leftpunch_sound.name =
-                                               std::string("default_dig_") +
-                                               params.main_group;
-                       }
-               } else {
-                       soundmaker->m_player_leftpunch_sound = sound_dig;
-               }
-       }
-
-       // Don't show cracks if not diggable
-       if (runData.dig_time_complete >= 100000.0) {
-       } else if (runData.dig_index < crack_animation_length) {
-               //TimeTaker timer("client.setTempMod");
-               //infostream<<"dig_index="<<dig_index<<std::endl;
-               client->setCrack(runData.dig_index, nodepos);
-       } else {
-               infostream << "Digging completed" << std::endl;
-               client->setCrack(-1, v3s16(0, 0, 0));
-
-               runData.dig_time = 0;
-               runData.digging = false;
-               // we successfully dug, now block it from repeating if we want to be safe
-               if (g_settings->getBool("safe_dig_and_place"))
-                       runData.digging_blocked = true;
-
-               runData.nodig_delay_timer =
-                               runData.dig_time_complete / (float)crack_animation_length;
-
-               // We don't want a corresponding delay to very time consuming nodes
-               // and nodes without digging time (e.g. torches) get a fixed delay.
-               if (runData.nodig_delay_timer > 0.3)
-                       runData.nodig_delay_timer = 0.3;
-               else if (runData.dig_instantly)
-                       runData.nodig_delay_timer = 0.15;
-
-               bool is_valid_position;
-               MapNode wasnode = map.getNodeNoEx(nodepos, &is_valid_position);
-               if (is_valid_position) {
-                       if (client->moddingEnabled() &&
-                                       client->getScript()->on_dignode(nodepos, wasnode)) {
-                               return;
-                       }
-
-                       const ContentFeatures &f = client->ndef()->get(wasnode);
-                       if (f.node_dig_prediction == "air") {
-                               client->removeNode(nodepos);
-                       } else if (!f.node_dig_prediction.empty()) {
-                               content_t id;
-                               bool found = client->ndef()->getId(f.node_dig_prediction, id);
-                               if (found)
-                                       client->addNode(nodepos, id, true);
-                       }
-                       // implicit else: no prediction
-               }
-
-               client->interact(2, pointed);
-
-               if (m_cache_enable_particles) {
-                       const ContentFeatures &features =
-                               client->getNodeDefManager()->get(wasnode);
-                       client->getParticleManager()->addDiggingParticles(client,
-                               player, nodepos, wasnode, features);
-               }
-
-
-               // Send event to trigger sound
-               client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
-       }
-
-       if (runData.dig_time_complete < 100000.0) {
-               runData.dig_time += dtime;
-       } else {
-               runData.dig_time = 0;
-               client->setCrack(-1, nodepos);
-       }
-
-       camera->setDigging(0);  // left click animation
-}
-
-
-void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
-               const CameraOrientation &cam)
-{
-       LocalPlayer *player = client->getEnv().getLocalPlayer();
-
-       /*
-               Fog range
-       */
-
-       if (draw_control->range_all) {
-               runData.fog_range = 100000 * BS;
-       } else {
-               runData.fog_range = draw_control->wanted_range * BS;
-       }
-
-       /*
-               Calculate general brightness
-       */
-       u32 daynight_ratio = client->getEnv().getDayNightRatio();
-       float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
-       float direct_brightness;
-       bool sunlight_seen;
-
-       if (m_cache_enable_noclip && m_cache_enable_free_move) {
-               direct_brightness = time_brightness;
-               sunlight_seen = true;
-       } else {
-               ScopeProfiler sp(g_profiler, "Detecting background light", SPT_AVG);
-               float old_brightness = sky->getBrightness();
-               direct_brightness = client->getEnv().getClientMap()
-                               .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
-                                       daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
-                                   / 255.0;
-       }
-
-       float time_of_day_smooth = runData.time_of_day_smooth;
-       float time_of_day = client->getEnv().getTimeOfDayF();
-
-       static const float maxsm = 0.05f;
-       static const float todsm = 0.05f;
-
-       if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
-                       std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
-                       std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
-               time_of_day_smooth = time_of_day;
-
-       if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
-               time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
-                               + (time_of_day + 1.0) * todsm;
-       else
-               time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
-                               + time_of_day * todsm;
-
-       runData.time_of_day_smooth = time_of_day_smooth;
-
-       sky->update(time_of_day_smooth, time_brightness, direct_brightness,
-                       sunlight_seen, camera->getCameraMode(), player->getYaw(),
-                       player->getPitch());
-
-       /*
-               Update clouds
-       */
-       if (clouds) {
-               if (sky->getCloudsVisible()) {
-                       clouds->setVisible(true);
-                       clouds->step(dtime);
-                       // camera->getPosition is not enough for 3rd person views
-                       v3f camera_node_position = camera->getCameraNode()->getPosition();
-                       v3s16 camera_offset      = camera->getOffset();
-                       camera_node_position.X   = camera_node_position.X + camera_offset.X * BS;
-                       camera_node_position.Y   = camera_node_position.Y + camera_offset.Y * BS;
-                       camera_node_position.Z   = camera_node_position.Z + camera_offset.Z * BS;
-                       clouds->update(camera_node_position,
-                                       sky->getCloudColor());
-                       if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
-                               // if inside clouds, and fog enabled, use that as sky
-                               // color(s)
-                               video::SColor clouds_dark = clouds->getColor()
-                                               .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
-                               sky->overrideColors(clouds_dark, clouds->getColor());
-                               sky->setBodiesVisible(false);
-                               runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
-                               // do not draw clouds after all
-                               clouds->setVisible(false);
-                       }
-               } else {
-                       clouds->setVisible(false);
-               }
-       }
-
-       /*
-               Update particles
-       */
-       client->getParticleManager()->step(dtime);
-
-       /*
-               Fog
-       */
-
-       if (m_cache_enable_fog) {
-               driver->setFog(
-                               sky->getBgColor(),
-                               video::EFT_FOG_LINEAR,
-                               runData.fog_range * m_cache_fog_start,
-                               runData.fog_range * 1.0,
-                               0.01,
-                               false, // pixel fog
-                               true // range fog
-               );
-       } else {
-               driver->setFog(
-                               sky->getBgColor(),
-                               video::EFT_FOG_LINEAR,
-                               100000 * BS,
-                               110000 * BS,
-                               0.01f,
-                               false, // pixel fog
-                               false // range fog
-               );
-       }
-
-       /*
-               Get chat messages from client
-       */
-
-       v2u32 screensize = driver->getScreenSize();
-
-       updateChat(dtime, screensize);
-
-       /*
-               Inventory
-       */
-
-       if (client->getPlayerItem() != runData.new_playeritem)
-               client->selectPlayerItem(runData.new_playeritem);
-
-       // Update local inventory if it has changed
-       if (client->getLocalInventoryUpdated()) {
-               //infostream<<"Updating local inventory"<<std::endl;
-               client->getLocalInventory(*local_inventory);
-               runData.update_wielded_item_trigger = true;
-       }
-
-       if (runData.update_wielded_item_trigger) {
-               // Update wielded tool
-               InventoryList *mlist = local_inventory->getList("main");
-
-               if (mlist && (client->getPlayerItem() < mlist->getSize())) {
-                       ItemStack item = mlist->getItem(client->getPlayerItem());
-                       if (item.getDefinition(itemdef_manager).name.empty()) { // override the hand
-                               InventoryList *hlist = local_inventory->getList("hand");
-                               if (hlist)
-                                       item = hlist->getItem(0);
-                       }
-                       camera->wield(item);
-               }
-
-               runData.update_wielded_item_trigger = false;
-       }
-
-       /*
-               Update block draw list every 200ms or when camera direction has
-               changed much
-       */
-       runData.update_draw_list_timer += dtime;
-
-       v3f camera_direction = camera->getDirection();
-       if (runData.update_draw_list_timer >= 0.2
-                       || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
-                       || m_camera_offset_changed) {
-               runData.update_draw_list_timer = 0;
-               client->getEnv().getClientMap().updateDrawList();
-               runData.update_draw_list_last_cam_dir = camera_direction;
-       }
-
-       m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, dtime);
-
-       /*
-          make sure menu is on top
-          1. Delete formspec menu reference if menu was removed
-          2. Else, make sure formspec menu is on top
-       */
-       if (current_formspec) {
-               if (current_formspec->getReferenceCount() == 1) {
-                       current_formspec->drop();
-                       current_formspec = NULL;
-               } else if (isMenuActive()) {
-                       guiroot->bringToFront(current_formspec);
-               }
-       }
-
-       /*
-               Drawing begins
-       */
-       const video::SColor &skycolor = sky->getSkyColor();
-
-       TimeTaker tt_draw("mainloop: draw");
-       driver->beginScene(true, true, skycolor);
-
-       bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
-                       (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
-                       (camera->getCameraMode() == CAMERA_MODE_FIRST));
-       bool draw_crosshair = (
-                       (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
-                       (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
-#ifdef HAVE_TOUCHSCREENGUI
-       try {
-               draw_crosshair = !g_settings->getBool("touchtarget");
-       } catch (SettingNotFoundException) {
-       }
-#endif
-       RenderingEngine::draw_scene(skycolor, m_game_ui->m_flags.show_hud,
-                       m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
-
-       /*
-               Profiler graph
-       */
-       if (m_game_ui->m_flags.show_profiler_graph)
-               graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
-
-       /*
-               Damage flash
-       */
-       if (runData.damage_flash > 0.0f) {
-               video::SColor color(runData.damage_flash, 180, 0, 0);
-               driver->draw2DRectangle(color,
-                                       core::rect<s32>(0, 0, screensize.X, screensize.Y),
-                                       NULL);
-
-               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)
-                       player->hurt_tilt_strength = 0.0f;
-       }
-
-       /*
-               Update minimap pos and rotation
-       */
-       if (mapper && m_game_ui->m_flags.show_minimap && m_game_ui->m_flags.show_hud) {
-               mapper->setPos(floatToInt(player->getPosition(), BS));
-               mapper->setAngle(player->getYaw());
-       }
-
-       /*
-               End scene
-       */
-       driver->endScene();
-
-       stats->drawtime = tt_draw.stop(true);
-       g_profiler->graphAdd("mainloop_draw", stats->drawtime / 1000.0f);
-}
-
-/* Log times and stuff for visualization */
-inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
-{
-       Profiler::GraphValues values;
-       g_profiler->graphGet(values);
-       graph->put(values);
-}
-
-
-
-/****************************************************************************
- Misc
- ****************************************************************************/
-
-/* On some computers framerate doesn't seem to be automatically limited
- */
-inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
-{
-       // not using getRealTime is necessary for wine
-       device->getTimer()->tick(); // Maker sure device time is up-to-date
-       u32 time = device->getTimer()->getTime();
-       u32 last_time = fps_timings->last_time;
-
-       if (time > last_time)  // Make sure time hasn't overflowed
-               fps_timings->busy_time = time - last_time;
-       else
-               fps_timings->busy_time = 0;
-
-       u32 frametime_min = 1000 / (g_menumgr.pausesGame()
-                       ? g_settings->getFloat("pause_fps_max")
-                       : g_settings->getFloat("fps_max"));
-
-       if (fps_timings->busy_time < frametime_min) {
-               fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
-               device->sleep(fps_timings->sleep_time);
-       } else {
-               fps_timings->sleep_time = 0;
-       }
-
-       /* Get the new value of the device timer. Note that device->sleep() may
-        * not sleep for the entire requested time as sleep may be interrupted and
-        * therefore it is arguably more accurate to get the new time from the
-        * device rather than calculating it by adding sleep_time to time.
-        */
-
-       device->getTimer()->tick(); // Update device timer
-       time = device->getTimer()->getTime();
-
-       if (time > last_time)  // Make sure last_time hasn't overflowed
-               *dtime = (time - last_time) / 1000.0;
-       else
-               *dtime = 0;
-
-       fps_timings->last_time = time;
-}
-
-void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
-{
-       const wchar_t *wmsg = wgettext(msg);
-       RenderingEngine::draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
-               draw_clouds);
-       delete[] wmsg;
-}
-
-void Game::settingChangedCallback(const std::string &setting_name, void *data)
-{
-       ((Game *)data)->readSettings();
-}
-
-void Game::readSettings()
-{
-       m_cache_doubletap_jump               = g_settings->getBool("doubletap_jump");
-       m_cache_enable_clouds                = g_settings->getBool("enable_clouds");
-       m_cache_enable_joysticks             = g_settings->getBool("enable_joysticks");
-       m_cache_enable_particles             = g_settings->getBool("enable_particles");
-       m_cache_enable_fog                   = g_settings->getBool("enable_fog");
-       m_cache_mouse_sensitivity            = g_settings->getFloat("mouse_sensitivity");
-       m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
-       m_repeat_right_click_time            = g_settings->getFloat("repeat_rightclick_time");
-
-       m_cache_enable_noclip                = g_settings->getBool("noclip");
-       m_cache_enable_free_move             = g_settings->getBool("free_move");
-
-       m_cache_fog_start                    = g_settings->getFloat("fog_start");
-
-       m_cache_cam_smoothing = 0;
-       if (g_settings->getBool("cinematic"))
-               m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
-       else
-               m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
-
-       m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
-       m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
-       m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
-
-       m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
-}
-
-/****************************************************************************/
-/****************************************************************************
- Shutdown / cleanup
- ****************************************************************************/
-/****************************************************************************/
-
-void Game::extendedResourceCleanup()
-{
-       // Extended resource accounting
-       infostream << "Irrlicht resources after cleanup:" << std::endl;
-       infostream << "\tRemaining meshes   : "
-                  << RenderingEngine::get_mesh_cache()->getMeshCount() << std::endl;
-       infostream << "\tRemaining textures : "
-                  << driver->getTextureCount() << std::endl;
-
-       for (unsigned int i = 0; i < driver->getTextureCount(); i++) {
-               irr::video::ITexture *texture = driver->getTextureByIndex(i);
-               infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str()
-                          << std::endl;
-       }
-
-       clearTextureNameCache();
-       infostream << "\tRemaining materials: "
-               << driver-> getMaterialRendererCount()
-                      << " (note: irrlicht doesn't support removing renderers)" << std::endl;
-}
-
-void Game::showDeathFormspec()
-{
-       static std::string formspec =
-               std::string(FORMSPEC_VERSION_STRING) +
-               SIZE_TAG
-               "bgcolor[#320000b4;true]"
-               "label[4.85,1.35;" + gettext("You died") + "]"
-               "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
-               ;
-
-       /* Create menu */
-       /* Note: FormspecFormSource and LocalFormspecHandler  *
-        * are deleted by guiFormSpecMenu                     */
-       FormspecFormSource *fs_src = new FormspecFormSource(formspec);
-       LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
-
-       GUIFormSpecMenu::create(current_formspec, client, &input->joystick, fs_src,
-               txt_dst, client->getFormspecPrepend());
-       current_formspec->setFocus("btn_respawn");
-}
-
-#define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
-void Game::showPauseMenu()
-{
-#ifdef __ANDROID__
-       static const std::string control_text = strgettext("Default Controls:\n"
-               "No menu visible:\n"
-               "- single tap: button activate\n"
-               "- double tap: place/use\n"
-               "- slide finger: look around\n"
-               "Menu/Inventory visible:\n"
-               "- double tap (outside):\n"
-               " -->close\n"
-               "- touch stack, touch slot:\n"
-               " --> move stack\n"
-               "- touch&drag, tap 2nd finger\n"
-               " --> place single item to slot\n"
-               );
-#else
-       static const std::string control_text_template = strgettext("Controls:\n"
-               "- %s: move forwards\n"
-               "- %s: move backwards\n"
-               "- %s: move left\n"
-               "- %s: move right\n"
-               "- %s: jump/climb\n"
-               "- %s: sneak/go down\n"
-               "- %s: drop item\n"
-               "- %s: inventory\n"
-               "- Mouse: turn/look\n"
-               "- Mouse left: dig/punch\n"
-               "- Mouse right: place/use\n"
-               "- Mouse wheel: select item\n"
-               "- %s: chat\n"
-       );
-
-        char control_text_buf[600];
-
-        porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
-                       GET_KEY_NAME(keymap_forward),
-                       GET_KEY_NAME(keymap_backward),
-                       GET_KEY_NAME(keymap_left),
-                       GET_KEY_NAME(keymap_right),
-                       GET_KEY_NAME(keymap_jump),
-                       GET_KEY_NAME(keymap_sneak),
-                       GET_KEY_NAME(keymap_drop),
-                       GET_KEY_NAME(keymap_inventory),
-                       GET_KEY_NAME(keymap_chat)
-                       );
-
-       std::string control_text = std::string(control_text_buf);
-       str_formspec_escape(control_text);
-#endif
-
-       float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
-       std::ostringstream os;
-
-       os << FORMSPEC_VERSION_STRING  << SIZE_TAG
-               << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
-               << strgettext("Continue") << "]";
-
-       if (!simple_singleplayer_mode) {
-               os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
-                       << strgettext("Change Password") << "]";
-       } else {
-               os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
-       }
-
-#ifndef __ANDROID__
-       os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
-               << strgettext("Sound Volume") << "]";
-       os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
-               << strgettext("Change Keys")  << "]";
-#endif
-       os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
-               << strgettext("Exit to Menu") << "]";
-       os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
-               << strgettext("Exit to OS")   << "]"
-               << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
-               << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
-               << "\n"
-               <<  strgettext("Game info:") << "\n";
-       const std::string &address = client->getAddressName();
-       static const std::string mode = strgettext("- Mode: ");
-       if (!simple_singleplayer_mode) {
-               Address serverAddress = client->getServerAddress();
-               if (!address.empty()) {
-                       os << mode << strgettext("Remote server") << "\n"
-                                       << strgettext("- Address: ") << address;
-               } else {
-                       os << mode << strgettext("Hosting server");
-               }
-               os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
-       } else {
-               os << mode << strgettext("Singleplayer") << "\n";
-       }
-       if (simple_singleplayer_mode || address.empty()) {
-               static const std::string on = strgettext("On");
-               static const std::string off = strgettext("Off");
-               const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
-               const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
-               const std::string &announced = g_settings->getBool("server_announce") ? on : off;
-               os << strgettext("- Damage: ") << damage << "\n"
-                               << strgettext("- Creative Mode: ") << creative << "\n";
-               if (!simple_singleplayer_mode) {
-                       const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
-                       os << strgettext("- PvP: ") << pvp << "\n"
-                                       << strgettext("- Public: ") << announced << "\n";
-                       std::string server_name = g_settings->get("server_name");
-                       str_formspec_escape(server_name);
-                       if (announced == on && !server_name.empty())
-                               os << strgettext("- Server Name: ") << server_name;
-
-               }
-       }
-       os << ";]";
-
-       /* Create menu */
-       /* Note: FormspecFormSource and LocalFormspecHandler  *
-        * are deleted by guiFormSpecMenu                     */
-       FormspecFormSource *fs_src = new FormspecFormSource(os.str());
-       LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
-
-       GUIFormSpecMenu::create(current_formspec, client, &input->joystick,
-                       fs_src, txt_dst, client->getFormspecPrepend());
-       current_formspec->setFocus("btn_continue");
-       current_formspec->doPause = true;
-}
-
-/****************************************************************************/
-/****************************************************************************
- extern function for launching the game
- ****************************************************************************/
-/****************************************************************************/
-
-void the_game(bool *kill,
-               bool random_input,
-               InputHandler *input,
-               const std::string &map_dir,
-               const std::string &playername,
-               const std::string &password,
-               const std::string &address,         // If empty local server is created
-               u16 port,
-
-               std::string &error_message,
-               ChatBackend &chat_backend,
-               bool *reconnect_requested,
-               const SubgameSpec &gamespec,        // Used for local game
-               bool simple_singleplayer_mode)
-{
-       Game game;
-
-       /* Make a copy of the server address because if a local singleplayer server
-        * is created then this is updated and we don't want to change the value
-        * passed to us by the calling function
-        */
-       std::string server_address = address;
-
-       try {
-
-               if (game.startup(kill, random_input, input, map_dir,
-                               playername, password, &server_address, port, error_message,
-                               reconnect_requested, &chat_backend, gamespec,
-                               simple_singleplayer_mode)) {
-                       game.run();
-                       game.shutdown();
-               }
-
-       } catch (SerializationError &e) {
-               error_message = std::string("A serialization error occurred:\n")
-                               + e.what() + "\n\nThe server is probably "
-                               " running a different version of " PROJECT_NAME_C ".";
-               errorstream << error_message << std::endl;
-       } catch (ServerError &e) {
-               error_message = e.what();
-               errorstream << "ServerError: " << error_message << std::endl;
-       } catch (ModError &e) {
-               error_message = e.what() + strgettext("\nCheck debug.txt for details.");
-               errorstream << "ModError: " << error_message << std::endl;
-       }
-}
diff --git a/src/game.h b/src/game.h
deleted file mode 100644 (file)
index 69e6eed..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include "irrlichttypes.h"
-#include <string>
-
-class InputHandler;
-class ChatBackend;  /* to avoid having to include chat.h */
-struct SubgameSpec;
-
-struct Jitter {
-       f32 max, min, avg, counter, max_sample, min_sample, max_fraction;
-};
-
-struct RunStats {
-       u32 drawtime;
-
-       Jitter dtime_jitter, busy_time_jitter;
-};
-
-struct CameraOrientation {
-       f32 camera_yaw;    // "right/left"
-       f32 camera_pitch;  // "up/down"
-};
-
-void the_game(bool *kill,
-               bool random_input,
-               InputHandler *input,
-               const std::string &map_dir,
-               const std::string &playername,
-               const std::string &password,
-               const std::string &address, // If "", local server is used
-               u16 port,
-               std::string &error_message,
-               ChatBackend &chat_backend,
-               bool *reconnect_requested,
-               const SubgameSpec &gamespec, // Used for local game
-               bool simple_singleplayer_mode);
index b194834e297d3f452546acea71aaf81184300f0a..1ccb4e6d1f7f76f0db61a48f98e055abc36fbf6c 100644 (file)
@@ -19,14 +19,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "guiChatConsole.h"
 #include "chat.h"
-#include "client.h"
+#include "client/client.h"
 #include "debug.h"
 #include "gettime.h"
-#include "keycode.h"
+#include "client/keycode.h"
 #include "settings.h"
 #include "porting.h"
 #include "client/tile.h"
-#include "fontengine.h"
+#include "client/fontengine.h"
 #include "log.h"
 #include "gettext.h"
 #include <string>
index a13929a4890bcdbd3d300573f6ccf19d58683eec..9a374b405e6782628677ec82c53c25360d0b2974 100644 (file)
@@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 */
 
 #include "guiConfirmRegistration.h"
-#include "client.h"
+#include "client/client.h"
 #include <IGUICheckBox.h>
 #include <IGUIButton.h>
 #include <IGUIStaticText.h>
index 67ab6437d27ba64bf5a56199b675ef44dcefb4d4..a3ef1b9a609ac83829e96713dfaf26c592d7a622 100644 (file)
@@ -32,11 +32,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "guiMainMenu.h"
 #include "sound.h"
 #include "client/sound_openal.h"
-#include "clouds.h"
+#include "client/clouds.h"
 #include "httpfetch.h"
 #include "log.h"
-#include "fontengine.h"
-#include "guiscalingfilter.h"
+#include "client/fontengine.h"
+#include "client/guiscalingfilter.h"
 #include "irrlicht_changes/static_text.h"
 
 #ifdef __ANDROID__
index f2305582192d4c229584da06cd2e28b41ede8d80..c52a6ee2716900c11238630d1b915b333742436a 100644 (file)
@@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "guiTable.h"
 #include "constants.h"
 #include "gamedef.h"
-#include "keycode.h"
+#include "client/keycode.h"
 #include "util/strfnd.h"
 #include <IGUICheckBox.h>
 #include <IGUIEditBox.h>
@@ -47,13 +47,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "mainmenumanager.h"
 #include "porting.h"
 #include "settings.h"
-#include "client.h"
-#include "fontengine.h"
+#include "client/client.h"
+#include "client/fontengine.h"
 #include "util/hex.h"
 #include "util/numeric.h"
 #include "util/string.h" // for parseColorString()
 #include "irrlicht_changes/static_text.h"
-#include "guiscalingfilter.h"
+#include "client/guiscalingfilter.h"
 #include "guiEditBoxWithScrollbar.h"
 #include "intlGUIEditBox.h"
 
@@ -673,10 +673,10 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme
                        pos.Y = stoi(v_pos[1]); //acts as offset
                        clip = true;
                }
-               
+
                if (!data->explicit_size && !clip)
                        warningstream << "invalid use of unclipped background without a size[] element" << std::endl;
-               
+
                m_backgrounds.emplace_back(name, pos, geom, clip);
 
                return;
index eded61e4c84468cbf08cbbd2768287c742049613..0aaa05e1830c4d6cfafa257cb4d6b390dcebb469 100644 (file)
@@ -24,7 +24,7 @@
 #include "irrlichttypes_extrabloated.h"
 #include "modalMenu.h"
 #include "gettext.h"
-#include "keycode.h"
+#include "client/keycode.h"
 #include <string>
 #include <vector>
 
index e29ef27e603aa39b66d6c2b6e8e4ca5f2af17e14..469c38dbeda3f683534add57615bc5e141e2f3fc 100644 (file)
@@ -17,7 +17,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
 
 #include "guiPasswordChange.h"
-#include "client.h"
+#include "client/client.h"
 #include <IGUICheckBox.h>
 #include <IGUIEditBox.h>
 #include <IGUIButton.h>
index b1a027e9b76495513b737f6cb7e874e7b4b4885c..a123bdd6d9a59f405bd8c27d073246250b5685f3 100644 (file)
@@ -36,7 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "util/string.h" // for parseColorString()
 #include "settings.h" // for settings
 #include "porting.h" // for dpi
-#include "guiscalingfilter.h"
+#include "client/guiscalingfilter.h"
 
 /*
        GUITable
diff --git a/src/guiscalingfilter.cpp b/src/guiscalingfilter.cpp
deleted file mode 100644 (file)
index 3b4377d..0000000
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
-Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
-
-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 "guiscalingfilter.h"
-#include "imagefilters.h"
-#include "porting.h"
-#include "settings.h"
-#include "util/numeric.h"
-#include <cstdio>
-#include "client/renderingengine.h"
-
-/* Maintain a static cache to store the images that correspond to textures
- * in a format that's manipulable by code.  Some platforms exhibit issues
- * converting textures back into images repeatedly, and some don't even
- * allow it at all.
- */
-std::map<io::path, video::IImage *> g_imgCache;
-
-/* Maintain a static cache of all pre-scaled textures.  These need to be
- * cleared as well when the cached images.
- */
-std::map<io::path, video::ITexture *> g_txrCache;
-
-/* Manually insert an image into the cache, useful to avoid texture-to-image
- * conversion whenever we can intercept it.
- */
-void guiScalingCache(io::path key, video::IVideoDriver *driver, video::IImage *value)
-{
-       if (!g_settings->getBool("gui_scaling_filter"))
-               return;
-       video::IImage *copied = driver->createImage(value->getColorFormat(),
-                       value->getDimension());
-       value->copyTo(copied);
-       g_imgCache[key] = copied;
-}
-
-// Manually clear the cache, e.g. when switching to different worlds.
-void guiScalingCacheClear()
-{
-       for (auto &it : g_imgCache) {
-               if (it.second)
-                       it.second->drop();
-       }
-       g_imgCache.clear();
-       for (auto &it : g_txrCache) {
-               if (it.second)
-                       RenderingEngine::get_video_driver()->removeTexture(it.second);
-       }
-       g_txrCache.clear();
-}
-
-/* Get a cached, high-quality pre-scaled texture for display purposes.  If the
- * texture is not already cached, attempt to create it.  Returns a pre-scaled texture,
- * or the original texture if unable to pre-scale it.
- */
-video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver,
-               video::ITexture *src, const core::rect<s32> &srcrect,
-               const core::rect<s32> &destrect)
-{
-       if (src == NULL)
-               return src;
-       if (!g_settings->getBool("gui_scaling_filter"))
-               return src;
-
-       // Calculate scaled texture name.
-       char rectstr[200];
-       porting::mt_snprintf(rectstr, sizeof(rectstr), "%d:%d:%d:%d:%d:%d",
-               srcrect.UpperLeftCorner.X,
-               srcrect.UpperLeftCorner.Y,
-               srcrect.getWidth(),
-               srcrect.getHeight(),
-               destrect.getWidth(),
-               destrect.getHeight());
-       io::path origname = src->getName().getPath();
-       io::path scalename = origname + "@guiScalingFilter:" + rectstr;
-
-       // Search for existing scaled texture.
-       video::ITexture *scaled = g_txrCache[scalename];
-       if (scaled)
-               return scaled;
-
-       // Try to find the texture converted to an image in the cache.
-       // If the image was not found, try to extract it from the texture.
-       video::IImage* srcimg = g_imgCache[origname];
-       if (srcimg == NULL) {
-               if (!g_settings->getBool("gui_scaling_filter_txr2img"))
-                       return src;
-               srcimg = driver->createImageFromData(src->getColorFormat(),
-                       src->getSize(), src->lock(), false);
-               src->unlock();
-               g_imgCache[origname] = srcimg;
-       }
-
-       // Create a new destination image and scale the source into it.
-       imageCleanTransparent(srcimg, 0);
-       video::IImage *destimg = driver->createImage(src->getColorFormat(),
-                       core::dimension2d<u32>((u32)destrect.getWidth(),
-                       (u32)destrect.getHeight()));
-       imageScaleNNAA(srcimg, srcrect, destimg);
-
-#ifdef __ANDROID__
-       // Android is very picky about textures being powers of 2, so expand
-       // the image dimensions to the next power of 2, if necessary, for
-       // that platform.
-       video::IImage *po2img = driver->createImage(src->getColorFormat(),
-                       core::dimension2d<u32>(npot2((u32)destrect.getWidth()),
-                       npot2((u32)destrect.getHeight())));
-       po2img->fill(video::SColor(0, 0, 0, 0));
-       destimg->copyTo(po2img);
-       destimg->drop();
-       destimg = po2img;
-#endif
-
-       // Convert the scaled image back into a texture.
-       scaled = driver->addTexture(scalename, destimg, NULL);
-       destimg->drop();
-       g_txrCache[scalename] = scaled;
-
-       return scaled;
-}
-
-/* Convenience wrapper for guiScalingResizeCached that accepts parameters that
- * are available at GUI imagebutton creation time.
- */
-video::ITexture *guiScalingImageButton(video::IVideoDriver *driver,
-               video::ITexture *src, s32 width, s32 height)
-{
-       if (src == NULL)
-               return src;
-       return guiScalingResizeCached(driver, src,
-               core::rect<s32>(0, 0, src->getSize().Width, src->getSize().Height),
-               core::rect<s32>(0, 0, width, height));
-}
-
-/* Replacement for driver->draw2DImage() that uses the high-quality pre-scaled
- * texture, if configured.
- */
-void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr,
-               const core::rect<s32> &destrect, const core::rect<s32> &srcrect,
-               const core::rect<s32> *cliprect, const video::SColor *const colors,
-               bool usealpha)
-{
-       // Attempt to pre-scale image in software in high quality.
-       video::ITexture *scaled = guiScalingResizeCached(driver, txr, srcrect, destrect);
-       if (scaled == NULL)
-               return;
-
-       // Correct source rect based on scaled image.
-       const core::rect<s32> mysrcrect = (scaled != txr)
-               ? core::rect<s32>(0, 0, destrect.getWidth(), destrect.getHeight())
-               : srcrect;
-
-       driver->draw2DImage(scaled, destrect, mysrcrect, cliprect, colors, usealpha);
-}
diff --git a/src/guiscalingfilter.h b/src/guiscalingfilter.h
deleted file mode 100644 (file)
index 4661bf8..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
-Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-
-/* Manually insert an image into the cache, useful to avoid texture-to-image
- * conversion whenever we can intercept it.
- */
-void guiScalingCache(io::path key, video::IVideoDriver *driver, video::IImage *value);
-
-// Manually clear the cache, e.g. when switching to different worlds.
-void guiScalingCacheClear();
-
-/* Get a cached, high-quality pre-scaled texture for display purposes.  If the
- * texture is not already cached, attempt to create it.  Returns a pre-scaled texture,
- * or the original texture if unable to pre-scale it.
- */
-video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, video::ITexture *src,
-               const core::rect<s32> &srcrect, const core::rect<s32> &destrect);
-
-/* Convenience wrapper for guiScalingResizeCached that accepts parameters that
- * are available at GUI imagebutton creation time.
- */
-video::ITexture *guiScalingImageButton(video::IVideoDriver *driver, video::ITexture *src,
-               s32 width, s32 height);
-
-/* Replacement for driver->draw2DImage() that uses the high-quality pre-scaled
- * texture, if configured.
- */
-void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr,
-               const core::rect<s32> &destrect, const core::rect<s32> &srcrect,
-               const core::rect<s32> *cliprect = 0, const video::SColor *const colors = 0,
-               bool usealpha = false);
diff --git a/src/imagefilters.cpp b/src/imagefilters.cpp
deleted file mode 100644 (file)
index dd02962..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
-Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
-
-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 "imagefilters.h"
-#include "util/numeric.h"
-#include <cmath>
-
-/* Fill in RGB values for transparent pixels, to correct for odd colors
- * appearing at borders when blending.  This is because many PNG optimizers
- * like to discard RGB values of transparent pixels, but when blending then
- * with non-transparent neighbors, their RGB values will shpw up nonetheless.
- *
- * This function modifies the original image in-place.
- *
- * Parameter "threshold" is the alpha level below which pixels are considered
- * transparent.  Should be 127 for 3d where alpha is threshold, but 0 for
- * 2d where alpha is blended.
- */
-void imageCleanTransparent(video::IImage *src, u32 threshold)
-{
-       core::dimension2d<u32> dim = src->getDimension();
-
-       // Walk each pixel looking for fully transparent ones.
-       // Note: loop y around x for better cache locality.
-       for (u32 ctry = 0; ctry < dim.Height; ctry++)
-       for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
-
-               // Ignore opaque pixels.
-               irr::video::SColor c = src->getPixel(ctrx, ctry);
-               if (c.getAlpha() > threshold)
-                       continue;
-
-               // Sample size and total weighted r, g, b values.
-               u32 ss = 0, sr = 0, sg = 0, sb = 0;
-
-               // Walk each neighbor pixel (clipped to image bounds).
-               for (u32 sy = (ctry < 1) ? 0 : (ctry - 1);
-                               sy <= (ctry + 1) && sy < dim.Height; sy++)
-               for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
-                               sx <= (ctrx + 1) && sx < dim.Width; sx++) {
-
-                       // Ignore transparent pixels.
-                       irr::video::SColor d = src->getPixel(sx, sy);
-                       if (d.getAlpha() <= threshold)
-                               continue;
-
-                       // Add RGB values weighted by alpha.
-                       u32 a = d.getAlpha();
-                       ss += a;
-                       sr += a * d.getRed();
-                       sg += a * d.getGreen();
-                       sb += a * d.getBlue();
-               }
-
-               // If we found any neighbor RGB data, set pixel to average
-               // weighted by alpha.
-               if (ss > 0) {
-                       c.setRed(sr / ss);
-                       c.setGreen(sg / ss);
-                       c.setBlue(sb / ss);
-                       src->setPixel(ctrx, ctry, c);
-               }
-       }
-}
-
-/* Scale a region of an image into another image, using nearest-neighbor with
- * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries
- * to prevent non-integer scaling ratio artifacts.  Note that this may cause
- * some blending at the edges where pixels don't line up perfectly, but this
- * filter is designed to produce the most accurate results for both upscaling
- * and downscaling.
- */
-void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::IImage *dest)
-{
-       double sx, sy, minsx, maxsx, minsy, maxsy, area, ra, ga, ba, aa, pw, ph, pa;
-       u32 dy, dx;
-       video::SColor pxl;
-
-       // Cache rectsngle boundaries.
-       double sox = srcrect.UpperLeftCorner.X * 1.0;
-       double soy = srcrect.UpperLeftCorner.Y * 1.0;
-       double sw = srcrect.getWidth() * 1.0;
-       double sh = srcrect.getHeight() * 1.0;
-
-       // Walk each destination image pixel.
-       // Note: loop y around x for better cache locality.
-       core::dimension2d<u32> dim = dest->getDimension();
-       for (dy = 0; dy < dim.Height; dy++)
-       for (dx = 0; dx < dim.Width; dx++) {
-
-               // Calculate floating-point source rectangle bounds.
-               // Do some basic clipping, and for mirrored/flipped rects,
-               // make sure min/max are in the right order.
-               minsx = sox + (dx * sw / dim.Width);
-               minsx = rangelim(minsx, 0, sw);
-               maxsx = minsx + sw / dim.Width;
-               maxsx = rangelim(maxsx, 0, sw);
-               if (minsx > maxsx)
-                       SWAP(double, minsx, maxsx);
-               minsy = soy + (dy * sh / dim.Height);
-               minsy = rangelim(minsy, 0, sh);
-               maxsy = minsy + sh / dim.Height;
-               maxsy = rangelim(maxsy, 0, sh);
-               if (minsy > maxsy)
-                       SWAP(double, minsy, maxsy);
-
-               // Total area, and integral of r, g, b values over that area,
-               // initialized to zero, to be summed up in next loops.
-               area = 0;
-               ra = 0;
-               ga = 0;
-               ba = 0;
-               aa = 0;
-
-               // Loop over the integral pixel positions described by those bounds.
-               for (sy = floor(minsy); sy < maxsy; sy++)
-               for (sx = floor(minsx); sx < maxsx; sx++) {
-
-                       // Calculate width, height, then area of dest pixel
-                       // that's covered by this source pixel.
-                       pw = 1;
-                       if (minsx > sx)
-                               pw += sx - minsx;
-                       if (maxsx < (sx + 1))
-                               pw += maxsx - sx - 1;
-                       ph = 1;
-                       if (minsy > sy)
-                               ph += sy - minsy;
-                       if (maxsy < (sy + 1))
-                               ph += maxsy - sy - 1;
-                       pa = pw * ph;
-
-                       // Get source pixel and add it to totals, weighted
-                       // by covered area and alpha.
-                       pxl = src->getPixel((u32)sx, (u32)sy);
-                       area += pa;
-                       ra += pa * pxl.getRed();
-                       ga += pa * pxl.getGreen();
-                       ba += pa * pxl.getBlue();
-                       aa += pa * pxl.getAlpha();
-               }
-
-               // Set the destination image pixel to the average color.
-               if (area > 0) {
-                       pxl.setRed(ra / area + 0.5);
-                       pxl.setGreen(ga / area + 0.5);
-                       pxl.setBlue(ba / area + 0.5);
-                       pxl.setAlpha(aa / area + 0.5);
-               } else {
-                       pxl.setRed(0);
-                       pxl.setGreen(0);
-                       pxl.setBlue(0);
-                       pxl.setAlpha(0);
-               }
-               dest->setPixel(dx, dy, pxl);
-       }
-}
diff --git a/src/imagefilters.h b/src/imagefilters.h
deleted file mode 100644 (file)
index 5676faf..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
-Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-
-/* Fill in RGB values for transparent pixels, to correct for odd colors
- * appearing at borders when blending.  This is because many PNG optimizers
- * like to discard RGB values of transparent pixels, but when blending then
- * with non-transparent neighbors, their RGB values will shpw up nonetheless.
- *
- * This function modifies the original image in-place.
- *
- * Parameter "threshold" is the alpha level below which pixels are considered
- * transparent.  Should be 127 for 3d where alpha is threshold, but 0 for
- * 2d where alpha is blended.
- */
-void imageCleanTransparent(video::IImage *src, u32 threshold);
-
-/* Scale a region of an image into another image, using nearest-neighbor with
- * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries
- * to prevent non-integer scaling ratio artifacts.  Note that this may cause
- * some blending at the edges where pixels don't line up perfectly, but this
- * filter is designed to produce the most accurate results for both upscaling
- * and downscaling.
- */
-void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::IImage *dest);
index f7d366c8a5b8c0335806c5da9b91c0303ac0f4fa..11a52f85c026a7098a40af96433d8e898e788241 100644 (file)
@@ -24,11 +24,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "tool.h"
 #include "inventory.h"
 #ifndef SERVER
-#include "mapblock_mesh.h"
-#include "mesh.h"
-#include "wieldmesh.h"
+#include "client/mapblock_mesh.h"
+#include "client/mesh.h"
+#include "client/wieldmesh.h"
 #include "client/tile.h"
-#include "client.h"
+#include "client/client.h"
 #endif
 #include "log.h"
 #include "settings.h"
diff --git a/src/keycode.cpp b/src/keycode.cpp
deleted file mode 100644 (file)
index 646d181..0000000
+++ /dev/null
@@ -1,384 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 "keycode.h"
-#include "exceptions.h"
-#include "settings.h"
-#include "log.h"
-#include "debug.h"
-#include "util/hex.h"
-#include "util/string.h"
-#include "util/basic_macros.h"
-
-class UnknownKeycode : public BaseException
-{
-public:
-       UnknownKeycode(const char *s) :
-               BaseException(s) {};
-};
-
-struct table_key {
-       const char *Name;
-       irr::EKEY_CODE Key;
-       wchar_t Char; // L'\0' means no character assigned
-       const char *LangName; // NULL means it doesn't have a human description
-};
-
-#define DEFINEKEY1(x, lang) /* Irrlicht key without character */ \
-       { #x, irr::x, L'\0', lang },
-#define DEFINEKEY2(x, ch, lang) /* Irrlicht key with character */ \
-       { #x, irr::x, ch, lang },
-#define DEFINEKEY3(ch) /* single Irrlicht key (e.g. KEY_KEY_X) */ \
-       { "KEY_KEY_" TOSTRING(ch), irr::KEY_KEY_ ## ch, (wchar_t) *TOSTRING(ch), TOSTRING(ch) },
-#define DEFINEKEY4(ch) /* single Irrlicht function key (e.g. KEY_F3) */ \
-       { "KEY_F" TOSTRING(ch), irr::KEY_F ## ch, L'\0', "F" TOSTRING(ch) },
-#define DEFINEKEY5(ch) /* key without Irrlicht keycode */ \
-       { ch, irr::KEY_KEY_CODES_COUNT, (wchar_t) *ch, ch },
-
-#define N_(text) text
-
-static const struct table_key table[] = {
-       // Keys that can be reliably mapped between Char and Key
-       DEFINEKEY3(0)
-       DEFINEKEY3(1)
-       DEFINEKEY3(2)
-       DEFINEKEY3(3)
-       DEFINEKEY3(4)
-       DEFINEKEY3(5)
-       DEFINEKEY3(6)
-       DEFINEKEY3(7)
-       DEFINEKEY3(8)
-       DEFINEKEY3(9)
-       DEFINEKEY3(A)
-       DEFINEKEY3(B)
-       DEFINEKEY3(C)
-       DEFINEKEY3(D)
-       DEFINEKEY3(E)
-       DEFINEKEY3(F)
-       DEFINEKEY3(G)
-       DEFINEKEY3(H)
-       DEFINEKEY3(I)
-       DEFINEKEY3(J)
-       DEFINEKEY3(K)
-       DEFINEKEY3(L)
-       DEFINEKEY3(M)
-       DEFINEKEY3(N)
-       DEFINEKEY3(O)
-       DEFINEKEY3(P)
-       DEFINEKEY3(Q)
-       DEFINEKEY3(R)
-       DEFINEKEY3(S)
-       DEFINEKEY3(T)
-       DEFINEKEY3(U)
-       DEFINEKEY3(V)
-       DEFINEKEY3(W)
-       DEFINEKEY3(X)
-       DEFINEKEY3(Y)
-       DEFINEKEY3(Z)
-       DEFINEKEY2(KEY_PLUS, L'+', "+")
-       DEFINEKEY2(KEY_COMMA, L',', ",")
-       DEFINEKEY2(KEY_MINUS, L'-', "-")
-       DEFINEKEY2(KEY_PERIOD, L'.', ".")
-
-       // Keys without a Char
-       DEFINEKEY1(KEY_LBUTTON, N_("Left Button"))
-       DEFINEKEY1(KEY_RBUTTON, N_("Right Button"))
-       DEFINEKEY1(KEY_CANCEL, N_("Cancel"))
-       DEFINEKEY1(KEY_MBUTTON, N_("Middle Button"))
-       DEFINEKEY1(KEY_XBUTTON1, N_("X Button 1"))
-       DEFINEKEY1(KEY_XBUTTON2, N_("X Button 2"))
-       DEFINEKEY1(KEY_BACK, N_("Backspace"))
-       DEFINEKEY1(KEY_TAB, N_("Tab"))
-       DEFINEKEY1(KEY_CLEAR, N_("Clear"))
-       DEFINEKEY1(KEY_RETURN, N_("Return"))
-       DEFINEKEY1(KEY_SHIFT, N_("Shift"))
-       DEFINEKEY1(KEY_CONTROL, N_("Control"))
-       DEFINEKEY1(KEY_MENU, N_("Menu"))
-       DEFINEKEY1(KEY_PAUSE, N_("Pause"))
-       DEFINEKEY1(KEY_CAPITAL, N_("Caps Lock"))
-       DEFINEKEY1(KEY_SPACE, N_("Space"))
-       DEFINEKEY1(KEY_PRIOR, N_("Page up"))
-       DEFINEKEY1(KEY_NEXT, N_("Page down"))
-       DEFINEKEY1(KEY_END, N_("End"))
-       DEFINEKEY1(KEY_HOME, N_("Home"))
-       DEFINEKEY1(KEY_LEFT, N_("Left"))
-       DEFINEKEY1(KEY_UP, N_("Up"))
-       DEFINEKEY1(KEY_RIGHT, N_("Right"))
-       DEFINEKEY1(KEY_DOWN, N_("Down"))
-       DEFINEKEY1(KEY_SELECT, N_("Select"))
-       DEFINEKEY1(KEY_PRINT, N_("Print"))
-       DEFINEKEY1(KEY_EXECUT, N_("Execute"))
-       DEFINEKEY1(KEY_SNAPSHOT, N_("Snapshot"))
-       DEFINEKEY1(KEY_INSERT, N_("Insert"))
-       DEFINEKEY1(KEY_DELETE, N_("Delete"))
-       DEFINEKEY1(KEY_HELP, N_("Help"))
-       DEFINEKEY1(KEY_LWIN, N_("Left Windows"))
-       DEFINEKEY1(KEY_RWIN, N_("Right Windows"))
-       DEFINEKEY1(KEY_NUMPAD0, N_("Numpad 0")) // These are not assigned to a char
-       DEFINEKEY1(KEY_NUMPAD1, N_("Numpad 1")) // to prevent interference with KEY_KEY_[0-9].
-       DEFINEKEY1(KEY_NUMPAD2, N_("Numpad 2"))
-       DEFINEKEY1(KEY_NUMPAD3, N_("Numpad 3"))
-       DEFINEKEY1(KEY_NUMPAD4, N_("Numpad 4"))
-       DEFINEKEY1(KEY_NUMPAD5, N_("Numpad 5"))
-       DEFINEKEY1(KEY_NUMPAD6, N_("Numpad 6"))
-       DEFINEKEY1(KEY_NUMPAD7, N_("Numpad 7"))
-       DEFINEKEY1(KEY_NUMPAD8, N_("Numpad 8"))
-       DEFINEKEY1(KEY_NUMPAD9, N_("Numpad 9"))
-       DEFINEKEY1(KEY_MULTIPLY, N_("Numpad *"))
-       DEFINEKEY1(KEY_ADD, N_("Numpad +"))
-       DEFINEKEY1(KEY_SEPARATOR, N_("Numpad ."))
-       DEFINEKEY1(KEY_SUBTRACT, N_("Numpad -"))
-       DEFINEKEY1(KEY_DECIMAL, NULL)
-       DEFINEKEY1(KEY_DIVIDE, N_("Numpad /"))
-       DEFINEKEY4(1)
-       DEFINEKEY4(2)
-       DEFINEKEY4(3)
-       DEFINEKEY4(4)
-       DEFINEKEY4(5)
-       DEFINEKEY4(6)
-       DEFINEKEY4(7)
-       DEFINEKEY4(8)
-       DEFINEKEY4(9)
-       DEFINEKEY4(10)
-       DEFINEKEY4(11)
-       DEFINEKEY4(12)
-       DEFINEKEY4(13)
-       DEFINEKEY4(14)
-       DEFINEKEY4(15)
-       DEFINEKEY4(16)
-       DEFINEKEY4(17)
-       DEFINEKEY4(18)
-       DEFINEKEY4(19)
-       DEFINEKEY4(20)
-       DEFINEKEY4(21)
-       DEFINEKEY4(22)
-       DEFINEKEY4(23)
-       DEFINEKEY4(24)
-       DEFINEKEY1(KEY_NUMLOCK, N_("Num Lock"))
-       DEFINEKEY1(KEY_SCROLL, N_("Scroll Lock"))
-       DEFINEKEY1(KEY_LSHIFT, N_("Left Shift"))
-       DEFINEKEY1(KEY_RSHIFT, N_("Right Shift"))
-       DEFINEKEY1(KEY_LCONTROL, N_("Left Control"))
-       DEFINEKEY1(KEY_RCONTROL, N_("Right Control"))
-       DEFINEKEY1(KEY_LMENU, N_("Left Menu"))
-       DEFINEKEY1(KEY_RMENU, N_("Right Menu"))
-
-       // Rare/weird keys
-       DEFINEKEY1(KEY_KANA, "Kana")
-       DEFINEKEY1(KEY_HANGUEL, "Hangul")
-       DEFINEKEY1(KEY_HANGUL, "Hangul")
-       DEFINEKEY1(KEY_JUNJA, "Junja")
-       DEFINEKEY1(KEY_FINAL, "Final")
-       DEFINEKEY1(KEY_KANJI, "Kanji")
-       DEFINEKEY1(KEY_HANJA, "Hanja")
-       DEFINEKEY1(KEY_ESCAPE, N_("IME Escape"))
-       DEFINEKEY1(KEY_CONVERT, N_("IME Convert"))
-       DEFINEKEY1(KEY_NONCONVERT, N_("IME Nonconvert"))
-       DEFINEKEY1(KEY_ACCEPT, N_("IME Accept"))
-       DEFINEKEY1(KEY_MODECHANGE, N_("IME Mode Change"))
-       DEFINEKEY1(KEY_APPS, N_("Apps"))
-       DEFINEKEY1(KEY_SLEEP, N_("Sleep"))
-#if !(IRRLICHT_VERSION_MAJOR <= 1 && IRRLICHT_VERSION_MINOR <= 7 && IRRLICHT_VERSION_REVISION < 3)
-       DEFINEKEY1(KEY_OEM_1, "OEM 1") // KEY_OEM_[0-9] and KEY_OEM_102 are assigned to multiple
-       DEFINEKEY1(KEY_OEM_2, "OEM 2") // different chars (on different platforms too) and thus w/o char
-       DEFINEKEY1(KEY_OEM_3, "OEM 3")
-       DEFINEKEY1(KEY_OEM_4, "OEM 4")
-       DEFINEKEY1(KEY_OEM_5, "OEM 5")
-       DEFINEKEY1(KEY_OEM_6, "OEM 6")
-       DEFINEKEY1(KEY_OEM_7, "OEM 7")
-       DEFINEKEY1(KEY_OEM_8, "OEM 8")
-       DEFINEKEY1(KEY_OEM_AX, "OEM AX")
-       DEFINEKEY1(KEY_OEM_102, "OEM 102")
-#endif
-       DEFINEKEY1(KEY_ATTN, "Attn")
-       DEFINEKEY1(KEY_CRSEL, "CrSel")
-       DEFINEKEY1(KEY_EXSEL, "ExSel")
-       DEFINEKEY1(KEY_EREOF, N_("Erase EOF"))
-       DEFINEKEY1(KEY_PLAY, N_("Play"))
-       DEFINEKEY1(KEY_ZOOM, N_("Zoom"))
-       DEFINEKEY1(KEY_PA1, "PA1")
-       DEFINEKEY1(KEY_OEM_CLEAR, N_("OEM Clear"))
-
-       // Keys without Irrlicht keycode
-       DEFINEKEY5("!")
-       DEFINEKEY5("\"")
-       DEFINEKEY5("#")
-       DEFINEKEY5("$")
-       DEFINEKEY5("%")
-       DEFINEKEY5("&")
-       DEFINEKEY5("'")
-       DEFINEKEY5("(")
-       DEFINEKEY5(")")
-       DEFINEKEY5("*")
-       DEFINEKEY5("/")
-       DEFINEKEY5(":")
-       DEFINEKEY5(";")
-       DEFINEKEY5("<")
-       DEFINEKEY5("=")
-       DEFINEKEY5(">")
-       DEFINEKEY5("?")
-       DEFINEKEY5("@")
-       DEFINEKEY5("[")
-       DEFINEKEY5("\\")
-       DEFINEKEY5("]")
-       DEFINEKEY5("^")
-       DEFINEKEY5("_")
-};
-
-#undef N_
-
-
-struct table_key lookup_keyname(const char *name)
-{
-       for (const auto &table_key : table) {
-               if (strcmp(table_key.Name, name) == 0)
-                       return table_key;
-       }
-
-       throw UnknownKeycode(name);
-}
-
-struct table_key lookup_keykey(irr::EKEY_CODE key)
-{
-       for (const auto &table_key : table) {
-               if (table_key.Key == key)
-                       return table_key;
-       }
-
-       std::ostringstream os;
-       os << "<Keycode " << (int) key << ">";
-       throw UnknownKeycode(os.str().c_str());
-}
-
-struct table_key lookup_keychar(wchar_t Char)
-{
-       for (const auto &table_key : table) {
-               if (table_key.Char == Char)
-                       return table_key;
-       }
-
-       std::ostringstream os;
-       os << "<Char " << hex_encode((char*) &Char, sizeof(wchar_t)) << ">";
-       throw UnknownKeycode(os.str().c_str());
-}
-
-KeyPress::KeyPress(const char *name)
-{
-       if (strlen(name) == 0) {
-               Key = irr::KEY_KEY_CODES_COUNT;
-               Char = L'\0';
-               m_name = "";
-               return;
-       }
-
-       if (strlen(name) <= 4) {
-               // Lookup by resulting character
-               int chars_read = mbtowc(&Char, name, 1);
-               FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character");
-               try {
-                       struct table_key k = lookup_keychar(Char);
-                       m_name = k.Name;
-                       Key = k.Key;
-                       return;
-               } catch (UnknownKeycode &e) {};
-       } else {
-               // Lookup by name
-               m_name = name;
-               try {
-                       struct table_key k = lookup_keyname(name);
-                       Key = k.Key;
-                       Char = k.Char;
-                       return;
-               } catch (UnknownKeycode &e) {};
-       }
-
-       // It's not a known key, complain and try to do something
-       Key = irr::KEY_KEY_CODES_COUNT;
-       int chars_read = mbtowc(&Char, name, 1);
-       FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character");
-       m_name = "";
-       warningstream << "KeyPress: Unknown key '" << name << "', falling back to first char.";
-}
-
-KeyPress::KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character)
-{
-       if (prefer_character)
-               Key = irr::KEY_KEY_CODES_COUNT;
-       else
-               Key = in.Key;
-       Char = in.Char;
-
-       try {
-               if (valid_kcode(Key))
-                       m_name = lookup_keykey(Key).Name;
-               else
-                       m_name = lookup_keychar(Char).Name;
-       } catch (UnknownKeycode &e) {
-               m_name = "";
-       };
-}
-
-const char *KeyPress::sym() const
-{
-       return m_name.c_str();
-}
-
-const char *KeyPress::name() const
-{
-       if (m_name.empty())
-               return "";
-       const char *ret;
-       if (valid_kcode(Key))
-               ret = lookup_keykey(Key).LangName;
-       else
-               ret = lookup_keychar(Char).LangName;
-       return ret ? ret : "<Unnamed key>";
-}
-
-const KeyPress EscapeKey("KEY_ESCAPE");
-const KeyPress CancelKey("KEY_CANCEL");
-
-/*
-       Key config
-*/
-
-// A simple cache for quicker lookup
-std::unordered_map<std::string, KeyPress> g_key_setting_cache;
-
-KeyPress getKeySetting(const char *settingname)
-{
-       std::unordered_map<std::string, KeyPress>::iterator n;
-       n = g_key_setting_cache.find(settingname);
-       if (n != g_key_setting_cache.end())
-               return n->second;
-
-       KeyPress k(g_settings->get(settingname).c_str());
-       g_key_setting_cache[settingname] = k;
-       return k;
-}
-
-void clearKeyCache()
-{
-       g_key_setting_cache.clear();
-}
-
-irr::EKEY_CODE keyname_to_keycode(const char *name)
-{
-       return lookup_keyname(name).Key;
-}
diff --git a/src/keycode.h b/src/keycode.h
deleted file mode 100644 (file)
index 7036705..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include "irrlichttypes.h"
-#include "Keycodes.h"
-#include <IEventReceiver.h>
-#include <string>
-
-/* A key press, consisting of either an Irrlicht keycode
-   or an actual char */
-
-class KeyPress
-{
-public:
-       KeyPress() = default;
-
-       KeyPress(const char *name);
-
-       KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character = false);
-
-       bool operator==(const KeyPress &o) const
-       {
-               return (Char > 0 && Char == o.Char) || (valid_kcode(Key) && Key == o.Key);
-       }
-
-       const char *sym() const;
-       const char *name() const;
-
-protected:
-       static bool valid_kcode(irr::EKEY_CODE k)
-       {
-               return k > 0 && k < irr::KEY_KEY_CODES_COUNT;
-       }
-
-       irr::EKEY_CODE Key = irr::KEY_KEY_CODES_COUNT;
-       wchar_t Char = L'\0';
-       std::string m_name = "";
-};
-
-extern const KeyPress EscapeKey;
-extern const KeyPress CancelKey;
-
-// Key configuration getter
-KeyPress getKeySetting(const char *settingname);
-
-// Clear fast lookup cache
-void clearKeyCache();
-
-irr::EKEY_CODE keyname_to_keycode(const char *name);
diff --git a/src/localplayer.cpp b/src/localplayer.cpp
deleted file mode 100644 (file)
index 1c65d3b..0000000
+++ /dev/null
@@ -1,1158 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 "localplayer.h"
-#include <cmath>
-#include "event.h"
-#include "collision.h"
-#include "nodedef.h"
-#include "settings.h"
-#include "environment.h"
-#include "map.h"
-#include "client.h"
-#include "content_cao.h"
-
-/*
-       LocalPlayer
-*/
-
-LocalPlayer::LocalPlayer(Client *client, const char *name):
-       Player(name, client->idef()),
-       m_client(client)
-{
-}
-
-static aabb3f getNodeBoundingBox(const std::vector<aabb3f> &nodeboxes)
-{
-       if (nodeboxes.empty())
-               return aabb3f(0, 0, 0, 0, 0, 0);
-
-       aabb3f b_max;
-
-       std::vector<aabb3f>::const_iterator it = nodeboxes.begin();
-       b_max = aabb3f(it->MinEdge, it->MaxEdge);
-
-       ++it;
-       for (; it != nodeboxes.end(); ++it)
-               b_max.addInternalBox(*it);
-
-       return b_max;
-}
-
-bool LocalPlayer::updateSneakNode(Map *map, const v3f &position,
-               const v3f &sneak_max)
-{
-       static const v3s16 dir9_center[9] = {
-               v3s16( 0, 0,  0),
-               v3s16( 1, 0,  0),
-               v3s16(-1, 0,  0),
-               v3s16( 0, 0,  1),
-               v3s16( 0, 0, -1),
-               v3s16( 1, 0,  1),
-               v3s16(-1, 0,  1),
-               v3s16( 1, 0, -1),
-               v3s16(-1, 0, -1)
-       };
-
-       const NodeDefManager *nodemgr = m_client->ndef();
-       MapNode node;
-       bool is_valid_position;
-       bool new_sneak_node_exists = m_sneak_node_exists;
-
-       // We want the top of the sneak node to be below the players feet
-       f32 position_y_mod = 0.05 * BS;
-       if (m_sneak_node_exists)
-               position_y_mod = m_sneak_node_bb_top.MaxEdge.Y - position_y_mod;
-
-       // Get position of current standing node
-       const v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), BS);
-
-       if (current_node != m_sneak_node) {
-               new_sneak_node_exists = false;
-       } else {
-               node = map->getNodeNoEx(current_node, &is_valid_position);
-               if (!is_valid_position || !nodemgr->get(node).walkable)
-                       new_sneak_node_exists = false;
-       }
-
-       // Keep old sneak node
-       if (new_sneak_node_exists)
-               return true;
-
-       // Get new sneak node
-       m_sneak_ladder_detected = false;
-       f32 min_distance_f = 100000.0 * BS;
-
-       for (const auto &d : dir9_center) {
-               const v3s16 p = current_node + d;
-               const v3f pf = intToFloat(p, BS);
-               const v2f diff(position.X - pf.X, position.Z - pf.Z);
-               f32 distance_f = diff.getLength();
-
-               if (distance_f > min_distance_f ||
-                               fabs(diff.X) > (.5 + .1) * BS + sneak_max.X ||
-                               fabs(diff.Y) > (.5 + .1) * BS + sneak_max.Z)
-                       continue;
-
-
-               // The node to be sneaked on has to be walkable
-               node = map->getNodeNoEx(p, &is_valid_position);
-               if (!is_valid_position || !nodemgr->get(node).walkable)
-                       continue;
-               // And the node(s) above have to be nonwalkable
-               bool ok = true;
-               if (!physics_override_sneak_glitch) {
-                       u16 height = ceilf(
-                                       (m_collisionbox.MaxEdge.Y - m_collisionbox.MinEdge.Y) / BS
-                       );
-                       for (u16 y = 1; y <= height; y++) {
-                               node = map->getNodeNoEx(p + v3s16(0, y, 0), &is_valid_position);
-                               if (!is_valid_position || nodemgr->get(node).walkable) {
-                                       ok = false;
-                                       break;
-                               }
-                       }
-               } else {
-                       // legacy behaviour: check just one node
-                       node = map->getNodeNoEx(p + v3s16(0, 1, 0), &is_valid_position);
-                       ok = is_valid_position && !nodemgr->get(node).walkable;
-               }
-               if (!ok)
-                       continue;
-
-               min_distance_f = distance_f;
-               m_sneak_node = p;
-               new_sneak_node_exists = true;
-       }
-
-       if (!new_sneak_node_exists)
-               return false;
-
-       // Update saved top bounding box of sneak node
-       node = map->getNodeNoEx(m_sneak_node);
-       std::vector<aabb3f> nodeboxes;
-       node.getCollisionBoxes(nodemgr, &nodeboxes);
-       m_sneak_node_bb_top = getNodeBoundingBox(nodeboxes);
-
-       if (physics_override_sneak_glitch) {
-               // Detect sneak ladder:
-               // Node two meters above sneak node must be solid
-               node = map->getNodeNoEx(m_sneak_node + v3s16(0, 2, 0),
-                       &is_valid_position);
-               if (is_valid_position && nodemgr->get(node).walkable) {
-                       // Node three meters above: must be non-solid
-                       node = map->getNodeNoEx(m_sneak_node + v3s16(0, 3, 0),
-                               &is_valid_position);
-                       m_sneak_ladder_detected = is_valid_position &&
-                               !nodemgr->get(node).walkable;
-               }
-       }
-       return true;
-}
-
-void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
-               std::vector<CollisionInfo> *collision_info)
-{
-       if (!collision_info || collision_info->empty()) {
-               // Node below the feet, update each ClientEnvironment::step()
-               m_standing_node = floatToInt(m_position, BS) - v3s16(0, 1, 0);
-       }
-
-       // Temporary option for old move code
-       if (!physics_override_new_move) {
-               old_move(dtime, env, pos_max_d, collision_info);
-               return;
-       }
-
-       Map *map = &env->getMap();
-       const NodeDefManager *nodemgr = m_client->ndef();
-
-       v3f position = getPosition();
-
-       // Copy parent position if local player is attached
-       if (isAttached) {
-               setPosition(overridePosition);
-               return;
-       }
-
-       PlayerSettings &player_settings = getPlayerSettings();
-
-       // Skip collision detection if noclip mode is used
-       bool fly_allowed = m_client->checkLocalPrivilege("fly");
-       bool noclip = m_client->checkLocalPrivilege("noclip") && player_settings.noclip;
-       bool free_move = player_settings.free_move && fly_allowed;
-
-       if (noclip && free_move) {
-               position += m_speed * dtime;
-               setPosition(position);
-               return;
-       }
-
-       /*
-               Collision detection
-       */
-
-       bool is_valid_position;
-       MapNode node;
-       v3s16 pp;
-
-       /*
-               Check if player is in liquid (the oscillating value)
-       */
-
-       // If in liquid, the threshold of coming out is at higher y
-       if (in_liquid)
-       {
-               pp = floatToInt(position + v3f(0,BS*0.1,0), BS);
-               node = map->getNodeNoEx(pp, &is_valid_position);
-               if (is_valid_position) {
-                       in_liquid = nodemgr->get(node.getContent()).isLiquid();
-                       liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
-               } else {
-                       in_liquid = false;
-               }
-       }
-       // If not in liquid, the threshold of going in is at lower y
-       else
-       {
-               pp = floatToInt(position + v3f(0,BS*0.5,0), BS);
-               node = map->getNodeNoEx(pp, &is_valid_position);
-               if (is_valid_position) {
-                       in_liquid = nodemgr->get(node.getContent()).isLiquid();
-                       liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
-               } else {
-                       in_liquid = false;
-               }
-       }
-
-
-       /*
-               Check if player is in liquid (the stable value)
-       */
-       pp = floatToInt(position + v3f(0,0,0), BS);
-       node = map->getNodeNoEx(pp, &is_valid_position);
-       if (is_valid_position) {
-               in_liquid_stable = nodemgr->get(node.getContent()).isLiquid();
-       } else {
-               in_liquid_stable = false;
-       }
-
-       /*
-               Check if player is climbing
-       */
-
-
-       pp = floatToInt(position + v3f(0,0.5*BS,0), BS);
-       v3s16 pp2 = floatToInt(position + v3f(0,-0.2*BS,0), BS);
-       node = map->getNodeNoEx(pp, &is_valid_position);
-       bool is_valid_position2;
-       MapNode node2 = map->getNodeNoEx(pp2, &is_valid_position2);
-
-       if (!(is_valid_position && is_valid_position2)) {
-               is_climbing = false;
-       } else {
-               is_climbing = (nodemgr->get(node.getContent()).climbable
-                               || nodemgr->get(node2.getContent()).climbable) && !free_move;
-       }
-
-       /*
-               Collision uncertainty radius
-               Make it a bit larger than the maximum distance of movement
-       */
-       //f32 d = pos_max_d * 1.1;
-       // A fairly large value in here makes moving smoother
-       f32 d = 0.15*BS;
-
-       // This should always apply, otherwise there are glitches
-       sanity_check(d > pos_max_d);
-
-       // Player object property step height is multiplied by BS in
-       // /src/script/common/c_content.cpp and /src/content_sao.cpp
-       float player_stepheight = (m_cao == nullptr) ? 0.0f :
-               (touching_ground ? m_cao->getStepHeight() : (0.2f * BS));
-
-       v3f accel_f = v3f(0,0,0);
-       const v3f initial_position = position;
-       const v3f initial_speed = m_speed;
-
-       collisionMoveResult result = collisionMoveSimple(env, m_client,
-               pos_max_d, m_collisionbox, player_stepheight, dtime,
-               &position, &m_speed, accel_f);
-
-       bool could_sneak = control.sneak && !free_move && !in_liquid &&
-               !is_climbing && physics_override_sneak;
-
-       // Add new collisions to the vector
-       if (collision_info && !free_move) {
-               v3f diff = intToFloat(m_standing_node, BS) - position;
-               f32 distance = diff.getLength();
-               // Force update each ClientEnvironment::step()
-               bool is_first = collision_info->empty();
-
-               for (const auto &colinfo : result.collisions) {
-                       collision_info->push_back(colinfo);
-
-                       if (colinfo.type != COLLISION_NODE ||
-                                       colinfo.new_speed.Y != 0 ||
-                                       (could_sneak && m_sneak_node_exists))
-                               continue;
-
-                       diff = intToFloat(colinfo.node_p, BS) - position;
-
-                       // Find nearest colliding node
-                       f32 len = diff.getLength();
-                       if (is_first || len < distance) {
-                               m_standing_node = colinfo.node_p;
-                               distance = len;
-                       }
-               }
-       }
-
-       /*
-               If the player's feet touch the topside of any node, this is
-               set to true.
-
-               Player is allowed to jump when this is true.
-       */
-       bool touching_ground_was = touching_ground;
-       touching_ground = result.touching_ground;
-       bool sneak_can_jump = false;
-
-       // Max. distance (X, Z) over border for sneaking determined by collision box
-       // * 0.49 to keep the center just barely on the node
-       v3f sneak_max = m_collisionbox.getExtent() * 0.49;
-
-       if (m_sneak_ladder_detected) {
-               // restore legacy behaviour (this makes the m_speed.Y hack necessary)
-               sneak_max = v3f(0.4 * BS, 0, 0.4 * BS);
-       }
-
-       /*
-               If sneaking, keep on top of last walked node and don't fall off
-       */
-       if (could_sneak && m_sneak_node_exists) {
-               const v3f sn_f = intToFloat(m_sneak_node, BS);
-               const v3f bmin = sn_f + m_sneak_node_bb_top.MinEdge;
-               const v3f bmax = sn_f + m_sneak_node_bb_top.MaxEdge;
-               const v3f old_pos = position;
-               const v3f old_speed = m_speed;
-               f32 y_diff = bmax.Y - position.Y;
-               m_standing_node = m_sneak_node;
-
-               // (BS * 0.6f) is the basic stepheight while standing on ground
-               if (y_diff < BS * 0.6f) {
-                       // Only center player when they're on the node
-                       position.X = rangelim(position.X,
-                               bmin.X - sneak_max.X, bmax.X + sneak_max.X);
-                       position.Z = rangelim(position.Z,
-                               bmin.Z - sneak_max.Z, bmax.Z + sneak_max.Z);
-
-                       if (position.X != old_pos.X)
-                               m_speed.X = 0;
-                       if (position.Z != old_pos.Z)
-                               m_speed.Z = 0;
-               }
-
-               if (y_diff > 0 && m_speed.Y <= 0 &&
-                               (physics_override_sneak_glitch || y_diff < BS * 0.6f)) {
-                       // Move player to the maximal height when falling or when
-                       // the ledge is climbed on the next step.
-
-                       // Smoothen the movement (based on 'position.Y = bmax.Y')
-                       position.Y += y_diff * dtime * 22.0f + BS * 0.01f;
-                       position.Y = std::min(position.Y, bmax.Y);
-                       m_speed.Y = 0;
-               }
-
-               // Allow jumping on node edges while sneaking
-               if (m_speed.Y == 0 || m_sneak_ladder_detected)
-                       sneak_can_jump = true;
-
-               if (collision_info &&
-                               m_speed.Y - old_speed.Y > BS) {
-                       // Collide with sneak node, report fall damage
-                       CollisionInfo sn_info;
-                       sn_info.node_p = m_sneak_node;
-                       sn_info.old_speed = old_speed;
-                       sn_info.new_speed = m_speed;
-                       collision_info->push_back(sn_info);
-               }
-       }
-
-       /*
-               Find the next sneak node if necessary
-       */
-       bool new_sneak_node_exists = false;
-
-       if (could_sneak)
-               new_sneak_node_exists = updateSneakNode(map, position, sneak_max);
-
-       /*
-               Set new position but keep sneak node set
-       */
-       setPosition(position);
-       m_sneak_node_exists = new_sneak_node_exists;
-
-       /*
-               Report collisions
-       */
-
-       if(!result.standing_on_object && !touching_ground_was && touching_ground) {
-               m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_REGAIN_GROUND));
-
-               // Set camera impact value to be used for view bobbing
-               camera_impact = getSpeed().Y * -1;
-       }
-
-       {
-               camera_barely_in_ceiling = false;
-               v3s16 camera_np = floatToInt(getEyePosition(), BS);
-               MapNode n = map->getNodeNoEx(camera_np);
-               if(n.getContent() != CONTENT_IGNORE){
-                       if(nodemgr->get(n).walkable && nodemgr->get(n).solidness == 2){
-                               camera_barely_in_ceiling = true;
-                       }
-               }
-       }
-
-       /*
-               Check properties of the node on which the player is standing
-       */
-       const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(m_standing_node));
-       // Determine if jumping is possible
-       m_can_jump = (touching_ground && !in_liquid && !is_climbing)
-                       || sneak_can_jump;
-       if (itemgroup_get(f.groups, "disable_jump"))
-               m_can_jump = false;
-
-       // Jump key pressed while jumping off from a bouncy block
-       if (m_can_jump && control.jump && itemgroup_get(f.groups, "bouncy") &&
-               m_speed.Y >= -0.5 * BS) {
-               float jumpspeed = movement_speed_jump * physics_override_jump;
-               if (m_speed.Y > 1) {
-                       // Reduce boost when speed already is high
-                       m_speed.Y += jumpspeed / (1 + (m_speed.Y / 16 ));
-               } else {
-                       m_speed.Y += jumpspeed;
-               }
-               setSpeed(m_speed);
-               m_can_jump = false;
-       }
-
-       // Autojump
-       handleAutojump(dtime, env, result, initial_position, initial_speed, pos_max_d);
-}
-
-void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d)
-{
-       move(dtime, env, pos_max_d, NULL);
-}
-
-void LocalPlayer::applyControl(float dtime, Environment *env)
-{
-       // Clear stuff
-       swimming_vertical = false;
-
-       setPitch(control.pitch);
-       setYaw(control.yaw);
-
-       // Nullify speed and don't run positioning code if the player is attached
-       if(isAttached)
-       {
-               setSpeed(v3f(0,0,0));
-               return;
-       }
-
-       PlayerSettings &player_settings = getPlayerSettings();
-
-       v3f move_direction = v3f(0,0,1);
-       move_direction.rotateXZBy(getYaw());
-
-       v3f speedH = v3f(0,0,0); // Horizontal (X, Z)
-       v3f speedV = v3f(0,0,0); // Vertical (Y)
-
-       bool fly_allowed = m_client->checkLocalPrivilege("fly");
-       bool fast_allowed = m_client->checkLocalPrivilege("fast");
-
-       bool free_move = fly_allowed && player_settings.free_move;
-       bool fast_move = fast_allowed && player_settings.fast_move;
-       // When aux1_descends is enabled the fast key is used to go down, so fast isn't possible
-       bool fast_climb = fast_move && control.aux1 && !player_settings.aux1_descends;
-       bool continuous_forward = player_settings.continuous_forward;
-       bool always_fly_fast = player_settings.always_fly_fast;
-
-       // Whether superspeed mode is used or not
-       bool superspeed = false;
-
-       if (always_fly_fast && free_move && fast_move)
-               superspeed = true;
-
-       // Old descend control
-       if (player_settings.aux1_descends)
-       {
-               // If free movement and fast movement, always move fast
-               if(free_move && fast_move)
-                       superspeed = true;
-
-               // Auxiliary button 1 (E)
-               if(control.aux1)
-               {
-                       if(free_move)
-                       {
-                               // In free movement mode, aux1 descends
-                               if(fast_move)
-                                       speedV.Y = -movement_speed_fast;
-                               else
-                                       speedV.Y = -movement_speed_walk;
-                       }
-                       else if(in_liquid || in_liquid_stable)
-                       {
-                               speedV.Y = -movement_speed_walk;
-                               swimming_vertical = true;
-                       }
-                       else if(is_climbing)
-                       {
-                               speedV.Y = -movement_speed_climb;
-                       }
-                       else
-                       {
-                               // If not free movement but fast is allowed, aux1 is
-                               // "Turbo button"
-                               if(fast_move)
-                                       superspeed = true;
-                       }
-               }
-       }
-       // New minecraft-like descend control
-       else
-       {
-               // Auxiliary button 1 (E)
-               if(control.aux1)
-               {
-                       if(!is_climbing)
-                       {
-                               // aux1 is "Turbo button"
-                               if(fast_move)
-                                       superspeed = true;
-                       }
-               }
-
-               if(control.sneak)
-               {
-                       if(free_move)
-                       {
-                               // In free movement mode, sneak descends
-                               if (fast_move && (control.aux1 || always_fly_fast))
-                                       speedV.Y = -movement_speed_fast;
-                               else
-                                       speedV.Y = -movement_speed_walk;
-                       }
-                       else if(in_liquid || in_liquid_stable)
-                       {
-                               if(fast_climb)
-                                       speedV.Y = -movement_speed_fast;
-                               else
-                                       speedV.Y = -movement_speed_walk;
-                               swimming_vertical = true;
-                       }
-                       else if(is_climbing)
-                       {
-                               if(fast_climb)
-                                       speedV.Y = -movement_speed_fast;
-                               else
-                                       speedV.Y = -movement_speed_climb;
-                       }
-               }
-       }
-
-       if (continuous_forward)
-               speedH += move_direction;
-
-       if (control.up) {
-               if (continuous_forward) {
-                       if (fast_move)
-                               superspeed = true;
-               } else {
-                       speedH += move_direction;
-               }
-       }
-       if (control.down) {
-               speedH -= move_direction;
-       }
-       if (!control.up && !control.down) {
-               speedH -= move_direction *
-                       (control.forw_move_joystick_axis / 32767.f);
-       }
-       if (control.left) {
-               speedH += move_direction.crossProduct(v3f(0,1,0));
-       }
-       if (control.right) {
-               speedH += move_direction.crossProduct(v3f(0,-1,0));
-       }
-       if (!control.left && !control.right) {
-               speedH -= move_direction.crossProduct(v3f(0,1,0)) *
-                       (control.sidew_move_joystick_axis / 32767.f);
-       }
-       if(control.jump)
-       {
-               if (free_move) {
-                       if (player_settings.aux1_descends || always_fly_fast) {
-                               if (fast_move)
-                                       speedV.Y = movement_speed_fast;
-                               else
-                                       speedV.Y = movement_speed_walk;
-                       } else {
-                               if(fast_move && control.aux1)
-                                       speedV.Y = movement_speed_fast;
-                               else
-                                       speedV.Y = movement_speed_walk;
-                       }
-               }
-               else if(m_can_jump)
-               {
-                       /*
-                               NOTE: The d value in move() affects jump height by
-                               raising the height at which the jump speed is kept
-                               at its starting value
-                       */
-                       v3f speedJ = getSpeed();
-                       if(speedJ.Y >= -0.5 * BS) {
-                               speedJ.Y = movement_speed_jump * physics_override_jump;
-                               setSpeed(speedJ);
-                               m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_JUMP));
-                       }
-               }
-               else if(in_liquid)
-               {
-                       if(fast_climb)
-                               speedV.Y = movement_speed_fast;
-                       else
-                               speedV.Y = movement_speed_walk;
-                       swimming_vertical = true;
-               }
-               else if(is_climbing)
-               {
-                       if(fast_climb)
-                               speedV.Y = movement_speed_fast;
-                       else
-                               speedV.Y = movement_speed_climb;
-               }
-       }
-
-       // The speed of the player (Y is ignored)
-       if(superspeed || (is_climbing && fast_climb) || ((in_liquid || in_liquid_stable) && fast_climb))
-               speedH = speedH.normalize() * movement_speed_fast;
-       else if(control.sneak && !free_move && !in_liquid && !in_liquid_stable)
-               speedH = speedH.normalize() * movement_speed_crouch;
-       else
-               speedH = speedH.normalize() * movement_speed_walk;
-
-       // Acceleration increase
-       f32 incH = 0; // Horizontal (X, Z)
-       f32 incV = 0; // Vertical (Y)
-       if((!touching_ground && !free_move && !is_climbing && !in_liquid) || (!free_move && m_can_jump && control.jump))
-       {
-               // Jumping and falling
-               if(superspeed || (fast_move && control.aux1))
-                       incH = movement_acceleration_fast * BS * dtime;
-               else
-                       incH = movement_acceleration_air * BS * dtime;
-               incV = 0; // No vertical acceleration in air
-       }
-       else if (superspeed || (is_climbing && fast_climb) || ((in_liquid || in_liquid_stable) && fast_climb))
-               incH = incV = movement_acceleration_fast * BS * dtime;
-       else
-               incH = incV = movement_acceleration_default * BS * dtime;
-
-       float slip_factor = 1.0f;
-       if (!free_move)
-               slip_factor = getSlipFactor(env, speedH);
-
-       // Accelerate to target speed with maximum increment
-       accelerateHorizontal(speedH * physics_override_speed,
-                       incH * physics_override_speed * slip_factor);
-       accelerateVertical(speedV * physics_override_speed,
-                       incV * physics_override_speed);
-}
-
-v3s16 LocalPlayer::getStandingNodePos()
-{
-       if(m_sneak_node_exists)
-               return m_sneak_node;
-       return m_standing_node;
-}
-
-v3s16 LocalPlayer::getFootstepNodePos()
-{
-       if (in_liquid_stable)
-               // Emit swimming sound if the player is in liquid
-               return floatToInt(getPosition(), BS);
-       if (touching_ground)
-               // BS * 0.05 below the player's feet ensures a 1/16th height
-               // nodebox is detected instead of the node below it.
-               return floatToInt(getPosition() - v3f(0, BS * 0.05f, 0), BS);
-       // A larger distance below is necessary for a footstep sound
-       // when landing after a jump or fall. BS * 0.5 ensures water
-       // sounds when swimming in 1 node deep water.
-       return floatToInt(getPosition() - v3f(0, BS * 0.5f, 0), BS);
-}
-
-v3s16 LocalPlayer::getLightPosition() const
-{
-       return floatToInt(m_position + v3f(0,BS+BS/2,0), BS);
-}
-
-v3f LocalPlayer::getEyeOffset() const
-{
-       float eye_height = camera_barely_in_ceiling ?
-               m_eye_height - 0.125f : m_eye_height;
-       return v3f(0, BS * eye_height, 0);
-}
-
-// Horizontal acceleration (X and Z), Y direction is ignored
-void LocalPlayer::accelerateHorizontal(const v3f &target_speed,
-       const f32 max_increase)
-{
-        if (max_increase == 0)
-                return;
-
-       v3f d_wanted = target_speed - m_speed;
-       d_wanted.Y = 0.0f;
-       f32 dl = d_wanted.getLength();
-       if (dl > max_increase)
-               dl = max_increase;
-
-       v3f d = d_wanted.normalize() * dl;
-
-       m_speed.X += d.X;
-       m_speed.Z += d.Z;
-}
-
-// Vertical acceleration (Y), X and Z directions are ignored
-void LocalPlayer::accelerateVertical(const v3f &target_speed, const f32 max_increase)
-{
-       if (max_increase == 0)
-               return;
-
-       f32 d_wanted = target_speed.Y - m_speed.Y;
-       if (d_wanted > max_increase)
-               d_wanted = max_increase;
-       else if (d_wanted < -max_increase)
-               d_wanted = -max_increase;
-
-       m_speed.Y += d_wanted;
-}
-
-// Temporary option for old move code
-void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
-               std::vector<CollisionInfo> *collision_info)
-{
-       Map *map = &env->getMap();
-       const NodeDefManager *nodemgr = m_client->ndef();
-
-       v3f position = getPosition();
-
-       // Copy parent position if local player is attached
-       if (isAttached) {
-               setPosition(overridePosition);
-               m_sneak_node_exists = false;
-               return;
-       }
-
-       PlayerSettings &player_settings = getPlayerSettings();
-
-       // Skip collision detection if noclip mode is used
-       bool fly_allowed = m_client->checkLocalPrivilege("fly");
-       bool noclip = m_client->checkLocalPrivilege("noclip") && player_settings.noclip;
-       bool free_move = noclip && fly_allowed && player_settings.free_move;
-       if (free_move) {
-               position += m_speed * dtime;
-               setPosition(position);
-               m_sneak_node_exists = false;
-               return;
-       }
-
-       /*
-               Collision detection
-       */
-       bool is_valid_position;
-       MapNode node;
-       v3s16 pp;
-
-       /*
-               Check if player is in liquid (the oscillating value)
-       */
-       if (in_liquid) {
-               // If in liquid, the threshold of coming out is at higher y
-               pp = floatToInt(position + v3f(0, BS * 0.1, 0), BS);
-               node = map->getNodeNoEx(pp, &is_valid_position);
-               if (is_valid_position) {
-                       in_liquid = nodemgr->get(node.getContent()).isLiquid();
-                       liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
-               } else {
-                       in_liquid = false;
-               }
-       } else {
-               // If not in liquid, the threshold of going in is at lower y
-               pp = floatToInt(position + v3f(0, BS * 0.5, 0), BS);
-               node = map->getNodeNoEx(pp, &is_valid_position);
-               if (is_valid_position) {
-                       in_liquid = nodemgr->get(node.getContent()).isLiquid();
-                       liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
-               } else {
-                       in_liquid = false;
-               }
-       }
-
-       /*
-               Check if player is in liquid (the stable value)
-       */
-       pp = floatToInt(position + v3f(0, 0, 0), BS);
-       node = map->getNodeNoEx(pp, &is_valid_position);
-       if (is_valid_position)
-               in_liquid_stable = nodemgr->get(node.getContent()).isLiquid();
-       else
-               in_liquid_stable = false;
-
-       /*
-               Check if player is climbing
-       */
-       pp = floatToInt(position + v3f(0, 0.5 * BS, 0), BS);
-       v3s16 pp2 = floatToInt(position + v3f(0, -0.2 * BS, 0), BS);
-       node = map->getNodeNoEx(pp, &is_valid_position);
-       bool is_valid_position2;
-       MapNode node2 = map->getNodeNoEx(pp2, &is_valid_position2);
-
-       if (!(is_valid_position && is_valid_position2))
-               is_climbing = false;
-       else
-               is_climbing = (nodemgr->get(node.getContent()).climbable ||
-                               nodemgr->get(node2.getContent()).climbable) && !free_move;
-
-       /*
-               Collision uncertainty radius
-               Make it a bit larger than the maximum distance of movement
-       */
-       //f32 d = pos_max_d * 1.1;
-       // A fairly large value in here makes moving smoother
-       f32 d = 0.15 * BS;
-       // This should always apply, otherwise there are glitches
-       sanity_check(d > pos_max_d);
-       // Maximum distance over border for sneaking
-       f32 sneak_max = BS * 0.4;
-
-       /*
-               If sneaking, keep in range from the last walked node and don't
-               fall off from it
-       */
-       if (control.sneak && m_sneak_node_exists &&
-                       !(fly_allowed && player_settings.free_move) && !in_liquid &&
-                       physics_override_sneak) {
-               f32 maxd = 0.5 * BS + sneak_max;
-               v3f lwn_f = intToFloat(m_sneak_node, BS);
-               position.X = rangelim(position.X, lwn_f.X - maxd, lwn_f.X + maxd);
-               position.Z = rangelim(position.Z, lwn_f.Z - maxd, lwn_f.Z + maxd);
-
-               if (!is_climbing) {
-                       // Move up if necessary
-                       f32 new_y = (lwn_f.Y - 0.5 * BS) + m_sneak_node_bb_ymax;
-                       if (position.Y < new_y)
-                               position.Y = new_y;
-                       /*
-                               Collision seems broken, since player is sinking when
-                               sneaking over the edges of current sneaking_node.
-                               TODO (when fixed): Set Y-speed only to 0 when position.Y < new_y.
-                       */
-                       if (m_speed.Y < 0)
-                               m_speed.Y = 0;
-               }
-       }
-
-       // this shouldn't be hardcoded but transmitted from server
-       float player_stepheight = touching_ground ? (BS * 0.6) : (BS * 0.2);
-
-       v3f accel_f = v3f(0, 0, 0);
-       const v3f initial_position = position;
-       const v3f initial_speed = m_speed;
-
-       collisionMoveResult result = collisionMoveSimple(env, m_client,
-               pos_max_d, m_collisionbox, player_stepheight, dtime,
-               &position, &m_speed, accel_f);
-
-       /*
-               If the player's feet touch the topside of any node, this is
-               set to true.
-
-               Player is allowed to jump when this is true.
-       */
-       bool touching_ground_was = touching_ground;
-       touching_ground = result.touching_ground;
-
-    //bool standing_on_unloaded = result.standing_on_unloaded;
-
-       /*
-               Check the nodes under the player to see from which node the
-               player is sneaking from, if any.  If the node from under
-               the player has been removed, the player falls.
-       */
-       f32 position_y_mod = 0.05 * BS;
-       if (m_sneak_node_bb_ymax > 0)
-               position_y_mod = m_sneak_node_bb_ymax - position_y_mod;
-       v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), BS);
-       if (m_sneak_node_exists &&
-                       nodemgr->get(map->getNodeNoEx(m_old_node_below)).name == "air" &&
-                       m_old_node_below_type != "air") {
-               // Old node appears to have been removed; that is,
-               // it wasn't air before but now it is
-               m_need_to_get_new_sneak_node = false;
-               m_sneak_node_exists = false;
-       } else if (nodemgr->get(map->getNodeNoEx(current_node)).name != "air") {
-               // We are on something, so make sure to recalculate the sneak
-               // node.
-               m_need_to_get_new_sneak_node = true;
-       }
-
-       if (m_need_to_get_new_sneak_node && physics_override_sneak) {
-               m_sneak_node_bb_ymax = 0;
-               v3s16 pos_i_bottom = floatToInt(position - v3f(0, position_y_mod, 0), BS);
-               v2f player_p2df(position.X, position.Z);
-               f32 min_distance_f = 100000.0 * BS;
-               // If already seeking from some node, compare to it.
-               v3s16 new_sneak_node = m_sneak_node;
-               for (s16 x= -1; x <= 1; x++)
-               for (s16 z= -1; z <= 1; z++) {
-                       v3s16 p = pos_i_bottom + v3s16(x, 0, z);
-                       v3f pf = intToFloat(p, BS);
-                       v2f node_p2df(pf.X, pf.Z);
-                       f32 distance_f = player_p2df.getDistanceFrom(node_p2df);
-                       f32 max_axis_distance_f = MYMAX(
-                                       std::fabs(player_p2df.X - node_p2df.X),
-                                       std::fabs(player_p2df.Y - node_p2df.Y));
-
-                       if (distance_f > min_distance_f ||
-                                       max_axis_distance_f > 0.5 * BS + sneak_max + 0.1 * BS)
-                               continue;
-
-                       // The node to be sneaked on has to be walkable
-                       node = map->getNodeNoEx(p, &is_valid_position);
-                       if (!is_valid_position || !nodemgr->get(node).walkable)
-                               continue;
-                       // And the node above it has to be nonwalkable
-                       node = map->getNodeNoEx(p + v3s16(0, 1, 0), &is_valid_position);
-                       if (!is_valid_position || nodemgr->get(node).walkable)
-                               continue;
-                       // If not 'sneak_glitch' the node 2 nodes above it has to be nonwalkable
-                       if (!physics_override_sneak_glitch) {
-                               node =map->getNodeNoEx(p + v3s16(0, 2, 0), &is_valid_position);
-                               if (!is_valid_position || nodemgr->get(node).walkable)
-                                       continue;
-                       }
-
-                       min_distance_f = distance_f;
-                       new_sneak_node = p;
-               }
-
-               bool sneak_node_found = (min_distance_f < 100000.0 * BS * 0.9);
-
-               m_sneak_node = new_sneak_node;
-               m_sneak_node_exists = sneak_node_found;
-
-               if (sneak_node_found) {
-                       f32 cb_max = 0;
-                       MapNode n = map->getNodeNoEx(m_sneak_node);
-                       std::vector<aabb3f> nodeboxes;
-                       n.getCollisionBoxes(nodemgr, &nodeboxes);
-                       for (const auto &box : nodeboxes) {
-                               if (box.MaxEdge.Y > cb_max)
-                                       cb_max = box.MaxEdge.Y;
-                       }
-                       m_sneak_node_bb_ymax = cb_max;
-               }
-
-               /*
-                       If sneaking, the player's collision box can be in air, so
-                       this has to be set explicitly
-               */
-               if (sneak_node_found && control.sneak)
-                       touching_ground = true;
-       }
-
-       /*
-               Set new position but keep sneak node set
-       */
-       bool sneak_node_exists = m_sneak_node_exists;
-       setPosition(position);
-       m_sneak_node_exists = sneak_node_exists;
-
-       /*
-               Report collisions
-       */
-       // Dont report if flying
-       if (collision_info && !(player_settings.free_move && fly_allowed)) {
-               for (const auto &info : result.collisions) {
-                       collision_info->push_back(info);
-               }
-       }
-
-       if (!result.standing_on_object && !touching_ground_was && touching_ground) {
-               m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_REGAIN_GROUND));
-               // Set camera impact value to be used for view bobbing
-               camera_impact = getSpeed().Y * -1;
-       }
-
-       {
-               camera_barely_in_ceiling = false;
-               v3s16 camera_np = floatToInt(getEyePosition(), BS);
-               MapNode n = map->getNodeNoEx(camera_np);
-               if (n.getContent() != CONTENT_IGNORE) {
-                       if (nodemgr->get(n).walkable && nodemgr->get(n).solidness == 2)
-                               camera_barely_in_ceiling = true;
-               }
-       }
-
-       /*
-               Update the node last under the player
-       */
-       m_old_node_below = floatToInt(position - v3f(0, BS / 2, 0), BS);
-       m_old_node_below_type = nodemgr->get(map->getNodeNoEx(m_old_node_below)).name;
-
-       /*
-               Check properties of the node on which the player is standing
-       */
-       const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(getStandingNodePos()));
-       // Determine if jumping is possible
-       m_can_jump = touching_ground && !in_liquid;
-       if (itemgroup_get(f.groups, "disable_jump"))
-               m_can_jump = false;
-       // Jump key pressed while jumping off from a bouncy block
-       if (m_can_jump && control.jump && itemgroup_get(f.groups, "bouncy") &&
-                       m_speed.Y >= -0.5 * BS) {
-               float jumpspeed = movement_speed_jump * physics_override_jump;
-               if (m_speed.Y > 1) {
-                       // Reduce boost when speed already is high
-                       m_speed.Y += jumpspeed / (1 + (m_speed.Y / 16 ));
-               } else {
-                       m_speed.Y += jumpspeed;
-               }
-               setSpeed(m_speed);
-               m_can_jump = false;
-       }
-
-       // Autojump
-       handleAutojump(dtime, env, result, initial_position, initial_speed, pos_max_d);
-}
-
-float LocalPlayer::getSlipFactor(Environment *env, const v3f &speedH)
-{
-       // Slip on slippery nodes
-       const NodeDefManager *nodemgr = env->getGameDef()->ndef();
-       Map *map = &env->getMap();
-       const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(
-                       getStandingNodePos()));
-       int slippery = 0;
-       if (f.walkable)
-               slippery = itemgroup_get(f.groups, "slippery");
-
-       if (slippery >= 1) {
-               if (speedH == v3f(0.0f)) {
-                       slippery = slippery * 2;
-               }
-               return core::clamp(1.0f / (slippery + 1), 0.001f, 1.0f);
-       }
-       return 1.0f;
-}
-
-void LocalPlayer::handleAutojump(f32 dtime, Environment *env,
-               const collisionMoveResult &result, const v3f &initial_position,
-               const v3f &initial_speed, f32 pos_max_d)
-{
-       PlayerSettings &player_settings = getPlayerSettings();
-       if (!player_settings.autojump)
-               return;
-
-       if (m_autojump) {
-               // release autojump after a given time
-               m_autojump_time -= dtime;
-               if (m_autojump_time <= 0.0f)
-                       m_autojump = false;
-               return;
-       }
-
-       bool control_forward = control.up ||
-                              (!control.up && !control.down &&
-                                              control.forw_move_joystick_axis < -0.05);
-       bool could_autojump =
-                       m_can_jump && !control.jump && !control.sneak && control_forward;
-       if (!could_autojump)
-               return;
-
-       bool horizontal_collision = false;
-       for (const auto &colinfo : result.collisions) {
-               if (colinfo.type == COLLISION_NODE && colinfo.plane != 1) {
-                       horizontal_collision = true;
-                       break; // one is enough
-               }
-       }
-
-       // must be running against something to trigger autojumping
-       if (!horizontal_collision)
-               return;
-
-       // check for nodes above
-       v3f headpos_min = m_position + m_collisionbox.MinEdge * 0.99f;
-       v3f headpos_max = m_position + m_collisionbox.MaxEdge * 0.99f;
-       headpos_min.Y = headpos_max.Y; // top face of collision box
-       v3s16 ceilpos_min = floatToInt(headpos_min, BS) + v3s16(0, 1, 0);
-       v3s16 ceilpos_max = floatToInt(headpos_max, BS) + v3s16(0, 1, 0);
-       const NodeDefManager *ndef = env->getGameDef()->ndef();
-       bool is_position_valid;
-       for (s16 z = ceilpos_min.Z; z <= ceilpos_max.Z; z++) {
-               for (s16 x = ceilpos_min.X; x <= ceilpos_max.X; x++) {
-                       MapNode n = env->getMap().getNodeNoEx(v3s16(x, ceilpos_max.Y, z), &is_position_valid);
-
-                       if (!is_position_valid)
-                               break;  // won't collide with the void outside
-                       if (n.getContent() == CONTENT_IGNORE)
-                               return; // players collide with ignore blocks -> same as walkable
-                       const ContentFeatures &f = ndef->get(n);
-                       if (f.walkable)
-                               return; // would bump head, don't jump
-               }
-       }
-
-       float jump_height = 1.1f; // TODO: better than a magic number
-       v3f jump_pos = initial_position + v3f(0.0f, jump_height * BS, 0.0f);
-       v3f jump_speed = initial_speed;
-
-       // try at peak of jump, zero step height
-       collisionMoveResult jump_result = collisionMoveSimple(env, m_client, pos_max_d,
-                       m_collisionbox, 0.0f, dtime, &jump_pos, &jump_speed,
-                       v3f(0, 0, 0));
-
-       // see if we can get a little bit farther horizontally if we had
-       // jumped
-       v3f run_delta = m_position - initial_position;
-       run_delta.Y = 0.0f;
-       v3f jump_delta = jump_pos - initial_position;
-       jump_delta.Y = 0.0f;
-       if (jump_delta.getLengthSQ() > run_delta.getLengthSQ() * 1.01f) {
-               m_autojump = true;
-               m_autojump_time = 0.1f;
-       }
-}
diff --git a/src/localplayer.h b/src/localplayer.h
deleted file mode 100644 (file)
index 7148bc4..0000000
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include "player.h"
-#include "environment.h"
-#include "constants.h"
-#include "settings.h"
-#include <list>
-
-class Client;
-class Environment;
-class GenericCAO;
-class ClientActiveObject;
-class ClientEnvironment;
-class IGameDef;
-struct collisionMoveResult;
-
-enum LocalPlayerAnimations
-{
-       NO_ANIM,
-       WALK_ANIM,
-       DIG_ANIM,
-       WD_ANIM
-}; // no local animation, walking, digging, both
-
-class LocalPlayer : public Player
-{
-public:
-       LocalPlayer(Client *client, const char *name);
-       virtual ~LocalPlayer() = default;
-
-       ClientActiveObject *parent = nullptr;
-
-       // Initialize hp to 0, so that no hearts will be shown if server
-       // doesn't support health points
-       u16 hp = 0;
-       bool isAttached = false;
-       bool touching_ground = false;
-       // This oscillates so that the player jumps a bit above the surface
-       bool in_liquid = false;
-       // This is more stable and defines the maximum speed of the player
-       bool in_liquid_stable = false;
-       // Gets the viscosity of water to calculate friction
-       u8 liquid_viscosity = 0;
-       bool is_climbing = false;
-       bool swimming_vertical = false;
-
-       float physics_override_speed = 1.0f;
-       float physics_override_jump = 1.0f;
-       float physics_override_gravity = 1.0f;
-       bool physics_override_sneak = true;
-       bool physics_override_sneak_glitch = false;
-       // Temporary option for old move code
-       bool physics_override_new_move = true;
-
-       v3f overridePosition;
-
-       void move(f32 dtime, Environment *env, f32 pos_max_d);
-       void move(f32 dtime, Environment *env, f32 pos_max_d,
-                       std::vector<CollisionInfo> *collision_info);
-       // Temporary option for old move code
-       void old_move(f32 dtime, Environment *env, f32 pos_max_d,
-                       std::vector<CollisionInfo> *collision_info);
-
-       void applyControl(float dtime, Environment *env);
-
-       v3s16 getStandingNodePos();
-       v3s16 getFootstepNodePos();
-
-       // Used to check if anything changed and prevent sending packets if not
-       v3f last_position;
-       v3f last_speed;
-       float last_pitch = 0.0f;
-       float last_yaw = 0.0f;
-       unsigned int last_keyPressed = 0;
-       u8 last_camera_fov = 0;
-       u8 last_wanted_range = 0;
-
-       float camera_impact = 0.0f;
-
-       bool makes_footstep_sound = true;
-
-       int last_animation = NO_ANIM;
-       float last_animation_speed;
-
-       std::string hotbar_image = "";
-       std::string hotbar_selected_image = "";
-
-       video::SColor light_color = video::SColor(255, 255, 255, 255);
-
-       float hurt_tilt_timer = 0.0f;
-       float hurt_tilt_strength = 0.0f;
-
-       GenericCAO *getCAO() const { return m_cao; }
-
-       void setCAO(GenericCAO *toset)
-       {
-               assert(!m_cao); // Pre-condition
-               m_cao = toset;
-       }
-
-       u32 maxHudId() const { return hud.size(); }
-
-       u16 getBreath() const { return m_breath; }
-       void setBreath(u16 breath) { m_breath = breath; }
-
-       v3s16 getLightPosition() const;
-
-       void setYaw(f32 yaw) { m_yaw = yaw; }
-       f32 getYaw() const { return m_yaw; }
-
-       void setPitch(f32 pitch) { m_pitch = pitch; }
-       f32 getPitch() const { return m_pitch; }
-
-       inline void setPosition(const v3f &position)
-       {
-               m_position = position;
-               m_sneak_node_exists = false;
-       }
-
-       v3f getPosition() const { return m_position; }
-       v3f getEyePosition() const { return m_position + getEyeOffset(); }
-       v3f getEyeOffset() const;
-       void setEyeHeight(float eye_height) { m_eye_height = eye_height; }
-
-       void setCollisionbox(const aabb3f &box) { m_collisionbox = box; }
-
-       float getZoomFOV() const { return m_zoom_fov; }
-       void setZoomFOV(float zoom_fov) { m_zoom_fov = zoom_fov; }
-
-       bool getAutojump() const { return m_autojump; }
-
-private:
-       void accelerateHorizontal(const v3f &target_speed, const f32 max_increase);
-       void accelerateVertical(const v3f &target_speed, const f32 max_increase);
-       bool updateSneakNode(Map *map, const v3f &position, const v3f &sneak_max);
-       float getSlipFactor(Environment *env, const v3f &speedH);
-       void handleAutojump(f32 dtime, Environment *env,
-                       const collisionMoveResult &result,
-                       const v3f &position_before_move, const v3f &speed_before_move,
-                       f32 pos_max_d);
-
-       v3f m_position;
-       v3s16 m_standing_node;
-
-       v3s16 m_sneak_node = v3s16(32767, 32767, 32767);
-       // Stores the top bounding box of m_sneak_node
-       aabb3f m_sneak_node_bb_top = aabb3f(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
-       // Whether the player is allowed to sneak
-       bool m_sneak_node_exists = false;
-       // Whether a "sneak ladder" structure is detected at the players pos
-       // see detectSneakLadder() in the .cpp for more info (always false if disabled)
-       bool m_sneak_ladder_detected = false;
-
-       // ***** Variables for temporary option of the old move code *****
-       // Stores the max player uplift by m_sneak_node
-       f32 m_sneak_node_bb_ymax = 0.0f;
-       // Whether recalculation of m_sneak_node and its top bbox is needed
-       bool m_need_to_get_new_sneak_node = true;
-       // Node below player, used to determine whether it has been removed,
-       // and its old type
-       v3s16 m_old_node_below = v3s16(32767, 32767, 32767);
-       std::string m_old_node_below_type = "air";
-       // ***** End of variables for temporary option *****
-
-       bool m_can_jump = false;
-       u16 m_breath = PLAYER_MAX_BREATH_DEFAULT;
-       f32 m_yaw = 0.0f;
-       f32 m_pitch = 0.0f;
-       bool camera_barely_in_ceiling = false;
-       aabb3f m_collisionbox = aabb3f(-BS * 0.30f, 0.0f, -BS * 0.30f, BS * 0.30f,
-                       BS * 1.75f, BS * 0.30f);
-       float m_eye_height = 1.625f;
-       float m_zoom_fov = 0.0f;
-       bool m_autojump = false;
-       float m_autojump_time = 0.0f;
-
-       GenericCAO *m_cao = nullptr;
-       Client *m_client;
-};
index 2033518e3b542471062687d1eacc69d38db9785d..546eb2a00e7ac487c6f5236ecf6e2b4621f0ebea 100644 (file)
@@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "server.h"
 #include "filesys.h"
 #include "version.h"
-#include "game.h"
+#include "client/game.h"
 #include "defaultsettings.h"
 #include "gettext.h"
 #include "log.h"
index cf815d16b6ff9bd8f7620fe36d524733a5f69b33..dfb4c783eee6e9c6f8e4eb59fddffedb880318e1 100644 (file)
@@ -31,7 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "content_nodemeta.h" // For legacy deserialization
 #include "serialization.h"
 #ifndef SERVER
-#include "mapblock_mesh.h"
+#include "client/mapblock_mesh.h"
 #endif
 #include "porting.h"
 #include "util/string.h"
diff --git a/src/mapblock_mesh.cpp b/src/mapblock_mesh.cpp
deleted file mode 100644 (file)
index ed8a073..0000000
+++ /dev/null
@@ -1,1389 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 "mapblock_mesh.h"
-#include "client.h"
-#include "mapblock.h"
-#include "map.h"
-#include "profiler.h"
-#include "shader.h"
-#include "mesh.h"
-#include "minimap.h"
-#include "content_mapblock.h"
-#include "util/directiontables.h"
-#include "client/meshgen/collector.h"
-#include "client/renderingengine.h"
-#include <array>
-
-/*
-       MeshMakeData
-*/
-
-MeshMakeData::MeshMakeData(Client *client, bool use_shaders,
-               bool use_tangent_vertices):
-       m_client(client),
-       m_use_shaders(use_shaders),
-       m_use_tangent_vertices(use_tangent_vertices)
-{}
-
-void MeshMakeData::fillBlockDataBegin(const v3s16 &blockpos)
-{
-       m_blockpos = blockpos;
-
-       v3s16 blockpos_nodes = m_blockpos*MAP_BLOCKSIZE;
-
-       m_vmanip.clear();
-       VoxelArea voxel_area(blockpos_nodes - v3s16(1,1,1) * MAP_BLOCKSIZE,
-                       blockpos_nodes + v3s16(1,1,1) * MAP_BLOCKSIZE*2-v3s16(1,1,1));
-       m_vmanip.addArea(voxel_area);
-}
-
-void MeshMakeData::fillBlockData(const v3s16 &block_offset, MapNode *data)
-{
-       v3s16 data_size(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
-       VoxelArea data_area(v3s16(0,0,0), data_size - v3s16(1,1,1));
-
-       v3s16 bp = m_blockpos + block_offset;
-       v3s16 blockpos_nodes = bp * MAP_BLOCKSIZE;
-       m_vmanip.copyFrom(data, data_area, v3s16(0,0,0), blockpos_nodes, data_size);
-}
-
-void MeshMakeData::fill(MapBlock *block)
-{
-       fillBlockDataBegin(block->getPos());
-
-       fillBlockData(v3s16(0,0,0), block->getData());
-
-       // Get map for reading neighbor blocks
-       Map *map = block->getParent();
-
-       for (const v3s16 &dir : g_26dirs) {
-               v3s16 bp = m_blockpos + dir;
-               MapBlock *b = map->getBlockNoCreateNoEx(bp);
-               if(b)
-                       fillBlockData(dir, b->getData());
-       }
-}
-
-void MeshMakeData::fillSingleNode(MapNode *node)
-{
-       m_blockpos = v3s16(0,0,0);
-
-       v3s16 blockpos_nodes = v3s16(0,0,0);
-       VoxelArea area(blockpos_nodes-v3s16(1,1,1)*MAP_BLOCKSIZE,
-                       blockpos_nodes+v3s16(1,1,1)*MAP_BLOCKSIZE*2-v3s16(1,1,1));
-       s32 volume = area.getVolume();
-       s32 our_node_index = area.index(1,1,1);
-
-       // Allocate this block + neighbors
-       m_vmanip.clear();
-       m_vmanip.addArea(area);
-
-       // Fill in data
-       MapNode *data = new MapNode[volume];
-       for(s32 i = 0; i < volume; i++)
-       {
-               if (i == our_node_index)
-                       data[i] = *node;
-               else
-                       data[i] = MapNode(CONTENT_AIR, LIGHT_MAX, 0);
-       }
-       m_vmanip.copyFrom(data, area, area.MinEdge, area.MinEdge, area.getExtent());
-       delete[] data;
-}
-
-void MeshMakeData::setCrack(int crack_level, v3s16 crack_pos)
-{
-       if (crack_level >= 0)
-               m_crack_pos_relative = crack_pos - m_blockpos*MAP_BLOCKSIZE;
-}
-
-void MeshMakeData::setSmoothLighting(bool smooth_lighting)
-{
-       m_smooth_lighting = smooth_lighting;
-}
-
-/*
-       Light and vertex color functions
-*/
-
-/*
-       Calculate non-smooth lighting at interior of node.
-       Single light bank.
-*/
-static u8 getInteriorLight(enum LightBank bank, MapNode n, s32 increment,
-       const NodeDefManager *ndef)
-{
-       u8 light = n.getLight(bank, ndef);
-       if (light > 0)
-               light = rangelim(light + increment, 0, LIGHT_SUN);
-       return decode_light(light);
-}
-
-/*
-       Calculate non-smooth lighting at interior of node.
-       Both light banks.
-*/
-u16 getInteriorLight(MapNode n, s32 increment, const NodeDefManager *ndef)
-{
-       u16 day = getInteriorLight(LIGHTBANK_DAY, n, increment, ndef);
-       u16 night = getInteriorLight(LIGHTBANK_NIGHT, n, increment, ndef);
-       return day | (night << 8);
-}
-
-/*
-       Calculate non-smooth lighting at face of node.
-       Single light bank.
-*/
-static u8 getFaceLight(enum LightBank bank, MapNode n, MapNode n2,
-       v3s16 face_dir, const NodeDefManager *ndef)
-{
-       u8 light;
-       u8 l1 = n.getLight(bank, ndef);
-       u8 l2 = n2.getLight(bank, ndef);
-       if(l1 > l2)
-               light = l1;
-       else
-               light = l2;
-
-       // Boost light level for light sources
-       u8 light_source = MYMAX(ndef->get(n).light_source,
-                       ndef->get(n2).light_source);
-       if(light_source > light)
-               light = light_source;
-
-       return decode_light(light);
-}
-
-/*
-       Calculate non-smooth lighting at face of node.
-       Both light banks.
-*/
-u16 getFaceLight(MapNode n, MapNode n2, const v3s16 &face_dir,
-       const NodeDefManager *ndef)
-{
-       u16 day = getFaceLight(LIGHTBANK_DAY, n, n2, face_dir, ndef);
-       u16 night = getFaceLight(LIGHTBANK_NIGHT, n, n2, face_dir, ndef);
-       return day | (night << 8);
-}
-
-/*
-       Calculate smooth lighting at the XYZ- corner of p.
-       Both light banks
-*/
-static u16 getSmoothLightCombined(const v3s16 &p,
-       const std::array<v3s16,8> &dirs, MeshMakeData *data)
-{
-       const NodeDefManager *ndef = data->m_client->ndef();
-
-       u16 ambient_occlusion = 0;
-       u16 light_count = 0;
-       u8 light_source_max = 0;
-       u16 light_day = 0;
-       u16 light_night = 0;
-       bool direct_sunlight = false;
-
-       auto add_node = [&] (u8 i, bool obstructed = false) -> bool {
-               if (obstructed) {
-                       ambient_occlusion++;
-                       return false;
-               }
-               MapNode n = data->m_vmanip.getNodeNoExNoEmerge(p + dirs[i]);
-               if (n.getContent() == CONTENT_IGNORE)
-                       return true;
-               const ContentFeatures &f = ndef->get(n);
-               if (f.light_source > light_source_max)
-                       light_source_max = f.light_source;
-               // Check f.solidness because fast-style leaves look better this way
-               if (f.param_type == CPT_LIGHT && f.solidness != 2) {
-                       u8 light_level_day = n.getLightNoChecks(LIGHTBANK_DAY, &f);
-                       u8 light_level_night = n.getLightNoChecks(LIGHTBANK_NIGHT, &f);
-                       if (light_level_day == LIGHT_SUN)
-                               direct_sunlight = true;
-                       light_day += decode_light(light_level_day);
-                       light_night += decode_light(light_level_night);
-                       light_count++;
-               } else {
-                       ambient_occlusion++;
-               }
-               return f.light_propagates;
-       };
-
-       std::array<bool, 4> obstructed = {{ 1, 1, 1, 1 }};
-       add_node(0);
-       bool opaque1 = !add_node(1);
-       bool opaque2 = !add_node(2);
-       bool opaque3 = !add_node(3);
-       obstructed[0] = opaque1 && opaque2;
-       obstructed[1] = opaque1 && opaque3;
-       obstructed[2] = opaque2 && opaque3;
-       for (u8 k = 0; k < 3; ++k)
-               if (add_node(k + 4, obstructed[k]))
-                       obstructed[3] = false;
-       if (add_node(7, obstructed[3])) { // wrap light around nodes
-               ambient_occlusion -= 3;
-               for (u8 k = 0; k < 3; ++k)
-                       add_node(k + 4, !obstructed[k]);
-       }
-
-       if (light_count == 0) {
-               light_day = light_night = 0;
-       } else {
-               light_day /= light_count;
-               light_night /= light_count;
-       }
-
-       // boost direct sunlight, if any
-       if (direct_sunlight)
-               light_day = 0xFF;
-
-       // Boost brightness around light sources
-       bool skip_ambient_occlusion_day = false;
-       if (decode_light(light_source_max) >= light_day) {
-               light_day = decode_light(light_source_max);
-               skip_ambient_occlusion_day = true;
-       }
-
-       bool skip_ambient_occlusion_night = false;
-       if(decode_light(light_source_max) >= light_night) {
-               light_night = decode_light(light_source_max);
-               skip_ambient_occlusion_night = true;
-       }
-
-       if (ambient_occlusion > 4) {
-               static thread_local const float ao_gamma = rangelim(
-                       g_settings->getFloat("ambient_occlusion_gamma"), 0.25, 4.0);
-
-               // Table of gamma space multiply factors.
-               static thread_local const float light_amount[3] = {
-                       powf(0.75, 1.0 / ao_gamma),
-                       powf(0.5,  1.0 / ao_gamma),
-                       powf(0.25, 1.0 / ao_gamma)
-               };
-
-               //calculate table index for gamma space multiplier
-               ambient_occlusion -= 5;
-
-               if (!skip_ambient_occlusion_day)
-                       light_day = rangelim(core::round32(
-                                       light_day * light_amount[ambient_occlusion]), 0, 255);
-               if (!skip_ambient_occlusion_night)
-                       light_night = rangelim(core::round32(
-                                       light_night * light_amount[ambient_occlusion]), 0, 255);
-       }
-
-       return light_day | (light_night << 8);
-}
-
-/*
-       Calculate smooth lighting at the given corner of p.
-       Both light banks.
-       Node at p is solid, and thus the lighting is face-dependent.
-*/
-u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, MeshMakeData *data)
-{
-       return getSmoothLightTransparent(p + face_dir, corner - 2 * face_dir, data);
-}
-
-/*
-       Calculate smooth lighting at the given corner of p.
-       Both light banks.
-       Node at p is not solid, and the lighting is not face-dependent.
-*/
-u16 getSmoothLightTransparent(const v3s16 &p, const v3s16 &corner, MeshMakeData *data)
-{
-       const std::array<v3s16,8> dirs = {{
-               // Always shine light
-               v3s16(0,0,0),
-               v3s16(corner.X,0,0),
-               v3s16(0,corner.Y,0),
-               v3s16(0,0,corner.Z),
-
-               // Can be obstructed
-               v3s16(corner.X,corner.Y,0),
-               v3s16(corner.X,0,corner.Z),
-               v3s16(0,corner.Y,corner.Z),
-               v3s16(corner.X,corner.Y,corner.Z)
-       }};
-       return getSmoothLightCombined(p, dirs, data);
-}
-
-void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio){
-       f32 rg = daynight_ratio / 1000.0f - 0.04f;
-       f32 b = (0.98f * daynight_ratio) / 1000.0f + 0.078f;
-       sunlight->r = rg;
-       sunlight->g = rg;
-       sunlight->b = b;
-}
-
-void final_color_blend(video::SColor *result,
-               u16 light, u32 daynight_ratio)
-{
-       video::SColorf dayLight;
-       get_sunlight_color(&dayLight, daynight_ratio);
-       final_color_blend(result,
-               encode_light(light, 0), dayLight);
-}
-
-void final_color_blend(video::SColor *result,
-               const video::SColor &data, const video::SColorf &dayLight)
-{
-       static const video::SColorf artificialColor(1.04f, 1.04f, 1.04f);
-
-       video::SColorf c(data);
-       f32 n = 1 - c.a;
-
-       f32 r = c.r * (c.a * dayLight.r + n * artificialColor.r) * 2.0f;
-       f32 g = c.g * (c.a * dayLight.g + n * artificialColor.g) * 2.0f;
-       f32 b = c.b * (c.a * dayLight.b + n * artificialColor.b) * 2.0f;
-
-       // Emphase blue a bit in darker places
-       // Each entry of this array represents a range of 8 blue levels
-       static const u8 emphase_blue_when_dark[32] = {
-               1, 4, 6, 6, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0,
-               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-       };
-
-       b += emphase_blue_when_dark[irr::core::clamp((s32) ((r + g + b) / 3 * 255),
-               0, 255) / 8] / 255.0f;
-
-       result->setRed(core::clamp((s32) (r * 255.0f), 0, 255));
-       result->setGreen(core::clamp((s32) (g * 255.0f), 0, 255));
-       result->setBlue(core::clamp((s32) (b * 255.0f), 0, 255));
-}
-
-/*
-       Mesh generation helpers
-*/
-
-/*
-       vertex_dirs: v3s16[4]
-*/
-static void getNodeVertexDirs(const v3s16 &dir, v3s16 *vertex_dirs)
-{
-       /*
-               If looked from outside the node towards the face, the corners are:
-               0: bottom-right
-               1: bottom-left
-               2: top-left
-               3: top-right
-       */
-       if (dir == v3s16(0, 0, 1)) {
-               // If looking towards z+, this is the face that is behind
-               // the center point, facing towards z+.
-               vertex_dirs[0] = v3s16(-1,-1, 1);
-               vertex_dirs[1] = v3s16( 1,-1, 1);
-               vertex_dirs[2] = v3s16( 1, 1, 1);
-               vertex_dirs[3] = v3s16(-1, 1, 1);
-       } else if (dir == v3s16(0, 0, -1)) {
-               // faces towards Z-
-               vertex_dirs[0] = v3s16( 1,-1,-1);
-               vertex_dirs[1] = v3s16(-1,-1,-1);
-               vertex_dirs[2] = v3s16(-1, 1,-1);
-               vertex_dirs[3] = v3s16( 1, 1,-1);
-       } else if (dir == v3s16(1, 0, 0)) {
-               // faces towards X+
-               vertex_dirs[0] = v3s16( 1,-1, 1);
-               vertex_dirs[1] = v3s16( 1,-1,-1);
-               vertex_dirs[2] = v3s16( 1, 1,-1);
-               vertex_dirs[3] = v3s16( 1, 1, 1);
-       } else if (dir == v3s16(-1, 0, 0)) {
-               // faces towards X-
-               vertex_dirs[0] = v3s16(-1,-1,-1);
-               vertex_dirs[1] = v3s16(-1,-1, 1);
-               vertex_dirs[2] = v3s16(-1, 1, 1);
-               vertex_dirs[3] = v3s16(-1, 1,-1);
-       } else if (dir == v3s16(0, 1, 0)) {
-               // faces towards Y+ (assume Z- as "down" in texture)
-               vertex_dirs[0] = v3s16( 1, 1,-1);
-               vertex_dirs[1] = v3s16(-1, 1,-1);
-               vertex_dirs[2] = v3s16(-1, 1, 1);
-               vertex_dirs[3] = v3s16( 1, 1, 1);
-       } else if (dir == v3s16(0, -1, 0)) {
-               // faces towards Y- (assume Z+ as "down" in texture)
-               vertex_dirs[0] = v3s16( 1,-1, 1);
-               vertex_dirs[1] = v3s16(-1,-1, 1);
-               vertex_dirs[2] = v3s16(-1,-1,-1);
-               vertex_dirs[3] = v3s16( 1,-1,-1);
-       }
-}
-
-static void getNodeTextureCoords(v3f base, const v3f &scale, const v3s16 &dir, float *u, float *v)
-{
-       if (dir.X > 0 || dir.Y > 0 || dir.Z < 0)
-               base -= scale;
-       if (dir == v3s16(0,0,1)) {
-               *u = -base.X - 1;
-               *v = -base.Y - 1;
-       } else if (dir == v3s16(0,0,-1)) {
-               *u = base.X + 1;
-               *v = -base.Y - 2;
-       } else if (dir == v3s16(1,0,0)) {
-               *u = base.Z + 1;
-               *v = -base.Y - 2;
-       } else if (dir == v3s16(-1,0,0)) {
-               *u = -base.Z - 1;
-               *v = -base.Y - 1;
-       } else if (dir == v3s16(0,1,0)) {
-               *u = base.X + 1;
-               *v = -base.Z - 2;
-       } else if (dir == v3s16(0,-1,0)) {
-               *u = base.X;
-               *v = base.Z;
-       }
-}
-
-struct FastFace
-{
-       TileSpec tile;
-       video::S3DVertex vertices[4]; // Precalculated vertices
-       /*!
-        * The face is divided into two triangles. If this is true,
-        * vertices 0 and 2 are connected, othervise vertices 1 and 3
-        * are connected.
-        */
-       bool vertex_0_2_connected;
-};
-
-static void makeFastFace(const TileSpec &tile, u16 li0, u16 li1, u16 li2, u16 li3,
-       const v3f &tp, const v3f &p, const v3s16 &dir, const v3f &scale, std::vector<FastFace> &dest)
-{
-       // Position is at the center of the cube.
-       v3f pos = p * BS;
-
-       float x0 = 0.0f;
-       float y0 = 0.0f;
-       float w = 1.0f;
-       float h = 1.0f;
-
-       v3f vertex_pos[4];
-       v3s16 vertex_dirs[4];
-       getNodeVertexDirs(dir, vertex_dirs);
-       if (tile.world_aligned)
-               getNodeTextureCoords(tp, scale, dir, &x0, &y0);
-
-       v3s16 t;
-       u16 t1;
-       switch (tile.rotation) {
-       case 0:
-               break;
-       case 1: //R90
-               t = vertex_dirs[0];
-               vertex_dirs[0] = vertex_dirs[3];
-               vertex_dirs[3] = vertex_dirs[2];
-               vertex_dirs[2] = vertex_dirs[1];
-               vertex_dirs[1] = t;
-               t1  = li0;
-               li0 = li3;
-               li3 = li2;
-               li2 = li1;
-               li1 = t1;
-               break;
-       case 2: //R180
-               t = vertex_dirs[0];
-               vertex_dirs[0] = vertex_dirs[2];
-               vertex_dirs[2] = t;
-               t = vertex_dirs[1];
-               vertex_dirs[1] = vertex_dirs[3];
-               vertex_dirs[3] = t;
-               t1  = li0;
-               li0 = li2;
-               li2 = t1;
-               t1  = li1;
-               li1 = li3;
-               li3 = t1;
-               break;
-       case 3: //R270
-               t = vertex_dirs[0];
-               vertex_dirs[0] = vertex_dirs[1];
-               vertex_dirs[1] = vertex_dirs[2];
-               vertex_dirs[2] = vertex_dirs[3];
-               vertex_dirs[3] = t;
-               t1  = li0;
-               li0 = li1;
-               li1 = li2;
-               li2 = li3;
-               li3 = t1;
-               break;
-       case 4: //FXR90
-               t = vertex_dirs[0];
-               vertex_dirs[0] = vertex_dirs[3];
-               vertex_dirs[3] = vertex_dirs[2];
-               vertex_dirs[2] = vertex_dirs[1];
-               vertex_dirs[1] = t;
-               t1  = li0;
-               li0 = li3;
-               li3 = li2;
-               li2 = li1;
-               li1 = t1;
-               y0 += h;
-               h *= -1;
-               break;
-       case 5: //FXR270
-               t = vertex_dirs[0];
-               vertex_dirs[0] = vertex_dirs[1];
-               vertex_dirs[1] = vertex_dirs[2];
-               vertex_dirs[2] = vertex_dirs[3];
-               vertex_dirs[3] = t;
-               t1  = li0;
-               li0 = li1;
-               li1 = li2;
-               li2 = li3;
-               li3 = t1;
-               y0 += h;
-               h *= -1;
-               break;
-       case 6: //FYR90
-               t = vertex_dirs[0];
-               vertex_dirs[0] = vertex_dirs[3];
-               vertex_dirs[3] = vertex_dirs[2];
-               vertex_dirs[2] = vertex_dirs[1];
-               vertex_dirs[1] = t;
-               t1  = li0;
-               li0 = li3;
-               li3 = li2;
-               li2 = li1;
-               li1 = t1;
-               x0 += w;
-               w *= -1;
-               break;
-       case 7: //FYR270
-               t = vertex_dirs[0];
-               vertex_dirs[0] = vertex_dirs[1];
-               vertex_dirs[1] = vertex_dirs[2];
-               vertex_dirs[2] = vertex_dirs[3];
-               vertex_dirs[3] = t;
-               t1  = li0;
-               li0 = li1;
-               li1 = li2;
-               li2 = li3;
-               li3 = t1;
-               x0 += w;
-               w *= -1;
-               break;
-       case 8: //FX
-               y0 += h;
-               h *= -1;
-               break;
-       case 9: //FY
-               x0 += w;
-               w *= -1;
-               break;
-       default:
-               break;
-       }
-
-       for (u16 i = 0; i < 4; i++) {
-               vertex_pos[i] = v3f(
-                               BS / 2 * vertex_dirs[i].X,
-                               BS / 2 * vertex_dirs[i].Y,
-                               BS / 2 * vertex_dirs[i].Z
-               );
-       }
-
-       for (v3f &vpos : vertex_pos) {
-               vpos.X *= scale.X;
-               vpos.Y *= scale.Y;
-               vpos.Z *= scale.Z;
-               vpos += pos;
-       }
-
-       f32 abs_scale = 1.0f;
-       if      (scale.X < 0.999f || scale.X > 1.001f) abs_scale = scale.X;
-       else if (scale.Y < 0.999f || scale.Y > 1.001f) abs_scale = scale.Y;
-       else if (scale.Z < 0.999f || scale.Z > 1.001f) abs_scale = scale.Z;
-
-       v3f normal(dir.X, dir.Y, dir.Z);
-
-       u16 li[4] = { li0, li1, li2, li3 };
-       u16 day[4];
-       u16 night[4];
-
-       for (u8 i = 0; i < 4; i++) {
-               day[i] = li[i] >> 8;
-               night[i] = li[i] & 0xFF;
-       }
-
-       bool vertex_0_2_connected = abs(day[0] - day[2]) + abs(night[0] - night[2])
-                       < abs(day[1] - day[3]) + abs(night[1] - night[3]);
-
-       v2f32 f[4] = {
-               core::vector2d<f32>(x0 + w * abs_scale, y0 + h),
-               core::vector2d<f32>(x0, y0 + h),
-               core::vector2d<f32>(x0, y0),
-               core::vector2d<f32>(x0 + w * abs_scale, y0) };
-
-       // equivalent to dest.push_back(FastFace()) but faster
-       dest.emplace_back();
-       FastFace& face = *dest.rbegin();
-
-       for (u8 i = 0; i < 4; i++) {
-               video::SColor c = encode_light(li[i], tile.emissive_light);
-               if (!tile.emissive_light)
-                       applyFacesShading(c, normal);
-
-               face.vertices[i] = video::S3DVertex(vertex_pos[i], normal, c, f[i]);
-       }
-
-       /*
-               Revert triangles for nicer looking gradient if the
-               brightness of vertices 1 and 3 differ less than
-               the brightness of vertices 0 and 2.
-               */
-       face.vertex_0_2_connected = vertex_0_2_connected;
-       face.tile = tile;
-}
-
-/*
-       Nodes make a face if contents differ and solidness differs.
-       Return value:
-               0: No face
-               1: Face uses m1's content
-               2: Face uses m2's content
-       equivalent: Whether the blocks share the same face (eg. water and glass)
-
-       TODO: Add 3: Both faces drawn with backface culling, remove equivalent
-*/
-static u8 face_contents(content_t m1, content_t m2, bool *equivalent,
-       const NodeDefManager *ndef)
-{
-       *equivalent = false;
-
-       if (m1 == m2 || m1 == CONTENT_IGNORE || m2 == CONTENT_IGNORE)
-               return 0;
-
-       const ContentFeatures &f1 = ndef->get(m1);
-       const ContentFeatures &f2 = ndef->get(m2);
-
-       // Contents don't differ for different forms of same liquid
-       if (f1.sameLiquid(f2))
-               return 0;
-
-       u8 c1 = f1.solidness;
-       u8 c2 = f2.solidness;
-
-       if (c1 == c2)
-               return 0;
-
-       if (c1 == 0)
-               c1 = f1.visual_solidness;
-       else if (c2 == 0)
-               c2 = f2.visual_solidness;
-
-       if (c1 == c2) {
-               *equivalent = true;
-               // If same solidness, liquid takes precense
-               if (f1.isLiquid())
-                       return 1;
-               if (f2.isLiquid())
-                       return 2;
-       }
-
-       if (c1 > c2)
-               return 1;
-
-       return 2;
-}
-
-/*
-       Gets nth node tile (0 <= n <= 5).
-*/
-void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, TileSpec &tile)
-{
-       const NodeDefManager *ndef = data->m_client->ndef();
-       const ContentFeatures &f = ndef->get(mn);
-       tile = f.tiles[tileindex];
-       bool has_crack = p == data->m_crack_pos_relative;
-       for (TileLayer &layer : tile.layers) {
-               if (layer.texture_id == 0)
-                       continue;
-               if (!layer.has_color)
-                       mn.getColor(f, &(layer.color));
-               // Apply temporary crack
-               if (has_crack)
-                       layer.material_flags |= MATERIAL_FLAG_CRACK;
-       }
-}
-
-/*
-       Gets node tile given a face direction.
-*/
-void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *data, TileSpec &tile)
-{
-       const NodeDefManager *ndef = data->m_client->ndef();
-
-       // Direction must be (1,0,0), (-1,0,0), (0,1,0), (0,-1,0),
-       // (0,0,1), (0,0,-1) or (0,0,0)
-       assert(dir.X * dir.X + dir.Y * dir.Y + dir.Z * dir.Z <= 1);
-
-       // Convert direction to single integer for table lookup
-       //  0 = (0,0,0)
-       //  1 = (1,0,0)
-       //  2 = (0,1,0)
-       //  3 = (0,0,1)
-       //  4 = invalid, treat as (0,0,0)
-       //  5 = (0,0,-1)
-       //  6 = (0,-1,0)
-       //  7 = (-1,0,0)
-       u8 dir_i = ((dir.X + 2 * dir.Y + 3 * dir.Z) & 7) * 2;
-
-       // Get rotation for things like chests
-       u8 facedir = mn.getFaceDir(ndef);
-
-       static const u16 dir_to_tile[24 * 16] =
-       {
-               // 0     +X    +Y    +Z           -Z    -Y    -X   ->   value=tile,rotation
-                  0,0,  2,0 , 0,0 , 4,0 ,  0,0,  5,0 , 1,0 , 3,0 ,  // rotate around y+ 0 - 3
-                  0,0,  4,0 , 0,3 , 3,0 ,  0,0,  2,0 , 1,1 , 5,0 ,
-                  0,0,  3,0 , 0,2 , 5,0 ,  0,0,  4,0 , 1,2 , 2,0 ,
-                  0,0,  5,0 , 0,1 , 2,0 ,  0,0,  3,0 , 1,3 , 4,0 ,
-
-                  0,0,  2,3 , 5,0 , 0,2 ,  0,0,  1,0 , 4,2 , 3,1 ,  // rotate around z+ 4 - 7
-                  0,0,  4,3 , 2,0 , 0,1 ,  0,0,  1,1 , 3,2 , 5,1 ,
-                  0,0,  3,3 , 4,0 , 0,0 ,  0,0,  1,2 , 5,2 , 2,1 ,
-                  0,0,  5,3 , 3,0 , 0,3 ,  0,0,  1,3 , 2,2 , 4,1 ,
-
-                  0,0,  2,1 , 4,2 , 1,2 ,  0,0,  0,0 , 5,0 , 3,3 ,  // rotate around z- 8 - 11
-                  0,0,  4,1 , 3,2 , 1,3 ,  0,0,  0,3 , 2,0 , 5,3 ,
-                  0,0,  3,1 , 5,2 , 1,0 ,  0,0,  0,2 , 4,0 , 2,3 ,
-                  0,0,  5,1 , 2,2 , 1,1 ,  0,0,  0,1 , 3,0 , 4,3 ,
-
-                  0,0,  0,3 , 3,3 , 4,1 ,  0,0,  5,3 , 2,3 , 1,3 ,  // rotate around x+ 12 - 15
-                  0,0,  0,2 , 5,3 , 3,1 ,  0,0,  2,3 , 4,3 , 1,0 ,
-                  0,0,  0,1 , 2,3 , 5,1 ,  0,0,  4,3 , 3,3 , 1,1 ,
-                  0,0,  0,0 , 4,3 , 2,1 ,  0,0,  3,3 , 5,3 , 1,2 ,
-
-                  0,0,  1,1 , 2,1 , 4,3 ,  0,0,  5,1 , 3,1 , 0,1 ,  // rotate around x- 16 - 19
-                  0,0,  1,2 , 4,1 , 3,3 ,  0,0,  2,1 , 5,1 , 0,0 ,
-                  0,0,  1,3 , 3,1 , 5,3 ,  0,0,  4,1 , 2,1 , 0,3 ,
-                  0,0,  1,0 , 5,1 , 2,3 ,  0,0,  3,1 , 4,1 , 0,2 ,
-
-                  0,0,  3,2 , 1,2 , 4,2 ,  0,0,  5,2 , 0,2 , 2,2 ,  // rotate around y- 20 - 23
-                  0,0,  5,2 , 1,3 , 3,2 ,  0,0,  2,2 , 0,1 , 4,2 ,
-                  0,0,  2,2 , 1,0 , 5,2 ,  0,0,  4,2 , 0,0 , 3,2 ,
-                  0,0,  4,2 , 1,1 , 2,2 ,  0,0,  3,2 , 0,3 , 5,2
-
-       };
-       u16 tile_index = facedir * 16 + dir_i;
-       getNodeTileN(mn, p, dir_to_tile[tile_index], data, tile);
-       tile.rotation = tile.world_aligned ? 0 : dir_to_tile[tile_index + 1];
-}
-
-static void getTileInfo(
-               // Input:
-               MeshMakeData *data,
-               const v3s16 &p,
-               const v3s16 &face_dir,
-               // Output:
-               bool &makes_face,
-               v3s16 &p_corrected,
-               v3s16 &face_dir_corrected,
-               u16 *lights,
-               TileSpec &tile
-       )
-{
-       VoxelManipulator &vmanip = data->m_vmanip;
-       const NodeDefManager *ndef = data->m_client->ndef();
-       v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE;
-
-       const MapNode &n0 = vmanip.getNodeRefUnsafe(blockpos_nodes + p);
-
-       // Don't even try to get n1 if n0 is already CONTENT_IGNORE
-       if (n0.getContent() == CONTENT_IGNORE) {
-               makes_face = false;
-               return;
-       }
-
-       const MapNode &n1 = vmanip.getNodeRefUnsafeCheckFlags(blockpos_nodes + p + face_dir);
-
-       if (n1.getContent() == CONTENT_IGNORE) {
-               makes_face = false;
-               return;
-       }
-
-       // This is hackish
-       bool equivalent = false;
-       u8 mf = face_contents(n0.getContent(), n1.getContent(),
-                       &equivalent, ndef);
-
-       if (mf == 0) {
-               makes_face = false;
-               return;
-       }
-
-       makes_face = true;
-
-       MapNode n = n0;
-
-       if (mf == 1) {
-               p_corrected = p;
-               face_dir_corrected = face_dir;
-       } else {
-               n = n1;
-               p_corrected = p + face_dir;
-               face_dir_corrected = -face_dir;
-       }
-
-       getNodeTile(n, p_corrected, face_dir_corrected, data, tile);
-       const ContentFeatures &f = ndef->get(n);
-       tile.emissive_light = f.light_source;
-
-       // eg. water and glass
-       if (equivalent) {
-               for (TileLayer &layer : tile.layers)
-                       layer.material_flags |= MATERIAL_FLAG_BACKFACE_CULLING;
-       }
-
-       if (!data->m_smooth_lighting) {
-               lights[0] = lights[1] = lights[2] = lights[3] =
-                               getFaceLight(n0, n1, face_dir, ndef);
-       } else {
-               v3s16 vertex_dirs[4];
-               getNodeVertexDirs(face_dir_corrected, vertex_dirs);
-
-               v3s16 light_p = blockpos_nodes + p_corrected;
-               for (u16 i = 0; i < 4; i++)
-                       lights[i] = getSmoothLightSolid(light_p, face_dir_corrected, vertex_dirs[i], data);
-       }
-}
-
-/*
-       startpos:
-       translate_dir: unit vector with only one of x, y or z
-       face_dir: unit vector with only one of x, y or z
-*/
-static void updateFastFaceRow(
-               MeshMakeData *data,
-               const v3s16 &&startpos,
-               v3s16 translate_dir,
-               const v3f &&translate_dir_f,
-               const v3s16 &&face_dir,
-               std::vector<FastFace> &dest)
-{
-       v3s16 p = startpos;
-
-       u16 continuous_tiles_count = 1;
-
-       bool makes_face = false;
-       v3s16 p_corrected;
-       v3s16 face_dir_corrected;
-       u16 lights[4] = {0, 0, 0, 0};
-       TileSpec tile;
-       getTileInfo(data, p, face_dir,
-                       makes_face, p_corrected, face_dir_corrected,
-                       lights, tile);
-
-       // Unroll this variable which has a significant build cost
-       TileSpec next_tile;
-       for (u16 j = 0; j < MAP_BLOCKSIZE; j++) {
-               // If tiling can be done, this is set to false in the next step
-               bool next_is_different = true;
-
-               v3s16 p_next;
-
-               bool next_makes_face = false;
-               v3s16 next_p_corrected;
-               v3s16 next_face_dir_corrected;
-               u16 next_lights[4] = {0, 0, 0, 0};
-
-               // If at last position, there is nothing to compare to and
-               // the face must be drawn anyway
-               if (j != MAP_BLOCKSIZE - 1) {
-                       p_next = p + translate_dir;
-
-                       getTileInfo(data, p_next, face_dir,
-                                       next_makes_face, next_p_corrected,
-                                       next_face_dir_corrected, next_lights,
-                                       next_tile);
-
-                       if (next_makes_face == makes_face
-                                       && next_p_corrected == p_corrected + translate_dir
-                                       && next_face_dir_corrected == face_dir_corrected
-                                       && memcmp(next_lights, lights, ARRLEN(lights) * sizeof(u16)) == 0
-                                       && next_tile.isTileable(tile)) {
-                               next_is_different = false;
-                               continuous_tiles_count++;
-                       }
-               }
-               if (next_is_different) {
-                       /*
-                               Create a face if there should be one
-                       */
-                       if (makes_face) {
-                               // Floating point conversion of the position vector
-                               v3f pf(p_corrected.X, p_corrected.Y, p_corrected.Z);
-                               // Center point of face (kind of)
-                               v3f sp = pf - ((f32)continuous_tiles_count * 0.5f - 0.5f)
-                                       * translate_dir_f;
-                               v3f scale(1, 1, 1);
-
-                               if (translate_dir.X != 0)
-                                       scale.X = continuous_tiles_count;
-                               if (translate_dir.Y != 0)
-                                       scale.Y = continuous_tiles_count;
-                               if (translate_dir.Z != 0)
-                                       scale.Z = continuous_tiles_count;
-
-                               makeFastFace(tile, lights[0], lights[1], lights[2], lights[3],
-                                               pf, sp, face_dir_corrected, scale, dest);
-
-                               g_profiler->avg("Meshgen: faces drawn by tiling", 0);
-                               for (int i = 1; i < continuous_tiles_count; i++)
-                                       g_profiler->avg("Meshgen: faces drawn by tiling", 1);
-                       }
-
-                       continuous_tiles_count = 1;
-               }
-
-               makes_face = next_makes_face;
-               p_corrected = next_p_corrected;
-               face_dir_corrected = next_face_dir_corrected;
-               std::memcpy(lights, next_lights, ARRLEN(lights) * sizeof(u16));
-               if (next_is_different)
-                       tile = next_tile;
-               p = p_next;
-       }
-}
-
-static void updateAllFastFaceRows(MeshMakeData *data,
-               std::vector<FastFace> &dest)
-{
-       /*
-               Go through every y,z and get top(y+) faces in rows of x+
-       */
-       for (s16 y = 0; y < MAP_BLOCKSIZE; y++)
-       for (s16 z = 0; z < MAP_BLOCKSIZE; z++)
-               updateFastFaceRow(data,
-                               v3s16(0, y, z),
-                               v3s16(1, 0, 0), //dir
-                               v3f  (1, 0, 0),
-                               v3s16(0, 1, 0), //face dir
-                               dest);
-
-       /*
-               Go through every x,y and get right(x+) faces in rows of z+
-       */
-       for (s16 x = 0; x < MAP_BLOCKSIZE; x++)
-       for (s16 y = 0; y < MAP_BLOCKSIZE; y++)
-               updateFastFaceRow(data,
-                               v3s16(x, y, 0),
-                               v3s16(0, 0, 1), //dir
-                               v3f  (0, 0, 1),
-                               v3s16(1, 0, 0), //face dir
-                               dest);
-
-       /*
-               Go through every y,z and get back(z+) faces in rows of x+
-       */
-       for (s16 z = 0; z < MAP_BLOCKSIZE; z++)
-       for (s16 y = 0; y < MAP_BLOCKSIZE; y++)
-               updateFastFaceRow(data,
-                               v3s16(0, y, z),
-                               v3s16(1, 0, 0), //dir
-                               v3f  (1, 0, 0),
-                               v3s16(0, 0, 1), //face dir
-                               dest);
-}
-
-static void applyTileColor(PreMeshBuffer &pmb)
-{
-       video::SColor tc = pmb.layer.color;
-       if (tc == video::SColor(0xFFFFFFFF))
-               return;
-       for (video::S3DVertex &vertex : pmb.vertices) {
-               video::SColor *c = &vertex.Color;
-               c->set(c->getAlpha(),
-                       c->getRed() * tc.getRed() / 255,
-                       c->getGreen() * tc.getGreen() / 255,
-                       c->getBlue() * tc.getBlue() / 255);
-       }
-}
-
-/*
-       MapBlockMesh
-*/
-
-MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
-       m_minimap_mapblock(NULL),
-       m_tsrc(data->m_client->getTextureSource()),
-       m_shdrsrc(data->m_client->getShaderSource()),
-       m_animation_force_timer(0), // force initial animation
-       m_last_crack(-1),
-       m_last_daynight_ratio((u32) -1)
-{
-       for (auto &m : m_mesh)
-               m = new scene::SMesh();
-       m_enable_shaders = data->m_use_shaders;
-       m_use_tangent_vertices = data->m_use_tangent_vertices;
-       m_enable_vbo = g_settings->getBool("enable_vbo");
-
-       if (g_settings->getBool("enable_minimap")) {
-               m_minimap_mapblock = new MinimapMapblock;
-               m_minimap_mapblock->getMinimapNodes(
-                       &data->m_vmanip, data->m_blockpos * MAP_BLOCKSIZE);
-       }
-
-       // 4-21ms for MAP_BLOCKSIZE=16  (NOTE: probably outdated)
-       // 24-155ms for MAP_BLOCKSIZE=32  (NOTE: probably outdated)
-       //TimeTaker timer1("MapBlockMesh()");
-
-       std::vector<FastFace> fastfaces_new;
-       fastfaces_new.reserve(512);
-
-       /*
-               We are including the faces of the trailing edges of the block.
-               This means that when something changes, the caller must
-               also update the meshes of the blocks at the leading edges.
-
-               NOTE: This is the slowest part of this method.
-       */
-       {
-               // 4-23ms for MAP_BLOCKSIZE=16  (NOTE: probably outdated)
-               //TimeTaker timer2("updateAllFastFaceRows()");
-               updateAllFastFaceRows(data, fastfaces_new);
-       }
-       // End of slow part
-
-       /*
-               Convert FastFaces to MeshCollector
-       */
-
-       MeshCollector collector;
-
-       {
-               // avg 0ms (100ms spikes when loading textures the first time)
-               // (NOTE: probably outdated)
-               //TimeTaker timer2("MeshCollector building");
-
-               for (const FastFace &f : fastfaces_new) {
-                       static const u16 indices[] = {0, 1, 2, 2, 3, 0};
-                       static const u16 indices_alternate[] = {0, 1, 3, 2, 3, 1};
-                       const u16 *indices_p =
-                               f.vertex_0_2_connected ? indices : indices_alternate;
-                       collector.append(f.tile, f.vertices, 4, indices_p, 6);
-               }
-       }
-
-       /*
-               Add special graphics:
-               - torches
-               - flowing water
-               - fences
-               - whatever
-       */
-
-       {
-               MapblockMeshGenerator generator(data, &collector);
-               generator.generate();
-       }
-
-       /*
-               Convert MeshCollector to SMesh
-       */
-
-       for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
-               for(u32 i = 0; i < collector.prebuffers[layer].size(); i++)
-               {
-                       PreMeshBuffer &p = collector.prebuffers[layer][i];
-
-                       applyTileColor(p);
-
-                       // Generate animation data
-                       // - Cracks
-                       if (p.layer.material_flags & MATERIAL_FLAG_CRACK) {
-                               // Find the texture name plus ^[crack:N:
-                               std::ostringstream os(std::ios::binary);
-                               os << m_tsrc->getTextureName(p.layer.texture_id) << "^[crack";
-                               if (p.layer.material_flags & MATERIAL_FLAG_CRACK_OVERLAY)
-                                       os << "o";  // use ^[cracko
-                               u8 tiles = p.layer.scale;
-                               if (tiles > 1)
-                                       os << ":" << (u32)tiles;
-                               os << ":" << (u32)p.layer.animation_frame_count << ":";
-                               m_crack_materials.insert(std::make_pair(
-                                               std::pair<u8, u32>(layer, i), os.str()));
-                               // Replace tile texture with the cracked one
-                               p.layer.texture = m_tsrc->getTextureForMesh(
-                                               os.str() + "0",
-                                               &p.layer.texture_id);
-                       }
-                       // - Texture animation
-                       if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) {
-                               // Add to MapBlockMesh in order to animate these tiles
-                               m_animation_tiles[std::pair<u8, u32>(layer, i)] = p.layer;
-                               m_animation_frames[std::pair<u8, u32>(layer, i)] = 0;
-                               if (g_settings->getBool(
-                                               "desynchronize_mapblock_texture_animation")) {
-                                       // Get starting position from noise
-                                       m_animation_frame_offsets[std::pair<u8, u32>(layer, i)] =
-                                                       100000 * (2.0 + noise3d(
-                                                       data->m_blockpos.X, data->m_blockpos.Y,
-                                                       data->m_blockpos.Z, 0));
-                               } else {
-                                       // Play all synchronized
-                                       m_animation_frame_offsets[std::pair<u8, u32>(layer, i)] = 0;
-                               }
-                               // Replace tile texture with the first animation frame
-                               p.layer.texture = (*p.layer.frames)[0].texture;
-                       }
-
-                       if (!m_enable_shaders) {
-                               // Extract colors for day-night animation
-                               // Dummy sunlight to handle non-sunlit areas
-                               video::SColorf sunlight;
-                               get_sunlight_color(&sunlight, 0);
-                               u32 vertex_count = p.vertices.size();
-                               for (u32 j = 0; j < vertex_count; j++) {
-                                       video::SColor *vc = &p.vertices[j].Color;
-                                       video::SColor copy = *vc;
-                                       if (vc->getAlpha() == 0) // No sunlight - no need to animate
-                                               final_color_blend(vc, copy, sunlight); // Finalize color
-                                       else // Record color to animate
-                                               m_daynight_diffs[std::pair<u8, u32>(layer, i)][j] = copy;
-
-                                       // The sunlight ratio has been stored,
-                                       // delete alpha (for the final rendering).
-                                       vc->setAlpha(255);
-                               }
-                       }
-
-                       // Create material
-                       video::SMaterial material;
-                       material.setFlag(video::EMF_LIGHTING, false);
-                       material.setFlag(video::EMF_BACK_FACE_CULLING, true);
-                       material.setFlag(video::EMF_BILINEAR_FILTER, false);
-                       material.setFlag(video::EMF_FOG_ENABLE, true);
-                       material.setTexture(0, p.layer.texture);
-
-                       if (m_enable_shaders) {
-                               material.MaterialType = m_shdrsrc->getShaderInfo(
-                                               p.layer.shader_id).material;
-                               p.layer.applyMaterialOptionsWithShaders(material);
-                               if (p.layer.normal_texture)
-                                       material.setTexture(1, p.layer.normal_texture);
-                               material.setTexture(2, p.layer.flags_texture);
-                       } else {
-                               p.layer.applyMaterialOptions(material);
-                       }
-
-                       scene::SMesh *mesh = (scene::SMesh *)m_mesh[layer];
-
-                       // Create meshbuffer, add to mesh
-                       if (m_use_tangent_vertices) {
-                               scene::SMeshBufferTangents *buf =
-                                               new scene::SMeshBufferTangents();
-                               buf->Material = material;
-                               buf->Vertices.reallocate(p.vertices.size());
-                               buf->Indices.reallocate(p.indices.size());
-                               for (const video::S3DVertex &v: p.vertices)
-                                       buf->Vertices.push_back(video::S3DVertexTangents(v.Pos, v.Color, v.TCoords));
-                               for (u16 i: p.indices)
-                                       buf->Indices.push_back(i);
-                               buf->recalculateBoundingBox();
-                               mesh->addMeshBuffer(buf);
-                               buf->drop();
-                       } else {
-                               scene::SMeshBuffer *buf = new scene::SMeshBuffer();
-                               buf->Material = material;
-                               buf->append(&p.vertices[0], p.vertices.size(),
-                                       &p.indices[0], p.indices.size());
-                               mesh->addMeshBuffer(buf);
-                               buf->drop();
-                       }
-               }
-
-               /*
-                       Do some stuff to the mesh
-               */
-               m_camera_offset = camera_offset;
-               translateMesh(m_mesh[layer],
-                       intToFloat(data->m_blockpos * MAP_BLOCKSIZE - camera_offset, BS));
-
-               if (m_use_tangent_vertices) {
-                       scene::IMeshManipulator* meshmanip =
-                               RenderingEngine::get_scene_manager()->getMeshManipulator();
-                       meshmanip->recalculateTangents(m_mesh[layer], true, false, false);
-               }
-
-               if (m_mesh[layer]) {
-#if 0
-                       // Usually 1-700 faces and 1-7 materials
-                       std::cout << "Updated MapBlock has " << fastfaces_new.size()
-                                       << " faces and uses " << m_mesh[layer]->getMeshBufferCount()
-                                       << " materials (meshbuffers)" << std::endl;
-#endif
-
-                       // Use VBO for mesh (this just would set this for ever buffer)
-                       if (m_enable_vbo)
-                               m_mesh[layer]->setHardwareMappingHint(scene::EHM_STATIC);
-               }
-       }
-
-       //std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl;
-
-       // Check if animation is required for this mesh
-       m_has_animation =
-               !m_crack_materials.empty() ||
-               !m_daynight_diffs.empty() ||
-               !m_animation_tiles.empty();
-}
-
-MapBlockMesh::~MapBlockMesh()
-{
-       for (scene::IMesh *m : m_mesh) {
-               if (m_enable_vbo && m)
-                       for (u32 i = 0; i < m->getMeshBufferCount(); i++) {
-                               scene::IMeshBuffer *buf = m->getMeshBuffer(i);
-                               RenderingEngine::get_video_driver()->removeHardwareBuffer(buf);
-                       }
-               m->drop();
-               m = NULL;
-       }
-       delete m_minimap_mapblock;
-}
-
-bool MapBlockMesh::animate(bool faraway, float time, int crack,
-       u32 daynight_ratio)
-{
-       if (!m_has_animation) {
-               m_animation_force_timer = 100000;
-               return false;
-       }
-
-       m_animation_force_timer = myrand_range(5, 100);
-
-       // Cracks
-       if (crack != m_last_crack) {
-               for (auto &crack_material : m_crack_materials) {
-                       scene::IMeshBuffer *buf = m_mesh[crack_material.first.first]->
-                               getMeshBuffer(crack_material.first.second);
-                       std::string basename = crack_material.second;
-
-                       // Create new texture name from original
-                       std::ostringstream os;
-                       os << basename << crack;
-                       u32 new_texture_id = 0;
-                       video::ITexture *new_texture =
-                                       m_tsrc->getTextureForMesh(os.str(), &new_texture_id);
-                       buf->getMaterial().setTexture(0, new_texture);
-
-                       // If the current material is also animated,
-                       // update animation info
-                       auto anim_iter = m_animation_tiles.find(crack_material.first);
-                       if (anim_iter != m_animation_tiles.end()) {
-                               TileLayer &tile = anim_iter->second;
-                               tile.texture = new_texture;
-                               tile.texture_id = new_texture_id;
-                               // force animation update
-                               m_animation_frames[crack_material.first] = -1;
-                       }
-               }
-
-               m_last_crack = crack;
-       }
-
-       // Texture animation
-       for (auto &animation_tile : m_animation_tiles) {
-               const TileLayer &tile = animation_tile.second;
-               // Figure out current frame
-               int frameoffset = m_animation_frame_offsets[animation_tile.first];
-               int frame = (int)(time * 1000 / tile.animation_frame_length_ms
-                               + frameoffset) % tile.animation_frame_count;
-               // If frame doesn't change, skip
-               if (frame == m_animation_frames[animation_tile.first])
-                       continue;
-
-               m_animation_frames[animation_tile.first] = frame;
-
-               scene::IMeshBuffer *buf = m_mesh[animation_tile.first.first]->
-                       getMeshBuffer(animation_tile.first.second);
-
-               const FrameSpec &animation_frame = (*tile.frames)[frame];
-               buf->getMaterial().setTexture(0, animation_frame.texture);
-               if (m_enable_shaders) {
-                       if (animation_frame.normal_texture)
-                               buf->getMaterial().setTexture(1,
-                                       animation_frame.normal_texture);
-                       buf->getMaterial().setTexture(2, animation_frame.flags_texture);
-               }
-       }
-
-       // Day-night transition
-       if (!m_enable_shaders && (daynight_ratio != m_last_daynight_ratio)) {
-               // Force reload mesh to VBO
-               if (m_enable_vbo)
-                       for (scene::IMesh *m : m_mesh)
-                               m->setDirty();
-               video::SColorf day_color;
-               get_sunlight_color(&day_color, daynight_ratio);
-
-               for (auto &daynight_diff : m_daynight_diffs) {
-                       scene::IMeshBuffer *buf = m_mesh[daynight_diff.first.first]->
-                               getMeshBuffer(daynight_diff.first.second);
-                       video::S3DVertex *vertices = (video::S3DVertex *)buf->getVertices();
-                       for (const auto &j : daynight_diff.second)
-                               final_color_blend(&(vertices[j.first].Color), j.second,
-                                               day_color);
-               }
-               m_last_daynight_ratio = daynight_ratio;
-       }
-
-       return true;
-}
-
-void MapBlockMesh::updateCameraOffset(v3s16 camera_offset)
-{
-       if (camera_offset != m_camera_offset) {
-               for (scene::IMesh *layer : m_mesh) {
-                       translateMesh(layer,
-                               intToFloat(m_camera_offset - camera_offset, BS));
-                       if (m_enable_vbo)
-                               layer->setDirty();
-               }
-               m_camera_offset = camera_offset;
-       }
-}
-
-video::SColor encode_light(u16 light, u8 emissive_light)
-{
-       // Get components
-       u32 day = (light & 0xff);
-       u32 night = (light >> 8);
-       // Add emissive light
-       night += emissive_light * 2.5f;
-       if (night > 255)
-               night = 255;
-       // Since we don't know if the day light is sunlight or
-       // artificial light, assume it is artificial when the night
-       // light bank is also lit.
-       if (day < night)
-               day = 0;
-       else
-               day = day - night;
-       u32 sum = day + night;
-       // Ratio of sunlight:
-       u32 r;
-       if (sum > 0)
-               r = day * 255 / sum;
-       else
-               r = 0;
-       // Average light:
-       float b = (day + night) / 2;
-       return video::SColor(r, b, b, b);
-}
diff --git a/src/mapblock_mesh.h b/src/mapblock_mesh.h
deleted file mode 100644 (file)
index 6af23a6..0000000
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include "client/tile.h"
-#include "voxel.h"
-#include <array>
-#include <map>
-
-class Client;
-class IShaderSource;
-
-/*
-       Mesh making stuff
-*/
-
-
-class MapBlock;
-struct MinimapMapblock;
-
-struct MeshMakeData
-{
-       VoxelManipulator m_vmanip;
-       v3s16 m_blockpos = v3s16(-1337,-1337,-1337);
-       v3s16 m_crack_pos_relative = v3s16(-1337,-1337,-1337);
-       bool m_smooth_lighting = false;
-
-       Client *m_client;
-       bool m_use_shaders;
-       bool m_use_tangent_vertices;
-
-       MeshMakeData(Client *client, bool use_shaders,
-                       bool use_tangent_vertices = false);
-
-       /*
-               Copy block data manually (to allow optimizations by the caller)
-       */
-       void fillBlockDataBegin(const v3s16 &blockpos);
-       void fillBlockData(const v3s16 &block_offset, MapNode *data);
-
-       /*
-               Copy central data directly from block, and other data from
-               parent of block.
-       */
-       void fill(MapBlock *block);
-
-       /*
-               Set up with only a single node at (1,1,1)
-       */
-       void fillSingleNode(MapNode *node);
-
-       /*
-               Set the (node) position of a crack
-       */
-       void setCrack(int crack_level, v3s16 crack_pos);
-
-       /*
-               Enable or disable smooth lighting
-       */
-       void setSmoothLighting(bool smooth_lighting);
-};
-
-/*
-       Holds a mesh for a mapblock.
-
-       Besides the SMesh*, this contains information used for animating
-       the vertex positions, colors and texture coordinates of the mesh.
-       For example:
-       - cracks [implemented]
-       - day/night transitions [implemented]
-       - animated flowing liquids [not implemented]
-       - animating vertex positions for e.g. axles [not implemented]
-*/
-class MapBlockMesh
-{
-public:
-       // Builds the mesh given
-       MapBlockMesh(MeshMakeData *data, v3s16 camera_offset);
-       ~MapBlockMesh();
-
-       // Main animation function, parameters:
-       //   faraway: whether the block is far away from the camera (~50 nodes)
-       //   time: the global animation time, 0 .. 60 (repeats every minute)
-       //   daynight_ratio: 0 .. 1000
-       //   crack: -1 .. CRACK_ANIMATION_LENGTH-1 (-1 for off)
-       // Returns true if anything has been changed.
-       bool animate(bool faraway, float time, int crack, u32 daynight_ratio);
-
-       scene::IMesh *getMesh()
-       {
-               return m_mesh[0];
-       }
-
-       scene::IMesh *getMesh(u8 layer)
-       {
-               return m_mesh[layer];
-       }
-
-       MinimapMapblock *moveMinimapMapblock()
-       {
-               MinimapMapblock *p = m_minimap_mapblock;
-               m_minimap_mapblock = NULL;
-               return p;
-       }
-
-       bool isAnimationForced() const
-       {
-               return m_animation_force_timer == 0;
-       }
-
-       void decreaseAnimationForceTimer()
-       {
-               if(m_animation_force_timer > 0)
-                       m_animation_force_timer--;
-       }
-
-       void updateCameraOffset(v3s16 camera_offset);
-
-private:
-       scene::IMesh *m_mesh[MAX_TILE_LAYERS];
-       MinimapMapblock *m_minimap_mapblock;
-       ITextureSource *m_tsrc;
-       IShaderSource *m_shdrsrc;
-
-       bool m_enable_shaders;
-       bool m_use_tangent_vertices;
-       bool m_enable_vbo;
-
-       // Must animate() be called before rendering?
-       bool m_has_animation;
-       int m_animation_force_timer;
-
-       // Animation info: cracks
-       // Last crack value passed to animate()
-       int m_last_crack;
-       // Maps mesh and mesh buffer (i.e. material) indices to base texture names
-       std::map<std::pair<u8, u32>, std::string> m_crack_materials;
-
-       // Animation info: texture animationi
-       // Maps mesh and mesh buffer indices to TileSpecs
-       // Keys are pairs of (mesh index, buffer index in the mesh)
-       std::map<std::pair<u8, u32>, TileLayer> m_animation_tiles;
-       std::map<std::pair<u8, u32>, int> m_animation_frames; // last animation frame
-       std::map<std::pair<u8, u32>, int> m_animation_frame_offsets;
-
-       // Animation info: day/night transitions
-       // Last daynight_ratio value passed to animate()
-       u32 m_last_daynight_ratio;
-       // For each mesh and mesh buffer, stores pre-baked colors
-       // of sunlit vertices
-       // Keys are pairs of (mesh index, buffer index in the mesh)
-       std::map<std::pair<u8, u32>, std::map<u32, video::SColor > > m_daynight_diffs;
-
-       // Camera offset info -> do we have to translate the mesh?
-       v3s16 m_camera_offset;
-};
-
-/*!
- * Encodes light of a node.
- * The result is not the final color, but a
- * half-baked vertex color.
- * You have to multiply the resulting color
- * with the node's color.
- *
- * \param light the first 8 bits are day light,
- * the last 8 bits are night light
- * \param emissive_light amount of light the surface emits,
- * from 0 to LIGHT_SUN.
- */
-video::SColor encode_light(u16 light, u8 emissive_light);
-
-// Compute light at node
-u16 getInteriorLight(MapNode n, s32 increment, const NodeDefManager *ndef);
-u16 getFaceLight(MapNode n, MapNode n2, const v3s16 &face_dir,
-       const NodeDefManager *ndef);
-u16 getSmoothLightSolid(const v3s16 &p, const v3s16 &face_dir, const v3s16 &corner, MeshMakeData *data);
-u16 getSmoothLightTransparent(const v3s16 &p, const v3s16 &corner, MeshMakeData *data);
-
-/*!
- * Returns the sunlight's color from the current
- * day-night ratio.
- */
-void get_sunlight_color(video::SColorf *sunlight, u32 daynight_ratio);
-
-/*!
- * Gives the final  SColor shown on screen.
- *
- * \param result output color
- * \param light first 8 bits are day light, second 8 bits are
- * night light
- */
-void final_color_blend(video::SColor *result,
-               u16 light, u32 daynight_ratio);
-
-/*!
- * Gives the final  SColor shown on screen.
- *
- * \param result output color
- * \param data the half-baked vertex color
- * \param dayLight color of the sunlight
- */
-void final_color_blend(video::SColor *result,
-               const video::SColor &data, const video::SColorf &dayLight);
-
-// Retrieves the TileSpec of a face of a node
-// Adds MATERIAL_FLAG_CRACK if the node is cracked
-// TileSpec should be passed as reference due to the underlying TileFrame and its vector
-// TileFrame vector copy cost very much to client
-void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, TileSpec &tile);
-void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *data, TileSpec &tile);
diff --git a/src/mesh.cpp b/src/mesh.cpp
deleted file mode 100644 (file)
index 7fc7531..0000000
+++ /dev/null
@@ -1,1135 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 "mesh.h"
-#include "debug.h"
-#include "log.h"
-#include "irrMap.h"
-#include <iostream>
-#include <IAnimatedMesh.h>
-#include <SAnimatedMesh.h>
-#include <IAnimatedMeshSceneNode.h>
-
-// In Irrlicht 1.8 the signature of ITexture::lock was changed from
-// (bool, u32) to (E_TEXTURE_LOCK_MODE, u32).
-#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 7
-#define MY_ETLM_READ_ONLY true
-#else
-#define MY_ETLM_READ_ONLY video::ETLM_READ_ONLY
-#endif
-
-inline static void applyShadeFactor(video::SColor& color, float factor)
-{
-       color.setRed(core::clamp(core::round32(color.getRed()*factor), 0, 255));
-       color.setGreen(core::clamp(core::round32(color.getGreen()*factor), 0, 255));
-       color.setBlue(core::clamp(core::round32(color.getBlue()*factor), 0, 255));
-}
-
-void applyFacesShading(video::SColor &color, const v3f &normal)
-{
-       /*
-               Some drawtypes have normals set to (0, 0, 0), this must result in
-               maximum brightness: shade factor 1.0.
-               Shade factors for aligned cube faces are:
-               +Y 1.000000 sqrt(1.0)
-               -Y 0.447213 sqrt(0.2)
-               +-X 0.670820 sqrt(0.45)
-               +-Z 0.836660 sqrt(0.7)
-       */
-       float x2 = normal.X * normal.X;
-       float y2 = normal.Y * normal.Y;
-       float z2 = normal.Z * normal.Z;
-       if (normal.Y < 0)
-               applyShadeFactor(color, 0.670820f * x2 + 0.447213f * y2 + 0.836660f * z2);
-       else if ((x2 > 1e-3) || (z2 > 1e-3))
-               applyShadeFactor(color, 0.670820f * x2 + 1.000000f * y2 + 0.836660f * z2);
-}
-
-scene::IAnimatedMesh* createCubeMesh(v3f scale)
-{
-       video::SColor c(255,255,255,255);
-       video::S3DVertex vertices[24] =
-       {
-               // Up
-               video::S3DVertex(-0.5,+0.5,-0.5, 0,1,0, c, 0,1),
-               video::S3DVertex(-0.5,+0.5,+0.5, 0,1,0, c, 0,0),
-               video::S3DVertex(+0.5,+0.5,+0.5, 0,1,0, c, 1,0),
-               video::S3DVertex(+0.5,+0.5,-0.5, 0,1,0, c, 1,1),
-               // Down
-               video::S3DVertex(-0.5,-0.5,-0.5, 0,-1,0, c, 0,0),
-               video::S3DVertex(+0.5,-0.5,-0.5, 0,-1,0, c, 1,0),
-               video::S3DVertex(+0.5,-0.5,+0.5, 0,-1,0, c, 1,1),
-               video::S3DVertex(-0.5,-0.5,+0.5, 0,-1,0, c, 0,1),
-               // Right
-               video::S3DVertex(+0.5,-0.5,-0.5, 1,0,0, c, 0,1),
-               video::S3DVertex(+0.5,+0.5,-0.5, 1,0,0, c, 0,0),
-               video::S3DVertex(+0.5,+0.5,+0.5, 1,0,0, c, 1,0),
-               video::S3DVertex(+0.5,-0.5,+0.5, 1,0,0, c, 1,1),
-               // Left
-               video::S3DVertex(-0.5,-0.5,-0.5, -1,0,0, c, 1,1),
-               video::S3DVertex(-0.5,-0.5,+0.5, -1,0,0, c, 0,1),
-               video::S3DVertex(-0.5,+0.5,+0.5, -1,0,0, c, 0,0),
-               video::S3DVertex(-0.5,+0.5,-0.5, -1,0,0, c, 1,0),
-               // Back
-               video::S3DVertex(-0.5,-0.5,+0.5, 0,0,1, c, 1,1),
-               video::S3DVertex(+0.5,-0.5,+0.5, 0,0,1, c, 0,1),
-               video::S3DVertex(+0.5,+0.5,+0.5, 0,0,1, c, 0,0),
-               video::S3DVertex(-0.5,+0.5,+0.5, 0,0,1, c, 1,0),
-               // Front
-               video::S3DVertex(-0.5,-0.5,-0.5, 0,0,-1, c, 0,1),
-               video::S3DVertex(-0.5,+0.5,-0.5, 0,0,-1, c, 0,0),
-               video::S3DVertex(+0.5,+0.5,-0.5, 0,0,-1, c, 1,0),
-               video::S3DVertex(+0.5,-0.5,-0.5, 0,0,-1, c, 1,1),
-       };
-
-       u16 indices[6] = {0,1,2,2,3,0};
-
-       scene::SMesh *mesh = new scene::SMesh();
-       for (u32 i=0; i<6; ++i)
-       {
-               scene::IMeshBuffer *buf = new scene::SMeshBuffer();
-               buf->append(vertices + 4 * i, 4, indices, 6);
-               // Set default material
-               buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
-               buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
-               buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
-               // Add mesh buffer to mesh
-               mesh->addMeshBuffer(buf);
-               buf->drop();
-       }
-
-       scene::SAnimatedMesh *anim_mesh = new scene::SAnimatedMesh(mesh);
-       mesh->drop();
-       scaleMesh(anim_mesh, scale);  // also recalculates bounding box
-       return anim_mesh;
-}
-
-void scaleMesh(scene::IMesh *mesh, v3f scale)
-{
-       if (mesh == NULL)
-               return;
-
-       aabb3f bbox;
-       bbox.reset(0, 0, 0);
-
-       u32 mc = mesh->getMeshBufferCount();
-       for (u32 j = 0; j < mc; j++) {
-               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
-               const u32 stride = getVertexPitchFromType(buf->getVertexType());
-               u32 vertex_count = buf->getVertexCount();
-               u8 *vertices = (u8 *)buf->getVertices();
-               for (u32 i = 0; i < vertex_count; i++)
-                       ((video::S3DVertex *)(vertices + i * stride))->Pos *= scale;
-
-               buf->recalculateBoundingBox();
-
-               // calculate total bounding box
-               if (j == 0)
-                       bbox = buf->getBoundingBox();
-               else
-                       bbox.addInternalBox(buf->getBoundingBox());
-       }
-       mesh->setBoundingBox(bbox);
-}
-
-void translateMesh(scene::IMesh *mesh, v3f vec)
-{
-       if (mesh == NULL)
-               return;
-
-       aabb3f bbox;
-       bbox.reset(0, 0, 0);
-
-       u32 mc = mesh->getMeshBufferCount();
-       for (u32 j = 0; j < mc; j++) {
-               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
-               const u32 stride = getVertexPitchFromType(buf->getVertexType());
-               u32 vertex_count = buf->getVertexCount();
-               u8 *vertices = (u8 *)buf->getVertices();
-               for (u32 i = 0; i < vertex_count; i++)
-                       ((video::S3DVertex *)(vertices + i * stride))->Pos += vec;
-
-               buf->recalculateBoundingBox();
-
-               // calculate total bounding box
-               if (j == 0)
-                       bbox = buf->getBoundingBox();
-               else
-                       bbox.addInternalBox(buf->getBoundingBox());
-       }
-       mesh->setBoundingBox(bbox);
-}
-
-void setMeshBufferColor(scene::IMeshBuffer *buf, const video::SColor &color)
-{
-       const u32 stride = getVertexPitchFromType(buf->getVertexType());
-       u32 vertex_count = buf->getVertexCount();
-       u8 *vertices = (u8 *) buf->getVertices();
-       for (u32 i = 0; i < vertex_count; i++)
-               ((video::S3DVertex *) (vertices + i * stride))->Color = color;
-}
-
-void setAnimatedMeshColor(scene::IAnimatedMeshSceneNode *node, const video::SColor &color)
-{
-       for (u32 i = 0; i < node->getMaterialCount(); ++i) {
-               node->getMaterial(i).EmissiveColor = color;
-       }
-}
-
-void setMeshColor(scene::IMesh *mesh, const video::SColor &color)
-{
-       if (mesh == NULL)
-               return;
-
-       u32 mc = mesh->getMeshBufferCount();
-       for (u32 j = 0; j < mc; j++)
-               setMeshBufferColor(mesh->getMeshBuffer(j), color);
-}
-
-void colorizeMeshBuffer(scene::IMeshBuffer *buf, const video::SColor *buffercolor)
-{
-       const u32 stride = getVertexPitchFromType(buf->getVertexType());
-       u32 vertex_count = buf->getVertexCount();
-       u8 *vertices = (u8 *) buf->getVertices();
-       for (u32 i = 0; i < vertex_count; i++) {
-               video::S3DVertex *vertex = (video::S3DVertex *) (vertices + i * stride);
-               video::SColor *vc = &(vertex->Color);
-               // Reset color
-               *vc = *buffercolor;
-               // Apply shading
-               applyFacesShading(*vc, vertex->Normal);
-       }
-}
-
-void setMeshColorByNormalXYZ(scene::IMesh *mesh,
-               const video::SColor &colorX,
-               const video::SColor &colorY,
-               const video::SColor &colorZ)
-{
-       if (mesh == NULL)
-               return;
-
-       u16 mc = mesh->getMeshBufferCount();
-       for (u16 j = 0; j < mc; j++) {
-               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
-               const u32 stride = getVertexPitchFromType(buf->getVertexType());
-               u32 vertex_count = buf->getVertexCount();
-               u8 *vertices = (u8 *)buf->getVertices();
-               for (u32 i = 0; i < vertex_count; i++) {
-                       video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride);
-                       f32 x = fabs(vertex->Normal.X);
-                       f32 y = fabs(vertex->Normal.Y);
-                       f32 z = fabs(vertex->Normal.Z);
-                       if (x >= y && x >= z)
-                               vertex->Color = colorX;
-                       else if (y >= z)
-                               vertex->Color = colorY;
-                       else
-                               vertex->Color = colorZ;
-               }
-       }
-}
-
-void setMeshColorByNormal(scene::IMesh *mesh, const v3f &normal,
-               const video::SColor &color)
-{
-       if (!mesh)
-               return;
-
-       u16 mc = mesh->getMeshBufferCount();
-       for (u16 j = 0; j < mc; j++) {
-               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
-               const u32 stride = getVertexPitchFromType(buf->getVertexType());
-               u32 vertex_count = buf->getVertexCount();
-               u8 *vertices = (u8 *)buf->getVertices();
-               for (u32 i = 0; i < vertex_count; i++) {
-                       video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride);
-                       if (normal == vertex->Normal) {
-                               vertex->Color = color;
-                       }
-               }
-       }
-}
-
-void rotateMeshXYby(scene::IMesh *mesh, f64 degrees)
-{
-       u16 mc = mesh->getMeshBufferCount();
-       for (u16 j = 0; j < mc; j++) {
-               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
-               const u32 stride = getVertexPitchFromType(buf->getVertexType());
-               u32 vertex_count = buf->getVertexCount();
-               u8 *vertices = (u8 *)buf->getVertices();
-               for (u32 i = 0; i < vertex_count; i++)
-                       ((video::S3DVertex *)(vertices + i * stride))->Pos.rotateXYBy(degrees);
-       }
-}
-
-void rotateMeshXZby(scene::IMesh *mesh, f64 degrees)
-{
-       u16 mc = mesh->getMeshBufferCount();
-       for (u16 j = 0; j < mc; j++) {
-               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
-               const u32 stride = getVertexPitchFromType(buf->getVertexType());
-               u32 vertex_count = buf->getVertexCount();
-               u8 *vertices = (u8 *)buf->getVertices();
-               for (u32 i = 0; i < vertex_count; i++)
-                       ((video::S3DVertex *)(vertices + i * stride))->Pos.rotateXZBy(degrees);
-       }
-}
-
-void rotateMeshYZby(scene::IMesh *mesh, f64 degrees)
-{
-       u16 mc = mesh->getMeshBufferCount();
-       for (u16 j = 0; j < mc; j++) {
-               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
-               const u32 stride = getVertexPitchFromType(buf->getVertexType());
-               u32 vertex_count = buf->getVertexCount();
-               u8 *vertices = (u8 *)buf->getVertices();
-               for (u32 i = 0; i < vertex_count; i++)
-                       ((video::S3DVertex *)(vertices + i * stride))->Pos.rotateYZBy(degrees);
-       }
-}
-
-void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir)
-{
-       int axisdir = facedir >> 2;
-       facedir &= 0x03;
-
-       u16 mc = mesh->getMeshBufferCount();
-       for (u16 j = 0; j < mc; j++) {
-               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
-               const u32 stride = getVertexPitchFromType(buf->getVertexType());
-               u32 vertex_count = buf->getVertexCount();
-               u8 *vertices = (u8 *)buf->getVertices();
-               for (u32 i = 0; i < vertex_count; i++) {
-                       video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride);
-                       switch (axisdir) {
-                               case 0:
-                                       if (facedir == 1)
-                                               vertex->Pos.rotateXZBy(-90);
-                                       else if (facedir == 2)
-                                               vertex->Pos.rotateXZBy(180);
-                                       else if (facedir == 3)
-                                               vertex->Pos.rotateXZBy(90);
-                                       break;
-                               case 1: // z+
-                                       vertex->Pos.rotateYZBy(90);
-                                       if (facedir == 1)
-                                               vertex->Pos.rotateXYBy(90);
-                                       else if (facedir == 2)
-                                               vertex->Pos.rotateXYBy(180);
-                                       else if (facedir == 3)
-                                               vertex->Pos.rotateXYBy(-90);
-                                       break;
-                               case 2: //z-
-                                       vertex->Pos.rotateYZBy(-90);
-                                       if (facedir == 1)
-                                               vertex->Pos.rotateXYBy(-90);
-                                       else if (facedir == 2)
-                                               vertex->Pos.rotateXYBy(180);
-                                       else if (facedir == 3)
-                                               vertex->Pos.rotateXYBy(90);
-                                       break;
-                               case 3:  //x+
-                                       vertex->Pos.rotateXYBy(-90);
-                                       if (facedir == 1)
-                                               vertex->Pos.rotateYZBy(90);
-                                       else if (facedir == 2)
-                                               vertex->Pos.rotateYZBy(180);
-                                       else if (facedir == 3)
-                                               vertex->Pos.rotateYZBy(-90);
-                                       break;
-                               case 4:  //x-
-                                       vertex->Pos.rotateXYBy(90);
-                                       if (facedir == 1)
-                                               vertex->Pos.rotateYZBy(-90);
-                                       else if (facedir == 2)
-                                               vertex->Pos.rotateYZBy(180);
-                                       else if (facedir == 3)
-                                               vertex->Pos.rotateYZBy(90);
-                                       break;
-                               case 5:
-                                       vertex->Pos.rotateXYBy(-180);
-                                       if (facedir == 1)
-                                               vertex->Pos.rotateXZBy(90);
-                                       else if (facedir == 2)
-                                               vertex->Pos.rotateXZBy(180);
-                                       else if (facedir == 3)
-                                               vertex->Pos.rotateXZBy(-90);
-                                       break;
-                               default:
-                                       break;
-                       }
-               }
-       }
-}
-
-void recalculateBoundingBox(scene::IMesh *src_mesh)
-{
-       aabb3f bbox;
-       bbox.reset(0,0,0);
-       for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) {
-               scene::IMeshBuffer *buf = src_mesh->getMeshBuffer(j);
-               buf->recalculateBoundingBox();
-               if (j == 0)
-                       bbox = buf->getBoundingBox();
-               else
-                       bbox.addInternalBox(buf->getBoundingBox());
-       }
-       src_mesh->setBoundingBox(bbox);
-}
-
-scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer)
-{
-       switch (mesh_buffer->getVertexType()) {
-       case video::EVT_STANDARD: {
-               video::S3DVertex *v = (video::S3DVertex *) mesh_buffer->getVertices();
-               u16 *indices = mesh_buffer->getIndices();
-               scene::SMeshBuffer *cloned_buffer = new scene::SMeshBuffer();
-               cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
-                       mesh_buffer->getIndexCount());
-               return cloned_buffer;
-       }
-       case video::EVT_2TCOORDS: {
-               video::S3DVertex2TCoords *v =
-                       (video::S3DVertex2TCoords *) mesh_buffer->getVertices();
-               u16 *indices = mesh_buffer->getIndices();
-               scene::SMeshBufferTangents *cloned_buffer =
-                       new scene::SMeshBufferTangents();
-               cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
-                       mesh_buffer->getIndexCount());
-               return cloned_buffer;
-       }
-       case video::EVT_TANGENTS: {
-               video::S3DVertexTangents *v =
-                       (video::S3DVertexTangents *) mesh_buffer->getVertices();
-               u16 *indices = mesh_buffer->getIndices();
-               scene::SMeshBufferTangents *cloned_buffer =
-                       new scene::SMeshBufferTangents();
-               cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
-                       mesh_buffer->getIndexCount());
-               return cloned_buffer;
-       }
-       }
-       // This should not happen.
-       sanity_check(false);
-       return NULL;
-}
-
-scene::SMesh* cloneMesh(scene::IMesh *src_mesh)
-{
-       scene::SMesh* dst_mesh = new scene::SMesh();
-       for (u16 j = 0; j < src_mesh->getMeshBufferCount(); j++) {
-               scene::IMeshBuffer *temp_buf = cloneMeshBuffer(
-                       src_mesh->getMeshBuffer(j));
-               dst_mesh->addMeshBuffer(temp_buf);
-               temp_buf->drop();
-
-       }
-       return dst_mesh;
-}
-
-scene::IMesh* convertNodeboxesToMesh(const std::vector<aabb3f> &boxes,
-               const f32 *uv_coords, float expand)
-{
-       scene::SMesh* dst_mesh = new scene::SMesh();
-
-       for (u16 j = 0; j < 6; j++)
-       {
-               scene::IMeshBuffer *buf = new scene::SMeshBuffer();
-               buf->getMaterial().setFlag(video::EMF_LIGHTING, false);
-               buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false);
-               dst_mesh->addMeshBuffer(buf);
-               buf->drop();
-       }
-
-       video::SColor c(255,255,255,255);
-
-       for (aabb3f box : boxes) {
-               box.repair();
-
-               box.MinEdge.X -= expand;
-               box.MinEdge.Y -= expand;
-               box.MinEdge.Z -= expand;
-               box.MaxEdge.X += expand;
-               box.MaxEdge.Y += expand;
-               box.MaxEdge.Z += expand;
-
-               // Compute texture UV coords
-               f32 tx1 = (box.MinEdge.X / BS) + 0.5;
-               f32 ty1 = (box.MinEdge.Y / BS) + 0.5;
-               f32 tz1 = (box.MinEdge.Z / BS) + 0.5;
-               f32 tx2 = (box.MaxEdge.X / BS) + 0.5;
-               f32 ty2 = (box.MaxEdge.Y / BS) + 0.5;
-               f32 tz2 = (box.MaxEdge.Z / BS) + 0.5;
-
-               f32 txc_default[24] = {
-                       // up
-                       tx1, 1 - tz2, tx2, 1 - tz1,
-                       // down
-                       tx1, tz1, tx2, tz2,
-                       // right
-                       tz1, 1 - ty2, tz2, 1 - ty1,
-                       // left
-                       1 - tz2, 1 - ty2, 1 - tz1, 1 - ty1,
-                       // back
-                       1 - tx2, 1 - ty2, 1 - tx1, 1 - ty1,
-                       // front
-                       tx1, 1 - ty2, tx2, 1 - ty1,
-               };
-
-               // use default texture UV mapping if not provided
-               const f32 *txc = uv_coords ? uv_coords : txc_default;
-
-               v3f min = box.MinEdge;
-               v3f max = box.MaxEdge;
-
-               video::S3DVertex vertices[24] =
-               {
-                       // up
-                       video::S3DVertex(min.X,max.Y,max.Z, 0,1,0, c, txc[0],txc[1]),
-                       video::S3DVertex(max.X,max.Y,max.Z, 0,1,0, c, txc[2],txc[1]),
-                       video::S3DVertex(max.X,max.Y,min.Z, 0,1,0, c, txc[2],txc[3]),
-                       video::S3DVertex(min.X,max.Y,min.Z, 0,1,0, c, txc[0],txc[3]),
-                       // down
-                       video::S3DVertex(min.X,min.Y,min.Z, 0,-1,0, c, txc[4],txc[5]),
-                       video::S3DVertex(max.X,min.Y,min.Z, 0,-1,0, c, txc[6],txc[5]),
-                       video::S3DVertex(max.X,min.Y,max.Z, 0,-1,0, c, txc[6],txc[7]),
-                       video::S3DVertex(min.X,min.Y,max.Z, 0,-1,0, c, txc[4],txc[7]),
-                       // right
-                       video::S3DVertex(max.X,max.Y,min.Z, 1,0,0, c, txc[ 8],txc[9]),
-                       video::S3DVertex(max.X,max.Y,max.Z, 1,0,0, c, txc[10],txc[9]),
-                       video::S3DVertex(max.X,min.Y,max.Z, 1,0,0, c, txc[10],txc[11]),
-                       video::S3DVertex(max.X,min.Y,min.Z, 1,0,0, c, txc[ 8],txc[11]),
-                       // left
-                       video::S3DVertex(min.X,max.Y,max.Z, -1,0,0, c, txc[12],txc[13]),
-                       video::S3DVertex(min.X,max.Y,min.Z, -1,0,0, c, txc[14],txc[13]),
-                       video::S3DVertex(min.X,min.Y,min.Z, -1,0,0, c, txc[14],txc[15]),
-                       video::S3DVertex(min.X,min.Y,max.Z, -1,0,0, c, txc[12],txc[15]),
-                       // back
-                       video::S3DVertex(max.X,max.Y,max.Z, 0,0,1, c, txc[16],txc[17]),
-                       video::S3DVertex(min.X,max.Y,max.Z, 0,0,1, c, txc[18],txc[17]),
-                       video::S3DVertex(min.X,min.Y,max.Z, 0,0,1, c, txc[18],txc[19]),
-                       video::S3DVertex(max.X,min.Y,max.Z, 0,0,1, c, txc[16],txc[19]),
-                       // front
-                       video::S3DVertex(min.X,max.Y,min.Z, 0,0,-1, c, txc[20],txc[21]),
-                       video::S3DVertex(max.X,max.Y,min.Z, 0,0,-1, c, txc[22],txc[21]),
-                       video::S3DVertex(max.X,min.Y,min.Z, 0,0,-1, c, txc[22],txc[23]),
-                       video::S3DVertex(min.X,min.Y,min.Z, 0,0,-1, c, txc[20],txc[23]),
-               };
-
-               u16 indices[] = {0,1,2,2,3,0};
-
-               for(u16 j = 0; j < 24; j += 4)
-               {
-                       scene::IMeshBuffer *buf = dst_mesh->getMeshBuffer(j / 4);
-                       buf->append(vertices + j, 4, indices, 6);
-               }
-       }
-       return dst_mesh;
-}
-
-struct vcache
-{
-       core::array<u32> tris;
-       float score;
-       s16 cachepos;
-       u16 NumActiveTris;
-};
-
-struct tcache
-{
-       u16 ind[3];
-       float score;
-       bool drawn;
-};
-
-const u16 cachesize = 32;
-
-float FindVertexScore(vcache *v)
-{
-       const float CacheDecayPower = 1.5f;
-       const float LastTriScore = 0.75f;
-       const float ValenceBoostScale = 2.0f;
-       const float ValenceBoostPower = 0.5f;
-       const float MaxSizeVertexCache = 32.0f;
-
-       if (v->NumActiveTris == 0)
-       {
-               // No tri needs this vertex!
-               return -1.0f;
-       }
-
-       float Score = 0.0f;
-       int CachePosition = v->cachepos;
-       if (CachePosition < 0)
-       {
-               // Vertex is not in FIFO cache - no score.
-       }
-       else
-       {
-               if (CachePosition < 3)
-               {
-                       // This vertex was used in the last triangle,
-                       // so it has a fixed score.
-                       Score = LastTriScore;
-               }
-               else
-               {
-                       // Points for being high in the cache.
-                       const float Scaler = 1.0f / (MaxSizeVertexCache - 3);
-                       Score = 1.0f - (CachePosition - 3) * Scaler;
-                       Score = powf(Score, CacheDecayPower);
-               }
-       }
-
-       // Bonus points for having a low number of tris still to
-       // use the vert, so we get rid of lone verts quickly.
-       float ValenceBoost = powf(v->NumActiveTris,
-                               -ValenceBoostPower);
-       Score += ValenceBoostScale * ValenceBoost;
-
-       return Score;
-}
-
-/*
-       A specialized LRU cache for the Forsyth algorithm.
-*/
-
-class f_lru
-{
-
-public:
-       f_lru(vcache *v, tcache *t): vc(v), tc(t)
-       {
-               for (int &i : cache) {
-                       i = -1;
-               }
-       }
-
-       // Adds this vertex index and returns the highest-scoring triangle index
-       u32 add(u16 vert, bool updatetris = false)
-       {
-               bool found = false;
-
-               // Mark existing pos as empty
-               for (u16 i = 0; i < cachesize; i++)
-               {
-                       if (cache[i] == vert)
-                       {
-                               // Move everything down
-                               for (u16 j = i; j; j--)
-                               {
-                                       cache[j] = cache[j - 1];
-                               }
-
-                               found = true;
-                               break;
-                       }
-               }
-
-               if (!found)
-               {
-                       if (cache[cachesize-1] != -1)
-                               vc[cache[cachesize-1]].cachepos = -1;
-
-                       // Move everything down
-                       for (u16 i = cachesize - 1; i; i--)
-                       {
-                               cache[i] = cache[i - 1];
-                       }
-               }
-
-               cache[0] = vert;
-
-               u32 highest = 0;
-               float hiscore = 0;
-
-               if (updatetris)
-               {
-                       // Update cache positions
-                       for (u16 i = 0; i < cachesize; i++)
-                       {
-                               if (cache[i] == -1)
-                                       break;
-
-                               vc[cache[i]].cachepos = i;
-                               vc[cache[i]].score = FindVertexScore(&vc[cache[i]]);
-                       }
-
-                       // Update triangle scores
-                       for (int i : cache) {
-                               if (i == -1)
-                                       break;
-
-                               const u16 trisize = vc[i].tris.size();
-                               for (u16 t = 0; t < trisize; t++)
-                               {
-                                       tcache *tri = &tc[vc[i].tris[t]];
-
-                                       tri->score =
-                                               vc[tri->ind[0]].score +
-                                               vc[tri->ind[1]].score +
-                                               vc[tri->ind[2]].score;
-
-                                       if (tri->score > hiscore)
-                                       {
-                                               hiscore = tri->score;
-                                               highest = vc[i].tris[t];
-                                       }
-                               }
-                       }
-               }
-
-               return highest;
-       }
-
-private:
-       s32 cache[cachesize];
-       vcache *vc;
-       tcache *tc;
-};
-
-/**
-Vertex cache optimization according to the Forsyth paper:
-http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html
-
-The function is thread-safe (read: you can optimize several meshes in different threads)
-
-\param mesh Source mesh for the operation.  */
-scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh)
-{
-       if (!mesh)
-               return 0;
-
-       scene::SMesh *newmesh = new scene::SMesh();
-       newmesh->BoundingBox = mesh->getBoundingBox();
-
-       const u32 mbcount = mesh->getMeshBufferCount();
-
-       for (u32 b = 0; b < mbcount; ++b)
-       {
-               const scene::IMeshBuffer *mb = mesh->getMeshBuffer(b);
-
-               if (mb->getIndexType() != video::EIT_16BIT)
-               {
-                       //os::Printer::log("Cannot optimize a mesh with 32bit indices", ELL_ERROR);
-                       newmesh->drop();
-                       return 0;
-               }
-
-               const u32 icount = mb->getIndexCount();
-               const u32 tcount = icount / 3;
-               const u32 vcount = mb->getVertexCount();
-               const u16 *ind = mb->getIndices();
-
-               vcache *vc = new vcache[vcount];
-               tcache *tc = new tcache[tcount];
-
-               f_lru lru(vc, tc);
-
-               // init
-               for (u16 i = 0; i < vcount; i++)
-               {
-                       vc[i].score = 0;
-                       vc[i].cachepos = -1;
-                       vc[i].NumActiveTris = 0;
-               }
-
-               // First pass: count how many times a vert is used
-               for (u32 i = 0; i < icount; i += 3)
-               {
-                       vc[ind[i]].NumActiveTris++;
-                       vc[ind[i + 1]].NumActiveTris++;
-                       vc[ind[i + 2]].NumActiveTris++;
-
-                       const u32 tri_ind = i/3;
-                       tc[tri_ind].ind[0] = ind[i];
-                       tc[tri_ind].ind[1] = ind[i + 1];
-                       tc[tri_ind].ind[2] = ind[i + 2];
-               }
-
-               // Second pass: list of each triangle
-               for (u32 i = 0; i < tcount; i++)
-               {
-                       vc[tc[i].ind[0]].tris.push_back(i);
-                       vc[tc[i].ind[1]].tris.push_back(i);
-                       vc[tc[i].ind[2]].tris.push_back(i);
-
-                       tc[i].drawn = false;
-               }
-
-               // Give initial scores
-               for (u16 i = 0; i < vcount; i++)
-               {
-                       vc[i].score = FindVertexScore(&vc[i]);
-               }
-               for (u32 i = 0; i < tcount; i++)
-               {
-                       tc[i].score =
-                                       vc[tc[i].ind[0]].score +
-                                       vc[tc[i].ind[1]].score +
-                                       vc[tc[i].ind[2]].score;
-               }
-
-               switch(mb->getVertexType())
-               {
-                       case video::EVT_STANDARD:
-                       {
-                               video::S3DVertex *v = (video::S3DVertex *) mb->getVertices();
-
-                               scene::SMeshBuffer *buf = new scene::SMeshBuffer();
-                               buf->Material = mb->getMaterial();
-
-                               buf->Vertices.reallocate(vcount);
-                               buf->Indices.reallocate(icount);
-
-                               core::map<const video::S3DVertex, const u16> sind; // search index for fast operation
-                               typedef core::map<const video::S3DVertex, const u16>::Node snode;
-
-                               // Main algorithm
-                               u32 highest = 0;
-                               u32 drawcalls = 0;
-                               for (;;)
-                               {
-                                       if (tc[highest].drawn)
-                                       {
-                                               bool found = false;
-                                               float hiscore = 0;
-                                               for (u32 t = 0; t < tcount; t++)
-                                               {
-                                                       if (!tc[t].drawn)
-                                                       {
-                                                               if (tc[t].score > hiscore)
-                                                               {
-                                                                       highest = t;
-                                                                       hiscore = tc[t].score;
-                                                                       found = true;
-                                                               }
-                                                       }
-                                               }
-                                               if (!found)
-                                                       break;
-                                       }
-
-                                       // Output the best triangle
-                                       u16 newind = buf->Vertices.size();
-
-                                       snode *s = sind.find(v[tc[highest].ind[0]]);
-
-                                       if (!s)
-                                       {
-                                               buf->Vertices.push_back(v[tc[highest].ind[0]]);
-                                               buf->Indices.push_back(newind);
-                                               sind.insert(v[tc[highest].ind[0]], newind);
-                                               newind++;
-                                       }
-                                       else
-                                       {
-                                               buf->Indices.push_back(s->getValue());
-                                       }
-
-                                       s = sind.find(v[tc[highest].ind[1]]);
-
-                                       if (!s)
-                                       {
-                                               buf->Vertices.push_back(v[tc[highest].ind[1]]);
-                                               buf->Indices.push_back(newind);
-                                               sind.insert(v[tc[highest].ind[1]], newind);
-                                               newind++;
-                                       }
-                                       else
-                                       {
-                                               buf->Indices.push_back(s->getValue());
-                                       }
-
-                                       s = sind.find(v[tc[highest].ind[2]]);
-
-                                       if (!s)
-                                       {
-                                               buf->Vertices.push_back(v[tc[highest].ind[2]]);
-                                               buf->Indices.push_back(newind);
-                                               sind.insert(v[tc[highest].ind[2]], newind);
-                                       }
-                                       else
-                                       {
-                                               buf->Indices.push_back(s->getValue());
-                                       }
-
-                                       vc[tc[highest].ind[0]].NumActiveTris--;
-                                       vc[tc[highest].ind[1]].NumActiveTris--;
-                                       vc[tc[highest].ind[2]].NumActiveTris--;
-
-                                       tc[highest].drawn = true;
-
-                                       for (u16 j : tc[highest].ind) {
-                                               vcache *vert = &vc[j];
-                                               for (u16 t = 0; t < vert->tris.size(); t++)
-                                               {
-                                                       if (highest == vert->tris[t])
-                                                       {
-                                                               vert->tris.erase(t);
-                                                               break;
-                                                       }
-                                               }
-                                       }
-
-                                       lru.add(tc[highest].ind[0]);
-                                       lru.add(tc[highest].ind[1]);
-                                       highest = lru.add(tc[highest].ind[2], true);
-                                       drawcalls++;
-                               }
-
-                               buf->setBoundingBox(mb->getBoundingBox());
-                               newmesh->addMeshBuffer(buf);
-                               buf->drop();
-                       }
-                       break;
-                       case video::EVT_2TCOORDS:
-                       {
-                               video::S3DVertex2TCoords *v = (video::S3DVertex2TCoords *) mb->getVertices();
-
-                               scene::SMeshBufferLightMap *buf = new scene::SMeshBufferLightMap();
-                               buf->Material = mb->getMaterial();
-
-                               buf->Vertices.reallocate(vcount);
-                               buf->Indices.reallocate(icount);
-
-                               core::map<const video::S3DVertex2TCoords, const u16> sind; // search index for fast operation
-                               typedef core::map<const video::S3DVertex2TCoords, const u16>::Node snode;
-
-                               // Main algorithm
-                               u32 highest = 0;
-                               u32 drawcalls = 0;
-                               for (;;)
-                               {
-                                       if (tc[highest].drawn)
-                                       {
-                                               bool found = false;
-                                               float hiscore = 0;
-                                               for (u32 t = 0; t < tcount; t++)
-                                               {
-                                                       if (!tc[t].drawn)
-                                                       {
-                                                               if (tc[t].score > hiscore)
-                                                               {
-                                                                       highest = t;
-                                                                       hiscore = tc[t].score;
-                                                                       found = true;
-                                                               }
-                                                       }
-                                               }
-                                               if (!found)
-                                                       break;
-                                       }
-
-                                       // Output the best triangle
-                                       u16 newind = buf->Vertices.size();
-
-                                       snode *s = sind.find(v[tc[highest].ind[0]]);
-
-                                       if (!s)
-                                       {
-                                               buf->Vertices.push_back(v[tc[highest].ind[0]]);
-                                               buf->Indices.push_back(newind);
-                                               sind.insert(v[tc[highest].ind[0]], newind);
-                                               newind++;
-                                       }
-                                       else
-                                       {
-                                               buf->Indices.push_back(s->getValue());
-                                       }
-
-                                       s = sind.find(v[tc[highest].ind[1]]);
-
-                                       if (!s)
-                                       {
-                                               buf->Vertices.push_back(v[tc[highest].ind[1]]);
-                                               buf->Indices.push_back(newind);
-                                               sind.insert(v[tc[highest].ind[1]], newind);
-                                               newind++;
-                                       }
-                                       else
-                                       {
-                                               buf->Indices.push_back(s->getValue());
-                                       }
-
-                                       s = sind.find(v[tc[highest].ind[2]]);
-
-                                       if (!s)
-                                       {
-                                               buf->Vertices.push_back(v[tc[highest].ind[2]]);
-                                               buf->Indices.push_back(newind);
-                                               sind.insert(v[tc[highest].ind[2]], newind);
-                                       }
-                                       else
-                                       {
-                                               buf->Indices.push_back(s->getValue());
-                                       }
-
-                                       vc[tc[highest].ind[0]].NumActiveTris--;
-                                       vc[tc[highest].ind[1]].NumActiveTris--;
-                                       vc[tc[highest].ind[2]].NumActiveTris--;
-
-                                       tc[highest].drawn = true;
-
-                                       for (u16 j : tc[highest].ind) {
-                                               vcache *vert = &vc[j];
-                                               for (u16 t = 0; t < vert->tris.size(); t++)
-                                               {
-                                                       if (highest == vert->tris[t])
-                                                       {
-                                                               vert->tris.erase(t);
-                                                               break;
-                                                       }
-                                               }
-                                       }
-
-                                       lru.add(tc[highest].ind[0]);
-                                       lru.add(tc[highest].ind[1]);
-                                       highest = lru.add(tc[highest].ind[2]);
-                                       drawcalls++;
-                               }
-
-                               buf->setBoundingBox(mb->getBoundingBox());
-                               newmesh->addMeshBuffer(buf);
-                               buf->drop();
-
-                       }
-                       break;
-                       case video::EVT_TANGENTS:
-                       {
-                               video::S3DVertexTangents *v = (video::S3DVertexTangents *) mb->getVertices();
-
-                               scene::SMeshBufferTangents *buf = new scene::SMeshBufferTangents();
-                               buf->Material = mb->getMaterial();
-
-                               buf->Vertices.reallocate(vcount);
-                               buf->Indices.reallocate(icount);
-
-                               core::map<const video::S3DVertexTangents, const u16> sind; // search index for fast operation
-                               typedef core::map<const video::S3DVertexTangents, const u16>::Node snode;
-
-                               // Main algorithm
-                               u32 highest = 0;
-                               u32 drawcalls = 0;
-                               for (;;)
-                               {
-                                       if (tc[highest].drawn)
-                                       {
-                                               bool found = false;
-                                               float hiscore = 0;
-                                               for (u32 t = 0; t < tcount; t++)
-                                               {
-                                                       if (!tc[t].drawn)
-                                                       {
-                                                               if (tc[t].score > hiscore)
-                                                               {
-                                                                       highest = t;
-                                                                       hiscore = tc[t].score;
-                                                                       found = true;
-                                                               }
-                                                       }
-                                               }
-                                               if (!found)
-                                                       break;
-                                       }
-
-                                       // Output the best triangle
-                                       u16 newind = buf->Vertices.size();
-
-                                       snode *s = sind.find(v[tc[highest].ind[0]]);
-
-                                       if (!s)
-                                       {
-                                               buf->Vertices.push_back(v[tc[highest].ind[0]]);
-                                               buf->Indices.push_back(newind);
-                                               sind.insert(v[tc[highest].ind[0]], newind);
-                                               newind++;
-                                       }
-                                       else
-                                       {
-                                               buf->Indices.push_back(s->getValue());
-                                       }
-
-                                       s = sind.find(v[tc[highest].ind[1]]);
-
-                                       if (!s)
-                                       {
-                                               buf->Vertices.push_back(v[tc[highest].ind[1]]);
-                                               buf->Indices.push_back(newind);
-                                               sind.insert(v[tc[highest].ind[1]], newind);
-                                               newind++;
-                                       }
-                                       else
-                                       {
-                                               buf->Indices.push_back(s->getValue());
-                                       }
-
-                                       s = sind.find(v[tc[highest].ind[2]]);
-
-                                       if (!s)
-                                       {
-                                               buf->Vertices.push_back(v[tc[highest].ind[2]]);
-                                               buf->Indices.push_back(newind);
-                                               sind.insert(v[tc[highest].ind[2]], newind);
-                                       }
-                                       else
-                                       {
-                                               buf->Indices.push_back(s->getValue());
-                                       }
-
-                                       vc[tc[highest].ind[0]].NumActiveTris--;
-                                       vc[tc[highest].ind[1]].NumActiveTris--;
-                                       vc[tc[highest].ind[2]].NumActiveTris--;
-
-                                       tc[highest].drawn = true;
-
-                                       for (u16 j : tc[highest].ind) {
-                                               vcache *vert = &vc[j];
-                                               for (u16 t = 0; t < vert->tris.size(); t++)
-                                               {
-                                                       if (highest == vert->tris[t])
-                                                       {
-                                                               vert->tris.erase(t);
-                                                               break;
-                                                       }
-                                               }
-                                       }
-
-                                       lru.add(tc[highest].ind[0]);
-                                       lru.add(tc[highest].ind[1]);
-                                       highest = lru.add(tc[highest].ind[2]);
-                                       drawcalls++;
-                               }
-
-                               buf->setBoundingBox(mb->getBoundingBox());
-                               newmesh->addMeshBuffer(buf);
-                               buf->drop();
-                       }
-                       break;
-               }
-
-               delete [] vc;
-               delete [] tc;
-
-       } // for each meshbuffer
-
-       return newmesh;
-}
diff --git a/src/mesh.h b/src/mesh.h
deleted file mode 100644 (file)
index 0c4094d..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include "nodedef.h"
-
-/*!
- * Applies shading to a color based on the surface's
- * normal vector.
- */
-void applyFacesShading(video::SColor &color, const v3f &normal);
-
-/*
-       Create a new cube mesh.
-       Vertices are at (+-scale.X/2, +-scale.Y/2, +-scale.Z/2).
-
-       The resulting mesh has 6 materials (up, down, right, left, back, front)
-       which must be defined by the caller.
-*/
-scene::IAnimatedMesh* createCubeMesh(v3f scale);
-
-/*
-       Multiplies each vertex coordinate by the specified scaling factors
-       (componentwise vector multiplication).
-*/
-void scaleMesh(scene::IMesh *mesh, v3f scale);
-
-/*
-       Translate each vertex coordinate by the specified vector.
-*/
-void translateMesh(scene::IMesh *mesh, v3f vec);
-
-/*!
- * Sets a constant color for all vertices in the mesh buffer.
- */
-void setMeshBufferColor(scene::IMeshBuffer *buf, const video::SColor &color);
-
-/*
-       Set a constant color for all vertices in the mesh
-*/
-void setMeshColor(scene::IMesh *mesh, const video::SColor &color);
-
-/*
-       Set a constant color for an animated mesh
-*/
-void setAnimatedMeshColor(scene::IAnimatedMeshSceneNode *node, const video::SColor &color);
-
-/*!
- * Overwrites the color of a mesh buffer.
- * The color is darkened based on the normal vector of the vertices.
- */
-void colorizeMeshBuffer(scene::IMeshBuffer *buf, const video::SColor *buffercolor);
-
-/*
-       Set the color of all vertices in the mesh.
-       For each vertex, determine the largest absolute entry in
-       the normal vector, and choose one of colorX, colorY or
-       colorZ accordingly.
-*/
-void setMeshColorByNormalXYZ(scene::IMesh *mesh,
-               const video::SColor &colorX,
-               const video::SColor &colorY,
-               const video::SColor &colorZ);
-
-void setMeshColorByNormal(scene::IMesh *mesh, const v3f &normal,
-               const video::SColor &color);
-
-/*
-       Rotate the mesh by 6d facedir value.
-       Method only for meshnodes, not suitable for entities.
-*/
-void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir);
-
-/*
-       Rotate the mesh around the axis and given angle in degrees.
-*/
-void rotateMeshXYby (scene::IMesh *mesh, f64 degrees);
-void rotateMeshXZby (scene::IMesh *mesh, f64 degrees);
-void rotateMeshYZby (scene::IMesh *mesh, f64 degrees);
-
-/*
- *  Clone the mesh buffer.
- *  The returned pointer should be dropped.
- */
-scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer);
-
-/*
-       Clone the mesh.
-*/
-scene::SMesh* cloneMesh(scene::IMesh *src_mesh);
-
-/*
-       Convert nodeboxes to mesh. Each tile goes into a different buffer.
-       boxes - set of nodeboxes to be converted into cuboids
-       uv_coords[24] - table of texture uv coords for each cuboid face
-       expand - factor by which cuboids will be resized
-*/
-scene::IMesh* convertNodeboxesToMesh(const std::vector<aabb3f> &boxes,
-               const f32 *uv_coords = NULL, float expand = 0);
-
-/*
-       Update bounding box for a mesh.
-*/
-void recalculateBoundingBox(scene::IMesh *src_mesh);
-
-/*
-       Vertex cache optimization according to the Forsyth paper:
-       http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html
-       Ported from irrlicht 1.8
-*/
-scene::IMesh* createForsythOptimizedMesh(const scene::IMesh *mesh);
diff --git a/src/mesh_generator_thread.cpp b/src/mesh_generator_thread.cpp
deleted file mode 100644 (file)
index be4bcc1..0000000
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013, 2017 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 "mesh_generator_thread.h"
-#include "settings.h"
-#include "profiler.h"
-#include "client.h"
-#include "mapblock.h"
-#include "map.h"
-
-/*
-       CachedMapBlockData
-*/
-
-CachedMapBlockData::~CachedMapBlockData()
-{
-       assert(refcount_from_queue == 0);
-
-       delete[] data;
-}
-
-/*
-       QueuedMeshUpdate
-*/
-
-QueuedMeshUpdate::~QueuedMeshUpdate()
-{
-       delete data;
-}
-
-/*
-       MeshUpdateQueue
-*/
-
-MeshUpdateQueue::MeshUpdateQueue(Client *client):
-       m_client(client)
-{
-       m_cache_enable_shaders = g_settings->getBool("enable_shaders");
-       m_cache_use_tangent_vertices = m_cache_enable_shaders && (
-               g_settings->getBool("enable_bumpmapping") ||
-               g_settings->getBool("enable_parallax_occlusion"));
-       m_cache_smooth_lighting = g_settings->getBool("smooth_lighting");
-       m_meshgen_block_cache_size = g_settings->getS32("meshgen_block_cache_size");
-}
-
-MeshUpdateQueue::~MeshUpdateQueue()
-{
-       MutexAutoLock lock(m_mutex);
-
-       for (auto &i : m_cache) {
-               delete i.second;
-       }
-
-       for (QueuedMeshUpdate *q : m_queue) {
-               delete q;
-       }
-}
-
-void MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent)
-{
-       MutexAutoLock lock(m_mutex);
-
-       cleanupCache();
-
-       /*
-               Cache the block data (force-update the center block, don't update the
-               neighbors but get them if they aren't already cached)
-       */
-       std::vector<CachedMapBlockData*> cached_blocks;
-       size_t cache_hit_counter = 0;
-       cached_blocks.reserve(3*3*3);
-       v3s16 dp;
-       for (dp.X = -1; dp.X <= 1; dp.X++)
-       for (dp.Y = -1; dp.Y <= 1; dp.Y++)
-       for (dp.Z = -1; dp.Z <= 1; dp.Z++) {
-               v3s16 p1 = p + dp;
-               CachedMapBlockData *cached_block;
-               if (dp == v3s16(0, 0, 0))
-                       cached_block = cacheBlock(map, p1, FORCE_UPDATE);
-               else
-                       cached_block = cacheBlock(map, p1, SKIP_UPDATE_IF_ALREADY_CACHED,
-                                       &cache_hit_counter);
-               cached_blocks.push_back(cached_block);
-       }
-       g_profiler->avg("MeshUpdateQueue MapBlock cache hit %",
-                       100.0f * cache_hit_counter / cached_blocks.size());
-
-       /*
-               Mark the block as urgent if requested
-       */
-       if (urgent)
-               m_urgents.insert(p);
-
-       /*
-               Find if block is already in queue.
-               If it is, update the data and quit.
-       */
-       for (QueuedMeshUpdate *q : m_queue) {
-               if (q->p == p) {
-                       // NOTE: We are not adding a new position to the queue, thus
-                       //       refcount_from_queue stays the same.
-                       if(ack_block_to_server)
-                               q->ack_block_to_server = true;
-                       q->crack_level = m_client->getCrackLevel();
-                       q->crack_pos = m_client->getCrackPos();
-                       return;
-               }
-       }
-
-       /*
-               Add the block
-       */
-       QueuedMeshUpdate *q = new QueuedMeshUpdate;
-       q->p = p;
-       q->ack_block_to_server = ack_block_to_server;
-       q->crack_level = m_client->getCrackLevel();
-       q->crack_pos = m_client->getCrackPos();
-       m_queue.push_back(q);
-
-       // This queue entry is a new reference to the cached blocks
-       for (CachedMapBlockData *cached_block : cached_blocks) {
-               cached_block->refcount_from_queue++;
-       }
-}
-
-// Returned pointer must be deleted
-// Returns NULL if queue is empty
-QueuedMeshUpdate *MeshUpdateQueue::pop()
-{
-       MutexAutoLock lock(m_mutex);
-
-       bool must_be_urgent = !m_urgents.empty();
-       for (std::vector<QueuedMeshUpdate*>::iterator i = m_queue.begin();
-                       i != m_queue.end(); ++i) {
-               QueuedMeshUpdate *q = *i;
-               if(must_be_urgent && m_urgents.count(q->p) == 0)
-                       continue;
-               m_queue.erase(i);
-               m_urgents.erase(q->p);
-               fillDataFromMapBlockCache(q);
-               return q;
-       }
-       return NULL;
-}
-
-CachedMapBlockData* MeshUpdateQueue::cacheBlock(Map *map, v3s16 p, UpdateMode mode,
-                       size_t *cache_hit_counter)
-{
-       std::map<v3s16, CachedMapBlockData*>::iterator it =
-                       m_cache.find(p);
-       if (it != m_cache.end()) {
-               // Already in cache
-               CachedMapBlockData *cached_block = it->second;
-               if (mode == SKIP_UPDATE_IF_ALREADY_CACHED) {
-                       if (cache_hit_counter)
-                               (*cache_hit_counter)++;
-                       return cached_block;
-               }
-               MapBlock *b = map->getBlockNoCreateNoEx(p);
-               if (b) {
-                       if (cached_block->data == NULL)
-                               cached_block->data =
-                                               new MapNode[MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE];
-                       memcpy(cached_block->data, b->getData(),
-                                       MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE * sizeof(MapNode));
-               } else {
-                       delete[] cached_block->data;
-                       cached_block->data = NULL;
-               }
-               return cached_block;
-       }
-
-       // Not yet in cache
-       CachedMapBlockData *cached_block = new CachedMapBlockData();
-       m_cache[p] = cached_block;
-       MapBlock *b = map->getBlockNoCreateNoEx(p);
-       if (b) {
-               cached_block->data =
-                               new MapNode[MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE];
-               memcpy(cached_block->data, b->getData(),
-                               MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE * sizeof(MapNode));
-       }
-       return cached_block;
-}
-
-CachedMapBlockData* MeshUpdateQueue::getCachedBlock(const v3s16 &p)
-{
-       std::map<v3s16, CachedMapBlockData*>::iterator it = m_cache.find(p);
-       if (it != m_cache.end()) {
-               return it->second;
-       }
-       return NULL;
-}
-
-void MeshUpdateQueue::fillDataFromMapBlockCache(QueuedMeshUpdate *q)
-{
-       MeshMakeData *data = new MeshMakeData(m_client, m_cache_enable_shaders,
-                       m_cache_use_tangent_vertices);
-       q->data = data;
-
-       data->fillBlockDataBegin(q->p);
-
-       std::time_t t_now = std::time(0);
-
-       // Collect data for 3*3*3 blocks from cache
-       v3s16 dp;
-       for (dp.X = -1; dp.X <= 1; dp.X++)
-       for (dp.Y = -1; dp.Y <= 1; dp.Y++)
-       for (dp.Z = -1; dp.Z <= 1; dp.Z++) {
-               v3s16 p = q->p + dp;
-               CachedMapBlockData *cached_block = getCachedBlock(p);
-               if (cached_block) {
-                       cached_block->refcount_from_queue--;
-                       cached_block->last_used_timestamp = t_now;
-                       if (cached_block->data)
-                               data->fillBlockData(dp, cached_block->data);
-               }
-       }
-
-       data->setCrack(q->crack_level, q->crack_pos);
-       data->setSmoothLighting(m_cache_smooth_lighting);
-}
-
-void MeshUpdateQueue::cleanupCache()
-{
-       const int mapblock_kB = MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE *
-                       sizeof(MapNode) / 1000;
-       g_profiler->avg("MeshUpdateQueue MapBlock cache size kB",
-                       mapblock_kB * m_cache.size());
-
-       // The cache size is kept roughly below cache_soft_max_size, not letting
-       // anything get older than cache_seconds_max or deleted before 2 seconds.
-       const int cache_seconds_max = 10;
-       const int cache_soft_max_size = m_meshgen_block_cache_size * 1000 / mapblock_kB;
-       int cache_seconds = MYMAX(2, cache_seconds_max -
-                       m_cache.size() / (cache_soft_max_size / cache_seconds_max));
-
-       int t_now = time(0);
-
-       for (std::map<v3s16, CachedMapBlockData*>::iterator it = m_cache.begin();
-                       it != m_cache.end(); ) {
-               CachedMapBlockData *cached_block = it->second;
-               if (cached_block->refcount_from_queue == 0 &&
-                               cached_block->last_used_timestamp < t_now - cache_seconds) {
-                       m_cache.erase(it++);
-                       delete cached_block;
-               } else {
-                       ++it;
-               }
-       }
-}
-
-/*
-       MeshUpdateThread
-*/
-
-MeshUpdateThread::MeshUpdateThread(Client *client):
-       UpdateThread("Mesh"),
-       m_queue_in(client)
-{
-       m_generation_interval = g_settings->getU16("mesh_generation_interval");
-       m_generation_interval = rangelim(m_generation_interval, 0, 50);
-}
-
-void MeshUpdateThread::updateBlock(Map *map, v3s16 p, bool ack_block_to_server,
-               bool urgent)
-{
-       // Allow the MeshUpdateQueue to do whatever it wants
-       m_queue_in.addBlock(map, p, ack_block_to_server, urgent);
-       deferUpdate();
-}
-
-void MeshUpdateThread::doUpdate()
-{
-       QueuedMeshUpdate *q;
-       while ((q = m_queue_in.pop())) {
-               if (m_generation_interval)
-                       sleep_ms(m_generation_interval);
-               ScopeProfiler sp(g_profiler, "Client: Mesh making");
-
-               MapBlockMesh *mesh_new = new MapBlockMesh(q->data, m_camera_offset);
-
-               MeshUpdateResult r;
-               r.p = q->p;
-               r.mesh = mesh_new;
-               r.ack_block_to_server = q->ack_block_to_server;
-
-               m_queue_out.push_back(r);
-
-               delete q;
-       }
-}
diff --git a/src/mesh_generator_thread.h b/src/mesh_generator_thread.h
deleted file mode 100644 (file)
index 9a42852..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013, 2017 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include <ctime>
-#include <mutex>
-#include "mapblock_mesh.h"
-#include "threading/mutex_auto_lock.h"
-#include "util/thread.h"
-
-struct CachedMapBlockData
-{
-       v3s16 p = v3s16(-1337, -1337, -1337);
-       MapNode *data = nullptr; // A copy of the MapBlock's data member
-       int refcount_from_queue = 0;
-       std::time_t last_used_timestamp = std::time(0);
-
-       CachedMapBlockData() = default;
-       ~CachedMapBlockData();
-};
-
-struct QueuedMeshUpdate
-{
-       v3s16 p = v3s16(-1337, -1337, -1337);
-       bool ack_block_to_server = false;
-       bool urgent = false;
-       int crack_level = -1;
-       v3s16 crack_pos;
-       MeshMakeData *data = nullptr; // This is generated in MeshUpdateQueue::pop()
-
-       QueuedMeshUpdate() = default;
-       ~QueuedMeshUpdate();
-};
-
-/*
-       A thread-safe queue of mesh update tasks and a cache of MapBlock data
-*/
-class MeshUpdateQueue
-{
-       enum UpdateMode
-       {
-               FORCE_UPDATE,
-               SKIP_UPDATE_IF_ALREADY_CACHED,
-       };
-
-public:
-       MeshUpdateQueue(Client *client);
-
-       ~MeshUpdateQueue();
-
-       // Caches the block at p and its neighbors (if needed) and queues a mesh
-       // update for the block at p
-       void addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent);
-
-       // Returned pointer must be deleted
-       // Returns NULL if queue is empty
-       QueuedMeshUpdate *pop();
-
-       u32 size()
-       {
-               MutexAutoLock lock(m_mutex);
-               return m_queue.size();
-       }
-
-private:
-       Client *m_client;
-       std::vector<QueuedMeshUpdate *> m_queue;
-       std::set<v3s16> m_urgents;
-       std::map<v3s16, CachedMapBlockData *> m_cache;
-       std::mutex m_mutex;
-
-       // TODO: Add callback to update these when g_settings changes
-       bool m_cache_enable_shaders;
-       bool m_cache_use_tangent_vertices;
-       bool m_cache_smooth_lighting;
-       int m_meshgen_block_cache_size;
-
-       CachedMapBlockData *cacheBlock(Map *map, v3s16 p, UpdateMode mode,
-                       size_t *cache_hit_counter = NULL);
-       CachedMapBlockData *getCachedBlock(const v3s16 &p);
-       void fillDataFromMapBlockCache(QueuedMeshUpdate *q);
-       void cleanupCache();
-};
-
-struct MeshUpdateResult
-{
-       v3s16 p = v3s16(-1338, -1338, -1338);
-       MapBlockMesh *mesh = nullptr;
-       bool ack_block_to_server = false;
-
-       MeshUpdateResult() = default;
-};
-
-class MeshUpdateThread : public UpdateThread
-{
-public:
-       MeshUpdateThread(Client *client);
-
-       // Caches the block at p and its neighbors (if needed) and queues a mesh
-       // update for the block at p
-       void updateBlock(Map *map, v3s16 p, bool ack_block_to_server, bool urgent);
-
-       v3s16 m_camera_offset;
-       MutexedQueue<MeshUpdateResult> m_queue_out;
-
-private:
-       MeshUpdateQueue m_queue_in;
-
-       // TODO: Add callback to update these when g_settings changes
-       int m_generation_interval;
-
-protected:
-       virtual void doUpdate();
-};
diff --git a/src/minimap.cpp b/src/minimap.cpp
deleted file mode 100644 (file)
index 4d83c08..0000000
+++ /dev/null
@@ -1,624 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 "minimap.h"
-#include <cmath>
-#include "client.h"
-#include "clientmap.h"
-#include "settings.h"
-#include "shader.h"
-#include "mapblock.h"
-#include "client/renderingengine.h"
-
-
-////
-//// MinimapUpdateThread
-////
-
-MinimapUpdateThread::~MinimapUpdateThread()
-{
-       for (auto &it : m_blocks_cache) {
-               delete it.second;
-       }
-
-       for (auto &q : m_update_queue) {
-               delete q.data;
-       }
-}
-
-bool MinimapUpdateThread::pushBlockUpdate(v3s16 pos, MinimapMapblock *data)
-{
-       MutexAutoLock lock(m_queue_mutex);
-
-       // Find if block is already in queue.
-       // If it is, update the data and quit.
-       for (QueuedMinimapUpdate &q : m_update_queue) {
-               if (q.pos == pos) {
-                       delete q.data;
-                       q.data = data;
-                       return false;
-               }
-       }
-
-       // Add the block
-       QueuedMinimapUpdate q;
-       q.pos  = pos;
-       q.data = data;
-       m_update_queue.push_back(q);
-
-       return true;
-}
-
-bool MinimapUpdateThread::popBlockUpdate(QueuedMinimapUpdate *update)
-{
-       MutexAutoLock lock(m_queue_mutex);
-
-       if (m_update_queue.empty())
-               return false;
-
-       *update = m_update_queue.front();
-       m_update_queue.pop_front();
-
-       return true;
-}
-
-void MinimapUpdateThread::enqueueBlock(v3s16 pos, MinimapMapblock *data)
-{
-       pushBlockUpdate(pos, data);
-       deferUpdate();
-}
-
-
-void MinimapUpdateThread::doUpdate()
-{
-       QueuedMinimapUpdate update;
-
-       while (popBlockUpdate(&update)) {
-               if (update.data) {
-                       // Swap two values in the map using single lookup
-                       std::pair<std::map<v3s16, MinimapMapblock*>::iterator, bool>
-                           result = m_blocks_cache.insert(std::make_pair(update.pos, update.data));
-                       if (!result.second) {
-                               delete result.first->second;
-                               result.first->second = update.data;
-                       }
-               } else {
-                       std::map<v3s16, MinimapMapblock *>::iterator it;
-                       it = m_blocks_cache.find(update.pos);
-                       if (it != m_blocks_cache.end()) {
-                               delete it->second;
-                               m_blocks_cache.erase(it);
-                       }
-               }
-       }
-
-       if (data->map_invalidated && data->mode != MINIMAP_MODE_OFF) {
-               getMap(data->pos, data->map_size, data->scan_height);
-               data->map_invalidated = false;
-       }
-}
-
-void MinimapUpdateThread::getMap(v3s16 pos, s16 size, s16 height)
-{
-       v3s16 pos_min(pos.X - size / 2, pos.Y - height / 2, pos.Z - size / 2);
-       v3s16 pos_max(pos_min.X + size - 1, pos.Y + height / 2, pos_min.Z + size - 1);
-       v3s16 blockpos_min = getNodeBlockPos(pos_min);
-       v3s16 blockpos_max = getNodeBlockPos(pos_max);
-
-// clear the map
-       for (int z = 0; z < size; z++)
-       for (int x = 0; x < size; x++) {
-               MinimapPixel &mmpixel = data->minimap_scan[x + z * size];
-               mmpixel.air_count = 0;
-               mmpixel.height = 0;
-               mmpixel.n = MapNode(CONTENT_AIR);
-       }
-
-// draw the map
-       v3s16 blockpos;
-       for (blockpos.Z = blockpos_min.Z; blockpos.Z <= blockpos_max.Z; ++blockpos.Z)
-       for (blockpos.Y = blockpos_min.Y; blockpos.Y <= blockpos_max.Y; ++blockpos.Y)
-       for (blockpos.X = blockpos_min.X; blockpos.X <= blockpos_max.X; ++blockpos.X) {
-               std::map<v3s16, MinimapMapblock *>::const_iterator pblock =
-                       m_blocks_cache.find(blockpos);
-               if (pblock == m_blocks_cache.end())
-                       continue;
-               const MinimapMapblock &block = *pblock->second;
-
-               v3s16 block_node_min(blockpos * MAP_BLOCKSIZE);
-               v3s16 block_node_max(block_node_min + MAP_BLOCKSIZE - 1);
-               // clip
-               v3s16 range_min = componentwise_max(block_node_min, pos_min);
-               v3s16 range_max = componentwise_min(block_node_max, pos_max);
-
-               v3s16 pos;
-               pos.Y = range_min.Y;
-               for (pos.Z = range_min.Z; pos.Z <= range_max.Z; ++pos.Z)
-               for (pos.X = range_min.X; pos.X <= range_max.X; ++pos.X) {
-                       v3s16 inblock_pos = pos - block_node_min;
-                       const MinimapPixel &in_pixel =
-                               block.data[inblock_pos.Z * MAP_BLOCKSIZE + inblock_pos.X];
-
-                       v3s16 inmap_pos = pos - pos_min;
-                       MinimapPixel &out_pixel =
-                               data->minimap_scan[inmap_pos.X + inmap_pos.Z * size];
-
-                       out_pixel.air_count += in_pixel.air_count;
-                       if (in_pixel.n.param0 != CONTENT_AIR) {
-                               out_pixel.n = in_pixel.n;
-                               out_pixel.height = inmap_pos.Y + in_pixel.height;
-                       }
-               }
-       }
-}
-
-////
-//// Mapper
-////
-
-Minimap::Minimap(Client *client)
-{
-       this->client    = client;
-       this->driver    = RenderingEngine::get_video_driver();
-       this->m_tsrc    = client->getTextureSource();
-       this->m_shdrsrc = client->getShaderSource();
-       this->m_ndef    = client->getNodeDefManager();
-
-       m_angle = 0.f;
-
-       // Initialize static settings
-       m_enable_shaders = g_settings->getBool("enable_shaders");
-       m_surface_mode_scan_height =
-               g_settings->getBool("minimap_double_scan_height") ? 256 : 128;
-
-       // Initialize minimap data
-       data = new MinimapData;
-       data->mode              = MINIMAP_MODE_OFF;
-       data->is_radar          = false;
-       data->map_invalidated   = true;
-       data->texture           = NULL;
-       data->heightmap_texture = NULL;
-       data->minimap_shape_round = g_settings->getBool("minimap_shape_round");
-
-       // Get round minimap textures
-       data->minimap_mask_round = driver->createImage(
-               m_tsrc->getTexture("minimap_mask_round.png"),
-               core::position2d<s32>(0, 0),
-               core::dimension2d<u32>(MINIMAP_MAX_SX, MINIMAP_MAX_SY));
-       data->minimap_overlay_round = m_tsrc->getTexture("minimap_overlay_round.png");
-
-       // Get square minimap textures
-       data->minimap_mask_square = driver->createImage(
-               m_tsrc->getTexture("minimap_mask_square.png"),
-               core::position2d<s32>(0, 0),
-               core::dimension2d<u32>(MINIMAP_MAX_SX, MINIMAP_MAX_SY));
-       data->minimap_overlay_square = m_tsrc->getTexture("minimap_overlay_square.png");
-
-       // Create player marker texture
-       data->player_marker = m_tsrc->getTexture("player_marker.png");
-       // Create object marker texture
-       data->object_marker_red = m_tsrc->getTexture("object_marker_red.png");
-
-       // Create mesh buffer for minimap
-       m_meshbuffer = getMinimapMeshBuffer();
-
-       // Initialize and start thread
-       m_minimap_update_thread = new MinimapUpdateThread();
-       m_minimap_update_thread->data = data;
-       m_minimap_update_thread->start();
-}
-
-Minimap::~Minimap()
-{
-       m_minimap_update_thread->stop();
-       m_minimap_update_thread->wait();
-
-       m_meshbuffer->drop();
-
-       data->minimap_mask_round->drop();
-       data->minimap_mask_square->drop();
-
-       driver->removeTexture(data->texture);
-       driver->removeTexture(data->heightmap_texture);
-       driver->removeTexture(data->minimap_overlay_round);
-       driver->removeTexture(data->minimap_overlay_square);
-       driver->removeTexture(data->object_marker_red);
-
-       delete data;
-       delete m_minimap_update_thread;
-}
-
-void Minimap::addBlock(v3s16 pos, MinimapMapblock *data)
-{
-       m_minimap_update_thread->enqueueBlock(pos, data);
-}
-
-void Minimap::toggleMinimapShape()
-{
-       MutexAutoLock lock(m_mutex);
-
-       data->minimap_shape_round = !data->minimap_shape_round;
-       g_settings->setBool("minimap_shape_round", data->minimap_shape_round);
-       m_minimap_update_thread->deferUpdate();
-}
-
-void Minimap::setMinimapShape(MinimapShape shape)
-{
-       MutexAutoLock lock(m_mutex);
-
-       if (shape == MINIMAP_SHAPE_SQUARE)
-               data->minimap_shape_round = false;
-       else if (shape == MINIMAP_SHAPE_ROUND)
-               data->minimap_shape_round = true;
-
-       g_settings->setBool("minimap_shape_round", data->minimap_shape_round);
-       m_minimap_update_thread->deferUpdate();
-}
-
-MinimapShape Minimap::getMinimapShape()
-{
-       if (data->minimap_shape_round) {
-               return MINIMAP_SHAPE_ROUND;
-       }
-
-       return MINIMAP_SHAPE_SQUARE;
-}
-
-void Minimap::setMinimapMode(MinimapMode mode)
-{
-       static const MinimapModeDef modedefs[MINIMAP_MODE_COUNT] = {
-               {false, 0, 0},
-               {false, m_surface_mode_scan_height, 256},
-               {false, m_surface_mode_scan_height, 128},
-               {false, m_surface_mode_scan_height, 64},
-               {true, 32, 128},
-               {true, 32, 64},
-               {true, 32, 32}
-       };
-
-       if (mode >= MINIMAP_MODE_COUNT)
-               return;
-
-       MutexAutoLock lock(m_mutex);
-
-       data->is_radar    = modedefs[mode].is_radar;
-       data->scan_height = modedefs[mode].scan_height;
-       data->map_size    = modedefs[mode].map_size;
-       data->mode        = mode;
-
-       m_minimap_update_thread->deferUpdate();
-}
-
-void Minimap::setPos(v3s16 pos)
-{
-       bool do_update = false;
-
-       {
-               MutexAutoLock lock(m_mutex);
-
-               if (pos != data->old_pos) {
-                       data->old_pos = data->pos;
-                       data->pos = pos;
-                       do_update = true;
-               }
-       }
-
-       if (do_update)
-               m_minimap_update_thread->deferUpdate();
-}
-
-void Minimap::setAngle(f32 angle)
-{
-       m_angle = angle;
-}
-
-void Minimap::blitMinimapPixelsToImageRadar(video::IImage *map_image)
-{
-       video::SColor c(240, 0, 0, 0);
-       for (s16 x = 0; x < data->map_size; x++)
-       for (s16 z = 0; z < data->map_size; z++) {
-               MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size];
-
-               if (mmpixel->air_count > 0)
-                       c.setGreen(core::clamp(core::round32(32 + mmpixel->air_count * 8), 0, 255));
-               else
-                       c.setGreen(0);
-
-               map_image->setPixel(x, data->map_size - z - 1, c);
-       }
-}
-
-void Minimap::blitMinimapPixelsToImageSurface(
-       video::IImage *map_image, video::IImage *heightmap_image)
-{
-       // This variable creation/destruction has a 1% cost on rendering minimap
-       video::SColor tilecolor;
-       for (s16 x = 0; x < data->map_size; x++)
-       for (s16 z = 0; z < data->map_size; z++) {
-               MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size];
-
-               const ContentFeatures &f = m_ndef->get(mmpixel->n);
-               const TileDef *tile = &f.tiledef[0];
-
-               // Color of the 0th tile (mostly this is the topmost)
-               if(tile->has_color)
-                       tilecolor = tile->color;
-               else
-                       mmpixel->n.getColor(f, &tilecolor);
-
-               tilecolor.setRed(tilecolor.getRed() * f.minimap_color.getRed() / 255);
-               tilecolor.setGreen(tilecolor.getGreen() * f.minimap_color.getGreen() / 255);
-               tilecolor.setBlue(tilecolor.getBlue() * f.minimap_color.getBlue() / 255);
-               tilecolor.setAlpha(240);
-
-               map_image->setPixel(x, data->map_size - z - 1, tilecolor);
-
-               u32 h = mmpixel->height;
-               heightmap_image->setPixel(x,data->map_size - z - 1,
-                       video::SColor(255, h, h, h));
-       }
-}
-
-video::ITexture *Minimap::getMinimapTexture()
-{
-       // update minimap textures when new scan is ready
-       if (data->map_invalidated)
-               return data->texture;
-
-       // create minimap and heightmap images in memory
-       core::dimension2d<u32> dim(data->map_size, data->map_size);
-       video::IImage *map_image       = driver->createImage(video::ECF_A8R8G8B8, dim);
-       video::IImage *heightmap_image = driver->createImage(video::ECF_A8R8G8B8, dim);
-       video::IImage *minimap_image   = driver->createImage(video::ECF_A8R8G8B8,
-               core::dimension2d<u32>(MINIMAP_MAX_SX, MINIMAP_MAX_SY));
-
-       // Blit MinimapPixels to images
-       if (data->is_radar)
-               blitMinimapPixelsToImageRadar(map_image);
-       else
-               blitMinimapPixelsToImageSurface(map_image, heightmap_image);
-
-       map_image->copyToScaling(minimap_image);
-       map_image->drop();
-
-       video::IImage *minimap_mask = data->minimap_shape_round ?
-               data->minimap_mask_round : data->minimap_mask_square;
-
-       if (minimap_mask) {
-               for (s16 y = 0; y < MINIMAP_MAX_SY; y++)
-               for (s16 x = 0; x < MINIMAP_MAX_SX; x++) {
-                       const video::SColor &mask_col = minimap_mask->getPixel(x, y);
-                       if (!mask_col.getAlpha())
-                               minimap_image->setPixel(x, y, video::SColor(0,0,0,0));
-               }
-       }
-
-       if (data->texture)
-               driver->removeTexture(data->texture);
-       if (data->heightmap_texture)
-               driver->removeTexture(data->heightmap_texture);
-
-       data->texture = driver->addTexture("minimap__", minimap_image);
-       data->heightmap_texture =
-               driver->addTexture("minimap_heightmap__", heightmap_image);
-       minimap_image->drop();
-       heightmap_image->drop();
-
-       data->map_invalidated = true;
-
-       return data->texture;
-}
-
-v3f Minimap::getYawVec()
-{
-       if (data->minimap_shape_round) {
-               return v3f(
-                       std::cos(m_angle * core::DEGTORAD),
-                       std::sin(m_angle * core::DEGTORAD),
-                       1.0);
-       }
-
-       return v3f(1.0, 0.0, 1.0);
-}
-
-scene::SMeshBuffer *Minimap::getMinimapMeshBuffer()
-{
-       scene::SMeshBuffer *buf = new scene::SMeshBuffer();
-       buf->Vertices.set_used(4);
-       buf->Indices.set_used(6);
-       static const video::SColor c(255, 255, 255, 255);
-
-       buf->Vertices[0] = video::S3DVertex(-1, -1, 0, 0, 0, 1, c, 0, 1);
-       buf->Vertices[1] = video::S3DVertex(-1,  1, 0, 0, 0, 1, c, 0, 0);
-       buf->Vertices[2] = video::S3DVertex( 1,  1, 0, 0, 0, 1, c, 1, 0);
-       buf->Vertices[3] = video::S3DVertex( 1, -1, 0, 0, 0, 1, c, 1, 1);
-
-       buf->Indices[0] = 0;
-       buf->Indices[1] = 1;
-       buf->Indices[2] = 2;
-       buf->Indices[3] = 2;
-       buf->Indices[4] = 3;
-       buf->Indices[5] = 0;
-
-       return buf;
-}
-
-void Minimap::drawMinimap()
-{
-       video::ITexture *minimap_texture = getMinimapTexture();
-       if (!minimap_texture)
-               return;
-
-       updateActiveMarkers();
-       v2u32 screensize = RenderingEngine::get_instance()->getWindowSize();
-       const u32 size = 0.25 * screensize.Y;
-
-       core::rect<s32> oldViewPort = driver->getViewPort();
-       core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
-       core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
-
-       driver->setViewPort(core::rect<s32>(
-               screensize.X - size - 10, 10,
-               screensize.X - 10, size + 10));
-       driver->setTransform(video::ETS_PROJECTION, core::matrix4());
-       driver->setTransform(video::ETS_VIEW, core::matrix4());
-
-       core::matrix4 matrix;
-       matrix.makeIdentity();
-
-       video::SMaterial &material = m_meshbuffer->getMaterial();
-       material.setFlag(video::EMF_TRILINEAR_FILTER, true);
-       material.Lighting = false;
-       material.TextureLayer[0].Texture = minimap_texture;
-       material.TextureLayer[1].Texture = data->heightmap_texture;
-
-       if (m_enable_shaders && !data->is_radar) {
-               u16 sid = m_shdrsrc->getShader("minimap_shader", 1, 1);
-               material.MaterialType = m_shdrsrc->getShaderInfo(sid).material;
-       } else {
-               material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
-       }
-
-       if (data->minimap_shape_round)
-               matrix.setRotationDegrees(core::vector3df(0, 0, 360 - m_angle));
-
-       // Draw minimap
-       driver->setTransform(video::ETS_WORLD, matrix);
-       driver->setMaterial(material);
-       driver->drawMeshBuffer(m_meshbuffer);
-
-       // Draw overlay
-       video::ITexture *minimap_overlay = data->minimap_shape_round ?
-               data->minimap_overlay_round : data->minimap_overlay_square;
-       material.TextureLayer[0].Texture = minimap_overlay;
-       material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
-       driver->setMaterial(material);
-       driver->drawMeshBuffer(m_meshbuffer);
-
-       // Draw player marker on minimap
-       if (data->minimap_shape_round) {
-               matrix.setRotationDegrees(core::vector3df(0, 0, 0));
-       } else {
-               matrix.setRotationDegrees(core::vector3df(0, 0, m_angle));
-       }
-
-       material.TextureLayer[0].Texture = data->player_marker;
-       driver->setTransform(video::ETS_WORLD, matrix);
-       driver->setMaterial(material);
-       driver->drawMeshBuffer(m_meshbuffer);
-
-       // Reset transformations
-       driver->setTransform(video::ETS_VIEW, oldViewMat);
-       driver->setTransform(video::ETS_PROJECTION, oldProjMat);
-       driver->setViewPort(oldViewPort);
-
-       // Draw player markers
-       v2s32 s_pos(screensize.X - size - 10, 10);
-       core::dimension2di imgsize(data->object_marker_red->getOriginalSize());
-       core::rect<s32> img_rect(0, 0, imgsize.Width, imgsize.Height);
-       static const video::SColor col(255, 255, 255, 255);
-       static const video::SColor c[4] = {col, col, col, col};
-       f32 sin_angle = std::sin(m_angle * core::DEGTORAD);
-       f32 cos_angle = std::cos(m_angle * core::DEGTORAD);
-       s32 marker_size2 =  0.025 * (float)size;
-       for (std::list<v2f>::const_iterator
-                       i = m_active_markers.begin();
-                       i != m_active_markers.end(); ++i) {
-               v2f posf = *i;
-               if (data->minimap_shape_round) {
-                       f32 t1 = posf.X * cos_angle - posf.Y * sin_angle;
-                       f32 t2 = posf.X * sin_angle + posf.Y * cos_angle;
-                       posf.X = t1;
-                       posf.Y = t2;
-               }
-               posf.X = (posf.X + 0.5) * (float)size;
-               posf.Y = (posf.Y + 0.5) * (float)size;
-               core::rect<s32> dest_rect(
-                       s_pos.X + posf.X - marker_size2,
-                       s_pos.Y + posf.Y - marker_size2,
-                       s_pos.X + posf.X + marker_size2,
-                       s_pos.Y + posf.Y + marker_size2);
-               driver->draw2DImage(data->object_marker_red, dest_rect,
-                       img_rect, &dest_rect, &c[0], true);
-       }
-}
-
-void Minimap::updateActiveMarkers()
-{
-       video::IImage *minimap_mask = data->minimap_shape_round ?
-               data->minimap_mask_round : data->minimap_mask_square;
-
-       const std::list<Nametag *> &nametags = client->getCamera()->getNametags();
-
-       m_active_markers.clear();
-
-       for (Nametag *nametag : nametags) {
-               v3s16 pos = floatToInt(nametag->parent_node->getPosition() +
-                       intToFloat(client->getCamera()->getOffset(), BS), BS);
-               pos -= data->pos - v3s16(data->map_size / 2,
-                               data->scan_height / 2,
-                               data->map_size / 2);
-               if (pos.X < 0 || pos.X > data->map_size ||
-                               pos.Y < 0 || pos.Y > data->scan_height ||
-                               pos.Z < 0 || pos.Z > data->map_size) {
-                       continue;
-               }
-               pos.X = ((float)pos.X / data->map_size) * MINIMAP_MAX_SX;
-               pos.Z = ((float)pos.Z / data->map_size) * MINIMAP_MAX_SY;
-               const video::SColor &mask_col = minimap_mask->getPixel(pos.X, pos.Z);
-               if (!mask_col.getAlpha()) {
-                       continue;
-               }
-
-               m_active_markers.emplace_back(((float)pos.X / (float)MINIMAP_MAX_SX) - 0.5,
-                       (1.0 - (float)pos.Z / (float)MINIMAP_MAX_SY) - 0.5);
-       }
-}
-
-////
-//// MinimapMapblock
-////
-
-void MinimapMapblock::getMinimapNodes(VoxelManipulator *vmanip, const v3s16 &pos)
-{
-
-       for (s16 x = 0; x < MAP_BLOCKSIZE; x++)
-       for (s16 z = 0; z < MAP_BLOCKSIZE; z++) {
-               s16 air_count = 0;
-               bool surface_found = false;
-               MinimapPixel *mmpixel = &data[z * MAP_BLOCKSIZE + x];
-
-               for (s16 y = MAP_BLOCKSIZE -1; y >= 0; y--) {
-                       v3s16 p(x, y, z);
-                       MapNode n = vmanip->getNodeNoEx(pos + p);
-                       if (!surface_found && n.getContent() != CONTENT_AIR) {
-                               mmpixel->height = y;
-                               mmpixel->n = n;
-                               surface_found = true;
-                       } else if (n.getContent() == CONTENT_AIR) {
-                               air_count++;
-                       }
-               }
-
-               if (!surface_found)
-                       mmpixel->n = MapNode(CONTENT_AIR);
-
-               mmpixel->air_count = air_count;
-       }
-}
diff --git a/src/minimap.h b/src/minimap.h
deleted file mode 100644 (file)
index 258d533..0000000
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include "irrlichttypes_extrabloated.h"
-#include "util/thread.h"
-#include "voxel.h"
-#include <map>
-#include <string>
-#include <vector>
-
-class Client;
-class ITextureSource;
-class IShaderSource;
-
-#define MINIMAP_MAX_SX 512
-#define MINIMAP_MAX_SY 512
-
-enum MinimapMode {
-       MINIMAP_MODE_OFF,
-       MINIMAP_MODE_SURFACEx1,
-       MINIMAP_MODE_SURFACEx2,
-       MINIMAP_MODE_SURFACEx4,
-       MINIMAP_MODE_RADARx1,
-       MINIMAP_MODE_RADARx2,
-       MINIMAP_MODE_RADARx4,
-       MINIMAP_MODE_COUNT,
-};
-
-enum MinimapShape {
-       MINIMAP_SHAPE_SQUARE,
-       MINIMAP_SHAPE_ROUND,
-};
-
-struct MinimapModeDef {
-       bool is_radar;
-       u16 scan_height;
-       u16 map_size;
-};
-
-struct MinimapPixel {
-       //! The topmost node that the minimap displays.
-       MapNode n;
-       u16 height;
-       u16 air_count;
-};
-
-struct MinimapMapblock {
-       void getMinimapNodes(VoxelManipulator *vmanip, const v3s16 &pos);
-
-       MinimapPixel data[MAP_BLOCKSIZE * MAP_BLOCKSIZE];
-};
-
-struct MinimapData {
-       bool is_radar;
-       MinimapMode mode;
-       v3s16 pos;
-       v3s16 old_pos;
-       u16 scan_height;
-       u16 map_size;
-       MinimapPixel minimap_scan[MINIMAP_MAX_SX * MINIMAP_MAX_SY];
-       bool map_invalidated;
-       bool minimap_shape_round;
-       video::IImage *minimap_mask_round = nullptr;
-       video::IImage *minimap_mask_square = nullptr;
-       video::ITexture *texture = nullptr;
-       video::ITexture *heightmap_texture = nullptr;
-       video::ITexture *minimap_overlay_round = nullptr;
-       video::ITexture *minimap_overlay_square = nullptr;
-       video::ITexture *player_marker = nullptr;
-       video::ITexture *object_marker_red = nullptr;
-};
-
-struct QueuedMinimapUpdate {
-       v3s16 pos;
-       MinimapMapblock *data = nullptr;
-};
-
-class MinimapUpdateThread : public UpdateThread {
-public:
-       MinimapUpdateThread() : UpdateThread("Minimap") {}
-       virtual ~MinimapUpdateThread();
-
-       void getMap(v3s16 pos, s16 size, s16 height);
-       void enqueueBlock(v3s16 pos, MinimapMapblock *data);
-       bool pushBlockUpdate(v3s16 pos, MinimapMapblock *data);
-       bool popBlockUpdate(QueuedMinimapUpdate *update);
-
-       MinimapData *data = nullptr;
-
-protected:
-       virtual void doUpdate();
-
-private:
-       std::mutex m_queue_mutex;
-       std::deque<QueuedMinimapUpdate> m_update_queue;
-       std::map<v3s16, MinimapMapblock *> m_blocks_cache;
-};
-
-class Minimap {
-public:
-       Minimap(Client *client);
-       ~Minimap();
-
-       void addBlock(v3s16 pos, MinimapMapblock *data);
-
-       v3f getYawVec();
-
-       void setPos(v3s16 pos);
-       v3s16 getPos() const { return data->pos; }
-       void setAngle(f32 angle);
-       f32 getAngle() const { return m_angle; }
-       void setMinimapMode(MinimapMode mode);
-       MinimapMode getMinimapMode() const { return data->mode; }
-       void toggleMinimapShape();
-       void setMinimapShape(MinimapShape shape);
-       MinimapShape getMinimapShape();
-
-
-       video::ITexture *getMinimapTexture();
-
-       void blitMinimapPixelsToImageRadar(video::IImage *map_image);
-       void blitMinimapPixelsToImageSurface(video::IImage *map_image,
-               video::IImage *heightmap_image);
-
-       scene::SMeshBuffer *getMinimapMeshBuffer();
-
-       void updateActiveMarkers();
-       void drawMinimap();
-
-       video::IVideoDriver *driver;
-       Client* client;
-       MinimapData *data;
-
-private:
-       ITextureSource *m_tsrc;
-       IShaderSource *m_shdrsrc;
-       const NodeDefManager *m_ndef;
-       MinimapUpdateThread *m_minimap_update_thread;
-       scene::SMeshBuffer *m_meshbuffer;
-       bool m_enable_shaders;
-       u16 m_surface_mode_scan_height;
-       f32 m_angle;
-       std::mutex m_mutex;
-       std::list<v2f> m_active_markers;
-};
index 57846f09524ae8d3fab10a7a4e53ea1799083f85..d03dc457dd526fbcdfdd7ec072e1ba8c4d7dfc1d 100644 (file)
@@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #pragma once
 
-#include "client.h"
+#include "client/client.h"
 #include "networkprotocol.h"
 
 class NetworkPacket;
index 1899b496e242e7719e67dc7ba910ccc4965d5de7..1be7d4eeba7c433a5323297ff9abfada233c3244 100644 (file)
@@ -17,15 +17,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
 
-#include "client.h"
+#include "client/client.h"
 
 #include "util/base64.h"
 #include "chatmessage.h"
-#include "clientmedia.h"
+#include "client/clientmedia.h"
 #include "log.h"
 #include "map.h"
 #include "mapsector.h"
-#include "minimap.h"
+#include "client/minimap.h"
 #include "modchannels.h"
 #include "nodedef.h"
 #include "serialization.h"
index c3f2ccd601eec199b209091fe7260253cecd4b45..70974a572f597edfec3a00c63d806edc4068acd5 100644 (file)
@@ -21,9 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "itemdef.h"
 #ifndef SERVER
-#include "mesh.h"
-#include "shader.h"
-#include "client.h"
+#include "client/mesh.h"
+#include "client/shader.h"
+#include "client/client.h"
 #include "client/renderingengine.h"
 #include "client/tile.h"
 #include <IMeshManipulator.h>
diff --git a/src/particles.cpp b/src/particles.cpp
deleted file mode 100644 (file)
index 25cfa08..0000000
+++ /dev/null
@@ -1,684 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 "particles.h"
-#include <cmath>
-#include "client.h"
-#include "collision.h"
-#include "client/clientevent.h"
-#include "client/renderingengine.h"
-#include "util/numeric.h"
-#include "light.h"
-#include "environment.h"
-#include "clientmap.h"
-#include "mapnode.h"
-#include "nodedef.h"
-#include "client.h"
-#include "settings.h"
-
-/*
-       Utility
-*/
-
-v3f random_v3f(v3f min, v3f max)
-{
-       return v3f( rand()/(float)RAND_MAX*(max.X-min.X)+min.X,
-                       rand()/(float)RAND_MAX*(max.Y-min.Y)+min.Y,
-                       rand()/(float)RAND_MAX*(max.Z-min.Z)+min.Z);
-}
-
-Particle::Particle(
-       IGameDef *gamedef,
-       LocalPlayer *player,
-       ClientEnvironment *env,
-       v3f pos,
-       v3f velocity,
-       v3f acceleration,
-       float expirationtime,
-       float size,
-       bool collisiondetection,
-       bool collision_removal,
-       bool object_collision,
-       bool vertical,
-       video::ITexture *texture,
-       v2f texpos,
-       v2f texsize,
-       const struct TileAnimationParams &anim,
-       u8 glow,
-       video::SColor color
-):
-       scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(),
-               RenderingEngine::get_scene_manager())
-{
-       // Misc
-       m_gamedef = gamedef;
-       m_env = env;
-
-       // Texture
-       m_material.setFlag(video::EMF_LIGHTING, false);
-       m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
-       m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
-       m_material.setFlag(video::EMF_FOG_ENABLE, true);
-       m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
-       m_material.setTexture(0, texture);
-       m_texpos = texpos;
-       m_texsize = texsize;
-       m_animation = anim;
-
-       // Color
-       m_base_color = color;
-       m_color = color;
-
-       // Particle related
-       m_pos = pos;
-       m_velocity = velocity;
-       m_acceleration = acceleration;
-       m_expiration = expirationtime;
-       m_player = player;
-       m_size = size;
-       m_collisiondetection = collisiondetection;
-       m_collision_removal = collision_removal;
-       m_object_collision = object_collision;
-       m_vertical = vertical;
-       m_glow = glow;
-
-       // Irrlicht stuff
-       m_collisionbox = aabb3f
-                       (-size/2,-size/2,-size/2,size/2,size/2,size/2);
-       this->setAutomaticCulling(scene::EAC_OFF);
-
-       // Init lighting
-       updateLight();
-
-       // Init model
-       updateVertices();
-}
-
-void Particle::OnRegisterSceneNode()
-{
-       if (IsVisible)
-               SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
-
-       ISceneNode::OnRegisterSceneNode();
-}
-
-void Particle::render()
-{
-       video::IVideoDriver* driver = SceneManager->getVideoDriver();
-       driver->setMaterial(m_material);
-       driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
-
-       u16 indices[] = {0,1,2, 2,3,0};
-       driver->drawVertexPrimitiveList(m_vertices, 4,
-                       indices, 2, video::EVT_STANDARD,
-                       scene::EPT_TRIANGLES, video::EIT_16BIT);
-}
-
-void Particle::step(float dtime)
-{
-       m_time += dtime;
-       if (m_collisiondetection) {
-               aabb3f box = m_collisionbox;
-               v3f p_pos = m_pos * BS;
-               v3f p_velocity = m_velocity * BS;
-               collisionMoveResult r = collisionMoveSimple(m_env, m_gamedef, BS * 0.5f,
-                       box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr,
-                       m_object_collision);
-               if (m_collision_removal && r.collides) {
-                       // force expiration of the particle
-                       m_expiration = -1.0;
-               } else {
-                       m_pos = p_pos / BS;
-                       m_velocity = p_velocity / BS;
-               }
-       } else {
-               m_velocity += m_acceleration * dtime;
-               m_pos += m_velocity * dtime;
-       }
-       if (m_animation.type != TAT_NONE) {
-               m_animation_time += dtime;
-               int frame_length_i, frame_count;
-               m_animation.determineParams(
-                               m_material.getTexture(0)->getSize(),
-                               &frame_count, &frame_length_i, NULL);
-               float frame_length = frame_length_i / 1000.0;
-               while (m_animation_time > frame_length) {
-                       m_animation_frame++;
-                       m_animation_time -= frame_length;
-               }
-       }
-
-       // Update lighting
-       updateLight();
-
-       // Update model
-       updateVertices();
-}
-
-void Particle::updateLight()
-{
-       u8 light = 0;
-       bool pos_ok;
-
-       v3s16 p = v3s16(
-               floor(m_pos.X+0.5),
-               floor(m_pos.Y+0.5),
-               floor(m_pos.Z+0.5)
-       );
-       MapNode n = m_env->getClientMap().getNodeNoEx(p, &pos_ok);
-       if (pos_ok)
-               light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef());
-       else
-               light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
-
-       u8 m_light = decode_light(light + m_glow);
-       m_color.set(255,
-               m_light * m_base_color.getRed() / 255,
-               m_light * m_base_color.getGreen() / 255,
-               m_light * m_base_color.getBlue() / 255);
-}
-
-void Particle::updateVertices()
-{
-       f32 tx0, tx1, ty0, ty1;
-
-       if (m_animation.type != TAT_NONE) {
-               const v2u32 texsize = m_material.getTexture(0)->getSize();
-               v2f texcoord, framesize_f;
-               v2u32 framesize;
-               texcoord = m_animation.getTextureCoords(texsize, m_animation_frame);
-               m_animation.determineParams(texsize, NULL, NULL, &framesize);
-               framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y);
-
-               tx0 = m_texpos.X + texcoord.X;
-               tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X;
-               ty0 = m_texpos.Y + texcoord.Y;
-               ty1 = m_texpos.Y + texcoord.Y + framesize_f.Y * m_texsize.Y;
-       } else {
-               tx0 = m_texpos.X;
-               tx1 = m_texpos.X + m_texsize.X;
-               ty0 = m_texpos.Y;
-               ty1 = m_texpos.Y + m_texsize.Y;
-       }
-
-       m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2,
-               0, 0, 0, 0, m_color, tx0, ty1);
-       m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2,
-               0, 0, 0, 0, m_color, tx1, ty1);
-       m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2,
-               0, 0, 0, 0, m_color, tx1, ty0);
-       m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2,
-               0, 0, 0, 0, m_color, tx0, ty0);
-
-       v3s16 camera_offset = m_env->getCameraOffset();
-       for (video::S3DVertex &vertex : m_vertices) {
-               if (m_vertical) {
-                       v3f ppos = m_player->getPosition()/BS;
-                       vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) /
-                               core::DEGTORAD + 90);
-               } else {
-                       vertex.Pos.rotateYZBy(m_player->getPitch());
-                       vertex.Pos.rotateXZBy(m_player->getYaw());
-               }
-               m_box.addInternalPoint(vertex.Pos);
-               vertex.Pos += m_pos*BS - intToFloat(camera_offset, BS);
-       }
-}
-
-/*
-       ParticleSpawner
-*/
-
-ParticleSpawner::ParticleSpawner(
-       IGameDef *gamedef,
-       LocalPlayer *player,
-       u16 amount,
-       float time,
-       v3f minpos, v3f maxpos,
-       v3f minvel, v3f maxvel,
-       v3f minacc, v3f maxacc,
-       float minexptime, float maxexptime,
-       float minsize, float maxsize,
-       bool collisiondetection,
-       bool collision_removal,
-       bool object_collision,
-       u16 attached_id,
-       bool vertical,
-       video::ITexture *texture,
-       u32 id,
-       const struct TileAnimationParams &anim,
-       u8 glow,
-       ParticleManager *p_manager
-):
-       m_particlemanager(p_manager)
-{
-       m_gamedef = gamedef;
-       m_player = player;
-       m_amount = amount;
-       m_spawntime = time;
-       m_minpos = minpos;
-       m_maxpos = maxpos;
-       m_minvel = minvel;
-       m_maxvel = maxvel;
-       m_minacc = minacc;
-       m_maxacc = maxacc;
-       m_minexptime = minexptime;
-       m_maxexptime = maxexptime;
-       m_minsize = minsize;
-       m_maxsize = maxsize;
-       m_collisiondetection = collisiondetection;
-       m_collision_removal = collision_removal;
-       m_object_collision = object_collision;
-       m_attached_id = attached_id;
-       m_vertical = vertical;
-       m_texture = texture;
-       m_time = 0;
-       m_animation = anim;
-       m_glow = glow;
-
-       for (u16 i = 0; i<=m_amount; i++)
-       {
-               float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
-               m_spawntimes.push_back(spawntime);
-       }
-}
-
-void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
-       bool is_attached, const v3f &attached_pos, float attached_yaw)
-{
-       v3f ppos = m_player->getPosition() / BS;
-       v3f pos = random_v3f(m_minpos, m_maxpos);
-
-       // Need to apply this first or the following check
-       // will be wrong for attached spawners
-       if (is_attached) {
-               pos.rotateXZBy(attached_yaw);
-               pos += attached_pos;
-       }
-
-       if (pos.getDistanceFrom(ppos) > radius)
-               return;
-
-       v3f vel = random_v3f(m_minvel, m_maxvel);
-       v3f acc = random_v3f(m_minacc, m_maxacc);
-
-       if (is_attached) {
-               // Apply attachment yaw
-               vel.rotateXZBy(attached_yaw);
-               acc.rotateXZBy(attached_yaw);
-       }
-
-       float exptime = rand() / (float)RAND_MAX
-                       * (m_maxexptime - m_minexptime)
-                       + m_minexptime;
-       float size = rand() / (float)RAND_MAX
-                       * (m_maxsize - m_minsize)
-                       + m_minsize;
-
-       m_particlemanager->addParticle(new Particle(
-               m_gamedef,
-               m_player,
-               env,
-               pos,
-               vel,
-               acc,
-               exptime,
-               size,
-               m_collisiondetection,
-               m_collision_removal,
-               m_object_collision,
-               m_vertical,
-               m_texture,
-               v2f(0.0, 0.0),
-               v2f(1.0, 1.0),
-               m_animation,
-               m_glow
-       ));
-}
-
-void ParticleSpawner::step(float dtime, ClientEnvironment* env)
-{
-       m_time += dtime;
-
-       static thread_local const float radius =
-                       g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
-
-       bool unloaded = false;
-       bool is_attached = false;
-       v3f attached_pos = v3f(0,0,0);
-       float attached_yaw = 0;
-       if (m_attached_id != 0) {
-               if (ClientActiveObject *attached = env->getActiveObject(m_attached_id)) {
-                       attached_pos = attached->getPosition() / BS;
-                       attached_yaw = attached->getYaw();
-                       is_attached = true;
-               } else {
-                       unloaded = true;
-               }
-       }
-
-       if (m_spawntime != 0) {
-               // Spawner exists for a predefined timespan
-               for (std::vector<float>::iterator i = m_spawntimes.begin();
-                               i != m_spawntimes.end();) {
-                       if ((*i) <= m_time && m_amount > 0) {
-                               m_amount--;
-
-                               // Pretend to, but don't actually spawn a particle if it is
-                               // attached to an unloaded object or distant from player.
-                               if (!unloaded)
-                                       spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
-
-                               i = m_spawntimes.erase(i);
-                       } else {
-                               ++i;
-                       }
-               }
-       } else {
-               // Spawner exists for an infinity timespan, spawn on a per-second base
-
-               // Skip this step if attached to an unloaded object
-               if (unloaded)
-                       return;
-
-               for (int i = 0; i <= m_amount; i++) {
-                       if (rand() / (float)RAND_MAX < dtime)
-                               spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
-               }
-       }
-}
-
-
-ParticleManager::ParticleManager(ClientEnvironment* env) :
-       m_env(env)
-{}
-
-ParticleManager::~ParticleManager()
-{
-       clearAll();
-}
-
-void ParticleManager::step(float dtime)
-{
-       stepParticles (dtime);
-       stepSpawners (dtime);
-}
-
-void ParticleManager::stepSpawners (float dtime)
-{
-       MutexAutoLock lock(m_spawner_list_lock);
-       for (std::map<u32, ParticleSpawner*>::iterator i =
-                       m_particle_spawners.begin();
-                       i != m_particle_spawners.end();)
-       {
-               if (i->second->get_expired())
-               {
-                       delete i->second;
-                       m_particle_spawners.erase(i++);
-               }
-               else
-               {
-                       i->second->step(dtime, m_env);
-                       ++i;
-               }
-       }
-}
-
-void ParticleManager::stepParticles (float dtime)
-{
-       MutexAutoLock lock(m_particle_list_lock);
-       for(std::vector<Particle*>::iterator i = m_particles.begin();
-                       i != m_particles.end();)
-       {
-               if ((*i)->get_expired())
-               {
-                       (*i)->remove();
-                       delete *i;
-                       i = m_particles.erase(i);
-               }
-               else
-               {
-                       (*i)->step(dtime);
-                       ++i;
-               }
-       }
-}
-
-void ParticleManager::clearAll ()
-{
-       MutexAutoLock lock(m_spawner_list_lock);
-       MutexAutoLock lock2(m_particle_list_lock);
-       for(std::map<u32, ParticleSpawner*>::iterator i =
-                       m_particle_spawners.begin();
-                       i != m_particle_spawners.end();)
-       {
-               delete i->second;
-               m_particle_spawners.erase(i++);
-       }
-
-       for(std::vector<Particle*>::iterator i =
-                       m_particles.begin();
-                       i != m_particles.end();)
-       {
-               (*i)->remove();
-               delete *i;
-               i = m_particles.erase(i);
-       }
-}
-
-void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
-       LocalPlayer *player)
-{
-       switch (event->type) {
-               case CE_DELETE_PARTICLESPAWNER: {
-                       MutexAutoLock lock(m_spawner_list_lock);
-                       if (m_particle_spawners.find(event->delete_particlespawner.id) !=
-                                       m_particle_spawners.end()) {
-                               delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
-                               m_particle_spawners.erase(event->delete_particlespawner.id);
-                       }
-                       // no allocated memory in delete event
-                       break;
-               }
-               case CE_ADD_PARTICLESPAWNER: {
-                       {
-                               MutexAutoLock lock(m_spawner_list_lock);
-                               if (m_particle_spawners.find(event->add_particlespawner.id) !=
-                                               m_particle_spawners.end()) {
-                                       delete m_particle_spawners.find(event->add_particlespawner.id)->second;
-                                       m_particle_spawners.erase(event->add_particlespawner.id);
-                               }
-                       }
-
-                       video::ITexture *texture =
-                               client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
-
-                       ParticleSpawner *toadd = new ParticleSpawner(client, player,
-                                       event->add_particlespawner.amount,
-                                       event->add_particlespawner.spawntime,
-                                       *event->add_particlespawner.minpos,
-                                       *event->add_particlespawner.maxpos,
-                                       *event->add_particlespawner.minvel,
-                                       *event->add_particlespawner.maxvel,
-                                       *event->add_particlespawner.minacc,
-                                       *event->add_particlespawner.maxacc,
-                                       event->add_particlespawner.minexptime,
-                                       event->add_particlespawner.maxexptime,
-                                       event->add_particlespawner.minsize,
-                                       event->add_particlespawner.maxsize,
-                                       event->add_particlespawner.collisiondetection,
-                                       event->add_particlespawner.collision_removal,
-                                       event->add_particlespawner.object_collision,
-                                       event->add_particlespawner.attached_id,
-                                       event->add_particlespawner.vertical,
-                                       texture,
-                                       event->add_particlespawner.id,
-                                       event->add_particlespawner.animation,
-                                       event->add_particlespawner.glow,
-                                       this);
-
-                       /* delete allocated content of event */
-                       delete event->add_particlespawner.minpos;
-                       delete event->add_particlespawner.maxpos;
-                       delete event->add_particlespawner.minvel;
-                       delete event->add_particlespawner.maxvel;
-                       delete event->add_particlespawner.minacc;
-                       delete event->add_particlespawner.texture;
-                       delete event->add_particlespawner.maxacc;
-
-                       {
-                               MutexAutoLock lock(m_spawner_list_lock);
-                               m_particle_spawners.insert(
-                                               std::pair<u32, ParticleSpawner*>(
-                                                               event->add_particlespawner.id,
-                                                               toadd));
-                       }
-                       break;
-               }
-               case CE_SPAWN_PARTICLE: {
-                       video::ITexture *texture =
-                               client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
-
-                       Particle *toadd = new Particle(client, player, m_env,
-                                       *event->spawn_particle.pos,
-                                       *event->spawn_particle.vel,
-                                       *event->spawn_particle.acc,
-                                       event->spawn_particle.expirationtime,
-                                       event->spawn_particle.size,
-                                       event->spawn_particle.collisiondetection,
-                                       event->spawn_particle.collision_removal,
-                                       event->spawn_particle.object_collision,
-                                       event->spawn_particle.vertical,
-                                       texture,
-                                       v2f(0.0, 0.0),
-                                       v2f(1.0, 1.0),
-                                       event->spawn_particle.animation,
-                                       event->spawn_particle.glow);
-
-                       addParticle(toadd);
-
-                       delete event->spawn_particle.pos;
-                       delete event->spawn_particle.vel;
-                       delete event->spawn_particle.acc;
-                       delete event->spawn_particle.texture;
-
-                       break;
-               }
-               default: break;
-       }
-}
-
-// The final burst of particles when a node is finally dug, *not* particles
-// spawned during the digging of a node.
-
-void ParticleManager::addDiggingParticles(IGameDef* gamedef,
-       LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
-{
-       // No particles for "airlike" nodes
-       if (f.drawtype == NDT_AIRLIKE)
-               return;
-
-       for (u16 j = 0; j < 16; j++) {
-               addNodeParticle(gamedef, player, pos, n, f);
-       }
-}
-
-// During the digging of a node particles are spawned individually by this
-// function, called from Game::handleDigging() in game.cpp.
-
-void ParticleManager::addNodeParticle(IGameDef* gamedef,
-       LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
-{
-       // No particles for "airlike" nodes
-       if (f.drawtype == NDT_AIRLIKE)
-               return;
-
-       // Texture
-       u8 texid = myrand_range(0, 5);
-       const TileLayer &tile = f.tiles[texid].layers[0];
-       video::ITexture *texture;
-       struct TileAnimationParams anim;
-       anim.type = TAT_NONE;
-
-       // Only use first frame of animated texture
-       if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
-               texture = (*tile.frames)[0].texture;
-       else
-               texture = tile.texture;
-
-       float size = (rand() % 8) / 64.0f;
-       float visual_size = BS * size;
-       if (tile.scale)
-               size /= tile.scale;
-       v2f texsize(size * 2.0f, size * 2.0f);
-       v2f texpos;
-       texpos.X = (rand() % 64) / 64.0f - texsize.X;
-       texpos.Y = (rand() % 64) / 64.0f - texsize.Y;
-
-       // Physics
-       v3f velocity(
-               (rand() % 150) / 50.0f - 1.5f,
-               (rand() % 150) / 50.0f,
-               (rand() % 150) / 50.0f - 1.5f
-       );
-       v3f acceleration(
-               0.0f,
-               -player->movement_gravity * player->physics_override_gravity / BS,
-               0.0f
-       );
-       v3f particlepos = v3f(
-               (f32)pos.X + (rand() % 100) / 200.0f - 0.25f,
-               (f32)pos.Y + (rand() % 100) / 200.0f - 0.25f,
-               (f32)pos.Z + (rand() % 100) / 200.0f - 0.25f
-       );
-
-       video::SColor color;
-       if (tile.has_color)
-               color = tile.color;
-       else
-               n.getColor(f, &color);
-
-       Particle* toadd = new Particle(
-               gamedef,
-               player,
-               m_env,
-               particlepos,
-               velocity,
-               acceleration,
-               (rand() % 100) / 100.0f, // expiration time
-               visual_size,
-               true,
-               false,
-               false,
-               false,
-               texture,
-               texpos,
-               texsize,
-               anim,
-               0,
-               color);
-
-       addParticle(toadd);
-}
-
-void ParticleManager::addParticle(Particle* toadd)
-{
-       MutexAutoLock lock(m_particle_list_lock);
-       m_particles.push_back(toadd);
-}
diff --git a/src/particles.h b/src/particles.h
deleted file mode 100644 (file)
index 3392e7e..0000000
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include <iostream>
-#include "irrlichttypes_extrabloated.h"
-#include "client/tile.h"
-#include "localplayer.h"
-#include "tileanimation.h"
-
-struct ClientEvent;
-class ParticleManager;
-class ClientEnvironment;
-struct MapNode;
-struct ContentFeatures;
-
-class Particle : public scene::ISceneNode
-{
-       public:
-       Particle(
-               IGameDef* gamedef,
-               LocalPlayer *player,
-               ClientEnvironment *env,
-               v3f pos,
-               v3f velocity,
-               v3f acceleration,
-               float expirationtime,
-               float size,
-               bool collisiondetection,
-               bool collision_removal,
-               bool object_collision,
-               bool vertical,
-               video::ITexture *texture,
-               v2f texpos,
-               v2f texsize,
-               const struct TileAnimationParams &anim,
-               u8 glow,
-               video::SColor color = video::SColor(0xFFFFFFFF)
-       );
-       ~Particle() = default;
-
-       virtual const aabb3f &getBoundingBox() const
-       {
-               return m_box;
-       }
-
-       virtual u32 getMaterialCount() const
-       {
-               return 1;
-       }
-
-       virtual video::SMaterial& getMaterial(u32 i)
-       {
-               return m_material;
-       }
-
-       virtual void OnRegisterSceneNode();
-       virtual void render();
-
-       void step(float dtime);
-
-       bool get_expired ()
-       { return m_expiration < m_time; }
-
-private:
-       void updateLight();
-       void updateVertices();
-
-       video::S3DVertex m_vertices[4];
-       float m_time = 0.0f;
-       float m_expiration;
-
-       ClientEnvironment *m_env;
-       IGameDef *m_gamedef;
-       aabb3f m_box;
-       aabb3f m_collisionbox;
-       video::SMaterial m_material;
-       v2f m_texpos;
-       v2f m_texsize;
-       v3f m_pos;
-       v3f m_velocity;
-       v3f m_acceleration;
-       LocalPlayer *m_player;
-       float m_size;
-       //! Color without lighting
-       video::SColor m_base_color;
-       //! Final rendered color
-       video::SColor m_color;
-       bool m_collisiondetection;
-       bool m_collision_removal;
-       bool m_object_collision;
-       bool m_vertical;
-       v3s16 m_camera_offset;
-       struct TileAnimationParams m_animation;
-       float m_animation_time = 0.0f;
-       int m_animation_frame = 0;
-       u8 m_glow;
-};
-
-class ParticleSpawner
-{
-public:
-       ParticleSpawner(IGameDef* gamedef,
-               LocalPlayer *player,
-               u16 amount,
-               float time,
-               v3f minp, v3f maxp,
-               v3f minvel, v3f maxvel,
-               v3f minacc, v3f maxacc,
-               float minexptime, float maxexptime,
-               float minsize, float maxsize,
-               bool collisiondetection,
-               bool collision_removal,
-               bool object_collision,
-               u16 attached_id,
-               bool vertical,
-               video::ITexture *texture,
-               u32 id,
-               const struct TileAnimationParams &anim, u8 glow,
-               ParticleManager* p_manager);
-
-       ~ParticleSpawner() = default;
-
-       void step(float dtime, ClientEnvironment *env);
-
-       bool get_expired ()
-       { return (m_amount <= 0) && m_spawntime != 0; }
-
-private:
-       void spawnParticle(ClientEnvironment *env, float radius,
-                       bool is_attached, const v3f &attached_pos,
-                       float attached_yaw);
-
-       ParticleManager *m_particlemanager;
-       float m_time;
-       IGameDef *m_gamedef;
-       LocalPlayer *m_player;
-       u16 m_amount;
-       float m_spawntime;
-       v3f m_minpos;
-       v3f m_maxpos;
-       v3f m_minvel;
-       v3f m_maxvel;
-       v3f m_minacc;
-       v3f m_maxacc;
-       float m_minexptime;
-       float m_maxexptime;
-       float m_minsize;
-       float m_maxsize;
-       video::ITexture *m_texture;
-       std::vector<float> m_spawntimes;
-       bool m_collisiondetection;
-       bool m_collision_removal;
-       bool m_object_collision;
-       bool m_vertical;
-       u16 m_attached_id;
-       struct TileAnimationParams m_animation;
-       u8 m_glow;
-};
-
-/**
- * Class doing particle as well as their spawners handling
- */
-class ParticleManager
-{
-friend class ParticleSpawner;
-public:
-       ParticleManager(ClientEnvironment* env);
-       ~ParticleManager();
-
-       void step (float dtime);
-
-       void handleParticleEvent(ClientEvent *event, Client *client,
-                       LocalPlayer *player);
-
-       void addDiggingParticles(IGameDef *gamedef, LocalPlayer *player, v3s16 pos,
-               const MapNode &n, const ContentFeatures &f);
-
-       void addNodeParticle(IGameDef *gamedef, LocalPlayer *player, v3s16 pos,
-               const MapNode &n, const ContentFeatures &f);
-
-       u32 getSpawnerId() const
-       {
-               for (u32 id = 0;; ++id) { // look for unused particlespawner id
-                       if (m_particle_spawners.find(id) == m_particle_spawners.end())
-                               return id;
-               }
-       }
-
-protected:
-       void addParticle(Particle* toadd);
-
-private:
-
-       void stepParticles (float dtime);
-       void stepSpawners (float dtime);
-
-       void clearAll ();
-
-       std::vector<Particle*> m_particles;
-       std::map<u32, ParticleSpawner*> m_particle_spawners;
-
-       ClientEnvironment* m_env;
-       std::mutex m_particle_list_lock;
-       std::mutex m_spawner_list_lock;
-};
index d98359d20d4dec1d1e0ead3c557997bb327fe1bc..bf89f748c6756ed42850589bacbd94e52ae7d383 100644 (file)
@@ -29,7 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "util/string.h"
 #include "server.h"
 #ifndef SERVER
-#include "client.h"
+#include "client/client.h"
 #endif
 
 
index 597883c2f32a1894453470e87167015773b974ec..f2cc9730b0cca8055da9cac1d21b130c8950b141 100644 (file)
@@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "s_client.h"
 #include "s_internal.h"
-#include "client.h"
+#include "client/client.h"
 #include "common/c_converter.h"
 #include "common/c_content.h"
 #include "s_item.h"
index e87f16ddd4eb459d40276a43d3c3c39246991e04..e9067a54c59784264c4df6b0ea52bf1f5c8101bd 100644 (file)
@@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "filesys.h"
 #include "porting.h"
 #include "server.h"
-#include "client.h"
+#include "client/client.h"
 #include "settings.h"
 
 #include <cerrno>
index 326cc6d5304af96d34cc21cae1662f2ad52a647f..462006777feb967b819a40bc6fef666130df260c 100644 (file)
@@ -21,9 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <cmath>
 #include "script/common/c_converter.h"
 #include "l_internal.h"
-#include "content_cao.h"
-#include "camera.h"
-#include "client.h"
+#include "client/content_cao.h"
+#include "client/camera.h"
+#include "client/client.h"
 
 LuaCamera::LuaCamera(Camera *m) : m_camera(m)
 {
index 72826775b9ec9bd3b9aa83fa8ad54a1152a93bb3..8a5867a32a01ec2fbef8a49b79e282b252cd763e 100644 (file)
@@ -20,10 +20,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "l_client.h"
 #include "chatmessage.h"
-#include "client.h"
+#include "client/client.h"
 #include "client/clientevent.h"
 #include "client/sound.h"
-#include "clientenvironment.h"
+#include "client/clientenvironment.h"
 #include "common/c_content.h"
 #include "common/c_converter.h"
 #include "cpp_api/s_base.h"
index 1e5149f7ab234fb2ed6e1c53981df1e2d866280b..ba2be0cb5edce498ec6e5bfcfe798e65f63508ea 100644 (file)
@@ -40,7 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "face_position_cache.h"
 #include "remoteplayer.h"
 #ifndef SERVER
-#include "client.h"
+#include "client/client.h"
 #endif
 
 struct EnumString ModApiEnvMod::es_ClearObjectsMode[] =
index 492422d92cd09d5feaf3e00c9366ebb62878bec3..7444d0e88803783fbf4f00cb237d3e2d1592c95b 100644 (file)
@@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "l_localplayer.h"
 #include "l_internal.h"
 #include "script/common/c_converter.h"
-#include "localplayer.h"
+#include "client/localplayer.h"
 #include "hud.h"
 #include "common/c_content.h"
 
index b59e790958de44f382a3577d6268a76b37760521..5fba76eb8d6dce432ecf87bf1af32d4955519f3f 100644 (file)
@@ -21,8 +21,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "lua_api/l_minimap.h"
 #include "lua_api/l_internal.h"
 #include "common/c_converter.h"
-#include "client.h"
-#include "minimap.h"
+#include "client/client.h"
+#include "client/minimap.h"
 #include "settings.h"
 
 LuaMinimap::LuaMinimap(Minimap *m) : m_minimap(m)
index 7783e5910adc38fef504ee4061b616b1932282b3..340903ebf69b69d26f0d0213d4be497858a6b829 100644 (file)
@@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "common/c_converter.h"
 #include "common/c_content.h"
 #include "server.h"
-#include "particles.h"
+#include "client/particles.h"
 
 // add_particle({pos=, velocity=, acceleration=, expirationtime=,
 //     size=, collisiondetection=, collision_removal=, object_collision=,
index d5c412556e4dd3a1283868d772bd1b2d85295193..3c7a821ca3c68d1862e76ccbaba54562f011fa44 100644 (file)
@@ -23,8 +23,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "common/c_converter.h"
 #include "lua_api/l_internal.h"
 #include "lua_api/l_object.h"
-#include "particles.h"
-#include "client.h"
+#include "client/particles.h"
+#include "client/client.h"
 #include "client/clientevent.h"
 
 int ModApiParticlesLocal::l_add_particle(lua_State *L)
index a6511ffd57690d6ef07403f26c1f6d49855240c0..86e5f2874a482f4af69c2452f74dff9bf9fe466b 100644 (file)
@@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 */
 
 #include "scripting_client.h"
-#include "client.h"
+#include "client/client.h"
 #include "cpp_api/s_internal.h"
 #include "lua_api/l_client.h"
 #include "lua_api/l_env.h"
diff --git a/src/shader.cpp b/src/shader.cpp
deleted file mode 100644 (file)
index 3b49a36..0000000
+++ /dev/null
@@ -1,873 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2013 Kahrl <kahrl@gmx.net>
-
-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 <fstream>
-#include <iterator>
-#include "shader.h"
-#include "irrlichttypes_extrabloated.h"
-#include "debug.h"
-#include "filesys.h"
-#include "util/container.h"
-#include "util/thread.h"
-#include "settings.h"
-#include <ICameraSceneNode.h>
-#include <IGPUProgrammingServices.h>
-#include <IMaterialRenderer.h>
-#include <IMaterialRendererServices.h>
-#include <IShaderConstantSetCallBack.h>
-#include "client/renderingengine.h"
-#include "EShaderTypes.h"
-#include "log.h"
-#include "gamedef.h"
-#include "client/tile.h"
-
-/*
-       A cache from shader name to shader path
-*/
-MutexedMap<std::string, std::string> g_shadername_to_path_cache;
-
-/*
-       Gets the path to a shader by first checking if the file
-         name_of_shader/filename
-       exists in shader_path and if not, using the data path.
-
-       If not found, returns "".
-
-       Utilizes a thread-safe cache.
-*/
-std::string getShaderPath(const std::string &name_of_shader,
-               const std::string &filename)
-{
-       std::string combined = name_of_shader + DIR_DELIM + filename;
-       std::string fullpath;
-       /*
-               Check from cache
-       */
-       bool incache = g_shadername_to_path_cache.get(combined, &fullpath);
-       if(incache)
-               return fullpath;
-
-       /*
-               Check from shader_path
-       */
-       std::string shader_path = g_settings->get("shader_path");
-       if (!shader_path.empty()) {
-               std::string testpath = shader_path + DIR_DELIM + combined;
-               if(fs::PathExists(testpath))
-                       fullpath = testpath;
-       }
-
-       /*
-               Check from default data directory
-       */
-       if (fullpath.empty()) {
-               std::string rel_path = std::string("client") + DIR_DELIM
-                               + "shaders" + DIR_DELIM
-                               + name_of_shader + DIR_DELIM
-                               + filename;
-               std::string testpath = porting::path_share + DIR_DELIM + rel_path;
-               if(fs::PathExists(testpath))
-                       fullpath = testpath;
-       }
-
-       // Add to cache (also an empty result is cached)
-       g_shadername_to_path_cache.set(combined, fullpath);
-
-       // Finally return it
-       return fullpath;
-}
-
-/*
-       SourceShaderCache: A cache used for storing source shaders.
-*/
-
-class SourceShaderCache
-{
-public:
-       void insert(const std::string &name_of_shader, const std::string &filename,
-               const std::string &program, bool prefer_local)
-       {
-               std::string combined = name_of_shader + DIR_DELIM + filename;
-               // Try to use local shader instead if asked to
-               if(prefer_local){
-                       std::string path = getShaderPath(name_of_shader, filename);
-                       if(!path.empty()){
-                               std::string p = readFile(path);
-                               if (!p.empty()) {
-                                       m_programs[combined] = p;
-                                       return;
-                               }
-                       }
-               }
-               m_programs[combined] = program;
-       }
-
-       std::string get(const std::string &name_of_shader,
-               const std::string &filename)
-       {
-               std::string combined = name_of_shader + DIR_DELIM + filename;
-               StringMap::iterator n = m_programs.find(combined);
-               if (n != m_programs.end())
-                       return n->second;
-               return "";
-       }
-
-       // Primarily fetches from cache, secondarily tries to read from filesystem
-       std::string getOrLoad(const std::string &name_of_shader,
-               const std::string &filename)
-       {
-               std::string combined = name_of_shader + DIR_DELIM + filename;
-               StringMap::iterator n = m_programs.find(combined);
-               if (n != m_programs.end())
-                       return n->second;
-               std::string path = getShaderPath(name_of_shader, filename);
-               if (path.empty()) {
-                       infostream << "SourceShaderCache::getOrLoad(): No path found for \""
-                               << combined << "\"" << std::endl;
-                       return "";
-               }
-               infostream << "SourceShaderCache::getOrLoad(): Loading path \""
-                       << path << "\"" << std::endl;
-               std::string p = readFile(path);
-               if (!p.empty()) {
-                       m_programs[combined] = p;
-                       return p;
-               }
-               return "";
-       }
-private:
-       StringMap m_programs;
-
-       std::string readFile(const std::string &path)
-       {
-               std::ifstream is(path.c_str(), std::ios::binary);
-               if(!is.is_open())
-                       return "";
-               std::ostringstream tmp_os;
-               tmp_os << is.rdbuf();
-               return tmp_os.str();
-       }
-};
-
-
-/*
-       ShaderCallback: Sets constants that can be used in shaders
-*/
-
-class ShaderCallback : public video::IShaderConstantSetCallBack
-{
-       std::vector<IShaderConstantSetter*> m_setters;
-
-public:
-       ShaderCallback(const std::vector<IShaderConstantSetterFactory *> &factories)
-       {
-               for (IShaderConstantSetterFactory *factory : factories)
-                       m_setters.push_back(factory->create());
-       }
-
-       ~ShaderCallback()
-       {
-               for (IShaderConstantSetter *setter : m_setters)
-                       delete setter;
-       }
-
-       virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData)
-       {
-               video::IVideoDriver *driver = services->getVideoDriver();
-               sanity_check(driver != NULL);
-
-               bool is_highlevel = userData;
-
-               for (IShaderConstantSetter *setter : m_setters)
-                       setter->onSetConstants(services, is_highlevel);
-       }
-};
-
-
-/*
-       MainShaderConstantSetter: Set basic constants required for almost everything
-*/
-
-class MainShaderConstantSetter : public IShaderConstantSetter
-{
-       CachedVertexShaderSetting<float, 16> m_world_view_proj;
-       CachedVertexShaderSetting<float, 16> m_world;
-
-public:
-       MainShaderConstantSetter() :
-               m_world_view_proj("mWorldViewProj"),
-               m_world("mWorld")
-       {}
-       ~MainShaderConstantSetter() = default;
-
-       virtual void onSetConstants(video::IMaterialRendererServices *services,
-                       bool is_highlevel)
-       {
-               video::IVideoDriver *driver = services->getVideoDriver();
-               sanity_check(driver);
-
-               // Set clip matrix
-               core::matrix4 worldViewProj;
-               worldViewProj = driver->getTransform(video::ETS_PROJECTION);
-               worldViewProj *= driver->getTransform(video::ETS_VIEW);
-               worldViewProj *= driver->getTransform(video::ETS_WORLD);
-               if (is_highlevel)
-                       m_world_view_proj.set(*reinterpret_cast<float(*)[16]>(worldViewProj.pointer()), services);
-               else
-                       services->setVertexShaderConstant(worldViewProj.pointer(), 0, 4);
-
-               // Set world matrix
-               core::matrix4 world = driver->getTransform(video::ETS_WORLD);
-               if (is_highlevel)
-                       m_world.set(*reinterpret_cast<float(*)[16]>(world.pointer()), services);
-               else
-                       services->setVertexShaderConstant(world.pointer(), 4, 4);
-
-       }
-};
-
-
-class MainShaderConstantSetterFactory : public IShaderConstantSetterFactory
-{
-public:
-       virtual IShaderConstantSetter* create()
-               { return new MainShaderConstantSetter(); }
-};
-
-
-/*
-       ShaderSource
-*/
-
-class ShaderSource : public IWritableShaderSource
-{
-public:
-       ShaderSource();
-       ~ShaderSource();
-
-       /*
-               - If shader material specified by name is found from cache,
-                 return the cached id.
-               - Otherwise generate the shader material, add to cache and return id.
-
-               The id 0 points to a null shader. Its material is EMT_SOLID.
-       */
-       u32 getShaderIdDirect(const std::string &name,
-               const u8 material_type, const u8 drawtype);
-
-       /*
-               If shader specified by the name pointed by the id doesn't
-               exist, create it, then return id.
-
-               Can be called from any thread. If called from some other thread
-               and not found in cache, the call is queued to the main thread
-               for processing.
-       */
-
-       u32 getShader(const std::string &name,
-               const u8 material_type, const u8 drawtype);
-
-       ShaderInfo getShaderInfo(u32 id);
-
-       // Processes queued shader requests from other threads.
-       // Shall be called from the main thread.
-       void processQueue();
-
-       // Insert a shader program into the cache without touching the
-       // filesystem. Shall be called from the main thread.
-       void insertSourceShader(const std::string &name_of_shader,
-               const std::string &filename, const std::string &program);
-
-       // Rebuild shaders from the current set of source shaders
-       // Shall be called from the main thread.
-       void rebuildShaders();
-
-       void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter)
-       {
-               m_setter_factories.push_back(setter);
-       }
-
-private:
-
-       // The id of the thread that is allowed to use irrlicht directly
-       std::thread::id m_main_thread;
-
-       // Cache of source shaders
-       // This should be only accessed from the main thread
-       SourceShaderCache m_sourcecache;
-
-       // A shader id is index in this array.
-       // The first position contains a dummy shader.
-       std::vector<ShaderInfo> m_shaderinfo_cache;
-       // The former container is behind this mutex
-       std::mutex m_shaderinfo_cache_mutex;
-
-       // Queued shader fetches (to be processed by the main thread)
-       RequestQueue<std::string, u32, u8, u8> m_get_shader_queue;
-
-       // Global constant setter factories
-       std::vector<IShaderConstantSetterFactory *> m_setter_factories;
-
-       // Shader callbacks
-       std::vector<ShaderCallback *> m_callbacks;
-};
-
-IWritableShaderSource *createShaderSource()
-{
-       return new ShaderSource();
-}
-
-/*
-       Generate shader given the shader name.
-*/
-ShaderInfo generate_shader(const std::string &name,
-               u8 material_type, u8 drawtype, std::vector<ShaderCallback *> &callbacks,
-               const std::vector<IShaderConstantSetterFactory *> &setter_factories,
-               SourceShaderCache *sourcecache);
-
-/*
-       Load shader programs
-*/
-void load_shaders(const std::string &name, SourceShaderCache *sourcecache,
-               video::E_DRIVER_TYPE drivertype, bool enable_shaders,
-               std::string &vertex_program, std::string &pixel_program,
-               std::string &geometry_program, bool &is_highlevel);
-
-ShaderSource::ShaderSource()
-{
-       m_main_thread = std::this_thread::get_id();
-
-       // Add a dummy ShaderInfo as the first index, named ""
-       m_shaderinfo_cache.emplace_back();
-
-       // Add main global constant setter
-       addShaderConstantSetterFactory(new MainShaderConstantSetterFactory());
-}
-
-ShaderSource::~ShaderSource()
-{
-       for (ShaderCallback *callback : m_callbacks) {
-               delete callback;
-       }
-       for (IShaderConstantSetterFactory *setter_factorie : m_setter_factories) {
-               delete setter_factorie;
-       }
-}
-
-u32 ShaderSource::getShader(const std::string &name,
-               const u8 material_type, const u8 drawtype)
-{
-       /*
-               Get shader
-       */
-
-       if (std::this_thread::get_id() == m_main_thread) {
-               return getShaderIdDirect(name, material_type, drawtype);
-       }
-
-       /*errorstream<<"getShader(): Queued: name=\""<<name<<"\""<<std::endl;*/
-
-       // We're gonna ask the result to be put into here
-
-       static ResultQueue<std::string, u32, u8, u8> result_queue;
-
-       // Throw a request in
-       m_get_shader_queue.add(name, 0, 0, &result_queue);
-
-       /* infostream<<"Waiting for shader from main thread, name=\""
-                       <<name<<"\""<<std::endl;*/
-
-       while(true) {
-               GetResult<std::string, u32, u8, u8>
-                       result = result_queue.pop_frontNoEx();
-
-               if (result.key == name) {
-                       return result.item;
-               }
-
-               errorstream << "Got shader with invalid name: " << result.key << std::endl;
-       }
-
-       infostream << "getShader(): Failed" << std::endl;
-
-       return 0;
-}
-
-/*
-       This method generates all the shaders
-*/
-u32 ShaderSource::getShaderIdDirect(const std::string &name,
-               const u8 material_type, const u8 drawtype)
-{
-       //infostream<<"getShaderIdDirect(): name=\""<<name<<"\""<<std::endl;
-
-       // Empty name means shader 0
-       if (name.empty()) {
-               infostream<<"getShaderIdDirect(): name is empty"<<std::endl;
-               return 0;
-       }
-
-       // Check if already have such instance
-       for(u32 i=0; i<m_shaderinfo_cache.size(); i++){
-               ShaderInfo *info = &m_shaderinfo_cache[i];
-               if(info->name == name && info->material_type == material_type &&
-                       info->drawtype == drawtype)
-                       return i;
-       }
-
-       /*
-               Calling only allowed from main thread
-       */
-       if (std::this_thread::get_id() != m_main_thread) {
-               errorstream<<"ShaderSource::getShaderIdDirect() "
-                               "called not from main thread"<<std::endl;
-               return 0;
-       }
-
-       ShaderInfo info = generate_shader(name, material_type, drawtype,
-                       m_callbacks, m_setter_factories, &m_sourcecache);
-
-       /*
-               Add shader to caches (add dummy shaders too)
-       */
-
-       MutexAutoLock lock(m_shaderinfo_cache_mutex);
-
-       u32 id = m_shaderinfo_cache.size();
-       m_shaderinfo_cache.push_back(info);
-
-       infostream<<"getShaderIdDirect(): "
-                       <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;
-
-       return id;
-}
-
-
-ShaderInfo ShaderSource::getShaderInfo(u32 id)
-{
-       MutexAutoLock lock(m_shaderinfo_cache_mutex);
-
-       if(id >= m_shaderinfo_cache.size())
-               return ShaderInfo();
-
-       return m_shaderinfo_cache[id];
-}
-
-void ShaderSource::processQueue()
-{
-
-
-}
-
-void ShaderSource::insertSourceShader(const std::string &name_of_shader,
-               const std::string &filename, const std::string &program)
-{
-       /*infostream<<"ShaderSource::insertSourceShader(): "
-                       "name_of_shader=\""<<name_of_shader<<"\", "
-                       "filename=\""<<filename<<"\""<<std::endl;*/
-
-       sanity_check(std::this_thread::get_id() == m_main_thread);
-
-       m_sourcecache.insert(name_of_shader, filename, program, true);
-}
-
-void ShaderSource::rebuildShaders()
-{
-       MutexAutoLock lock(m_shaderinfo_cache_mutex);
-
-       /*// Oh well... just clear everything, they'll load sometime.
-       m_shaderinfo_cache.clear();
-       m_name_to_id.clear();*/
-
-       /*
-               FIXME: Old shader materials can't be deleted in Irrlicht,
-               or can they?
-               (This would be nice to do in the destructor too)
-       */
-
-       // Recreate shaders
-       for (ShaderInfo &i : m_shaderinfo_cache) {
-               ShaderInfo *info = &i;
-               if (!info->name.empty()) {
-                       *info = generate_shader(info->name, info->material_type,
-                                       info->drawtype, m_callbacks,
-                                       m_setter_factories, &m_sourcecache);
-               }
-       }
-}
-
-
-ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtype,
-               std::vector<ShaderCallback *> &callbacks,
-               const std::vector<IShaderConstantSetterFactory *> &setter_factories,
-               SourceShaderCache *sourcecache)
-{
-       ShaderInfo shaderinfo;
-       shaderinfo.name = name;
-       shaderinfo.material_type = material_type;
-       shaderinfo.drawtype = drawtype;
-       shaderinfo.material = video::EMT_SOLID;
-       switch (material_type) {
-       case TILE_MATERIAL_OPAQUE:
-       case TILE_MATERIAL_LIQUID_OPAQUE:
-               shaderinfo.base_material = video::EMT_SOLID;
-               break;
-       case TILE_MATERIAL_ALPHA:
-       case TILE_MATERIAL_LIQUID_TRANSPARENT:
-               shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
-               break;
-       case TILE_MATERIAL_BASIC:
-       case TILE_MATERIAL_WAVING_LEAVES:
-       case TILE_MATERIAL_WAVING_PLANTS:
-               shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
-               break;
-       }
-
-       bool enable_shaders = g_settings->getBool("enable_shaders");
-       if (!enable_shaders)
-               return shaderinfo;
-
-       video::IVideoDriver *driver = RenderingEngine::get_video_driver();
-
-       video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices();
-       if(!gpu){
-               errorstream<<"generate_shader(): "
-                               "failed to generate \""<<name<<"\", "
-                               "GPU programming not supported."
-                               <<std::endl;
-               return shaderinfo;
-       }
-
-       // Choose shader language depending on driver type and settings
-       // Then load shaders
-       std::string vertex_program;
-       std::string pixel_program;
-       std::string geometry_program;
-       bool is_highlevel;
-       load_shaders(name, sourcecache, driver->getDriverType(),
-                       enable_shaders, vertex_program, pixel_program,
-                       geometry_program, is_highlevel);
-       // Check hardware/driver support
-       if (!vertex_program.empty() &&
-                       !driver->queryFeature(video::EVDF_VERTEX_SHADER_1_1) &&
-                       !driver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1)){
-               infostream<<"generate_shader(): vertex shaders disabled "
-                               "because of missing driver/hardware support."
-                               <<std::endl;
-               vertex_program = "";
-       }
-       if (!pixel_program.empty() &&
-                       !driver->queryFeature(video::EVDF_PIXEL_SHADER_1_1) &&
-                       !driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1)){
-               infostream<<"generate_shader(): pixel shaders disabled "
-                               "because of missing driver/hardware support."
-                               <<std::endl;
-               pixel_program = "";
-       }
-       if (!geometry_program.empty() &&
-                       !driver->queryFeature(video::EVDF_GEOMETRY_SHADER)){
-               infostream<<"generate_shader(): geometry shaders disabled "
-                               "because of missing driver/hardware support."
-                               <<std::endl;
-               geometry_program = "";
-       }
-
-       // If no shaders are used, don't make a separate material type
-       if (vertex_program.empty() && pixel_program.empty() && geometry_program.empty())
-               return shaderinfo;
-
-       // Create shaders header
-       std::string shaders_header = "#version 120\n";
-
-       static const char* drawTypes[] = {
-               "NDT_NORMAL",
-               "NDT_AIRLIKE",
-               "NDT_LIQUID",
-               "NDT_FLOWINGLIQUID",
-               "NDT_GLASSLIKE",
-               "NDT_ALLFACES",
-               "NDT_ALLFACES_OPTIONAL",
-               "NDT_TORCHLIKE",
-               "NDT_SIGNLIKE",
-               "NDT_PLANTLIKE",
-               "NDT_FENCELIKE",
-               "NDT_RAILLIKE",
-               "NDT_NODEBOX",
-               "NDT_GLASSLIKE_FRAMED",
-               "NDT_FIRELIKE",
-               "NDT_GLASSLIKE_FRAMED_OPTIONAL",
-               "NDT_PLANTLIKE_ROOTED",
-       };
-
-       for (int i = 0; i < 14; i++){
-               shaders_header += "#define ";
-               shaders_header += drawTypes[i];
-               shaders_header += " ";
-               shaders_header += itos(i);
-               shaders_header += "\n";
-       }
-
-       static const char* materialTypes[] = {
-               "TILE_MATERIAL_BASIC",
-               "TILE_MATERIAL_ALPHA",
-               "TILE_MATERIAL_LIQUID_TRANSPARENT",
-               "TILE_MATERIAL_LIQUID_OPAQUE",
-               "TILE_MATERIAL_WAVING_LEAVES",
-               "TILE_MATERIAL_WAVING_PLANTS",
-               "TILE_MATERIAL_OPAQUE"
-       };
-
-       for (int i = 0; i < 7; i++){
-               shaders_header += "#define ";
-               shaders_header += materialTypes[i];
-               shaders_header += " ";
-               shaders_header += itos(i);
-               shaders_header += "\n";
-       }
-
-       shaders_header += "#define MATERIAL_TYPE ";
-       shaders_header += itos(material_type);
-       shaders_header += "\n";
-       shaders_header += "#define DRAW_TYPE ";
-       shaders_header += itos(drawtype);
-       shaders_header += "\n";
-
-       if (g_settings->getBool("generate_normalmaps")) {
-               shaders_header += "#define GENERATE_NORMALMAPS 1\n";
-       } else {
-               shaders_header += "#define GENERATE_NORMALMAPS 0\n";
-       }
-       shaders_header += "#define NORMALMAPS_STRENGTH ";
-       shaders_header += ftos(g_settings->getFloat("normalmaps_strength"));
-       shaders_header += "\n";
-       float sample_step;
-       int smooth = (int)g_settings->getFloat("normalmaps_smooth");
-       switch (smooth){
-       case 0:
-               sample_step = 0.0078125; // 1.0 / 128.0
-               break;
-       case 1:
-               sample_step = 0.00390625; // 1.0 / 256.0
-               break;
-       case 2:
-               sample_step = 0.001953125; // 1.0 / 512.0
-               break;
-       default:
-               sample_step = 0.0078125;
-               break;
-       }
-       shaders_header += "#define SAMPLE_STEP ";
-       shaders_header += ftos(sample_step);
-       shaders_header += "\n";
-
-       if (g_settings->getBool("enable_bumpmapping"))
-               shaders_header += "#define ENABLE_BUMPMAPPING\n";
-
-       if (g_settings->getBool("enable_parallax_occlusion")){
-               int mode = g_settings->getFloat("parallax_occlusion_mode");
-               float scale = g_settings->getFloat("parallax_occlusion_scale");
-               float bias = g_settings->getFloat("parallax_occlusion_bias");
-               int iterations = g_settings->getFloat("parallax_occlusion_iterations");
-               shaders_header += "#define ENABLE_PARALLAX_OCCLUSION\n";
-               shaders_header += "#define PARALLAX_OCCLUSION_MODE ";
-               shaders_header += itos(mode);
-               shaders_header += "\n";
-               shaders_header += "#define PARALLAX_OCCLUSION_SCALE ";
-               shaders_header += ftos(scale);
-               shaders_header += "\n";
-               shaders_header += "#define PARALLAX_OCCLUSION_BIAS ";
-               shaders_header += ftos(bias);
-               shaders_header += "\n";
-               shaders_header += "#define PARALLAX_OCCLUSION_ITERATIONS ";
-               shaders_header += itos(iterations);
-               shaders_header += "\n";
-       }
-
-       shaders_header += "#define USE_NORMALMAPS ";
-       if (g_settings->getBool("enable_bumpmapping") || g_settings->getBool("enable_parallax_occlusion"))
-               shaders_header += "1\n";
-       else
-               shaders_header += "0\n";
-
-       if (g_settings->getBool("enable_waving_water")){
-               shaders_header += "#define ENABLE_WAVING_WATER 1\n";
-               shaders_header += "#define WATER_WAVE_HEIGHT ";
-               shaders_header += ftos(g_settings->getFloat("water_wave_height"));
-               shaders_header += "\n";
-               shaders_header += "#define WATER_WAVE_LENGTH ";
-               shaders_header += ftos(g_settings->getFloat("water_wave_length"));
-               shaders_header += "\n";
-               shaders_header += "#define WATER_WAVE_SPEED ";
-               shaders_header += ftos(g_settings->getFloat("water_wave_speed"));
-               shaders_header += "\n";
-       } else{
-               shaders_header += "#define ENABLE_WAVING_WATER 0\n";
-       }
-
-       shaders_header += "#define ENABLE_WAVING_LEAVES ";
-       if (g_settings->getBool("enable_waving_leaves"))
-               shaders_header += "1\n";
-       else
-               shaders_header += "0\n";
-
-       shaders_header += "#define ENABLE_WAVING_PLANTS ";
-       if (g_settings->getBool("enable_waving_plants"))
-               shaders_header += "1\n";
-       else
-               shaders_header += "0\n";
-
-       if (g_settings->getBool("tone_mapping"))
-               shaders_header += "#define ENABLE_TONE_MAPPING\n";
-
-       shaders_header += "#define FOG_START ";
-       shaders_header += ftos(rangelim(g_settings->getFloat("fog_start"), 0.0f, 0.99f));
-       shaders_header += "\n";
-
-       // Call addHighLevelShaderMaterial() or addShaderMaterial()
-       const c8* vertex_program_ptr = 0;
-       const c8* pixel_program_ptr = 0;
-       const c8* geometry_program_ptr = 0;
-       if (!vertex_program.empty()) {
-               vertex_program = shaders_header + vertex_program;
-               vertex_program_ptr = vertex_program.c_str();
-       }
-       if (!pixel_program.empty()) {
-               pixel_program = shaders_header + pixel_program;
-               pixel_program_ptr = pixel_program.c_str();
-       }
-       if (!geometry_program.empty()) {
-               geometry_program = shaders_header + geometry_program;
-               geometry_program_ptr = geometry_program.c_str();
-       }
-       ShaderCallback *cb = new ShaderCallback(setter_factories);
-       s32 shadermat = -1;
-       if(is_highlevel){
-               infostream<<"Compiling high level shaders for "<<name<<std::endl;
-               shadermat = gpu->addHighLevelShaderMaterial(
-                       vertex_program_ptr,   // Vertex shader program
-                       "vertexMain",         // Vertex shader entry point
-                       video::EVST_VS_1_1,   // Vertex shader version
-                       pixel_program_ptr,    // Pixel shader program
-                       "pixelMain",          // Pixel shader entry point
-                       video::EPST_PS_1_2,   // Pixel shader version
-                       geometry_program_ptr, // Geometry shader program
-                       "geometryMain",       // Geometry shader entry point
-                       video::EGST_GS_4_0,   // Geometry shader version
-                       scene::EPT_TRIANGLES,      // Geometry shader input
-                       scene::EPT_TRIANGLE_STRIP, // Geometry shader output
-                       0,                         // Support maximum number of vertices
-                       cb, // Set-constant callback
-                       shaderinfo.base_material,  // Base material
-                       1                          // Userdata passed to callback
-                       );
-               if(shadermat == -1){
-                       errorstream<<"generate_shader(): "
-                                       "failed to generate \""<<name<<"\", "
-                                       "addHighLevelShaderMaterial failed."
-                                       <<std::endl;
-                       dumpShaderProgram(warningstream, "Vertex", vertex_program);
-                       dumpShaderProgram(warningstream, "Pixel", pixel_program);
-                       dumpShaderProgram(warningstream, "Geometry", geometry_program);
-                       delete cb;
-                       return shaderinfo;
-               }
-       }
-       else{
-               infostream<<"Compiling assembly shaders for "<<name<<std::endl;
-               shadermat = gpu->addShaderMaterial(
-                       vertex_program_ptr,   // Vertex shader program
-                       pixel_program_ptr,    // Pixel shader program
-                       cb, // Set-constant callback
-                       shaderinfo.base_material,  // Base material
-                       0                     // Userdata passed to callback
-                       );
-
-               if(shadermat == -1){
-                       errorstream<<"generate_shader(): "
-                                       "failed to generate \""<<name<<"\", "
-                                       "addShaderMaterial failed."
-                                       <<std::endl;
-                       dumpShaderProgram(warningstream, "Vertex", vertex_program);
-                       dumpShaderProgram(warningstream,"Pixel", pixel_program);
-                       delete cb;
-                       return shaderinfo;
-               }
-       }
-       callbacks.push_back(cb);
-
-       // HACK, TODO: investigate this better
-       // Grab the material renderer once more so minetest doesn't crash on exit
-       driver->getMaterialRenderer(shadermat)->grab();
-
-       // Apply the newly created material type
-       shaderinfo.material = (video::E_MATERIAL_TYPE) shadermat;
-       return shaderinfo;
-}
-
-void load_shaders(const std::string &name, SourceShaderCache *sourcecache,
-               video::E_DRIVER_TYPE drivertype, bool enable_shaders,
-               std::string &vertex_program, std::string &pixel_program,
-               std::string &geometry_program, bool &is_highlevel)
-{
-       vertex_program = "";
-       pixel_program = "";
-       geometry_program = "";
-       is_highlevel = false;
-
-       if(enable_shaders){
-               // Look for high level shaders
-               if(drivertype == video::EDT_DIRECT3D9){
-                       // Direct3D 9: HLSL
-                       // (All shaders in one file)
-                       vertex_program = sourcecache->getOrLoad(name, "d3d9.hlsl");
-                       pixel_program = vertex_program;
-                       geometry_program = vertex_program;
-               }
-               else if(drivertype == video::EDT_OPENGL){
-                       // OpenGL: GLSL
-                       vertex_program = sourcecache->getOrLoad(name, "opengl_vertex.glsl");
-                       pixel_program = sourcecache->getOrLoad(name, "opengl_fragment.glsl");
-                       geometry_program = sourcecache->getOrLoad(name, "opengl_geometry.glsl");
-               }
-               if (!vertex_program.empty() || !pixel_program.empty() || !geometry_program.empty()){
-                       is_highlevel = true;
-                       return;
-               }
-       }
-
-}
-
-void dumpShaderProgram(std::ostream &output_stream,
-               const std::string &program_type, const std::string &program)
-{
-       output_stream << program_type << " shader program:" << std::endl <<
-               "----------------------------------" << std::endl;
-       size_t pos = 0;
-       size_t prev = 0;
-       s16 line = 1;
-       while ((pos = program.find('\n', prev)) != std::string::npos) {
-               output_stream << line++ << ": "<< program.substr(prev, pos - prev) <<
-                       std::endl;
-               prev = pos + 1;
-       }
-       output_stream << line << ": " << program.substr(prev) << std::endl <<
-               "End of " << program_type << " shader program." << std::endl <<
-               " " << std::endl;
-}
diff --git a/src/shader.h b/src/shader.h
deleted file mode 100644 (file)
index 583c776..0000000
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2013 Kahrl <kahrl@gmx.net>
-
-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.
-*/
-
-#pragma once
-
-#include <IMaterialRendererServices.h>
-#include "irrlichttypes_bloated.h"
-#include <string>
-
-class IGameDef;
-
-/*
-       shader.{h,cpp}: Shader handling stuff.
-*/
-
-/*
-       Gets the path to a shader by first checking if the file
-         name_of_shader/filename
-       exists in shader_path and if not, using the data path.
-
-       If not found, returns "".
-
-       Utilizes a thread-safe cache.
-*/
-std::string getShaderPath(const std::string &name_of_shader,
-               const std::string &filename);
-
-struct ShaderInfo {
-       std::string name = "";
-       video::E_MATERIAL_TYPE base_material = video::EMT_SOLID;
-       video::E_MATERIAL_TYPE material = video::EMT_SOLID;
-       u8 drawtype = 0;
-       u8 material_type = 0;
-
-       ShaderInfo() = default;
-       virtual ~ShaderInfo() = default;
-};
-
-/*
-       Setter of constants for shaders
-*/
-
-namespace irr { namespace video {
-       class IMaterialRendererServices;
-} }
-
-
-class IShaderConstantSetter {
-public:
-       virtual ~IShaderConstantSetter() = default;
-       virtual void onSetConstants(video::IMaterialRendererServices *services,
-                       bool is_highlevel) = 0;
-};
-
-
-class IShaderConstantSetterFactory {
-public:
-       virtual ~IShaderConstantSetterFactory() = default;
-       virtual IShaderConstantSetter* create() = 0;
-};
-
-
-template <typename T, std::size_t count=1>
-class CachedShaderSetting {
-       const char *m_name;
-       T m_sent[count];
-       bool has_been_set = false;
-       bool is_pixel;
-protected:
-       CachedShaderSetting(const char *name, bool is_pixel) :
-               m_name(name), is_pixel(is_pixel)
-       {}
-public:
-       void set(const T value[count], video::IMaterialRendererServices *services)
-       {
-               if (has_been_set && std::equal(m_sent, m_sent + count, value))
-                       return;
-               if (is_pixel)
-                       services->setPixelShaderConstant(m_name, value, count);
-               else
-                       services->setVertexShaderConstant(m_name, value, count);
-               std::copy(value, value + count, m_sent);
-               has_been_set = true;
-       }
-};
-
-template <typename T, std::size_t count = 1>
-class CachedPixelShaderSetting : public CachedShaderSetting<T, count> {
-public:
-       CachedPixelShaderSetting(const char *name) :
-               CachedShaderSetting<T, count>(name, true){}
-};
-
-template <typename T, std::size_t count = 1>
-class CachedVertexShaderSetting : public CachedShaderSetting<T, count> {
-public:
-       CachedVertexShaderSetting(const char *name) :
-               CachedShaderSetting<T, count>(name, false){}
-};
-
-
-/*
-       ShaderSource creates and caches shaders.
-*/
-
-class IShaderSource {
-public:
-       IShaderSource() = default;
-       virtual ~IShaderSource() = default;
-
-       virtual u32 getShaderIdDirect(const std::string &name,
-               const u8 material_type, const u8 drawtype){return 0;}
-       virtual ShaderInfo getShaderInfo(u32 id){return ShaderInfo();}
-       virtual u32 getShader(const std::string &name,
-               const u8 material_type, const u8 drawtype){return 0;}
-};
-
-class IWritableShaderSource : public IShaderSource {
-public:
-       IWritableShaderSource() = default;
-       virtual ~IWritableShaderSource() = default;
-
-       virtual u32 getShaderIdDirect(const std::string &name,
-               const u8 material_type, const u8 drawtype){return 0;}
-       virtual ShaderInfo getShaderInfo(u32 id){return ShaderInfo();}
-       virtual u32 getShader(const std::string &name,
-               const u8 material_type, const u8 drawtype){return 0;}
-
-       virtual void processQueue()=0;
-       virtual void insertSourceShader(const std::string &name_of_shader,
-               const std::string &filename, const std::string &program)=0;
-       virtual void rebuildShaders()=0;
-       virtual void addShaderConstantSetterFactory(IShaderConstantSetterFactory *setter) = 0;
-};
-
-IWritableShaderSource *createShaderSource();
-
-void dumpShaderProgram(std::ostream &output_stream,
-       const std::string &program_type, const std::string &program);
diff --git a/src/sky.cpp b/src/sky.cpp
deleted file mode 100644 (file)
index faf12ba..0000000
+++ /dev/null
@@ -1,755 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 "sky.h"
-#include "IVideoDriver.h"
-#include "ISceneManager.h"
-#include "ICameraSceneNode.h"
-#include "S3DVertex.h"
-#include "client/tile.h"
-#include "noise.h"  // easeCurve
-#include "profiler.h"
-#include "util/numeric.h"
-#include <cmath>
-#include "client/renderingengine.h"
-#include "settings.h"
-#include "camera.h"  // CameraModes
-
-
-Sky::Sky(s32 id, ITextureSource *tsrc):
-               scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(),
-                       RenderingEngine::get_scene_manager(), id)
-{
-       setAutomaticCulling(scene::EAC_OFF);
-       m_box.MaxEdge.set(0, 0, 0);
-       m_box.MinEdge.set(0, 0, 0);
-
-       // Create material
-
-       video::SMaterial mat;
-       mat.Lighting = false;
-#ifdef __ANDROID__
-       mat.ZBuffer = video::ECFN_DISABLED;
-#else
-       mat.ZBuffer = video::ECFN_NEVER;
-#endif
-       mat.ZWriteEnable = false;
-       mat.AntiAliasing = 0;
-       mat.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE;
-       mat.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE;
-       mat.BackfaceCulling = false;
-
-       m_materials[0] = mat;
-
-       m_materials[1] = mat;
-       //m_materials[1].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
-       m_materials[1].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
-
-       m_materials[2] = mat;
-       m_materials[2].setTexture(0, tsrc->getTextureForMesh("sunrisebg.png"));
-       m_materials[2].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
-       //m_materials[2].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR;
-
-       m_sun_texture = tsrc->isKnownSourceImage("sun.png") ?
-               tsrc->getTextureForMesh("sun.png") : NULL;
-       m_moon_texture = tsrc->isKnownSourceImage("moon.png") ?
-               tsrc->getTextureForMesh("moon.png") : NULL;
-       m_sun_tonemap = tsrc->isKnownSourceImage("sun_tonemap.png") ?
-               tsrc->getTexture("sun_tonemap.png") : NULL;
-       m_moon_tonemap = tsrc->isKnownSourceImage("moon_tonemap.png") ?
-               tsrc->getTexture("moon_tonemap.png") : NULL;
-
-       if (m_sun_texture) {
-               m_materials[3] = mat;
-               m_materials[3].setTexture(0, m_sun_texture);
-               m_materials[3].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
-               if (m_sun_tonemap)
-                       m_materials[3].Lighting = true;
-       }
-
-       if (m_moon_texture) {
-               m_materials[4] = mat;
-               m_materials[4].setTexture(0, m_moon_texture);
-               m_materials[4].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
-               if (m_moon_tonemap)
-                       m_materials[4].Lighting = true;
-       }
-
-       for (v3f &star : m_stars) {
-               star = v3f(
-                       myrand_range(-10000, 10000),
-                       myrand_range(-10000, 10000),
-                       myrand_range(-10000, 10000)
-               );
-               star.normalize();
-       }
-
-       m_directional_colored_fog = g_settings->getBool("directional_colored_fog");
-}
-
-
-void Sky::OnRegisterSceneNode()
-{
-       if (IsVisible)
-               SceneManager->registerNodeForRendering(this, scene::ESNRP_SKY_BOX);
-
-       scene::ISceneNode::OnRegisterSceneNode();
-}
-
-
-void Sky::render()
-{
-       if (!m_visible)
-               return;
-
-       video::IVideoDriver* driver = SceneManager->getVideoDriver();
-       scene::ICameraSceneNode* camera = SceneManager->getActiveCamera();
-
-       if (!camera || !driver)
-               return;
-
-       ScopeProfiler sp(g_profiler, "Sky::render()", SPT_AVG);
-
-       // Draw perspective skybox
-
-       core::matrix4 translate(AbsoluteTransformation);
-       translate.setTranslation(camera->getAbsolutePosition());
-
-       // Draw the sky box between the near and far clip plane
-       const f32 viewDistance = (camera->getNearValue() + camera->getFarValue()) * 0.5f;
-       core::matrix4 scale;
-       scale.setScale(core::vector3df(viewDistance, viewDistance, viewDistance));
-
-       driver->setTransform(video::ETS_WORLD, translate * scale);
-
-       if (m_sunlight_seen) {
-               float sunsize = 0.07;
-               video::SColorf suncolor_f(1, 1, 0, 1);
-               //suncolor_f.r = 1;
-               //suncolor_f.g = MYMAX(0.3, MYMIN(1.0, 0.7 + m_time_brightness * 0.5));
-               //suncolor_f.b = MYMAX(0.0, m_brightness * 0.95);
-               video::SColorf suncolor2_f(1, 1, 1, 1);
-               // The values below were probably meant to be suncolor2_f instead of a
-               // reassignment of suncolor_f. However, the resulting colour was chosen
-               // and is our long-running classic colour. So preserve, but comment-out
-               // the unnecessary first assignments above.
-               suncolor_f.r = 1;
-               suncolor_f.g = MYMAX(0.3, MYMIN(1.0, 0.85 + m_time_brightness * 0.5));
-               suncolor_f.b = MYMAX(0.0, m_brightness);
-
-               float moonsize = 0.04;
-               video::SColorf mooncolor_f(0.50, 0.57, 0.65, 1);
-               video::SColorf mooncolor2_f(0.85, 0.875, 0.9, 1);
-
-               float nightlength = 0.415;
-               float wn = nightlength / 2;
-               float wicked_time_of_day = 0;
-               if (m_time_of_day > wn && m_time_of_day < 1.0 - wn)
-                       wicked_time_of_day = (m_time_of_day - wn) / (1.0 - wn * 2) * 0.5 + 0.25;
-               else if (m_time_of_day < 0.5)
-                       wicked_time_of_day = m_time_of_day / wn * 0.25;
-               else
-                       wicked_time_of_day = 1.0 - ((1.0 - m_time_of_day) / wn * 0.25);
-               /*std::cerr<<"time_of_day="<<m_time_of_day<<" -> "
-                               <<"wicked_time_of_day="<<wicked_time_of_day<<std::endl;*/
-
-               video::SColor suncolor = suncolor_f.toSColor();
-               video::SColor suncolor2 = suncolor2_f.toSColor();
-               video::SColor mooncolor = mooncolor_f.toSColor();
-               video::SColor mooncolor2 = mooncolor2_f.toSColor();
-
-               // Calculate offset normalized to the X dimension of a 512x1 px tonemap
-               float offset = (1.0 - fabs(sin((m_time_of_day - 0.5) * irr::core::PI))) * 511;
-
-               if (m_sun_tonemap) {
-                       u8 * texels = (u8 *)m_sun_tonemap->lock();
-                       video::SColor* texel = (video::SColor *)(texels + (u32)offset * 4);
-                       video::SColor texel_color (255, texel->getRed(),
-                               texel->getGreen(), texel->getBlue());
-                       m_sun_tonemap->unlock();
-                       m_materials[3].EmissiveColor = texel_color;
-               }
-
-               if (m_moon_tonemap) {
-                       u8 * texels = (u8 *)m_moon_tonemap->lock();
-                       video::SColor* texel = (video::SColor *)(texels + (u32)offset * 4);
-                       video::SColor texel_color (255, texel->getRed(),
-                               texel->getGreen(), texel->getBlue());
-                       m_moon_tonemap->unlock();
-                       m_materials[4].EmissiveColor = texel_color;
-               }
-
-               const f32 t = 1.0f;
-               const f32 o = 0.0f;
-               static const u16 indices[4] = {0, 1, 2, 3};
-               video::S3DVertex vertices[4];
-
-               driver->setMaterial(m_materials[1]);
-
-               video::SColor cloudyfogcolor = m_bgcolor;
-
-               // Draw far cloudy fog thing blended with skycolor
-               for (u32 j = 0; j < 4; j++) {
-                       video::SColor c = cloudyfogcolor.getInterpolated(m_skycolor, 0.45);
-                       vertices[0] = video::S3DVertex(-1, 0.08, -1, 0, 0, 1, c, t, t);
-                       vertices[1] = video::S3DVertex( 1, 0.08, -1, 0, 0, 1, c, o, t);
-                       vertices[2] = video::S3DVertex( 1, 0.12, -1, 0, 0, 1, c, o, o);
-                       vertices[3] = video::S3DVertex(-1, 0.12, -1, 0, 0, 1, c, t, o);
-                       for (video::S3DVertex &vertex : vertices) {
-                               if (j == 0)
-                                       // Don't switch
-                                       {}
-                               else if (j == 1)
-                                       // Switch from -Z (south) to +X (east)
-                                       vertex.Pos.rotateXZBy(90);
-                               else if (j == 2)
-                                       // Switch from -Z (south) to -X (west)
-                                       vertex.Pos.rotateXZBy(-90);
-                               else
-                                       // Switch from -Z (south) to +Z (north)
-                                       vertex.Pos.rotateXZBy(-180);
-                       }
-                       driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-               }
-
-               // Draw far cloudy fog thing
-               for (u32 j = 0; j < 4; j++) {
-                       video::SColor c = cloudyfogcolor;
-                       vertices[0] = video::S3DVertex(-1, -1.0, -1, 0, 0, 1, c, t, t);
-                       vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 0, 1, c, o, t);
-                       vertices[2] = video::S3DVertex( 1, 0.08, -1, 0, 0, 1, c, o, o);
-                       vertices[3] = video::S3DVertex(-1, 0.08, -1, 0, 0, 1, c, t, o);
-                       for (video::S3DVertex &vertex : vertices) {
-                               if (j == 0)
-                                       // Don't switch
-                                       {}
-                               else if (j == 1)
-                                       // Switch from -Z (south) to +X (east)
-                                       vertex.Pos.rotateXZBy(90);
-                               else if (j == 2)
-                                       // Switch from -Z (south) to -X (west)
-                                       vertex.Pos.rotateXZBy(-90);
-                               else
-                                       // Switch from -Z (south) to +Z (north)
-                                       vertex.Pos.rotateXZBy(-180);
-                       }
-                       driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-               }
-
-               // Draw bottom far cloudy fog thing
-               video::SColor c = cloudyfogcolor;
-               vertices[0] = video::S3DVertex(-1, -1.0, -1, 0, 1, 0, c, t, t);
-               vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 1, 0, c, o, t);
-               vertices[2] = video::S3DVertex( 1, -1.0, 1, 0, 1, 0, c, o, o);
-               vertices[3] = video::S3DVertex(-1, -1.0, 1, 0, 1, 0, c, t, o);
-               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-
-               // If sun, moon and stars are (temporarily) disabled, abort here
-               if (!m_bodies_visible)
-                       return;
-
-               driver->setMaterial(m_materials[2]);
-
-               // Draw sunrise/sunset horizon glow texture (textures/base/pack/sunrisebg.png)
-               {
-                       float mid1 = 0.25;
-                       float mid = wicked_time_of_day < 0.5 ? mid1 : (1.0 - mid1);
-                       float a_ = 1.0f - std::fabs(wicked_time_of_day - mid) * 35.0f;
-                       float a = easeCurve(MYMAX(0, MYMIN(1, a_)));
-                       //std::cerr<<"a_="<<a_<<" a="<<a<<std::endl;
-                       video::SColor c(255, 255, 255, 255);
-                       float y = -(1.0 - a) * 0.22;
-                       vertices[0] = video::S3DVertex(-1, -0.05 + y, -1, 0, 0, 1, c, t, t);
-                       vertices[1] = video::S3DVertex( 1, -0.05 + y, -1, 0, 0, 1, c, o, t);
-                       vertices[2] = video::S3DVertex( 1,   0.2 + y, -1, 0, 0, 1, c, o, o);
-                       vertices[3] = video::S3DVertex(-1,   0.2 + y, -1, 0, 0, 1, c, t, o);
-                       for (video::S3DVertex &vertex : vertices) {
-                               if (wicked_time_of_day < 0.5)
-                                       // Switch from -Z (south) to +X (east)
-                                       vertex.Pos.rotateXZBy(90);
-                               else
-                                       // Switch from -Z (south) to -X (west)
-                                       vertex.Pos.rotateXZBy(-90);
-                       }
-                       driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-               }
-
-               // Draw stars
-               do {
-                       driver->setMaterial(m_materials[1]);
-                       float starbrightness = MYMAX(0, MYMIN(1,
-                               (0.285 - fabs(wicked_time_of_day < 0.5 ?
-                               wicked_time_of_day : (1.0 - wicked_time_of_day))) * 10));
-                       float f = starbrightness;
-                       float d = 0.007/2;
-                       video::SColor starcolor(255, f * 90, f * 90, f * 90);
-                       if (starcolor.getBlue() < m_skycolor.getBlue())
-                               break;
-#ifdef __ANDROID__
-                       u16 indices[SKY_STAR_COUNT * 3];
-                       video::S3DVertex vertices[SKY_STAR_COUNT * 3];
-                       for (u32 i = 0; i < SKY_STAR_COUNT; i++) {
-                               indices[i * 3 + 0] = i * 3 + 0;
-                               indices[i * 3 + 1] = i * 3 + 1;
-                               indices[i * 3 + 2] = i * 3 + 2;
-                               v3f r = m_stars[i];
-                               core::CMatrix4<f32> a;
-                               a.buildRotateFromTo(v3f(0, 1, 0), r);
-                               v3f p = v3f(-d, 1, -d);
-                               v3f p1 = v3f(d, 1, 0);
-                               v3f p2 = v3f(-d, 1, d);
-                               a.rotateVect(p);
-                               a.rotateVect(p1);
-                               a.rotateVect(p2);
-                               p.rotateXYBy(wicked_time_of_day * 360 - 90);
-                               p1.rotateXYBy(wicked_time_of_day * 360 - 90);
-                               p2.rotateXYBy(wicked_time_of_day * 360 - 90);
-                               vertices[i * 3 + 0].Pos = p;
-                               vertices[i * 3 + 0].Color = starcolor;
-                               vertices[i * 3 + 1].Pos = p1;
-                               vertices[i * 3 + 1].Color = starcolor;
-                               vertices[i * 3 + 2].Pos = p2;
-                               vertices[i * 3 + 2].Color = starcolor;
-                       }
-                       driver->drawIndexedTriangleList(vertices, SKY_STAR_COUNT * 3,
-                                       indices, SKY_STAR_COUNT);
-#else
-                       u16 indices[SKY_STAR_COUNT * 4];
-                       video::S3DVertex vertices[SKY_STAR_COUNT * 4];
-                       for (u32 i = 0; i < SKY_STAR_COUNT; i++) {
-                               indices[i * 4 + 0] = i * 4 + 0;
-                               indices[i * 4 + 1] = i * 4 + 1;
-                               indices[i * 4 + 2] = i * 4 + 2;
-                               indices[i * 4 + 3] = i * 4 + 3;
-                               v3f r = m_stars[i];
-                               core::CMatrix4<f32> a;
-                               a.buildRotateFromTo(v3f(0, 1, 0), r);
-                               v3f p = v3f(-d, 1, -d);
-                               v3f p1 = v3f( d, 1, -d);
-                               v3f p2 = v3f( d, 1, d);
-                               v3f p3 = v3f(-d, 1, d);
-                               a.rotateVect(p);
-                               a.rotateVect(p1);
-                               a.rotateVect(p2);
-                               a.rotateVect(p3);
-                               p.rotateXYBy(wicked_time_of_day * 360 - 90);
-                               p1.rotateXYBy(wicked_time_of_day * 360 - 90);
-                               p2.rotateXYBy(wicked_time_of_day * 360 - 90);
-                               p3.rotateXYBy(wicked_time_of_day * 360 - 90);
-                               vertices[i * 4 + 0].Pos = p;
-                               vertices[i * 4 + 0].Color = starcolor;
-                               vertices[i * 4 + 1].Pos = p1;
-                               vertices[i * 4 + 1].Color = starcolor;
-                               vertices[i * 4 + 2].Pos = p2;
-                               vertices[i * 4 + 2].Color = starcolor;
-                               vertices[i * 4 + 3].Pos = p3;
-                               vertices[i * 4 + 3].Color = starcolor;
-                       }
-                       driver->drawVertexPrimitiveList(vertices, SKY_STAR_COUNT * 4,
-                               indices, SKY_STAR_COUNT, video::EVT_STANDARD,
-                               scene::EPT_QUADS, video::EIT_16BIT);
-#endif
-               } while(false);
-
-               // Draw sun
-               if (wicked_time_of_day > 0.15 && wicked_time_of_day < 0.85) {
-                       if (!m_sun_texture) {
-                               driver->setMaterial(m_materials[1]);
-                               float d = sunsize * 1.7;
-                               video::SColor c = suncolor;
-                               c.setAlpha(0.05 * 255);
-                               vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
-                               vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
-                               vertices[2] = video::S3DVertex( d,  d, -1, 0, 0, 1, c, o, o);
-                               vertices[3] = video::S3DVertex(-d,  d, -1, 0, 0, 1, c, t, o);
-                               for (video::S3DVertex &vertex : vertices) {
-                                       // Switch from -Z (south) to +X (east)
-                                       vertex.Pos.rotateXZBy(90);
-                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
-                               }
-                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-
-                               d = sunsize * 1.2;
-                               c = suncolor;
-                               c.setAlpha(0.15 * 255);
-                               vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
-                               vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
-                               vertices[2] = video::S3DVertex( d,  d, -1, 0, 0, 1, c, o, o);
-                               vertices[3] = video::S3DVertex(-d,  d, -1, 0, 0, 1, c, t, o);
-                               for (video::S3DVertex &vertex : vertices) {
-                                       // Switch from -Z (south) to +X (east)
-                                       vertex.Pos.rotateXZBy(90);
-                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
-                               }
-                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-
-                               d = sunsize;
-                               vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, suncolor, t, t);
-                               vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, suncolor, o, t);
-                               vertices[2] = video::S3DVertex( d,  d, -1, 0, 0, 1, suncolor, o, o);
-                               vertices[3] = video::S3DVertex(-d,  d, -1, 0, 0, 1, suncolor, t, o);
-                               for (video::S3DVertex &vertex : vertices) {
-                                       // Switch from -Z (south) to +X (east)
-                                       vertex.Pos.rotateXZBy(90);
-                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
-                               }
-                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-
-                               d = sunsize * 0.7;
-                               vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, suncolor2, t, t);
-                               vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, suncolor2, o, t);
-                               vertices[2] = video::S3DVertex( d,  d, -1, 0, 0, 1, suncolor2, o, o);
-                               vertices[3] = video::S3DVertex(-d,  d, -1, 0, 0, 1, suncolor2, t, o);
-                               for (video::S3DVertex &vertex : vertices) {
-                                       // Switch from -Z (south) to +X (east)
-                                       vertex.Pos.rotateXZBy(90);
-                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
-                               }
-                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-                       } else {
-                               driver->setMaterial(m_materials[3]);
-                               float d = sunsize * 1.7;
-                               video::SColor c;
-                               if (m_sun_tonemap)
-                                       c = video::SColor (0, 0, 0, 0);
-                               else
-                                       c = video::SColor (255, 255, 255, 255);
-                               vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
-                               vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
-                               vertices[2] = video::S3DVertex( d,  d, -1, 0, 0, 1, c, o, o);
-                               vertices[3] = video::S3DVertex(-d,  d, -1, 0, 0, 1, c, t, o);
-                               for (video::S3DVertex &vertex : vertices) {
-                                       // Switch from -Z (south) to +X (east)
-                                       vertex.Pos.rotateXZBy(90);
-                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
-                               }
-                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-                       }
-               }
-
-               // Draw moon
-               if (wicked_time_of_day < 0.3 || wicked_time_of_day > 0.7) {
-                       if (!m_moon_texture) {
-                               driver->setMaterial(m_materials[1]);
-                               float d = moonsize * 1.9;
-                               video::SColor c = mooncolor;
-                               c.setAlpha(0.05 * 255);
-                               vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
-                               vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
-                               vertices[2] = video::S3DVertex( d,  d, -1, 0, 0, 1, c, o, o);
-                               vertices[3] = video::S3DVertex(-d,  d, -1, 0, 0, 1, c, t, o);
-                               for (video::S3DVertex &vertex : vertices) {
-                                       // Switch from -Z (south) to -X (west)
-                                       vertex.Pos.rotateXZBy(-90);
-                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
-                               }
-                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-
-                               d = moonsize * 1.3;
-                               c = mooncolor;
-                               c.setAlpha(0.15 * 255);
-                               vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
-                               vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
-                               vertices[2] = video::S3DVertex( d,  d, -1, 0, 0, 1, c, o, o);
-                               vertices[3] = video::S3DVertex(-d,  d, -1, 0, 0, 1, c, t, o);
-                               for (video::S3DVertex &vertex : vertices) {
-                                       // Switch from -Z (south) to -X (west)
-                                       vertex.Pos.rotateXZBy(-90);
-                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
-                               }
-                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-
-                               d = moonsize;
-                               vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, mooncolor, t, t);
-                               vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, mooncolor, o, t);
-                               vertices[2] = video::S3DVertex( d,  d, -1, 0, 0, 1, mooncolor, o, o);
-                               vertices[3] = video::S3DVertex(-d,  d, -1, 0, 0, 1, mooncolor, t, o);
-                               for (video::S3DVertex &vertex : vertices) {
-                                       // Switch from -Z (south) to -X (west)
-                                       vertex.Pos.rotateXZBy(-90);
-                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
-                               }
-                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-
-                               float d2 = moonsize * 0.6;
-                               vertices[0] = video::S3DVertex(-d, -d,  -1, 0, 0, 1, mooncolor2, t, t);
-                               vertices[1] = video::S3DVertex( d2,-d,  -1, 0, 0, 1, mooncolor2, o, t);
-                               vertices[2] = video::S3DVertex( d2, d2, -1, 0, 0, 1, mooncolor2, o, o);
-                               vertices[3] = video::S3DVertex(-d,  d2, -1, 0, 0, 1, mooncolor2, t, o);
-                               for (video::S3DVertex &vertex : vertices) {
-                                       // Switch from -Z (south) to -X (west)
-                                       vertex.Pos.rotateXZBy(-90);
-                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
-                               }
-                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-                       } else {
-                               driver->setMaterial(m_materials[4]);
-                               float d = moonsize * 1.9;
-                               video::SColor c;
-                               if (m_moon_tonemap)
-                                       c = video::SColor (0, 0, 0, 0);
-                               else
-                                       c = video::SColor (255, 255, 255, 255);
-                               vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
-                               vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
-                               vertices[2] = video::S3DVertex( d,  d, -1, 0, 0, 1, c, o, o);
-                               vertices[3] = video::S3DVertex(-d,  d, -1, 0, 0, 1, c, t, o);
-                               for (video::S3DVertex &vertex : vertices) {
-                                       // Switch from -Z (south) to -X (west)
-                                       vertex.Pos.rotateXZBy(-90);
-                                       vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
-                               }
-                               driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-                       }
-               }
-
-               // Draw far cloudy fog thing below east and west horizons
-               for (u32 j = 0; j < 2; j++) {
-                       video::SColor c = cloudyfogcolor;
-                       vertices[0] = video::S3DVertex(-1, -1.0,  -1, 0, 0, 1, c, t, t);
-                       vertices[1] = video::S3DVertex( 1, -1.0,  -1, 0, 0, 1, c, o, t);
-                       vertices[2] = video::S3DVertex( 1, -0.02, -1, 0, 0, 1, c, o, o);
-                       vertices[3] = video::S3DVertex(-1, -0.02, -1, 0, 0, 1, c, t, o);
-                       for (video::S3DVertex &vertex : vertices) {
-                               //if (wicked_time_of_day < 0.5)
-                               if (j == 0)
-                                       // Switch from -Z (south) to +X (east)
-                                       vertex.Pos.rotateXZBy(90);
-                               else
-                                       // Switch from -Z (south) to -X (west)
-                                       vertex.Pos.rotateXZBy(-90);
-                       }
-                       driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-               }
-       }
-}
-
-
-void Sky::update(float time_of_day, float time_brightness,
-               float direct_brightness, bool sunlight_seen,
-               CameraMode cam_mode, float yaw, float pitch)
-{
-       // Stabilize initial brightness and color values by flooding updates
-       if (m_first_update) {
-               /*dstream<<"First update with time_of_day="<<time_of_day
-                               <<" time_brightness="<<time_brightness
-                               <<" direct_brightness="<<direct_brightness
-                               <<" sunlight_seen="<<sunlight_seen<<std::endl;*/
-               m_first_update = false;
-               for (u32 i = 0; i < 100; i++) {
-                       update(time_of_day, time_brightness, direct_brightness,
-                               sunlight_seen, cam_mode, yaw, pitch);
-               }
-               return;
-       }
-
-       m_time_of_day = time_of_day;
-       m_time_brightness = time_brightness;
-       m_sunlight_seen = sunlight_seen;
-       m_bodies_visible = true;
-
-       bool is_dawn = (time_brightness >= 0.20 && time_brightness < 0.35);
-
-       /*
-       Development colours
-
-       video::SColorf bgcolor_bright_normal_f(170. / 255, 200. / 255, 230. / 255, 1.0);
-       video::SColorf bgcolor_bright_dawn_f(0.666, 200. / 255 * 0.7, 230. / 255 * 0.5, 1.0);
-       video::SColorf bgcolor_bright_dawn_f(0.666, 0.549, 0.220, 1.0);
-       video::SColorf bgcolor_bright_dawn_f(0.666 * 1.2, 0.549 * 1.0, 0.220 * 1.0, 1.0);
-       video::SColorf bgcolor_bright_dawn_f(0.666 * 1.2, 0.549 * 1.0, 0.220 * 1.2, 1.0);
-
-       video::SColorf cloudcolor_bright_dawn_f(1.0, 0.591, 0.4);
-       video::SColorf cloudcolor_bright_dawn_f(1.0, 0.65, 0.44);
-       video::SColorf cloudcolor_bright_dawn_f(1.0, 0.7, 0.5);
-       */
-
-       video::SColorf bgcolor_bright_normal_f = video::SColor(255, 155, 193, 240);
-       video::SColorf bgcolor_bright_indoor_f = video::SColor(255, 100, 100, 100);
-       video::SColorf bgcolor_bright_dawn_f = video::SColor(255, 186, 193, 240);
-       video::SColorf bgcolor_bright_night_f = video::SColor(255, 64, 144, 255);
-
-       video::SColorf skycolor_bright_normal_f = video::SColor(255, 140, 186, 250);
-       video::SColorf skycolor_bright_dawn_f = video::SColor(255, 180, 186, 250);
-       video::SColorf skycolor_bright_night_f = video::SColor(255, 0, 107, 255);
-
-       // pure white: becomes "diffuse light component" for clouds
-       video::SColorf cloudcolor_bright_normal_f = video::SColor(255, 255, 255, 255);
-       // dawn-factoring version of pure white (note: R is above 1.0)
-       video::SColorf cloudcolor_bright_dawn_f(255.0f/240.0f, 223.0f/240.0f, 191.0f/255.0f);
-
-       float cloud_color_change_fraction = 0.95;
-       if (sunlight_seen) {
-               if (std::fabs(time_brightness - m_brightness) < 0.2f) {
-                       m_brightness = m_brightness * 0.95 + time_brightness * 0.05;
-               } else {
-                       m_brightness = m_brightness * 0.80 + time_brightness * 0.20;
-                       cloud_color_change_fraction = 0.0;
-               }
-       } else {
-               if (direct_brightness < m_brightness)
-                       m_brightness = m_brightness * 0.95 + direct_brightness * 0.05;
-               else
-                       m_brightness = m_brightness * 0.98 + direct_brightness * 0.02;
-       }
-
-       m_clouds_visible = true;
-       float color_change_fraction = 0.98f;
-       if (sunlight_seen) {
-               if (is_dawn) { // Dawn
-                       m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated(
-                               bgcolor_bright_dawn_f, color_change_fraction);
-                       m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated(
-                               skycolor_bright_dawn_f, color_change_fraction);
-                       m_cloudcolor_bright_f = m_cloudcolor_bright_f.getInterpolated(
-                               cloudcolor_bright_dawn_f, color_change_fraction);
-               } else {
-                       if (time_brightness < 0.13f) { // Night
-                               m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated(
-                                       bgcolor_bright_night_f, color_change_fraction);
-                               m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated(
-                                       skycolor_bright_night_f, color_change_fraction);
-                       } else { // Day
-                               m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated(
-                                       bgcolor_bright_normal_f, color_change_fraction);
-                               m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated(
-                                       skycolor_bright_normal_f, color_change_fraction);
-                       }
-
-                       m_cloudcolor_bright_f = m_cloudcolor_bright_f.getInterpolated(
-                               cloudcolor_bright_normal_f, color_change_fraction);
-               }
-       } else {
-               m_bgcolor_bright_f = m_bgcolor_bright_f.getInterpolated(
-                       bgcolor_bright_indoor_f, color_change_fraction);
-               m_skycolor_bright_f = m_skycolor_bright_f.getInterpolated(
-                       bgcolor_bright_indoor_f, color_change_fraction);
-               m_cloudcolor_bright_f = m_cloudcolor_bright_f.getInterpolated(
-                       cloudcolor_bright_normal_f, color_change_fraction);
-               m_clouds_visible = false;
-       }
-
-       video::SColor bgcolor_bright = m_bgcolor_bright_f.toSColor();
-       m_bgcolor = video::SColor(
-               255,
-               bgcolor_bright.getRed() * m_brightness,
-               bgcolor_bright.getGreen() * m_brightness,
-               bgcolor_bright.getBlue() * m_brightness
-       );
-
-       video::SColor skycolor_bright = m_skycolor_bright_f.toSColor();
-       m_skycolor = video::SColor(
-               255,
-               skycolor_bright.getRed() * m_brightness,
-               skycolor_bright.getGreen() * m_brightness,
-               skycolor_bright.getBlue() * m_brightness
-       );
-
-       // Horizon coloring based on sun and moon direction during sunset and sunrise
-       video::SColor pointcolor = video::SColor(m_bgcolor.getAlpha(), 255, 255, 255);
-       if (m_directional_colored_fog) {
-               if (m_horizon_blend() != 0) {
-                       // Calculate hemisphere value from yaw, (inverted in third person front view)
-                       s8 dir_factor = 1;
-                       if (cam_mode > CAMERA_MODE_THIRD)
-                               dir_factor = -1;
-                       f32 pointcolor_blend = wrapDegrees_0_360(yaw * dir_factor + 90);
-                       if (pointcolor_blend > 180)
-                               pointcolor_blend = 360 - pointcolor_blend;
-                       pointcolor_blend /= 180;
-                       // Bound view angle to determine where transition starts and ends
-                       pointcolor_blend = rangelim(1 - pointcolor_blend * 1.375, 0, 1 / 1.375) *
-                               1.375;
-                       // Combine the colors when looking up or down, otherwise turning looks weird
-                       pointcolor_blend += (0.5 - pointcolor_blend) *
-                               (1 - MYMIN((90 - std::fabs(pitch)) / 90 * 1.5, 1));
-                       // Invert direction to match where the sun and moon are rising
-                       if (m_time_of_day > 0.5)
-                               pointcolor_blend = 1 - pointcolor_blend;
-                       // Horizon colors of sun and moon
-                       f32 pointcolor_light = rangelim(m_time_brightness * 3, 0.2, 1);
-
-                       video::SColorf pointcolor_sun_f(1, 1, 1, 1);
-                       if (m_sun_tonemap) {
-                               pointcolor_sun_f.r = pointcolor_light *
-                                       (float)m_materials[3].EmissiveColor.getRed() / 255;
-                               pointcolor_sun_f.b = pointcolor_light *
-                                       (float)m_materials[3].EmissiveColor.getBlue() / 255;
-                               pointcolor_sun_f.g = pointcolor_light *
-                                       (float)m_materials[3].EmissiveColor.getGreen() / 255;
-                       } else {
-                               pointcolor_sun_f.r = pointcolor_light * 1;
-                               pointcolor_sun_f.b = pointcolor_light *
-                                       (0.25 + (rangelim(m_time_brightness, 0.25, 0.75) - 0.25) * 2 * 0.75);
-                               pointcolor_sun_f.g = pointcolor_light * (pointcolor_sun_f.b * 0.375 +
-                                       (rangelim(m_time_brightness, 0.05, 0.15) - 0.05) * 10 * 0.625);
-                       }
-
-                       video::SColorf pointcolor_moon_f(0.5 * pointcolor_light,
-                               0.6 * pointcolor_light, 0.8 * pointcolor_light, 1);
-                       if (m_moon_tonemap) {
-                               pointcolor_moon_f.r = pointcolor_light *
-                                       (float)m_materials[4].EmissiveColor.getRed() / 255;
-                               pointcolor_moon_f.b = pointcolor_light *
-                                       (float)m_materials[4].EmissiveColor.getBlue() / 255;
-                               pointcolor_moon_f.g = pointcolor_light *
-                                       (float)m_materials[4].EmissiveColor.getGreen() / 255;
-                       }
-
-                       video::SColor pointcolor_sun = pointcolor_sun_f.toSColor();
-                       video::SColor pointcolor_moon = pointcolor_moon_f.toSColor();
-                       // Calculate the blend color
-                       pointcolor = m_mix_scolor(pointcolor_moon, pointcolor_sun, pointcolor_blend);
-               }
-               m_bgcolor = m_mix_scolor(m_bgcolor, pointcolor, m_horizon_blend() * 0.5);
-               m_skycolor = m_mix_scolor(m_skycolor, pointcolor, m_horizon_blend() * 0.25);
-       }
-
-       float cloud_direct_brightness = 0.0f;
-       if (sunlight_seen) {
-               if (!m_directional_colored_fog) {
-                       cloud_direct_brightness = time_brightness;
-                       // Boost cloud brightness relative to sky, at dawn, dusk and at night
-                       if (time_brightness < 0.7f)
-                               cloud_direct_brightness *= 1.3f;
-               } else {
-                       cloud_direct_brightness = std::fmin(m_horizon_blend() * 0.15f +
-                               m_time_brightness, 1.0f);
-                       // Set the same minimum cloud brightness at night
-                       if (time_brightness < 0.5f)
-                               cloud_direct_brightness = std::fmax(cloud_direct_brightness,
-                                       time_brightness * 1.3f);
-               }
-       } else {
-               cloud_direct_brightness = direct_brightness;
-       }
-
-       m_cloud_brightness = m_cloud_brightness * cloud_color_change_fraction +
-               cloud_direct_brightness * (1.0 - cloud_color_change_fraction);
-       m_cloudcolor_f = video::SColorf(
-               m_cloudcolor_bright_f.r * m_cloud_brightness,
-               m_cloudcolor_bright_f.g * m_cloud_brightness,
-               m_cloudcolor_bright_f.b * m_cloud_brightness,
-               1.0
-       );
-       if (m_directional_colored_fog) {
-               m_cloudcolor_f = m_mix_scolorf(m_cloudcolor_f,
-                       video::SColorf(pointcolor), m_horizon_blend() * 0.25);
-       }
-}
diff --git a/src/sky.h b/src/sky.h
deleted file mode 100644 (file)
index b66a499..0000000
--- a/src/sky.h
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 <ISceneNode.h>
-#include "camera.h"
-#include "irrlichttypes_extrabloated.h"
-
-#pragma once
-
-#define SKY_MATERIAL_COUNT 5
-#define SKY_STAR_COUNT 200
-
-class ITextureSource;
-
-// Skybox, rendered with zbuffer turned off, before all other nodes.
-class Sky : public scene::ISceneNode
-{
-public:
-       //! constructor
-       Sky(s32 id, ITextureSource *tsrc);
-
-       virtual void OnRegisterSceneNode();
-
-       //! renders the node.
-       virtual void render();
-
-       virtual const aabb3f &getBoundingBox() const { return m_box; }
-
-       // Used by Irrlicht for optimizing rendering
-       virtual video::SMaterial &getMaterial(u32 i) { return m_materials[i]; }
-
-       // Used by Irrlicht for optimizing rendering
-       virtual u32 getMaterialCount() const { return SKY_MATERIAL_COUNT; }
-
-       void update(float m_time_of_day, float time_brightness, float direct_brightness,
-                       bool sunlight_seen, CameraMode cam_mode, float yaw, float pitch);
-
-       float getBrightness() { return m_brightness; }
-
-       const video::SColor &getBgColor() const
-       {
-               return m_visible ? m_bgcolor : m_fallback_bg_color;
-       }
-
-       const video::SColor &getSkyColor() const
-       {
-               return m_visible ? m_skycolor : m_fallback_bg_color;
-       }
-
-       bool getCloudsVisible() const { return m_clouds_visible && m_clouds_enabled; }
-       const video::SColorf &getCloudColor() const { return m_cloudcolor_f; }
-
-       void setVisible(bool visible) { m_visible = visible; }
-       // Set only from set_sky API
-       void setCloudsEnabled(bool clouds_enabled) { m_clouds_enabled = clouds_enabled; }
-       void setFallbackBgColor(const video::SColor &fallback_bg_color)
-       {
-               m_fallback_bg_color = fallback_bg_color;
-       }
-       void overrideColors(const video::SColor &bgcolor, const video::SColor &skycolor)
-       {
-               m_bgcolor = bgcolor;
-               m_skycolor = skycolor;
-       }
-       void setBodiesVisible(bool visible) { m_bodies_visible = visible; }
-
-private:
-       aabb3f m_box;
-       video::SMaterial m_materials[SKY_MATERIAL_COUNT];
-
-       // How much sun & moon transition should affect horizon color
-       float m_horizon_blend()
-       {
-               if (!m_sunlight_seen)
-                       return 0;
-               float x = m_time_of_day >= 0.5 ? (1 - m_time_of_day) * 2
-                                              : m_time_of_day * 2;
-
-               if (x <= 0.3)
-                       return 0;
-               if (x <= 0.4) // when the sun and moon are aligned
-                       return (x - 0.3) * 10;
-               if (x <= 0.5)
-                       return (0.5 - x) * 10;
-               return 0;
-       }
-
-       // Mix two colors by a given amount
-       video::SColor m_mix_scolor(video::SColor col1, video::SColor col2, f32 factor)
-       {
-               video::SColor result = video::SColor(
-                               col1.getAlpha() * (1 - factor) + col2.getAlpha() * factor,
-                               col1.getRed() * (1 - factor) + col2.getRed() * factor,
-                               col1.getGreen() * (1 - factor) + col2.getGreen() * factor,
-                               col1.getBlue() * (1 - factor) + col2.getBlue() * factor);
-               return result;
-       }
-       video::SColorf m_mix_scolorf(video::SColorf col1, video::SColorf col2, f32 factor)
-       {
-               video::SColorf result =
-                               video::SColorf(col1.r * (1 - factor) + col2.r * factor,
-                                               col1.g * (1 - factor) + col2.g * factor,
-                                               col1.b * (1 - factor) + col2.b * factor,
-                                               col1.a * (1 - factor) + col2.a * factor);
-               return result;
-       }
-
-       bool m_visible = true;
-       // Used when m_visible=false
-       video::SColor m_fallback_bg_color = video::SColor(255, 255, 255, 255);
-       bool m_first_update = true;
-       float m_time_of_day;
-       float m_time_brightness;
-       bool m_sunlight_seen;
-       float m_brightness = 0.5f;
-       float m_cloud_brightness = 0.5f;
-       bool m_clouds_visible; // Whether clouds are disabled due to player underground
-       bool m_clouds_enabled = true; // Initialised to true, reset only by set_sky API
-       bool m_directional_colored_fog;
-       bool m_bodies_visible = true; // sun, moon, stars
-       video::SColorf m_bgcolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f);
-       video::SColorf m_skycolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f);
-       video::SColorf m_cloudcolor_bright_f = video::SColorf(1.0f, 1.0f, 1.0f, 1.0f);
-       video::SColor m_bgcolor;
-       video::SColor m_skycolor;
-       video::SColorf m_cloudcolor_f;
-       v3f m_stars[SKY_STAR_COUNT];
-       video::ITexture *m_sun_texture;
-       video::ITexture *m_moon_texture;
-       video::ITexture *m_sun_tonemap;
-       video::ITexture *m_moon_tonemap;
-};
index dd3d75a5bb16f79aeca8f0f0eb63deeca00f2c90..3813af949e599375cc1264fec71b8d71221f0229 100644 (file)
@@ -21,7 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include <string>
 #include "exceptions.h"
-#include "keycode.h"
+#include "client/keycode.h"
 
 class TestKeycode : public TestBase {
 public:
diff --git a/src/wieldmesh.cpp b/src/wieldmesh.cpp
deleted file mode 100644 (file)
index 7791a5a..0000000
+++ /dev/null
@@ -1,685 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2014 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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 "wieldmesh.h"
-#include "settings.h"
-#include "shader.h"
-#include "inventory.h"
-#include "client.h"
-#include "itemdef.h"
-#include "nodedef.h"
-#include "mesh.h"
-#include "content_mapblock.h"
-#include "mapblock_mesh.h"
-#include "client/meshgen/collector.h"
-#include "client/tile.h"
-#include "log.h"
-#include "util/numeric.h"
-#include <map>
-#include <IMeshManipulator.h>
-
-#define WIELD_SCALE_FACTOR 30.0
-#define WIELD_SCALE_FACTOR_EXTRUDED 40.0
-
-#define MIN_EXTRUSION_MESH_RESOLUTION 16
-#define MAX_EXTRUSION_MESH_RESOLUTION 512
-
-static scene::IMesh *createExtrusionMesh(int resolution_x, int resolution_y)
-{
-       const f32 r = 0.5;
-
-       scene::IMeshBuffer *buf = new scene::SMeshBuffer();
-       video::SColor c(255,255,255,255);
-       v3f scale(1.0, 1.0, 0.1);
-
-       // Front and back
-       {
-               video::S3DVertex vertices[8] = {
-                       // z-
-                       video::S3DVertex(-r,+r,-r, 0,0,-1, c, 0,0),
-                       video::S3DVertex(+r,+r,-r, 0,0,-1, c, 1,0),
-                       video::S3DVertex(+r,-r,-r, 0,0,-1, c, 1,1),
-                       video::S3DVertex(-r,-r,-r, 0,0,-1, c, 0,1),
-                       // z+
-                       video::S3DVertex(-r,+r,+r, 0,0,+1, c, 0,0),
-                       video::S3DVertex(-r,-r,+r, 0,0,+1, c, 0,1),
-                       video::S3DVertex(+r,-r,+r, 0,0,+1, c, 1,1),
-                       video::S3DVertex(+r,+r,+r, 0,0,+1, c, 1,0),
-               };
-               u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
-               buf->append(vertices, 8, indices, 12);
-       }
-
-       f32 pixelsize_x = 1 / (f32) resolution_x;
-       f32 pixelsize_y = 1 / (f32) resolution_y;
-
-       for (int i = 0; i < resolution_x; ++i) {
-               f32 pixelpos_x = i * pixelsize_x - 0.5;
-               f32 x0 = pixelpos_x;
-               f32 x1 = pixelpos_x + pixelsize_x;
-               f32 tex0 = (i + 0.1) * pixelsize_x;
-               f32 tex1 = (i + 0.9) * pixelsize_x;
-               video::S3DVertex vertices[8] = {
-                       // x-
-                       video::S3DVertex(x0,-r,-r, -1,0,0, c, tex0,1),
-                       video::S3DVertex(x0,-r,+r, -1,0,0, c, tex1,1),
-                       video::S3DVertex(x0,+r,+r, -1,0,0, c, tex1,0),
-                       video::S3DVertex(x0,+r,-r, -1,0,0, c, tex0,0),
-                       // x+
-                       video::S3DVertex(x1,-r,-r, +1,0,0, c, tex0,1),
-                       video::S3DVertex(x1,+r,-r, +1,0,0, c, tex0,0),
-                       video::S3DVertex(x1,+r,+r, +1,0,0, c, tex1,0),
-                       video::S3DVertex(x1,-r,+r, +1,0,0, c, tex1,1),
-               };
-               u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
-               buf->append(vertices, 8, indices, 12);
-       }
-       for (int i = 0; i < resolution_y; ++i) {
-               f32 pixelpos_y = i * pixelsize_y - 0.5;
-               f32 y0 = -pixelpos_y - pixelsize_y;
-               f32 y1 = -pixelpos_y;
-               f32 tex0 = (i + 0.1) * pixelsize_y;
-               f32 tex1 = (i + 0.9) * pixelsize_y;
-               video::S3DVertex vertices[8] = {
-                       // y-
-                       video::S3DVertex(-r,y0,-r, 0,-1,0, c, 0,tex0),
-                       video::S3DVertex(+r,y0,-r, 0,-1,0, c, 1,tex0),
-                       video::S3DVertex(+r,y0,+r, 0,-1,0, c, 1,tex1),
-                       video::S3DVertex(-r,y0,+r, 0,-1,0, c, 0,tex1),
-                       // y+
-                       video::S3DVertex(-r,y1,-r, 0,+1,0, c, 0,tex0),
-                       video::S3DVertex(-r,y1,+r, 0,+1,0, c, 0,tex1),
-                       video::S3DVertex(+r,y1,+r, 0,+1,0, c, 1,tex1),
-                       video::S3DVertex(+r,y1,-r, 0,+1,0, c, 1,tex0),
-               };
-               u16 indices[12] = {0,1,2,2,3,0,4,5,6,6,7,4};
-               buf->append(vertices, 8, indices, 12);
-       }
-
-       // Create mesh object
-       scene::SMesh *mesh = new scene::SMesh();
-       mesh->addMeshBuffer(buf);
-       buf->drop();
-       scaleMesh(mesh, scale);  // also recalculates bounding box
-       return mesh;
-}
-
-/*
-       Caches extrusion meshes so that only one of them per resolution
-       is needed. Also caches one cube (for convenience).
-
-       E.g. there is a single extrusion mesh that is used for all
-       16x16 px images, another for all 256x256 px images, and so on.
-
-       WARNING: Not thread safe. This should not be a problem since
-       rendering related classes (such as WieldMeshSceneNode) will be
-       used from the rendering thread only.
-*/
-class ExtrusionMeshCache: public IReferenceCounted
-{
-public:
-       // Constructor
-       ExtrusionMeshCache()
-       {
-               for (int resolution = MIN_EXTRUSION_MESH_RESOLUTION;
-                               resolution <= MAX_EXTRUSION_MESH_RESOLUTION;
-                               resolution *= 2) {
-                       m_extrusion_meshes[resolution] =
-                               createExtrusionMesh(resolution, resolution);
-               }
-               m_cube = createCubeMesh(v3f(1.0, 1.0, 1.0));
-       }
-       // Destructor
-       virtual ~ExtrusionMeshCache()
-       {
-               for (auto &extrusion_meshe : m_extrusion_meshes) {
-                       extrusion_meshe.second->drop();
-               }
-               m_cube->drop();
-       }
-       // Get closest extrusion mesh for given image dimensions
-       // Caller must drop the returned pointer
-       scene::IMesh* create(core::dimension2d<u32> dim)
-       {
-               // handle non-power of two textures inefficiently without cache
-               if (!is_power_of_two(dim.Width) || !is_power_of_two(dim.Height)) {
-                       return createExtrusionMesh(dim.Width, dim.Height);
-               }
-
-               int maxdim = MYMAX(dim.Width, dim.Height);
-
-               std::map<int, scene::IMesh*>::iterator
-                       it = m_extrusion_meshes.lower_bound(maxdim);
-
-               if (it == m_extrusion_meshes.end()) {
-                       // no viable resolution found; use largest one
-                       it = m_extrusion_meshes.find(MAX_EXTRUSION_MESH_RESOLUTION);
-                       sanity_check(it != m_extrusion_meshes.end());
-               }
-
-               scene::IMesh *mesh = it->second;
-               mesh->grab();
-               return mesh;
-       }
-       // Returns a 1x1x1 cube mesh with one meshbuffer (material) per face
-       // Caller must drop the returned pointer
-       scene::IMesh* createCube()
-       {
-               m_cube->grab();
-               return m_cube;
-       }
-
-private:
-       std::map<int, scene::IMesh*> m_extrusion_meshes;
-       scene::IMesh *m_cube;
-};
-
-ExtrusionMeshCache *g_extrusion_mesh_cache = NULL;
-
-
-WieldMeshSceneNode::WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id, bool lighting):
-       scene::ISceneNode(mgr->getRootSceneNode(), mgr, id),
-       m_material_type(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF),
-       m_lighting(lighting)
-{
-       m_enable_shaders = g_settings->getBool("enable_shaders");
-       m_anisotropic_filter = g_settings->getBool("anisotropic_filter");
-       m_bilinear_filter = g_settings->getBool("bilinear_filter");
-       m_trilinear_filter = g_settings->getBool("trilinear_filter");
-
-       // If this is the first wield mesh scene node, create a cache
-       // for extrusion meshes (and a cube mesh), otherwise reuse it
-       if (!g_extrusion_mesh_cache)
-               g_extrusion_mesh_cache = new ExtrusionMeshCache();
-       else
-               g_extrusion_mesh_cache->grab();
-
-       // Disable bounding box culling for this scene node
-       // since we won't calculate the bounding box.
-       setAutomaticCulling(scene::EAC_OFF);
-
-       // Create the child scene node
-       scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube();
-       m_meshnode = SceneManager->addMeshSceneNode(dummymesh, this, -1);
-       m_meshnode->setReadOnlyMaterials(false);
-       m_meshnode->setVisible(false);
-       dummymesh->drop(); // m_meshnode grabbed it
-}
-
-WieldMeshSceneNode::~WieldMeshSceneNode()
-{
-       sanity_check(g_extrusion_mesh_cache);
-       if (g_extrusion_mesh_cache->drop())
-               g_extrusion_mesh_cache = nullptr;
-}
-
-void WieldMeshSceneNode::setCube(const ContentFeatures &f,
-                       v3f wield_scale)
-{
-       scene::IMesh *cubemesh = g_extrusion_mesh_cache->createCube();
-       scene::SMesh *copy = cloneMesh(cubemesh);
-       cubemesh->drop();
-       postProcessNodeMesh(copy, f, false, true, &m_material_type, &m_colors, true);
-       changeToMesh(copy);
-       copy->drop();
-       m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR);
-}
-
-void WieldMeshSceneNode::setExtruded(const std::string &imagename,
-       const std::string &overlay_name, v3f wield_scale, ITextureSource *tsrc,
-       u8 num_frames)
-{
-       video::ITexture *texture = tsrc->getTexture(imagename);
-       if (!texture) {
-               changeToMesh(nullptr);
-               return;
-       }
-       video::ITexture *overlay_texture =
-               overlay_name.empty() ? NULL : tsrc->getTexture(overlay_name);
-
-       core::dimension2d<u32> dim = texture->getSize();
-       // Detect animation texture and pull off top frame instead of using entire thing
-       if (num_frames > 1) {
-               u32 frame_height = dim.Height / num_frames;
-               dim = core::dimension2d<u32>(dim.Width, frame_height);
-       }
-       scene::IMesh *original = g_extrusion_mesh_cache->create(dim);
-       scene::SMesh *mesh = cloneMesh(original);
-       original->drop();
-       //set texture
-       mesh->getMeshBuffer(0)->getMaterial().setTexture(0,
-               tsrc->getTexture(imagename));
-       if (overlay_texture) {
-               scene::IMeshBuffer *copy = cloneMeshBuffer(mesh->getMeshBuffer(0));
-               copy->getMaterial().setTexture(0, overlay_texture);
-               mesh->addMeshBuffer(copy);
-               copy->drop();
-       }
-       changeToMesh(mesh);
-       mesh->drop();
-
-       m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR_EXTRUDED);
-
-       // Customize materials
-       for (u32 layer = 0; layer < m_meshnode->getMaterialCount(); layer++) {
-               video::SMaterial &material = m_meshnode->getMaterial(layer);
-               material.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE;
-               material.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE;
-               material.MaterialType = m_material_type;
-               material.setFlag(video::EMF_BACK_FACE_CULLING, true);
-               // Enable bi/trilinear filtering only for high resolution textures
-               if (dim.Width > 32) {
-                       material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter);
-                       material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter);
-               } else {
-                       material.setFlag(video::EMF_BILINEAR_FILTER, false);
-                       material.setFlag(video::EMF_TRILINEAR_FILTER, false);
-               }
-               material.setFlag(video::EMF_ANISOTROPIC_FILTER, m_anisotropic_filter);
-               // mipmaps cause "thin black line" artifacts
-#if (IRRLICHT_VERSION_MAJOR >= 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR >= 2
-               material.setFlag(video::EMF_USE_MIP_MAPS, false);
-#endif
-               if (m_enable_shaders) {
-                       material.setTexture(2, tsrc->getShaderFlagsTexture(false));
-               }
-       }
-}
-
-scene::SMesh *createSpecialNodeMesh(Client *client, content_t id, std::vector<ItemPartColor> *colors)
-{
-       MeshMakeData mesh_make_data(client, false, false);
-       MeshCollector collector;
-       mesh_make_data.setSmoothLighting(false);
-       MapblockMeshGenerator gen(&mesh_make_data, &collector);
-       gen.renderSingle(id);
-       colors->clear();
-       scene::SMesh *mesh = new scene::SMesh();
-       for (auto &prebuffers : collector.prebuffers)
-               for (PreMeshBuffer &p : prebuffers) {
-                       if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) {
-                               const FrameSpec &frame = (*p.layer.frames)[0];
-                               p.layer.texture = frame.texture;
-                               p.layer.normal_texture = frame.normal_texture;
-                       }
-                       for (video::S3DVertex &v : p.vertices)
-                               v.Color.setAlpha(255);
-                       scene::SMeshBuffer *buf = new scene::SMeshBuffer();
-                       buf->Material.setTexture(0, p.layer.texture);
-                       p.layer.applyMaterialOptions(buf->Material);
-                       mesh->addMeshBuffer(buf);
-                       buf->append(&p.vertices[0], p.vertices.size(),
-                                       &p.indices[0], p.indices.size());
-                       buf->drop();
-                       colors->push_back(
-                               ItemPartColor(p.layer.has_color, p.layer.color));
-               }
-       return mesh;
-}
-
-void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client)
-{
-       ITextureSource *tsrc = client->getTextureSource();
-       IItemDefManager *idef = client->getItemDefManager();
-       IShaderSource *shdrsrc = client->getShaderSource();
-       const NodeDefManager *ndef = client->getNodeDefManager();
-       const ItemDefinition &def = item.getDefinition(idef);
-       const ContentFeatures &f = ndef->get(def.name);
-       content_t id = ndef->getId(def.name);
-
-       scene::SMesh *mesh = nullptr;
-
-       if (m_enable_shaders) {
-               u32 shader_id = shdrsrc->getShader("wielded_shader", TILE_MATERIAL_BASIC, NDT_NORMAL);
-               m_material_type = shdrsrc->getShaderInfo(shader_id).material;
-       }
-
-       // Color-related
-       m_colors.clear();
-       m_base_color = idef->getItemstackColor(item, client);
-
-       // If wield_image is defined, it overrides everything else
-       if (!def.wield_image.empty()) {
-               setExtruded(def.wield_image, def.wield_overlay, def.wield_scale, tsrc,
-                       1);
-               m_colors.emplace_back();
-               // overlay is white, if present
-               m_colors.emplace_back(true, video::SColor(0xFFFFFFFF));
-               return;
-       }
-
-       // Handle nodes
-       // See also CItemDefManager::createClientCached()
-       if (def.type == ITEM_NODE) {
-               if (f.mesh_ptr[0]) {
-                       // e.g. mesh nodes and nodeboxes
-                       mesh = cloneMesh(f.mesh_ptr[0]);
-                       postProcessNodeMesh(mesh, f, m_enable_shaders, true,
-                               &m_material_type, &m_colors);
-                       changeToMesh(mesh);
-                       mesh->drop();
-                       // mesh is pre-scaled by BS * f->visual_scale
-                       m_meshnode->setScale(
-                                       def.wield_scale * WIELD_SCALE_FACTOR
-                                       / (BS * f.visual_scale));
-               } else {
-                       switch (f.drawtype) {
-                               case NDT_AIRLIKE: {
-                                       changeToMesh(nullptr);
-                                       break;
-                               }
-                               case NDT_PLANTLIKE: {
-                                       setExtruded(tsrc->getTextureName(f.tiles[0].layers[0].texture_id),
-                                               tsrc->getTextureName(f.tiles[0].layers[1].texture_id),
-                                               def.wield_scale, tsrc,
-                                               f.tiles[0].layers[0].animation_frame_count);
-                                       // Add color
-                                       const TileLayer &l0 = f.tiles[0].layers[0];
-                                       m_colors.emplace_back(l0.has_color, l0.color);
-                                       const TileLayer &l1 = f.tiles[0].layers[1];
-                                       m_colors.emplace_back(l1.has_color, l1.color);
-                                       break;
-                               }
-                               case NDT_PLANTLIKE_ROOTED: {
-                                       setExtruded(tsrc->getTextureName(f.special_tiles[0].layers[0].texture_id),
-                                               "", def.wield_scale, tsrc,
-                                               f.special_tiles[0].layers[0].animation_frame_count);
-                                       // Add color
-                                       const TileLayer &l0 = f.special_tiles[0].layers[0];
-                                       m_colors.emplace_back(l0.has_color, l0.color);
-                                       break;
-                               }
-                               case NDT_NORMAL:
-                               case NDT_ALLFACES:
-                               case NDT_LIQUID:
-                               case NDT_FLOWINGLIQUID: {
-                                       setCube(f, def.wield_scale);
-                                       break;
-                               }
-                               default: {
-                                       mesh = createSpecialNodeMesh(client, id, &m_colors);
-                                       changeToMesh(mesh);
-                                       mesh->drop();
-                                       m_meshnode->setScale(
-                                                       def.wield_scale * WIELD_SCALE_FACTOR
-                                                       / (BS * f.visual_scale));
-                               }
-                       }
-               }
-               u32 material_count = m_meshnode->getMaterialCount();
-               for (u32 i = 0; i < material_count; ++i) {
-                       video::SMaterial &material = m_meshnode->getMaterial(i);
-                       material.MaterialType = m_material_type;
-                       material.setFlag(video::EMF_BACK_FACE_CULLING, true);
-                       material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter);
-                       material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter);
-               }
-               return;
-       }
-       else if (!def.inventory_image.empty()) {
-               setExtruded(def.inventory_image, def.inventory_overlay, def.wield_scale,
-                       tsrc, 1);
-               m_colors.emplace_back();
-               // overlay is white, if present
-               m_colors.emplace_back(true, video::SColor(0xFFFFFFFF));
-               return;
-       }
-
-       // no wield mesh found
-       changeToMesh(nullptr);
-}
-
-void WieldMeshSceneNode::setColor(video::SColor c)
-{
-       assert(!m_lighting);
-       scene::IMesh *mesh = m_meshnode->getMesh();
-       if (!mesh)
-               return;
-
-       u8 red = c.getRed();
-       u8 green = c.getGreen();
-       u8 blue = c.getBlue();
-       u32 mc = mesh->getMeshBufferCount();
-       for (u32 j = 0; j < mc; j++) {
-               video::SColor bc(m_base_color);
-               if ((m_colors.size() > j) && (m_colors[j].override_base))
-                       bc = m_colors[j].color;
-               video::SColor buffercolor(255,
-                       bc.getRed() * red / 255,
-                       bc.getGreen() * green / 255,
-                       bc.getBlue() * blue / 255);
-               scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
-               colorizeMeshBuffer(buf, &buffercolor);
-       }
-}
-
-void WieldMeshSceneNode::render()
-{
-       // note: if this method is changed to actually do something,
-       // you probably should implement OnRegisterSceneNode as well
-}
-
-void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh)
-{
-       if (!mesh) {
-               scene::IMesh *dummymesh = g_extrusion_mesh_cache->createCube();
-               m_meshnode->setVisible(false);
-               m_meshnode->setMesh(dummymesh);
-               dummymesh->drop();  // m_meshnode grabbed it
-       } else {
-               m_meshnode->setMesh(mesh);
-       }
-
-       m_meshnode->setMaterialFlag(video::EMF_LIGHTING, m_lighting);
-       // need to normalize normals when lighting is enabled (because of setScale())
-       m_meshnode->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, m_lighting);
-       m_meshnode->setVisible(true);
-}
-
-void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
-{
-       ITextureSource *tsrc = client->getTextureSource();
-       IItemDefManager *idef = client->getItemDefManager();
-       const NodeDefManager *ndef = client->getNodeDefManager();
-       const ItemDefinition &def = item.getDefinition(idef);
-       const ContentFeatures &f = ndef->get(def.name);
-       content_t id = ndef->getId(def.name);
-
-       FATAL_ERROR_IF(!g_extrusion_mesh_cache, "Extrusion mesh cache is not yet initialized");
-       
-       scene::SMesh *mesh = nullptr;
-
-       // Shading is on by default
-       result->needs_shading = true;
-
-       // If inventory_image is defined, it overrides everything else
-       if (!def.inventory_image.empty()) {
-               mesh = getExtrudedMesh(tsrc, def.inventory_image,
-                       def.inventory_overlay);
-               result->buffer_colors.emplace_back();
-               // overlay is white, if present
-               result->buffer_colors.emplace_back(true, video::SColor(0xFFFFFFFF));
-               // Items with inventory images do not need shading
-               result->needs_shading = false;
-       } else if (def.type == ITEM_NODE) {
-               if (f.mesh_ptr[0]) {
-                       mesh = cloneMesh(f.mesh_ptr[0]);
-                       scaleMesh(mesh, v3f(0.12, 0.12, 0.12));
-                       postProcessNodeMesh(mesh, f, false, false, nullptr,
-                               &result->buffer_colors);
-               } else {
-                       switch (f.drawtype) {
-                               case NDT_PLANTLIKE: {
-                                       mesh = getExtrudedMesh(tsrc,
-                                               tsrc->getTextureName(f.tiles[0].layers[0].texture_id),
-                                               tsrc->getTextureName(f.tiles[0].layers[1].texture_id));
-                                       // Add color
-                                       const TileLayer &l0 = f.tiles[0].layers[0];
-                                       result->buffer_colors.emplace_back(l0.has_color, l0.color);
-                                       const TileLayer &l1 = f.tiles[0].layers[1];
-                                       result->buffer_colors.emplace_back(l1.has_color, l1.color);
-                                       break;
-                               }
-                               case NDT_PLANTLIKE_ROOTED: {
-                                       mesh = getExtrudedMesh(tsrc,
-                                               tsrc->getTextureName(f.special_tiles[0].layers[0].texture_id), "");
-                                       // Add color
-                                       const TileLayer &l0 = f.special_tiles[0].layers[0];
-                                       result->buffer_colors.emplace_back(l0.has_color, l0.color);
-                                       break;
-                               }
-                               case NDT_NORMAL:
-                               case NDT_ALLFACES:
-                               case NDT_LIQUID:
-                               case NDT_FLOWINGLIQUID: {
-                                       scene::IMesh *cube = g_extrusion_mesh_cache->createCube();
-                                       mesh = cloneMesh(cube);
-                                       cube->drop();
-                                       scaleMesh(mesh, v3f(1.2, 1.2, 1.2));
-                                       // add overlays
-                                       postProcessNodeMesh(mesh, f, false, false, nullptr,
-                                               &result->buffer_colors);
-                                       break;
-                               }
-                               default: {
-                                       mesh = createSpecialNodeMesh(client, id, &result->buffer_colors);
-                                       scaleMesh(mesh, v3f(0.12, 0.12, 0.12));
-                               }
-                       }
-               }
-
-               u32 mc = mesh->getMeshBufferCount();
-               for (u32 i = 0; i < mc; ++i) {
-                       scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
-                       video::SMaterial &material = buf->getMaterial();
-                       material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
-                       material.setFlag(video::EMF_BILINEAR_FILTER, false);
-                       material.setFlag(video::EMF_TRILINEAR_FILTER, false);
-                       material.setFlag(video::EMF_BACK_FACE_CULLING, true);
-                       material.setFlag(video::EMF_LIGHTING, false);
-               }
-
-               rotateMeshXZby(mesh, -45);
-               rotateMeshYZby(mesh, -30);
-       }
-       result->mesh = mesh;
-}
-
-
-
-scene::SMesh *getExtrudedMesh(ITextureSource *tsrc,
-       const std::string &imagename, const std::string &overlay_name)
-{
-       // check textures
-       video::ITexture *texture = tsrc->getTextureForMesh(imagename);
-       if (!texture) {
-               return NULL;
-       }
-       video::ITexture *overlay_texture =
-               (overlay_name.empty()) ? NULL : tsrc->getTexture(overlay_name);
-
-       // get mesh
-       core::dimension2d<u32> dim = texture->getSize();
-       scene::IMesh *original = g_extrusion_mesh_cache->create(dim);
-       scene::SMesh *mesh = cloneMesh(original);
-       original->drop();
-
-       //set texture
-       mesh->getMeshBuffer(0)->getMaterial().setTexture(0,
-               tsrc->getTexture(imagename));
-       if (overlay_texture) {
-               scene::IMeshBuffer *copy = cloneMeshBuffer(mesh->getMeshBuffer(0));
-               copy->getMaterial().setTexture(0, overlay_texture);
-               mesh->addMeshBuffer(copy);
-               copy->drop();
-       }
-       // Customize materials
-       for (u32 layer = 0; layer < mesh->getMeshBufferCount(); layer++) {
-               video::SMaterial &material = mesh->getMeshBuffer(layer)->getMaterial();
-               material.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE;
-               material.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE;
-               material.setFlag(video::EMF_BILINEAR_FILTER, false);
-               material.setFlag(video::EMF_TRILINEAR_FILTER, false);
-               material.setFlag(video::EMF_BACK_FACE_CULLING, true);
-               material.setFlag(video::EMF_LIGHTING, false);
-               material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
-       }
-       scaleMesh(mesh, v3f(2.0, 2.0, 2.0));
-
-       return mesh;
-}
-
-void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f,
-       bool use_shaders, bool set_material, const video::E_MATERIAL_TYPE *mattype,
-       std::vector<ItemPartColor> *colors, bool apply_scale)
-{
-       u32 mc = mesh->getMeshBufferCount();
-       // Allocate colors for existing buffers
-       colors->clear();
-       for (u32 i = 0; i < mc; ++i)
-               colors->push_back(ItemPartColor());
-
-       for (u32 i = 0; i < mc; ++i) {
-               const TileSpec *tile = &(f.tiles[i]);
-               scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
-               for (int layernum = 0; layernum < MAX_TILE_LAYERS; layernum++) {
-                       const TileLayer *layer = &tile->layers[layernum];
-                       if (layer->texture_id == 0)
-                               continue;
-                       if (layernum != 0) {
-                               scene::IMeshBuffer *copy = cloneMeshBuffer(buf);
-                               copy->getMaterial() = buf->getMaterial();
-                               mesh->addMeshBuffer(copy);
-                               copy->drop();
-                               buf = copy;
-                               colors->push_back(
-                                       ItemPartColor(layer->has_color, layer->color));
-                       } else {
-                               (*colors)[i] = ItemPartColor(layer->has_color, layer->color);
-                       }
-                       video::SMaterial &material = buf->getMaterial();
-                       if (set_material)
-                               layer->applyMaterialOptions(material);
-                       if (mattype) {
-                               material.MaterialType = *mattype;
-                       }
-                       if (layer->animation_frame_count > 1) {
-                               const FrameSpec &animation_frame = (*layer->frames)[0];
-                               material.setTexture(0, animation_frame.texture);
-                       } else {
-                               material.setTexture(0, layer->texture);
-                       }
-                       if (use_shaders) {
-                               if (layer->normal_texture) {
-                                       if (layer->animation_frame_count > 1) {
-                                               const FrameSpec &animation_frame = (*layer->frames)[0];
-                                               material.setTexture(1, animation_frame.normal_texture);
-                                       } else
-                                               material.setTexture(1, layer->normal_texture);
-                               }
-                               material.setTexture(2, layer->flags_texture);
-                       }
-                       if (apply_scale && tile->world_aligned) {
-                               u32 n = buf->getVertexCount();
-                               for (u32 k = 0; k != n; ++k)
-                                       buf->getTCoords(k) /= layer->scale;
-                       }
-               }
-       }
-}
diff --git a/src/wieldmesh.h b/src/wieldmesh.h
deleted file mode 100644 (file)
index 0908d3a..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2014 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-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.
-*/
-
-#pragma once
-
-#include <string>
-#include <vector>
-#include "irrlichttypes_extrabloated.h"
-
-struct ItemStack;
-class Client;
-class ITextureSource;
-struct ContentFeatures;
-
-/*!
- * Holds color information of an item mesh's buffer.
- */
-struct ItemPartColor
-{
-       /*!
-        * If this is false, the global base color of the item
-        * will be used instead of the specific color of the
-        * buffer.
-        */
-       bool override_base = false;
-       /*!
-        * The color of the buffer.
-        */
-       video::SColor color = 0;
-
-       ItemPartColor() = default;
-
-       ItemPartColor(bool override, video::SColor color) :
-                       override_base(override), color(color)
-       {
-       }
-};
-
-struct ItemMesh
-{
-       scene::IMesh *mesh = nullptr;
-       /*!
-        * Stores the color of each mesh buffer.
-        */
-       std::vector<ItemPartColor> buffer_colors;
-       /*!
-        * If false, all faces of the item should have the same brightness.
-        * Disables shading based on normal vectors.
-        */
-       bool needs_shading = true;
-
-       ItemMesh() = default;
-};
-
-/*
-       Wield item scene node, renders the wield mesh of some item
-*/
-class WieldMeshSceneNode : public scene::ISceneNode
-{
-public:
-       WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id = -1, bool lighting = false);
-       virtual ~WieldMeshSceneNode();
-
-       void setCube(const ContentFeatures &f, v3f wield_scale);
-       void setExtruded(const std::string &imagename, const std::string &overlay_image,
-                       v3f wield_scale, ITextureSource *tsrc, u8 num_frames);
-       void setItem(const ItemStack &item, Client *client);
-
-       // Sets the vertex color of the wield mesh.
-       // Must only be used if the constructor was called with lighting = false
-       void setColor(video::SColor color);
-
-       scene::IMesh *getMesh() { return m_meshnode->getMesh(); }
-
-       virtual void render();
-
-       virtual const aabb3f &getBoundingBox() const { return m_bounding_box; }
-
-private:
-       void changeToMesh(scene::IMesh *mesh);
-
-       // Child scene node with the current wield mesh
-       scene::IMeshSceneNode *m_meshnode = nullptr;
-       video::E_MATERIAL_TYPE m_material_type;
-
-       // True if EMF_LIGHTING should be enabled.
-       bool m_lighting;
-
-       bool m_enable_shaders;
-       bool m_anisotropic_filter;
-       bool m_bilinear_filter;
-       bool m_trilinear_filter;
-       /*!
-        * Stores the colors of the mesh's mesh buffers.
-        * This does not include lighting.
-        */
-       std::vector<ItemPartColor> m_colors;
-       /*!
-        * The base color of this mesh. This is the default
-        * for all mesh buffers.
-        */
-       video::SColor m_base_color;
-
-       // Bounding box culling is disabled for this type of scene node,
-       // so this variable is just required so we can implement
-       // getBoundingBox() and is set to an empty box.
-       aabb3f m_bounding_box;
-};
-
-void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result);
-
-scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, const std::string &imagename,
-               const std::string &overlay_name);
-
-/*!
- * Applies overlays, textures and optionally materials to the given mesh and
- * extracts tile colors for colorization.
- * \param mattype overrides the buffer's material type, but can also
- * be NULL to leave the original material.
- * \param colors returns the colors of the mesh buffers in the mesh.
- */
-void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, bool use_shaders,
-               bool set_material, const video::E_MATERIAL_TYPE *mattype,
-               std::vector<ItemPartColor> *colors, bool apply_scale = false);
index 22e688a1fb67d15df0b7e7b824ac76d35cd206fd..c89520aadde33335b4995d9b628426f1fbf95cd6 100644 (file)
@@ -33,6 +33,77 @@ src/client/render/sidebyside.cpp
 src/client/render/stereo.cpp
 src/client/tile.cpp
 src/client/tile.h
+src/client/fontengine.h
+src/client/clientenvironment.cpp
+src/client/mapblock_mesh.cpp
+src/client/sound_openal.h
+src/client/clouds.cpp
+src/client/fontengine.cpp
+src/client/camera.h
+src/client/hud.cpp
+src/client/clientmap.cpp
+src/client/sound_openal.cpp
+src/client/minimap.h
+src/client/content_cao.cpp
+src/client/localplayer.h
+src/client/mapblock_mesh.h
+src/client/mesh.cpp
+src/client/sound.cpp
+src/client/guiscalingfilter.cpp
+src/client/content_cso.cpp
+src/client/gameui.cpp
+src/client/wieldmesh.cpp
+src/client/clientmedia.h
+src/client/game.cpp
+src/client/keys.h
+src/client/client.h
+src/client/shader.cpp
+src/client/clientmap.h
+src/client/inputhandler.h
+src/client/content_mapblock.h
+src/client/game.h
+src/client/mesh.h
+src/client/camera.cpp
+src/client/sky.h
+src/client/mesh_generator_thread.cpp
+src/client/guiscalingfilter.h
+src/client/clientobject.cpp
+src/client/tile.cpp
+src/client/hud.h
+src/client/inputhandler.cpp
+src/client/clientevent.h
+src/client/gameui.h
+src/client/content_cso.h
+src/client/sky.cpp
+src/client/localplayer.cpp
+src/client/content_mapblock.cpp
+src/client/clientobject.h
+src/client/filecache.cpp
+src/client/particles.h
+src/client/clientenvironment.h
+src/client/imagefilters.h
+src/client/renderingengine.cpp
+src/client/tile.h
+src/client/clientmedia.cpp
+src/client/event_manager.h
+src/client/joystick_controller.h
+src/client/clouds.h
+src/client/clientlauncher.h
+src/client/content_cao.h
+src/client/minimap.cpp
+src/client/sound.h
+src/client/keycode.cpp
+src/client/particles.cpp
+src/client/joystick_controller.cpp
+src/client/keycode.h
+src/client/wieldmesh.h
+src/client/filecache.h
+src/client/shader.h
+src/client/mesh_generator_thread.h
+src/client/renderingengine.h
+src/client/client.cpp
+src/client/imagefilters.cpp
+src/client/clientlauncher.cpp
 src/clouds.cpp
 src/clouds.h
 src/collision.cpp