]> git.lizzy.rs Git - minetest.git/blobdiff - src/client/game.cpp
Dual wielding
[minetest.git] / src / client / game.cpp
index 91c93ef7f874ebad86bde99c00f8d0219919cf3e..e31db44a831768ee47532ca630c060ff274cfe55 100644 (file)
@@ -71,6 +71,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "version.h"
 #include "script/scripting_client.h"
 #include "hud.h"
+#include "clientdynamicinfo.h"
 
 #if USE_SOUND
        #include "client/sound_openal.h"
@@ -414,6 +415,8 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
        CachedPixelShaderSetting<float> m_fog_distance;
        CachedVertexShaderSetting<float> m_animation_timer_vertex;
        CachedPixelShaderSetting<float> m_animation_timer_pixel;
+       CachedVertexShaderSetting<float> m_animation_timer_delta_vertex;
+       CachedPixelShaderSetting<float> m_animation_timer_delta_pixel;
        CachedPixelShaderSetting<float, 3> m_day_light;
        CachedPixelShaderSetting<float, 4> m_star_color;
        CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
@@ -427,25 +430,30 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
        CachedPixelShaderSetting<SamplerLayer_t> m_texture3;
        CachedPixelShaderSetting<float, 2> m_texel_size0;
        std::array<float, 2> m_texel_size0_values;
-       CachedPixelShaderSetting<float> m_exposure_factor_pixel;
-       float m_user_exposure_factor;
+       CachedStructPixelShaderSetting<float, 7> m_exposure_params_pixel;
+       float m_user_exposure_compensation;
        bool m_bloom_enabled;
        CachedPixelShaderSetting<float> m_bloom_intensity_pixel;
        float m_bloom_intensity;
+       CachedPixelShaderSetting<float> m_bloom_strength_pixel;
+       float m_bloom_strength;
        CachedPixelShaderSetting<float> m_bloom_radius_pixel;
        float m_bloom_radius;
+       CachedPixelShaderSetting<float> m_saturation_pixel;
 
 public:
        void onSettingsChange(const std::string &name)
        {
                if (name == "enable_fog")
                        m_fog_enabled = g_settings->getBool("enable_fog");
-               if (name == "exposure_factor")
-                       m_user_exposure_factor = g_settings->getFloat("exposure_factor", 0.1f, 10.0f);
+               if (name == "exposure_compensation")
+                       m_user_exposure_compensation = g_settings->getFloat("exposure_compensation", -1.0f, 1.0f);
                if (name == "bloom_intensity")
                        m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f);
+               if (name == "bloom_strength_factor")
+                       m_bloom_strength = RenderingEngine::BASE_BLOOM_STRENGTH * g_settings->getFloat("bloom_strength_factor", 0.1f, 10.0f);
                if (name == "bloom_radius")
-                       m_bloom_radius = g_settings->getFloat("bloom_radius", 1.0f, 64.0f);
+                       m_bloom_radius = g_settings->getFloat("bloom_radius", 0.1f, 8.0f);
        }
 
        static void settingsCallback(const std::string &name, void *userdata)
@@ -465,6 +473,8 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
                m_fog_distance("fogDistance"),
                m_animation_timer_vertex("animationTimer"),
                m_animation_timer_pixel("animationTimer"),
+               m_animation_timer_delta_vertex("animationTimerDelta"),
+               m_animation_timer_delta_pixel("animationTimerDelta"),
                m_day_light("dayLight"),
                m_star_color("starColor"),
                m_eye_position_pixel("eyePosition"),
@@ -477,19 +487,28 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
                m_texture2("texture2"),
                m_texture3("texture3"),
                m_texel_size0("texelSize0"),
-               m_exposure_factor_pixel("exposureFactor"),
+               m_exposure_params_pixel("exposureParams",
+                               std::array<const char*, 7> {
+                                               "luminanceMin", "luminanceMax", "exposureCorrection",
+                                               "speedDarkBright", "speedBrightDark", "centerWeightPower", "compensationFactor"
+                               }),
                m_bloom_intensity_pixel("bloomIntensity"),
