]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - src/game.cpp
Use thread_local instead from some static settings (#5955)
[dragonfireclient.git] / src / game.cpp
index 6ef471c9e6e4be78cfcdf1088c5350c3ff297efe..c32ab6f30d48dd4044a677f275bc06421cea39eb 100644 (file)
@@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <iomanip>
 #include "camera.h"
 #include "client.h"
+#include "client/inputhandler.h"
 #include "client/tile.h"     // For TextureSource
 #include "client/keys.h"
 #include "client/joystick_controller.h"
@@ -50,28 +51,20 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "quicktune_shortcutter.h"
 #include "server.h"
 #include "settings.h"
-#include "shader.h"          // For ShaderSource
 #include "sky.h"
 #include "subgame.h"
 #include "tool.h"
+#include "util/basic_macros.h"
 #include "util/directiontables.h"
 #include "util/pointedthing.h"
 #include "irrlicht_changes/static_text.h"
 #include "version.h"
-#include "minimap.h"
-#include "mapblock_mesh.h"
-#include "script/clientscripting.h"
-
-#include "sound.h"
+#include "script/scripting_client.h"
 
 #if USE_SOUND
        #include "sound_openal.h"
 #endif
 
-#ifdef HAVE_TOUCHSCREENGUI
-       #include "touchscreengui.h"
-#endif
-
 extern Settings *g_settings;
 extern Profiler *g_profiler;
 
@@ -79,14 +72,15 @@ extern Profiler *g_profiler;
        Text input system
 */
 
-struct TextDestNodeMetadata : public TextDest {
+struct TextDestNodeMetadata : public TextDest
+{
        TextDestNodeMetadata(v3s16 p, Client *client)
        {
                m_p = p;
                m_client = client;
        }
        // This is deprecated I guess? -celeron55
-       void gotText(std::wstring text)
+       void gotText(const std::wstring &text)
        {
                std::string ntext = wide_to_utf8(text);
                infostream << "Submitting 'text' field of node at (" << m_p.X << ","
@@ -104,13 +98,14 @@ struct TextDestNodeMetadata : public TextDest {
        Client *m_client;
 };
 
-struct TextDestPlayerInventory : public TextDest {
+struct TextDestPlayerInventory : public TextDest
+{
        TextDestPlayerInventory(Client *client)
        {
                m_client = client;
                m_formname = "";
        }
-       TextDestPlayerInventory(Client *client, std::string formname)
+       TextDestPlayerInventory(Client *client, const std::string &formname)
        {
                m_client = client;
                m_formname = formname;
@@ -125,23 +120,18 @@ struct TextDestPlayerInventory : public TextDest {
 
 struct LocalFormspecHandler : public TextDest
 {
-       LocalFormspecHandler(std::string formname):
-               m_client(0)
+       LocalFormspecHandler(const std::string &formname):
+               m_client(NULL)
        {
                m_formname = formname;
        }
 
-       LocalFormspecHandler(std::string formname, Client *client):
+       LocalFormspecHandler(const std::string &formname, Client *client):
                m_client(client)
        {
                m_formname = formname;
        }
 
-       void gotText(std::wstring message)
-       {
-               errorstream << "LocalFormspecHandler::gotText old style message received" << std::endl;
-       }
-
        void gotText(const StringMap &fields)
        {
                if (m_formname == "MT_PAUSE_MENU") {
@@ -205,7 +195,8 @@ class NodeMetadataFormSource: public IFormSource
 
                return meta->getString("formspec");
        }
-       std::string resolveText(std::string str)
+
+       virtual std::string resolveText(const std::string &str)
        {
                NodeMetadata *meta = m_map->getNodeMetadata(m_p);
 
@@ -478,6 +469,7 @@ class SoundMaker
        ISoundManager *m_sound;
        INodeDefManager *m_ndef;
 public:
+       bool makes_footstep_sound;
        float m_player_step_timer;
 
        SimpleSoundSpec m_player_step_sound;
@@ -487,6 +479,7 @@ class SoundMaker
        SoundMaker(ISoundManager *sound, INodeDefManager *ndef):
                m_sound(sound),
                m_ndef(ndef),
+               makes_footstep_sound(true),
                m_player_step_timer(0)
        {
        }
@@ -495,7 +488,8 @@ class SoundMaker
        {
                if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
                        m_player_step_timer = 0.03;
-                       m_sound->playSound(m_player_step_sound, false);
+                       if (makes_footstep_sound)
+                               m_sound->playSound(m_player_step_sound, false);
                }
        }
 
@@ -569,27 +563,35 @@ class SoundMaker
 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)
+               std::set<std::string> &dst_paths,
+               std::set<std::string> &dst_datas)
        {
                if (m_fetched.count(name))
                        return;
 
                m_fetched.insert(name);
-               std::string base = porting::path_share + DIR_DELIM + "sounds";
-               dst_paths.insert(base + DIR_DELIM + name + ".ogg");
-               dst_paths.insert(base + DIR_DELIM + name + ".0.ogg");
-               dst_paths.insert(base + DIR_DELIM + name + ".1.ogg");
-               dst_paths.insert(base + DIR_DELIM + name + ".2.ogg");
-               dst_paths.insert(base + DIR_DELIM + name + ".3.ogg");
-               dst_paths.insert(base + DIR_DELIM + name + ".4.ogg");
-               dst_paths.insert(base + DIR_DELIM + name + ".5.ogg");
-               dst_paths.insert(base + DIR_DELIM + name + ".6.ogg");
-               dst_paths.insert(base + DIR_DELIM + name + ".7.ogg");
-               dst_paths.insert(base + DIR_DELIM + name + ".8.ogg");
-               dst_paths.insert(base + DIR_DELIM + name + ".9.ogg");
+
+               paths_insert(dst_paths, porting::path_share, name);
+               paths_insert(dst_paths, porting::path_user,  name);
        }
 };
 
@@ -713,16 +715,19 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
                m_eye_position_pixel.set(eye_position_array, services);
                m_eye_position_vertex.set(eye_position_array, services);
 
-               float minimap_yaw_array[3];
-               v3f minimap_yaw = m_client->getMinimap()->getYawVec();
+               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;
+                       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);
+                       minimap_yaw.getAs3Values(minimap_yaw_array);
 #endif
-               m_minimap_yaw.set(minimap_yaw_array, services);
+                       m_minimap_yaw.set(minimap_yaw_array, services);
+
+               }
 
                SamplerLayer_t base_tex = 0,
                                normal_tex = 1,
@@ -1028,7 +1033,7 @@ void KeyCache::populate()
        key[KeyType::SPECIAL1]     = getKeySetting("keymap_special1");
        key[KeyType::SNEAK]        = getKeySetting("keymap_sneak");
 
-       key[KeyType::AUTORUN]      = getKeySetting("keymap_autorun");
+       key[KeyType::AUTOFORWARD]  = getKeySetting("keymap_autoforward");
 
        key[KeyType::DROP]         = getKeySetting("keymap_drop");
        key[KeyType::INVENTORY]    = getKeySetting("keymap_inventory");
@@ -1040,6 +1045,11 @@ void KeyCache::populate()
        key[KeyType::FREEMOVE]     = getKeySetting("keymap_freemove");
        key[KeyType::FASTMOVE]     = getKeySetting("keymap_fastmove");
        key[KeyType::NOCLIP]       = getKeySetting("keymap_noclip");
+       key[KeyType::HOTBAR_PREV]  = getKeySetting("keymap_hotbar_previous");
+       key[KeyType::HOTBAR_NEXT]  = getKeySetting("keymap_hotbar_next");
+       key[KeyType::MUTE]         = getKeySetting("keymap_mute");
+       key[KeyType::INC_VOLUME]   = getKeySetting("keymap_increase_volume");
+       key[KeyType::DEC_VOLUME]   = getKeySetting("keymap_decrease_volume");
        key[KeyType::CINEMATIC]    = getKeySetting("keymap_cinematic");
        key[KeyType::SCREENSHOT]   = getKeySetting("keymap_screenshot");
        key[KeyType::TOGGLE_HUD]   = getKeySetting("keymap_toggle_hud");
@@ -1111,6 +1121,7 @@ struct GameRunData {
        PointedThing pointed_old;
        bool digging;
        bool ldown_for_dig;
+       bool dig_instantly;
        bool left_punch;
        bool update_wielded_item_trigger;
        bool reset_jump_timer;
@@ -1190,7 +1201,7 @@ class Game {
                        u16 port,
                        const SubgameSpec &gamespec);
        bool initSound();
-       bool createSingleplayerServer(const std::string map_dir,
+       bool createSingleplayerServer(const std::string &map_dir,
                        const SubgameSpec &gamespec, u16 port, std::string *address);
 
        // Client creation
@@ -1227,7 +1238,7 @@ class Game {
        void toggleFast();
        void toggleNoClip();
        void toggleCinematic();
-       void toggleAutorun();
+       void toggleAutoforward();
 
        void toggleChat();
        void toggleHud();
@@ -1622,10 +1633,26 @@ void Game::run()
                        && client->checkPrivilege("fast");
 #endif
 
+       irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
+               g_settings->getU16("screen_h"));
+
        while (device->run()
                        && !(*kill || g_gamecallback->shutdown_requested
                        || (server && server->getShutdownRequested()))) {
 
+               const irr::core::dimension2d<u32> &current_screen_size =
+                       device->getVideoDriver()->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()
                 */
@@ -1674,6 +1701,8 @@ void Game::shutdown()
                driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
        }
 #endif
+       if (current_formspec)
+               current_formspec->quitMenu();
 
        showOverlayMessage(wgettext("Shutting down..."), 0, 0, false);
 
@@ -1725,9 +1754,10 @@ bool Game::init(
                u16 port,
                const SubgameSpec &gamespec)
 {
+       texture_src = createTextureSource(device);
+
        showOverlayMessage(wgettext("Loading..."), 0, 0);
 
-       texture_src = createTextureSource(device);
        shader_src = createShaderSource(device);
 
        itemdef_manager = createItemDefManager();
@@ -1779,7 +1809,7 @@ bool Game::initSound()
        return true;
 }
 
-bool Game::createSingleplayerServer(const std::string map_dir,
+bool Game::createSingleplayerServer(const std::string &map_dir,
                const SubgameSpec &gamespec, u16 port, std::string *address)
 {
        showOverlayMessage(wgettext("Creating server..."), 0, 5);
@@ -1923,7 +1953,8 @@ bool Game::createClient(const std::string &playername,
        }
 
        mapper = client->getMinimap();
-       mapper->setMinimapMode(MINIMAP_MODE_OFF);
+       if (mapper)
+               mapper->setMinimapMode(MINIMAP_MODE_OFF);
 
        return true;
 }
@@ -2036,7 +2067,7 @@ bool Game::connectToServer(const std::string &playername,
        }
 
        client = new Client(device,
-                       playername.c_str(), password,
+                       playername.c_str(), password, *address,
                        *draw_control, texture_src, shader_src,
                        itemdef_manager, nodedef_manager, sound, eventmgr,
                        connect_address.isIPv6(), &flags);
@@ -2048,7 +2079,7 @@ bool Game::connectToServer(const std::string &playername,
        connect_address.print(&infostream);
        infostream << std::endl;
 
-       client->connect(connect_address, *address,
+       client->connect(connect_address,
                simple_singleplayer_mode || local_server_mode);
 
        /*
@@ -2183,12 +2214,14 @@ bool Game::getServerContent(bool *aborted)
                if (!client->itemdefReceived()) {
                        const wchar_t *text = wgettext("Item definitions...");
                        progress = 25;
-                       draw_load_screen(text, device, guienv, dtime, progress);
+                       draw_load_screen(text, device, guienv, texture_src,
+                               dtime, progress);
                        delete[] text;
                } else if (!client->nodedefReceived()) {
                        const wchar_t *text = wgettext("Node definitions...");
                        progress = 30;
-                       draw_load_screen(text, device, guienv, dtime, progress);
+                       draw_load_screen(text, device, guienv, texture_src,
+                               dtime, progress);
                        delete[] text;
                } else {
                        std::stringstream message;
@@ -2212,7 +2245,7 @@ bool Game::getServerContent(bool *aborted)
 
                        progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
                        draw_load_screen(utf8_to_wide(message.str()), device,
-                                       guienv, dtime, progress);
+                                       guienv, texture_src, dtime, progress);
                }
        }
 
@@ -2399,7 +2432,7 @@ void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
 void Game::processUserInput(f32 dtime)
 {
        // Reset input if window not active or some menu is active
-       if (!device->isWindowActive() || !noMenuActive() || guienv->hasFocus(gui_chat_console)) {
+       if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
                input->clear();
 #ifdef HAVE_TOUCHSCREENGUI
                g_touchscreengui->hide();
@@ -2440,8 +2473,8 @@ void Game::processKeyInput()
 {
        if (wasKeyDown(KeyType::DROP)) {
                dropSelectedItem();
-       } else if (wasKeyDown(KeyType::AUTORUN)) {
-               toggleAutorun();
+       } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
+               toggleAutoforward();
        } else if (wasKeyDown(KeyType::INVENTORY)) {
                openInventory();
        } else if (wasKeyDown(KeyType::ESC) || input->wasKeyDown(CancelKey)) {
@@ -2464,6 +2497,30 @@ void Game::processKeyInput()
                toggleFast();
        } else if (wasKeyDown(KeyType::NOCLIP)) {
                toggleNoClip();
+       } else if (wasKeyDown(KeyType::MUTE)) {
+               float volume = g_settings->getFloat("sound_volume");
+               if (volume < 0.001f) {
+                       g_settings->setFloat("sound_volume", 1.0f);
+                       m_statustext = narrow_to_wide(gettext("Volume changed to 100%"));
+               } else {
+                       g_settings->setFloat("sound_volume", 0.0f);
+                       m_statustext = narrow_to_wide(gettext("Volume changed to 0%"));
+               }
+               runData.statustext_time = 0;
+       } else if (wasKeyDown(KeyType::INC_VOLUME)) {
+               float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
+               char buf[100];
+               g_settings->setFloat("sound_volume", new_volume);
+               snprintf(buf, sizeof(buf), gettext("Volume changed to %d%%"), myround(new_volume * 100));
+               m_statustext = narrow_to_wide(buf);
+               runData.statustext_time = 0;
+       } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
+               float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
+               char buf[100];
+               g_settings->setFloat("sound_volume", new_volume);
+               snprintf(buf, sizeof(buf), gettext("Volume changed to %d%%"), myround(new_volume * 100));
+               m_statustext = narrow_to_wide(buf);
+               runData.statustext_time = 0;
        } else if (wasKeyDown(KeyType::CINEMATIC)) {
                toggleCinematic();
        } else if (wasKeyDown(KeyType::SCREENSHOT)) {
@@ -2531,11 +2588,13 @@ void Game::processItemSelection(u16 *new_playeritem)
 
        s32 dir = wheel;
 
-       if (input->joystick.wasKeyDown(KeyType::SCROLL_DOWN)) {
+       if (input->joystick.wasKeyDown(KeyType::SCROLL_DOWN) ||
+                       wasKeyDown(KeyType::HOTBAR_NEXT)) {
                dir = -1;
        }
 
-       if (input->joystick.wasKeyDown(KeyType::SCROLL_UP)) {
+       if (input->joystick.wasKeyDown(KeyType::SCROLL_UP) ||
+                       wasKeyDown(KeyType::HOTBAR_PREV)) {
                dir = 1;
        }
 
@@ -2696,15 +2755,15 @@ void Game::toggleCinematic()
        m_statustext = msg[cinematic];
 }
 
-// Add WoW-style autorun by toggling continuous forward.
-void Game::toggleAutorun()
+// Autoforward by toggling continuous forward.
+void Game::toggleAutoforward()
 {
-       static const wchar_t *msg[] = { L"autorun disabled", L"autorun enabled" };
-       bool autorun_enabled = !g_settings->getBool("continuous_forward");
-       g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
+       static const wchar_t *msg[] = { L"autoforward disabled", L"autoforward enabled" };
+       bool autoforward_enabled = !g_settings->getBool("continuous_forward");
+       g_settings->set("continuous_forward", bool_to_cstr(autoforward_enabled));
 
        runData.statustext_time = 0;
-       m_statustext = msg[autorun_enabled ? 1 : 0];
+       m_statustext = msg[autoforward_enabled ? 1 : 0];
 }
 
 void Game::toggleChat()
@@ -2728,7 +2787,7 @@ void Game::toggleHud()
 
 void Game::toggleMinimap(bool shift_pressed)
 {
-       if (!flags.show_hud || !g_settings->getBool("enable_minimap"))
+       if (!mapper || !flags.show_hud || !g_settings->getBool("enable_minimap"))
                return;
 
        if (shift_pressed) {
@@ -2903,7 +2962,8 @@ void Game::toggleFullViewRange()
 
 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
 {
-       if ((device->isWindowActive() && noMenuActive()) || random_input) {
+       if ((device->isWindowActive() && device->isWindowFocused()
+                       && !isMenuActive()) || random_input) {
 
 #ifndef __ANDROID__
                if (!random_input) {
@@ -2928,8 +2988,7 @@ void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
                        device->getCursorControl()->setVisible(true);
 #endif
 
-               if (!m_first_loop_after_window_activation)
-                       m_first_loop_after_window_activation = true;
+               m_first_loop_after_window_activation = true;
 
        }
 }
@@ -3045,11 +3104,10 @@ inline void Game::step(f32 *dtime)
 
 void Game::processClientEvents(CameraOrientation *cam)
 {
-       ClientEvent event = client->getClientEvent();
-
        LocalPlayer *player = client->getEnv().getLocalPlayer();
 
-       for ( ; event.type != CE_NONE; event = client->getClientEvent()) {
+       while (client->hasClientEvents()) {
+               ClientEvent event = client->getClientEvent();
 
                switch (event.type) {
                case CE_PLAYER_DAMAGE:
@@ -3173,8 +3231,7 @@ void Game::processClientEvents(CameraOrientation *cam)
                        {
                                HudElement *e = player->removeHud(event.hudrm.id);
 
-                               if (e != NULL)
-                                       delete e;
+                               delete e;
                        }
                        break;
 
@@ -3246,6 +3303,8 @@ void Game::processClientEvents(CameraOrientation *cam)
 
                case CE_SET_SKY:
                        sky->setVisible(false);
+                       // Whether clouds are visible in front of a custom skybox
+                       sky->setCloudsEnabled(event.set_sky.clouds);
 
                        if (skybox) {
                                skybox->remove();
@@ -3255,6 +3314,7 @@ void Game::processClientEvents(CameraOrientation *cam)
                        // 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);
@@ -3286,6 +3346,19 @@ void Game::processClientEvents(CameraOrientation *cam)
                                        event.override_day_night_ratio.ratio_f * 1000);
                        break;
 
+               case CE_CLOUD_PARAMS:
+                       if (clouds) {
+                               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));
+                       }
+                       break;
+
                default:
                        // unknown or unhandled type
                        break;
@@ -3376,14 +3449,25 @@ void Game::updateSound(f32 dtime)
                              v3f(0, 0, 0), // velocity
                              camera->getDirection(),
                              camera->getCameraNode()->getUpVector());
-       sound->setListenerGain(g_settings->getFloat("sound_volume"));
 
+       // 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);
 
-       //      Update sound maker
-       soundmaker->step(dtime);
+       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;
@@ -3486,6 +3570,10 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
                        client->setCrack(-1, v3s16(0, 0, 0));
                        runData.dig_time = 0.0;
                }
+       } else if (runData.dig_instantly && 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 && !isLeftPressed()) {
@@ -3502,12 +3590,13 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
                runData.repeat_rightclick_timer = 0;
 
        if (playeritem_def.usable && isLeftPressed()) {
-               if (getLeftClicked())
+               if (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()) {
+               if (playeritem.name.empty() && hand_def.tool_capabilities != NULL) {
                        playeritem_toolcap = *hand_def.tool_capabilities;
                }
                handlePointingAtNode(pointed, playeritem_def, playeritem_toolcap, dtime);
@@ -3548,7 +3637,7 @@ PointedThing Game::updatePointedThing(
        std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
        selectionboxes->clear();
        hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
-       static const bool show_entity_selectionbox = g_settings->getBool(
+       static thread_local const bool show_entity_selectionbox = g_settings->getBool(
                "show_entity_selectionbox");
 
        ClientMap &map = client->getEnv().getClientMap();
@@ -3625,12 +3714,9 @@ PointedThing Game::updatePointedThing(
                float sin_r = 0.08 * sin(timerf);
                float sin_g = 0.08 * sin(timerf + irr::core::PI * 0.5);
                float sin_b = 0.08 * 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));
+               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);
@@ -3659,6 +3745,13 @@ void Game::handlePointingAtNode(const PointedThing &pointed, const ItemDefinitio
        */
 
        ClientMap &map = client->getEnv().getClientMap();
+
+       if (runData.nodig_delay_timer <= 0.0 && isLeftPressed()
+                       && client->checkPrivilege("interact")) {
+               handleDigging(pointed, nodepos, playeritem_toolcap, dtime);
+       }
+
+       // This should be done after digging handling
        NodeMetadata *meta = map.getNodeMetadata(nodepos);
 
        if (meta) {
@@ -3672,11 +3765,6 @@ void Game::handlePointingAtNode(const PointedThing &pointed, const ItemDefinitio
                }
        }
 
-       if (runData.nodig_delay_timer <= 0.0 && isLeftPressed()
-                       && client->checkPrivilege("interact")) {
-               handleDigging(pointed, nodepos, playeritem_toolcap, dtime);
-       }
-
        if ((getRightClicked() ||
                        runData.repeat_rightclick_timer >= m_repeat_right_click_time) &&
                        client->checkPrivilege("interact")) {
@@ -3716,6 +3804,9 @@ void Game::handlePointingAtNode(const PointedThing &pointed, const ItemDefinitio
                                // 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();
@@ -3798,15 +3889,6 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
        ClientMap &map = client->getEnv().getClientMap();
        MapNode n = client->getEnv().getClientMap().getNodeNoEx(nodepos);
 
-       if (!runData.digging) {
-               infostream << "Started digging" << std::endl;
-               if (client->moddingEnabled() && client->getScript()->on_punchnode(nodepos, n))
-                       return;
-               client->interact(0, pointed);
-               runData.digging = true;
-               runData.ldown_for_dig = true;
-       }
-
        // NOTE: Similar piece of code exists on the server side for
        // cheat detection.
        // Get digging parameters
@@ -3838,12 +3920,22 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
                }
        }
 
-       if (runData.dig_time_complete >= 0.001) {
+       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 torches
+               // This is for e.g. torches
                runData.dig_index = crack_animation_length;
        }
 
@@ -3878,25 +3970,19 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
                runData.nodig_delay_timer =
                                runData.dig_time_complete / (float)crack_animation_length;
 
-               // We don't want a corresponding delay to
-               // very time consuming nodes
+               // 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;
-
-               // We want a slight delay to very little
-               // time consuming nodes
-               const float mindelay = 0.15;
-
-               if (runData.nodig_delay_timer < mindelay)
-                       runData.nodig_delay_timer = mindelay;
+               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()) {
-                               if (client->getScript()->on_dignode(nodepos, wasnode)) {
-                                       return;
-                               }
+                       if (client->moddingEnabled() &&
+                                       client->getScript()->on_dignode(nodepos, wasnode)) {
+                               return;
                        }
                        client->removeNode(nodepos);
                }
@@ -4100,7 +4186,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
                if (current_formspec->getReferenceCount() == 1) {
                        current_formspec->drop();
                        current_formspec = NULL;
-               } else if (!noMenuActive()) {
+               } else if (isMenuActive()) {
                        guiroot->bringToFront(current_formspec);
                }
        }
@@ -4114,7 +4200,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
        TimeTaker tt_draw("mainloop: draw");
        driver->beginScene(true, true, skycolor);
 
-       draw_scene(driver, smgr, *camera, *client, player, *hud, *mapper,
+       draw_scene(driver, smgr, *camera, *client, player, *hud, mapper,
                        guienv, screensize, skycolor, flags.show_hud,
                        flags.show_minimap);
 
@@ -4149,7 +4235,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
        /*
                Update minimap pos and rotation
        */
-       if (flags.show_minimap && flags.show_hud) {
+       if (mapper && flags.show_minimap && flags.show_hud) {
                mapper->setPos(floatToInt(player->getPosition(), BS));
                mapper->setAngle(player->getYaw());
        }
@@ -4357,7 +4443,8 @@ inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
 void Game::showOverlayMessage(const wchar_t *msg, float dtime,
                int percent, bool draw_clouds)
 {
-       draw_load_screen(msg, device, guienv, dtime, percent, draw_clouds);
+       draw_load_screen(msg, device, guienv, texture_src, dtime, percent,
+               draw_clouds);
        delete[] msg;
 }
 
@@ -4421,6 +4508,7 @@ void Game::extendedResourceCleanup()
                       << " (note: irrlicht doesn't support removing renderers)" << std::endl;
 }
 
+#define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
 void Game::showPauseMenu()
 {
 #ifdef __ANDROID__
@@ -4438,21 +4526,41 @@ void Game::showPauseMenu()
                " --> place single item to slot\n"
                );
 #else
-       static const std::string control_text = strgettext("Default Controls:\n"
-               "- WASD: move\n"
-               "- Space: jump/climb\n"
-               "- Shift: sneak/go down\n"
-               "- Q: drop item\n"
-               "- I: inventory\n"
+       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"
-               "- T: chat\n"
+               "- %s: chat\n"
        );
+
+        char control_text_buf[600];
+
+        snprintf(control_text_buf, ARRLEN(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.5 : 0.1;
+       float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
        std::ostringstream os;
 
        os << FORMSPEC_VERSION_STRING  << SIZE_TAG
@@ -4462,6 +4570,8 @@ void Game::showPauseMenu()
        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__
@@ -4475,10 +4585,43 @@ void Game::showPauseMenu()
        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.5,6;;" << PROJECT_NAME_C "\n"
-               << g_build_info << "\n"
-               << "path_user = " << wrap_rows(porting::path_user, 20)
-               << "\n;]";
+               << "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 != "") {
+                       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 == "") {
+               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 != "")
+                               os << strgettext("- Server Name: ") << server_name;
+
+               }
+       }
+       os << ";]";
 
        /* Create menu */
        /* Note: FormspecFormSource and LocalFormspecHandler  *