-               m_bloom_radius_pixel("bloomRadius")
+               m_bloom_strength_pixel("bloomStrength"),
+               m_bloom_radius_pixel("bloomRadius"),
+               m_saturation_pixel("saturation")
        {
                g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
-               g_settings->registerChangedCallback("exposure_factor", settingsCallback, this);
+               g_settings->registerChangedCallback("exposure_compensation", settingsCallback, this);
                g_settings->registerChangedCallback("bloom_intensity", settingsCallback, this);
+               g_settings->registerChangedCallback("bloom_strength_factor", settingsCallback, this);
                g_settings->registerChangedCallback("bloom_radius", settingsCallback, this);
+               g_settings->registerChangedCallback("saturation", settingsCallback, this);
                m_fog_enabled = g_settings->getBool("enable_fog");
-               m_user_exposure_factor = g_settings->getFloat("exposure_factor", 0.1f, 10.0f);
+               m_user_exposure_compensation = g_settings->getFloat("exposure_compensation", -1.0f, 1.0f);
                m_bloom_enabled = g_settings->getBool("enable_bloom");
                m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f);
-               m_bloom_radius = g_settings->getFloat("bloom_radius", 1.0f, 64.0f);
+               m_bloom_strength = RenderingEngine::BASE_BLOOM_STRENGTH * g_settings->getFloat("bloom_strength_factor", 0.1f, 10.0f);
+               m_bloom_radius = g_settings->getFloat("bloom_radius", 0.1f, 8.0f);
        }
 
        ~GameGlobalShaderConstantSetter()
@@ -536,6 +555,10 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
                m_animation_timer_vertex.set(&animation_timer_f, services);
                m_animation_timer_pixel.set(&animation_timer_f, services);
 
+               float animation_timer_delta_f = (float)m_client->getEnv().getFrameTimeDelta() / 100000.f;
+               m_animation_timer_delta_vertex.set(&animation_timer_delta_f, services);
+               m_animation_timer_delta_pixel.set(&animation_timer_delta_f, services);
+
                float eye_position_array[3];
                v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
                epos.getAs3Values(eye_position_array);
@@ -567,15 +590,25 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
 
                m_texel_size0.set(m_texel_size0_values.data(), services);
 
-               float exposure_factor = RenderingEngine::DEFAULT_EXPOSURE_FACTOR * m_user_exposure_factor;
-               if (std::isnan(exposure_factor))
-                       exposure_factor = RenderingEngine::DEFAULT_EXPOSURE_FACTOR;
-               m_exposure_factor_pixel.set(&exposure_factor, services);
+               const AutoExposure &exposure_params = m_client->getEnv().getLocalPlayer()->getLighting().exposure;
+               std::array<float, 7> exposure_buffer = {
+                       std::pow(2.0f, exposure_params.luminance_min),
+                       std::pow(2.0f, exposure_params.luminance_max),
+                       exposure_params.exposure_correction,
+                       exposure_params.speed_dark_bright,
+                       exposure_params.speed_bright_dark,
+                       exposure_params.center_weight_power,
+                       powf(2.f, m_user_exposure_compensation)
+               };
+               m_exposure_params_pixel.set(exposure_buffer.data(), services);
 
                if (m_bloom_enabled) {
                        m_bloom_intensity_pixel.set(&m_bloom_intensity, services);
                        m_bloom_radius_pixel.set(&m_bloom_radius, services);
+                       m_bloom_strength_pixel.set(&m_bloom_strength, services);
                }
+               float saturation = m_client->getEnv().getLocalPlayer()->getLighting().saturation;
+               m_saturation_pixel.set(&saturation, services);
        }
 
        void onSetMaterial(const video::SMaterial &material)
@@ -683,6 +716,7 @@ struct GameRunData {
 
        float damage_flash;
        float update_draw_list_timer;
+       float touch_blocks_timer;
 
        f32 fog_range;
 
@@ -807,7 +841,8 @@ class Game {
                        bool look_for_object, const v3s16 &camera_offset);
        void handlePointingAtNothing(const ItemStack &playerItem);
        void handlePointingAtNode(const PointedThing &pointed,
-                       const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
+                       const ItemStack &selected_item, const ItemStack &hand_item,
+                       const ItemStack &place_item, f32 dtime);
        void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
                        const v3f &player_position, bool show_debug);
        void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
@@ -879,17 +914,21 @@ class Game {
        void updateChat(f32 dtime);
 
        bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
-               const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
+               const v3s16 &nodepos, const v3s16 &neighborpos, const PointedThing &pointed,
                const NodeMetadata *meta);
        static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
 
        f32 getSensitivityScaleFactor() const;
+       ClientDynamicInfo getCurrentDynamicInfo() const;
 
        InputHandler *input = nullptr;
 
        Client *client = nullptr;
        Server *server = nullptr;
 
+       ClientDynamicInfo client_display_info{};
+       float dynamic_info_send_timer = 0;
+
        IWritableTextureSource *texture_src = nullptr;
        IWritableShaderSource *shader_src = nullptr;
 
@@ -975,10 +1014,6 @@ class Game {
        // this happens in pause menu in singleplayer
        bool m_is_paused = false;
 
-#if IRRLICHT_VERSION_MT_REVISION < 5
-       int m_reset_HW_buffer_counter = 0;
-#endif
-
 #ifdef HAVE_TOUCHSCREENGUI
        bool m_cache_hold_aux1;
        bool m_touch_use_crosshair;
@@ -1158,31 +1193,44 @@ void Game::run()
                        && client->checkPrivilege("fast");
 #endif
 
-       irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
+       v2u32 previous_screen_size(g_settings->getU16("screen_w"),
                g_settings->getU16("screen_h"));
 
        while (m_rendering_engine->run()
                        && !(*kill || g_gamecallback->shutdown_requested
                        || (server && server->isShutdownRequested()))) {
 
-               const irr::core::dimension2d<u32> &current_screen_size =
-                       m_rendering_engine->get_video_driver()->getScreenSize();
+               // Calculate dtime =
+               //    m_rendering_engine->run() from this iteration
+               //  + Sleep time until the wanted FPS are reached
+               draw_times.limit(device, &dtime);
+
+               const auto current_dynamic_info = getCurrentDynamicInfo();
+               if (!current_dynamic_info.equal(client_display_info)) {
+                       client_display_info = current_dynamic_info;
+                       dynamic_info_send_timer = 0.2f;
+               }
+
+               if (dynamic_info_send_timer > 0) {
+                       dynamic_info_send_timer -= dtime;
+                       if (dynamic_info_send_timer <= 0) {
+                               client->sendUpdateClientInfo(current_dynamic_info);
+                       }
+               }
+
+               const auto &current_screen_size = current_dynamic_info.render_target_size;
+
                // 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);
+                       g_settings->setU16("screen_w", current_screen_size.X);
+                       g_settings->setU16("screen_h", current_screen_size.Y);
                        previous_screen_size = current_screen_size;
                }
 
-               // Calculate dtime =
-               //    m_rendering_engine->run() from this iteration
-               //  + Sleep time until the wanted FPS are reached
-               draw_times.limit(device, &dtime);
-
                // Prepare render data for next iteration
 
                updateStats(&stats, draw_times, dtime);
@@ -1196,8 +1244,6 @@ void Game::run()
                processQueues();
 
                m_game_ui->clearInfoText();
-               hud->resizeHotbar();
-
 
                updateProfilers(stats, draw_times, dtime);
                processUserInput(dtime);
@@ -1244,11 +1290,7 @@ void Game::run()
 void Game::shutdown()
 {
        m_rendering_engine->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
+
        auto formspec = m_game_ui->getFormspecGUI();
        if (formspec)
                formspec->quitMenu();
@@ -1443,6 +1485,11 @@ bool Game::createClient(const GameStartData &start_data)
        if (client->modsLoaded())
                client->getScript()->on_camera_ready(camera);
        client->setCamera(camera);
+#ifdef HAVE_TOUCHSCREENGUI
+       if (g_touchscreengui) {
+               g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed());
+       }
+#endif
 
        /* Clouds
         */
@@ -1787,19 +1834,19 @@ inline bool Game::handleCallbacks()
 
        if (g_gamecallback->changepassword_requested) {
                (new GUIPasswordChange(guienv, guiroot, -1,
-                                      &g_menumgr, client, texture_src))->drop();
+                                          &g_menumgr, client, texture_src))->drop();
                g_gamecallback->changepassword_requested = false;
        }
 
        if (g_gamecallback->changevolume_requested) {
                (new GUIVolumeChange(guienv, guiroot, -1,
-                                    &g_menumgr, texture_src))->drop();
+                                        &g_menumgr, texture_src))->drop();
                g_gamecallback->changevolume_requested = false;
        }
 
        if (g_gamecallback->keyconfig_requested) {
                (new GUIKeyChangeMenu(guienv, guiroot, -1,
-                                     &g_menumgr, texture_src))->drop();
+                                         &g_menumgr, texture_src))->drop();
                g_gamecallback->keyconfig_requested = false;
        }
 
@@ -2057,7 +2104,7 @@ void Game::processKeyInput()
        } else if (wasKeyDown(KeyType::MINIMAP)) {
                toggleMinimap(isKeyDown(KeyType::SNEAK));
        } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
-               m_game_ui->toggleChat();
+               m_game_ui->toggleChat(client);
        } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
                toggleFog();
        } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
@@ -2101,10 +2148,9 @@ void Game::processItemSelection(u16 *new_playeritem)
        /* Item selection using mouse wheel
         */
        *new_playeritem = player->getWieldIndex();
-
        s32 wheel = input->getMouseWheel();
        u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
-                   player->hud_hotbar_itemcount - 1);
+                       player->hud_hotbar_itemcount - 1);
 
        s32 dir = wheel;
 
@@ -2128,6 +2174,9 @@ void Game::processItemSelection(u16 *new_playeritem)
                        break;
                }
        }
+
+       // Clamp selection again in case it wasn't changed but max_item was
+       *new_playeritem = MYMIN(*new_playeritem, max_item);
 }
 
 
@@ -2501,6 +2550,13 @@ void Game::checkZoomEnabled()
 
 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
 {
+#ifndef __ANDROID__
+       if (isMenuActive())
+               device->getCursorControl()->setRelativeMode(false);
+       else
+               device->getCursorControl()->setRelativeMode(true);
+#endif
+
        if ((device->isWindowActive() && device->isWindowFocused()
                        && !isMenuActive()) || input->isRandom()) {
 
@@ -2546,6 +2602,19 @@ f32 Game::getSensitivityScaleFactor() const
        return tan(fov_y / 2.0f) * 1.3763818698f;
 }
 
+ClientDynamicInfo Game::getCurrentDynamicInfo() const
+{
+       v2u32 screen_size = RenderingEngine::getWindowSize();
+       f32 density = RenderingEngine::getDisplayDensity();
+       f32 gui_scaling = g_settings->getFloat("gui_scaling") * density;
+       f32 hud_scaling = g_settings->getFloat("hud_scaling") * density;
+
+       return {
+               screen_size, gui_scaling, hud_scaling,
+               ClientDynamicInfo::calculateMaxFSSize(screen_size)
+       };
+}
+
 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
 {
 #ifdef HAVE_TOUCHSCREENGUI
@@ -2939,6 +3008,9 @@ void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
                );
        }
 
+       // Orbit Tilt:
+       sky->setBodyOrbitTilt(event->set_sky->body_orbit_tilt);
+
        delete event->set_sky;
 }
 
@@ -3104,9 +3176,9 @@ 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());
+                                 v3f(0, 0, 0), // velocity
+                                 camera->getDirection(),
+                                 camera->getCameraNode()->getUpVector());
 
        bool mute_sound = g_settings->getBool("mute_sound");
        if (mute_sound) {
@@ -3148,7 +3220,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
                Calculate what block is the crosshair pointing to
        */
 
-       ItemStack selected_item, hand_item;
+       ItemStack selected_item, hand_item, offhand_item, place_item;
        const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
 
        const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
@@ -3189,6 +3261,8 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
                        !runData.btn_down_for_dig,
                        camera_offset);
 
+       player->getOffhandWieldedItem(&offhand_item, &place_item, itemdef_manager, pointed);
+
        if (pointed != runData.pointed_old)
                infostream << "Pointing at " << pointed.dump() << std::endl;
 
@@ -3254,7 +3328,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
                                !client->getScript()->on_item_use(selected_item, pointed)))
                        client->interact(INTERACT_USE, pointed);
        } else if (pointed.type == POINTEDTHING_NODE) {
-               handlePointingAtNode(pointed, selected_item, hand_item, dtime);
+               handlePointingAtNode(pointed, selected_item, hand_item, place_item, dtime);
        } else if (pointed.type == POINTEDTHING_OBJECT) {
                v3f player_position  = player->getPosition();
                bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
@@ -3267,13 +3341,13 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
                if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
                        client->getScript()->on_item_use(selected_item, pointed);
        } else if (wasKeyPressed(KeyType::PLACE)) {
-               handlePointingAtNothing(selected_item);
+               handlePointingAtNothing(place_item);
        }
 
        runData.pointed_old = pointed;
 
        if (runData.punching || wasKeyPressed(KeyType::DIG))
-               camera->setDigging(0); // dig animation
+               camera->setDigging(0, MAINHAND); // dig animation
 
        input->clearWasKeyPressed();
        input->clearWasKeyReleased();
@@ -3297,7 +3371,7 @@ PointedThing Game::updatePointedThing(
 {
        std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
        selectionboxes->clear();
-       hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
+       hud->setSelectedFaceNormal(v3f());
        static thread_local const bool show_entity_selectionbox = g_settings->getBool(
                "show_entity_selectionbox");
 
@@ -3321,7 +3395,13 @@ PointedThing Game::updatePointedThing(
                        v3f pos = runData.selected_object->getPosition();
                        selectionboxes->push_back(aabb3f(selection_box));
                        hud->setSelectionPos(pos, camera_offset);
+                       GenericCAO* gcao = dynamic_cast<GenericCAO*>(runData.selected_object);
+                       if (gcao != nullptr && gcao->getProperties().rotate_selectionbox)
+                               hud->setSelectionRotation(gcao->getSceneNode()->getAbsoluteTransformation().getRotationDegrees());
+                       else
+                               hud->setSelectionRotation(v3f());
                }
+               hud->setSelectedFaceNormal(result.raw_intersection_normal);
        } else if (result.type == POINTEDTHING_NODE) {
                // Update selection boxes
                MapNode n = map.getNode(result.node_undersurface);
@@ -3339,10 +3419,8 @@ PointedThing Game::updatePointedThing(
                }
                hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
                        camera_offset);
-               hud->setSelectedFaceNormal(v3f(
-                       result.intersection_normal.X,
-                       result.intersection_normal.Y,
-                       result.intersection_normal.Z));
+               hud->setSelectionRotation(v3f());
+               hud->setSelectedFaceNormal(result.intersection_normal);
        }
 
        // Update selection mesh light level and vertex colors
@@ -3393,10 +3471,11 @@ void Game::handlePointingAtNothing(const ItemStack &playerItem)
 
 
 void Game::handlePointingAtNode(const PointedThing &pointed,
-       const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
+       const ItemStack &selected_item, const ItemStack &hand_item,
+       const ItemStack &place_item, f32 dtime)
 {
        v3s16 nodepos = pointed.node_undersurface;
-       v3s16 neighbourpos = pointed.node_abovesurface;
+       v3s16 neighborpos = pointed.node_abovesurface;
 
        /*
                Check information text of node
@@ -3431,7 +3510,10 @@ void Game::handlePointingAtNode(const PointedThing &pointed,
                infostream << "Place button pressed while looking at ground" << std::endl;
 
                // Placing animation (always shown for feedback)
-               camera->setDigging(1);
+               if (place_item == selected_item)
+                       camera->setDigging(1, MAINHAND);
+               else
+                       camera->setDigging(1, OFFHAND);
 
                soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
 
@@ -3439,8 +3521,8 @@ void Game::handlePointingAtNode(const PointedThing &pointed,
                // make that happen
                // And also set the sound and send the interact
                // But first check for meta formspec and rightclickable
-               auto &def = selected_item.getDefinition(itemdef_manager);
-               bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
+               auto &def = place_item.getDefinition(itemdef_manager);
+               bool placed = nodePlacement(def, place_item, nodepos, neighborpos,
                        pointed, meta);
 
                if (placed && client->modsLoaded())
@@ -3449,7 +3531,7 @@ void Game::handlePointingAtNode(const PointedThing &pointed,
 }
 
 bool Game::nodePlacement(const ItemDefinition &selected_def,
-       const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
+       const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighborpos,
        const PointedThing &pointed, const NodeMetadata *meta)
 {
        const auto &prediction = selected_def.node_placement_prediction;
@@ -3499,7 +3581,7 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
 
        verbosestream << "Node placement prediction for "
                << selected_def.name << " is " << prediction << std::endl;
-       v3s16 p = neighbourpos;
+       v3s16 p = neighborpos;
 
        // Place inside node itself if buildable_to
        MapNode n_under = map.getNode(nodepos, &is_valid_position);
@@ -3533,24 +3615,23 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
 
        const ContentFeatures &predicted_f = nodedef->get(id);
 
-       // Predict param2 for facedir and wallmounted nodes
-       // Compare core.item_place_node() for what the server does
-       u8 param2 = 0;
+       // Compare core.item_place_node() for what the server does with param2
+       MapNode predicted_node(id, 0, 0);
 
        const u8 place_param2 = selected_def.place_param2;
 
        if (place_param2) {
-               param2 = place_param2;
+               predicted_node.setParam2(place_param2);
        } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
                        predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
-               v3s16 dir = nodepos - neighbourpos;
+               v3s16 dir = nodepos - neighborpos;
 
                if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
-                       param2 = dir.Y < 0 ? 1 : 0;
+                       predicted_node.setParam2(dir.Y < 0 ? 1 : 0);
                } else if (abs(dir.X) > abs(dir.Z)) {
-                       param2 = dir.X < 0 ? 3 : 2;
+                       predicted_node.setParam2(dir.X < 0 ? 3 : 2);
                } else {
-                       param2 = dir.Z < 0 ? 5 : 4;
+                       predicted_node.setParam2(dir.Z < 0 ? 5 : 4);
                }
        } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
                        predicted_f.param_type_2 == CPT2_COLORED_FACEDIR ||
@@ -3559,29 +3640,36 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
                v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
 
                if (abs(dir.X) > abs(dir.Z)) {
-                       param2 = dir.X < 0 ? 3 : 1;
+                       predicted_node.setParam2(dir.X < 0 ? 3 : 1);
                } else {
-                       param2 = dir.Z < 0 ? 2 : 0;
+                       predicted_node.setParam2(dir.Z < 0 ? 2 : 0);
                }
        }
 
        // Check attachment if node is in group attached_node
-       if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
-               const 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),
-               };
+       int an = itemgroup_get(predicted_f.groups, "attached_node");
+       if (an != 0) {
                v3s16 pp;
 
-               if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
-                               predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
-                       pp = p + wallmounted_dirs[param2];
-               else
+               if (an == 3) {
                        pp = p + v3s16(0, -1, 0);
+               } else if (an == 4) {
+                       pp = p + v3s16(0, 1, 0);
+               } else if (an == 2) {
+                       if (predicted_f.param_type_2 == CPT2_FACEDIR ||
+                                       predicted_f.param_type_2 == CPT2_COLORED_FACEDIR ||
+                                       predicted_f.param_type_2 == CPT2_4DIR ||
+                                       predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
+                               pp = p + facedir_dirs[predicted_node.getFaceDir(nodedef)];
+                       } else {
+                               pp = p;
+                       }
+               } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
+                               predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
+                       pp = p + predicted_node.getWallMountedDir(nodedef);
+               } else {
+                       pp = p + v3s16(0, -1, 0);
+               }
 
                if (!nodedef->get(map.getNode(pp)).walkable) {
                        soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
@@ -3601,36 +3689,34 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
                if (!indexstr.empty()) {
                        s32 index = mystoi(indexstr);
                        if (predicted_f.param_type_2 == CPT2_COLOR) {
-                               param2 = index;
+                               predicted_node.setParam2(index);
                        } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
                                // param2 = pure palette index + other
-                               param2 = (index & 0xf8) | (param2 & 0x07);
+                               predicted_node.setParam2((index & 0xf8) | (predicted_node.getParam2() & 0x07));
                        } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
                                // param2 = pure palette index + other
-                               param2 = (index & 0xe0) | (param2 & 0x1f);
+                               predicted_node.setParam2((index & 0xe0) | (predicted_node.getParam2() & 0x1f));
                        } else if (predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
                                // param2 = pure palette index + other
-                               param2 = (index & 0xfc) | (param2 & 0x03);
+                               predicted_node.setParam2((index & 0xfc) | (predicted_node.getParam2() & 0x03));
                        }
                }
        }
 
        // 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
+               // Don't 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 ||
+               if (!predicted_f.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))) {
+                               (predicted_f.walkable &&
+                                       neighborpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
+                                       neighborpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
                        // This triggers the required mesh update too
-                       client->addNode(p, n);
+                       client->addNode(p, predicted_node);
                        // Report to server
                        client->interact(INTERACT_PLACE, pointed);
                        // A node is predicted, also play a sound
@@ -3828,7 +3914,7 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
                client->setCrack(-1, nodepos);
        }
 
-       camera->setDigging(0);  // Dig animation
+       camera->setDigging(0, MAINHAND);  // Dig animation
 }
 
 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
@@ -3872,7 +3958,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
                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;
+                                       / 255.0;
        }
 
        float time_of_day_smooth = runData.time_of_day_smooth;
@@ -3994,9 +4080,11 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
 
        if (client->updateWieldedItem()) {
                // Update wielded tool
-               ItemStack selected_item, hand_item;
+               ItemStack selected_item, hand_item, offhand_item;
                ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
-               camera->wield(tool_item);
+               camera->wield(tool_item, MAINHAND);
+               player->getOffhandWieldedItem(&offhand_item, nullptr, itemdef_manager, PointedThing());
+               camera->wield(offhand_item, OFFHAND);
        }
 
        /*
@@ -4004,6 +4092,9 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
                changed much
        */
        runData.update_draw_list_timer += dtime;
+       runData.touch_blocks_timer += dtime;
+
+       bool draw_list_updated = false;
 
        float update_draw_list_delta = 0.2f;
 
@@ -4015,6 +4106,12 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
                runData.update_draw_list_timer = 0;
                client->getEnv().getClientMap().updateDrawList();
                runData.update_draw_list_last_cam_dir = camera_direction;
+               draw_list_updated = true;
+       }
+
+       if (runData.touch_blocks_timer > update_draw_list_delta && !draw_list_updated) {
+               client->getEnv().getClientMap().touchMapBlocks();
+               runData.touch_blocks_timer = 0;
        }
 
        if (RenderingEngine::get_shadow_renderer()) {
@@ -4095,29 +4192,6 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
        /*
                ==================== End scene ====================
        */
-#if IRRLICHT_VERSION_MT_REVISION < 5
-       if (++m_reset_HW_buffer_counter > 500) {
-               /*
-                 Periodically remove all mesh HW buffers.
-
-                 Work around for a quirk in Irrlicht where a HW buffer is only
-                 released after 20000 iterations (triggered from endScene()).
-
-                 Without this, all loaded but unused meshes will retain their HW
-                 buffers for at least 5 minutes, at which point looking up the HW buffers
-                 becomes a bottleneck and the framerate drops (as much as 30%).
-
-                 Tests showed that numbers between 50 and 1000 are good, so picked 500.
-                 There are no other public Irrlicht APIs that allow interacting with the
-                 HW buffers without tracking the status of every individual mesh.
-
-                 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
-               */
-               infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
-               driver->removeAllHardwareBuffers();
-               m_reset_HW_buffer_counter = 0;
-       }
-#endif
 
        driver->endScene();
 
@@ -4233,7 +4307,7 @@ void Game::readSettings()
        m_cache_enable_fog                   = g_settings->getBool("enable_fog");
        m_cache_mouse_sensitivity            = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f);
        m_cache_joystick_frustum_sensitivity = std::max(g_settings->getFloat("joystick_frustum_sensitivity"), 0.001f);
-       m_repeat_place_time                  = g_settings->getFloat("repeat_place_time", 0.25f, 2.0);
+       m_repeat_place_time                  = g_settings->getFloat("repeat_place_time", 0.16f, 2.0);
 
        m_cache_enable_noclip                = g_settings->getBool("noclip");
        m_cache_enable_free_move             = g_settings->getBool("free_move");