]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - src/client/game.cpp
Merge branch 'master' of https://github.com/minetest/minetest
[dragonfireclient.git] / src / client / game.cpp
index d2a75104085d59555d1765dabecc77bca05be897..e439d0e32da366d7a056fa93b977c8fd34591a68 100644 (file)
@@ -122,7 +122,7 @@ Game::Game() :
 
        readSettings();
 
-#ifdef __ANDROID__
+#ifdef HAVE_TOUCHSCREENGUI
        m_cache_hold_aux1 = false;      // This is initialised properly later
 #endif
 
@@ -243,19 +243,19 @@ bool Game::startup(bool *kill,
 void Game::run()
 {
        ProfilerGraph graph;
-       RunStats stats              = { 0 };
-       FpsControl draw_times       = { 0 };
+       RunStats stats = {};
+       FpsControl draw_times;
        f32 dtime; // in seconds
 
        /* Clear the profiler */
        Profiler::GraphValues dummyvalues;
        g_profiler->graphGet(dummyvalues);
 
-       draw_times.last_time = m_rendering_engine->get_timer_time();
+       draw_times.reset();
 
        set_light_table(g_settings->getFloat("display_gamma"));
 
-#ifdef __ANDROID__
+#ifdef HAVE_TOUCHSCREENGUI
        m_cache_hold_aux1 = g_settings->getBool("fast_move")
                        && client->checkPrivilege("fast");
 #endif
@@ -283,7 +283,7 @@ void Game::run()
                // Calculate dtime =
                //    m_rendering_engine->run() from this iteration
                //  + Sleep time until the wanted FPS are reached
-               limitFps(&draw_times, &dtime);
+               draw_times.limit(device, &dtime);
 
                // Prepare render data for next iteration
 
@@ -313,10 +313,9 @@ void Game::run()
                step(&dtime);
                processClientEvents(&cam_view_target);
                updateDebugState();
-               updateCamera(draw_times.busy_time, dtime);
+               updateCamera(dtime);
                updateSound(dtime);
-               processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
-                       m_game_ui->m_flags.show_basic_debug);
+               processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
                updateFrame(&graph, &stats, dtime, cam_view);
                updateProfilerGraphs(&graph);
 
@@ -473,9 +472,8 @@ bool Game::createSingleplayerServer(const std::string &map_dir,
        }
 
        if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
-               *error_message = "Unable to listen on " +
-                               bind_addr.serializeString() +
-                               " because IPv6 is disabled";
+               *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
+                       bind_addr.serializeString().c_str());
                errorstream << *error_message << std::endl;
                return false;
        }
@@ -508,7 +506,7 @@ bool Game::createClient(const GameStartData &start_data)
        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";
+                       *error_message = gettext("Connection failed for unknown reason");
                        errorstream << *error_message << std::endl;
                }
                return false;
@@ -517,7 +515,7 @@ bool Game::createClient(const GameStartData &start_data)
        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";
+                       *error_message = gettext("Connection failed for unknown reason");
                        errorstream << *error_message << std::endl;
                }
                return false;
@@ -533,8 +531,8 @@ bool Game::createClient(const GameStartData &start_data)
        /* Camera
         */
        camera = new Camera(*draw_control, client, m_rendering_engine);
-       if (!camera->successfullyCreated(*error_message))
-               return false;
+       if (client->modsLoaded())
+               client->getScript()->on_camera_ready(camera);
        client->setCamera(camera);
 
        /* Clouds
@@ -575,7 +573,7 @@ bool Game::createClient(const GameStartData &start_data)
                str += L" [";
                str += text;
                str += L"]";
-               delete text;
+               delete[] text;
        }
        str += L" [";
        str += L"Minetest Hackclient";
@@ -650,7 +648,6 @@ bool Game::connectToServer(const GameStartData &start_data,
                connect_address.Resolve(start_data.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;
@@ -661,29 +658,35 @@ bool Game::connectToServer(const GameStartData &start_data,
                        local_server_mode = true;
                }
        } catch (ResolveError &e) {
-               *error_message = std::string("Couldn't resolve address: ") + e.what();
+               *error_message = fmtgettext("Couldn't resolve address: %s", 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";
+               *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
                errorstream << *error_message << std::endl;
                return false;
        }
 
-       client = new Client(start_data.name.c_str(),
-                       start_data.password, start_data.address,
-                       *draw_control, texture_src, shader_src,
-                       itemdef_manager, nodedef_manager, sound, eventmgr,
-                       m_rendering_engine, connect_address.isIPv6(), m_game_ui.get());
+       try {
+               client = new Client(start_data.name.c_str(),
+                               start_data.password, start_data.address,
+                               *draw_control, texture_src, shader_src,
+                               itemdef_manager, nodedef_manager, sound, eventmgr,
+                               m_rendering_engine, connect_address.isIPv6(), m_game_ui.get());
+               client->migrateModStorage();
+       } catch (const BaseException &e) {
+               *error_message = fmtgettext("Error creating client: %s", e.what());
+               errorstream << *error_message << std::endl;
+               return false;
+       }
 
        client->m_simple_singleplayer_mode = simple_singleplayer_mode;
 
        infostream << "Connecting to server at ";
-       connect_address.print(&infostream);
+       connect_address.print(infostream);
        infostream << std::endl;
 
        client->connect(connect_address,
@@ -696,15 +699,15 @@ bool Game::connectToServer(const GameStartData &start_data,
        try {
                input->clear();
 
-               FpsControl fps_control = { 0 };
+               FpsControl fps_control;
                f32 dtime;
                f32 wait_time = 0; // in seconds
 
-               fps_control.last_time = m_rendering_engine->get_timer_time();
+               fps_control.reset();
 
                while (m_rendering_engine->run()) {
 
-                       limitFps(&fps_control, &dtime);
+                       fps_control.limit(device, &dtime);
 
                        // Update client and server
                        client->step(dtime);
@@ -723,8 +726,7 @@ bool Game::connectToServer(const GameStartData &start_data,
                                break;
 
                        if (client->accessDenied()) {
-                               *error_message = "Access denied. Reason: "
-                                               + client->accessDeniedReason();
+                               *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
                                *reconnect_requested = client->reconnectRequested();
                                errorstream << *error_message << std::endl;
                                break;
@@ -750,7 +752,7 @@ bool Game::connectToServer(const GameStartData &start_data,
                                wait_time += dtime;
                                // Only time out if we aren't waiting for the server we started
                                if (!start_data.address.empty() && wait_time > 10) {
-                                       *error_message = "Connection timed out.";
+                                       *error_message = gettext("Connection timed out.");
                                        errorstream << *error_message << std::endl;
                                        break;
                                }
@@ -772,14 +774,14 @@ bool Game::getServerContent(bool *aborted)
 {
        input->clear();
 
-       FpsControl fps_control = { 0 };
+       FpsControl fps_control;
        f32 dtime; // in seconds
 
-       fps_control.last_time = m_rendering_engine->get_timer_time();
+       fps_control.reset();
 
        while (m_rendering_engine->run()) {
 
-               limitFps(&fps_control, &dtime);
+               fps_control.limit(device, &dtime);
 
                // Update client and server
                client->step(dtime);
@@ -798,7 +800,7 @@ bool Game::getServerContent(bool *aborted)
                        return false;
 
                if (client->getState() < LC_Init) {
-                       *error_message = "Client disconnected";
+                       *error_message = gettext("Client disconnected");
                        errorstream << *error_message << std::endl;
                        return false;
                }
@@ -880,8 +882,7 @@ inline void Game::updateInteractTimers(f32 dtime)
 inline bool Game::checkConnection()
 {
        if (client->accessDenied()) {
-               *error_message = "Access denied. Reason: "
-                               + client->accessDeniedReason();
+               *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
                *reconnect_requested = client->reconnectRequested();
                errorstream << *error_message << std::endl;
                return false;
@@ -936,17 +937,16 @@ void Game::processQueues()
 
 void Game::updateDebugState()
 {
-       bool has_basic_debug = client->checkPrivilege("basic_debug");
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
        bool has_debug = client->checkPrivilege("debug");
+       bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
 
        if (m_game_ui->m_flags.show_basic_debug) {
-               if (!has_basic_debug) {
+               if (!has_basic_debug)
                        m_game_ui->m_flags.show_basic_debug = false;
-               }
        } else if (m_game_ui->m_flags.show_minimal_debug) {
-               if (has_basic_debug) {
+               if (has_basic_debug)
                        m_game_ui->m_flags.show_basic_debug = true;
-               }
        }
        if (!has_basic_debug)
                hud->disableBlockBounds();
@@ -977,10 +977,10 @@ void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
        }
 
        // Update update graphs
-       g_profiler->graphAdd("Time non-rendering [ms]",
+       g_profiler->graphAdd("Time non-rendering [us]",
                draw_times.busy_time - stats.drawtime);
 
-       g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time);
+       g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
        g_profiler->graphAdd("FPS", 1.0f / dtime);
 }
 
@@ -1013,9 +1013,9 @@ void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
        /* Busytime average and jitter calculation
         */
        jp = &stats->busy_time_jitter;
-       jp->avg = jp->avg + draw_times.busy_time * 0.02;
+       jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
 
-       jitter = draw_times.busy_time - jp->avg;
+       jitter = draw_times.getBusyMs() - jp->avg;
 
        if (jitter > jp->max)
                jp->max = jitter;
@@ -1052,6 +1052,7 @@ void Game::processUserInput(f32 dtime)
        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->show();
                g_touchscreengui->step(dtime);
        }
 #endif
@@ -1290,15 +1291,22 @@ void Game::openInventory()
        InventoryLocation inventoryloc;
        inventoryloc.setCurrentPlayer();
 
-       if (!client->modsLoaded()
-                       || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
-               TextDest *txt_dst = new TextDestPlayerInventory(client);
-               auto *&formspec = m_game_ui->updateFormspec("");
-               GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
-                       &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
+       if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
+               delete fs_src;
+               return;
+       }
 
-               formspec->setFormSpec(fs_src->getForm(), inventoryloc);
+       if (fs_src->getForm().empty()) {
+               delete fs_src;
+               return;
        }
+
+       TextDest *txt_dst = new TextDestPlayerInventory(client);
+       auto *&formspec = m_game_ui->updateFormspec("");
+       GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
+               &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
+
+       formspec->setFormSpec(fs_src->getForm(), inventoryloc);
 }
 
 void Game::openEnderchest()
@@ -1398,7 +1406,7 @@ void Game::toggleFast()
                m_game_ui->showTranslatedStatusText("Fast mode disabled");
        }
 
-#ifdef __ANDROID__
+#ifdef HAVE_TOUCHSCREENGUI
        m_cache_hold_aux1 = fast_move && has_fast_privs;
 #endif
 }
@@ -1469,27 +1477,27 @@ void Game::toggleCinematic()
 
 void Game::toggleBlockBounds()
 {
-       if (client->checkPrivilege("basic_debug")) {
-               enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
-               switch (newmode) {
-                       case Hud::BLOCK_BOUNDS_OFF:
-                               m_game_ui->showTranslatedStatusText("Block bounds hidden");
-                               break;
-                       case Hud::BLOCK_BOUNDS_CURRENT:
-                               m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
-                               break;
-                       case Hud::BLOCK_BOUNDS_NEAR:
-                               m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
-                               break;
-                       case Hud::BLOCK_BOUNDS_MAX:
-                               m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
-                               break;
-                       default:
-                               break;
-               }
-
-       } else {
-               m_game_ui->showTranslatedStatusText("Can't show block bounds (need 'basic_debug' privilege)");
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
+       if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
+               m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
+               return;
+       }
+       enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
+       switch (newmode) {
+               case Hud::BLOCK_BOUNDS_OFF:
+                       m_game_ui->showTranslatedStatusText("Block bounds hidden");
+                       break;
+               case Hud::BLOCK_BOUNDS_CURRENT:
+                       m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
+                       break;
+               case Hud::BLOCK_BOUNDS_NEAR:
+                       m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
+                       break;
+               case Hud::BLOCK_BOUNDS_MAX:
+                       m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
+                       break;
+               default:
+                       break;
        }
 }
 
@@ -1556,6 +1564,9 @@ void Game::toggleFog()
 
 void Game::toggleDebug()
 {
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
+       bool has_debug = client->checkPrivilege("debug");
+       bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
        // Initial: No debug info
        // 1x toggle: Debug text
        // 2x toggle: Debug text with profiler graph
@@ -1565,26 +1576,23 @@ void Game::toggleDebug()
        // The debug text can be in 2 modes: minimal and basic.
        // * Minimal: Only technical client info that not gameplay-relevant
        // * Basic: Info that might give gameplay advantage, e.g. pos, angle
-       // Basic mode is used when player has "basic_debug" priv,
+       // Basic mode is used when player has the debug HUD flag set,
        // otherwise the Minimal mode is used.
        if (!m_game_ui->m_flags.show_minimal_debug) {
                m_game_ui->m_flags.show_minimal_debug = true;
-               if (client->checkPrivilege("basic_debug")) {
+               if (has_basic_debug)
                        m_game_ui->m_flags.show_basic_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) {
-               if (client->checkPrivilege("basic_debug")) {
+               if (has_basic_debug)
                        m_game_ui->m_flags.show_basic_debug = true;
-               }
                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")) {
-               if (client->checkPrivilege("basic_debug")) {
+               if (has_basic_debug)
                        m_game_ui->m_flags.show_basic_debug = true;
-               }
                m_game_ui->m_flags.show_profiler_graph = false;
                draw_control->show_wireframe = true;
                m_game_ui->showTranslatedStatusText("Wireframe shown");
@@ -1593,7 +1601,7 @@ void Game::toggleDebug()
                m_game_ui->m_flags.show_basic_debug = false;
                m_game_ui->m_flags.show_profiler_graph = false;
                draw_control->show_wireframe = false;
-               if (client->checkPrivilege("debug")) {
+               if (has_debug) {
                        m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
                } else {
                        m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
@@ -1755,6 +1763,10 @@ void Game::updatePlayerControl(const CameraOrientation &cam)
        //TimeTaker tt("update player control", NULL, PRECISION_NANO);
 
        PlayerControl control(
+               isKeyDown(KeyType::FORWARD),
+               isKeyDown(KeyType::BACKWARD),
+               isKeyDown(KeyType::LEFT),
+               isKeyDown(KeyType::RIGHT),
                isKeyDown(KeyType::JUMP) || player->getAutojump(),
                isKeyDown(KeyType::AUX1),
                isKeyDown(KeyType::SNEAK),
@@ -1774,10 +1786,10 @@ void Game::updatePlayerControl(const CameraOrientation &cam)
                control.movement_direction = 0.0f;
        }
 
-#ifdef ANDROID
-       /* For Android, simulate holding down AUX1 (fast move) if the user has
+#ifdef HAVE_TOUCHSCREENGUI
+       /* For touch, 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
+        * touch then its meaning is inverted (i.e. holding aux1 means walk and
         * not fast)
         */
        if (m_cache_hold_aux1) {
@@ -1785,39 +1797,7 @@ void Game::updatePlayerControl(const CameraOrientation &cam)
        }
 #endif
 
-       u32 keypress_bits = (
-                       ( (u32)(control.jump  & 0x1) << 4) |
-                       ( (u32)(control.aux1  & 0x1) << 5) |
-                       ( (u32)(control.sneak & 0x1) << 6) |
-                       ( (u32)(control.dig   & 0x1) << 7) |
-                       ( (u32)(control.place & 0x1) << 8) |
-                       ( (u32)(control.zoom  & 0x1) << 9)
-               );
-
-       // Set direction keys to ensure mod compatibility
-       if (control.movement_speed > 0.001f) {
-               float absolute_direction;
-
-               // Check in original orientation (absolute value indicates forward / backward)
-               absolute_direction = abs(control.movement_direction);
-               if (absolute_direction < (3.0f / 8.0f * M_PI))
-                       keypress_bits |= (u32)(0x1 << 0); // Forward
-               if (absolute_direction > (5.0f / 8.0f * M_PI))
-                       keypress_bits |= (u32)(0x1 << 1); // Backward
-
-               // Rotate entire coordinate system by 90 degrees (absolute value indicates left / right)
-               absolute_direction = control.movement_direction + M_PI_2;
-               if (absolute_direction >= M_PI)
-                       absolute_direction -= 2 * M_PI;
-               absolute_direction = abs(absolute_direction);
-               if (absolute_direction < (3.0f / 8.0f * M_PI))
-                       keypress_bits |= (u32)(0x1 << 2); // Left
-               if (absolute_direction > (5.0f / 8.0f * M_PI))
-                       keypress_bits |= (u32)(0x1 << 3); // Right
-       }
-
        client->setPlayerControl(control);
-       player->keyPressed = keypress_bits;
 
        //tt.stop();
 }
@@ -2172,7 +2152,7 @@ void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
 {
        sky->setStarsVisible(event->star_params->visible);
-       sky->setStarCount(event->star_params->count, false);
+       sky->setStarCount(event->star_params->count);
        sky->setStarColor(event->star_params->starcolor);
        sky->setStarScale(event->star_params->scale);
        delete event->star_params;
@@ -2209,7 +2189,7 @@ void Game::processClientEvents(CameraOrientation *cam)
        }
 }
 
-void Game::updateChat(f32 dtime, const v2u32 &screensize)
+void Game::updateChat(f32 dtime)
 {
        // Get new messages from error log buffer
        while (!m_chat_log_buf.empty())
@@ -2225,11 +2205,17 @@ void Game::updateChat(f32 dtime, const v2u32 &screensize)
        chat_backend->step(dtime);
 
        // Display all messages in a static text element
-       m_game_ui->setChatText(chat_backend->getRecentChat(),
-               chat_backend->getRecentBuffer().getLineCount());
+       auto &buf = chat_backend->getRecentBuffer();
+       if (buf.getLinesModified()) {
+               buf.resetLinesModified();
+               m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
+       }
+
+       // Make sure that the size is still correct
+       m_game_ui->updateChatSize();
 }
 
-void Game::updateCamera(u32 busy_time, f32 dtime)
+void Game::updateCamera(f32 dtime)
 {
        LocalPlayer *player = client->getEnv().getLocalPlayer();
 
@@ -2259,7 +2245,7 @@ void Game::updateCamera(u32 busy_time, f32 dtime)
        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->update(player, dtime, tool_reload_ratio);
        camera->step(dtime);
 
        v3f camera_position = camera->getPosition();
@@ -2333,7 +2319,7 @@ void Game::updateSound(f32 dtime)
 }
 
 
-void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
+void Game::processPlayerInteraction(f32 dtime, bool show_hud)
 {
        LocalPlayer *player = client->getEnv().getLocalPlayer();
 
@@ -2391,10 +2377,12 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
                        !runData.btn_down_for_dig,
                        camera_offset);
 
-       if (pointed != runData.pointed_old) {
+       if (pointed != runData.pointed_old)
                infostream << "Pointing at " << pointed.dump() << std::endl;
-               hud->updateSelectionMesh(camera_offset);
-       }
+
+       // Note that updating the selection mesh every frame is not particularly efficient,
+       // but the halo rendering code is already inefficient so there's no point in optimizing it here
+       hud->updateSelectionMesh(camera_offset);
 
        // Allow digging again if button is not pressed
        if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
@@ -2455,7 +2443,9 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
                handlePointingAtNode(pointed, selected_item, hand_item, dtime);
        } else if (pointed.type == POINTEDTHING_OBJECT) {
                v3f player_position  = player->getPosition();
-               handlePointingAtObject(pointed, tool_item, player_position, show_debug);
+               bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
+               handlePointingAtObject(pointed, tool_item, player_position,
+                               m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
        } else if (isKeyDown(KeyType::DIG)) {
                // When button is held down in air, show continuous animation
                runData.punching = true;
@@ -2614,9 +2604,8 @@ void Game::handlePointingAtNode(const PointedThing &pointed,
        } else {
                MapNode n = map.getNode(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 (nodedef_manager->get(n).name == "unknown") {
+                       m_game_ui->setInfoText(L"Unknown node");
                }
        }
 
@@ -2904,7 +2893,8 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
        // cheat detection.
        // Get digging parameters
        DigParams params = getDigParams(nodedef_manager->get(n).groups,
-                       &selected_item.getToolCapabilities(itemdef_manager));
+                       &selected_item.getToolCapabilities(itemdef_manager),
+                       selected_item.wear);
 
        // If can't dig, try hand
        if (!params.diggable) {
@@ -3155,12 +3145,28 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
        }
 
        /*
-               Get chat messages from client
+               Damage camera tilt
        */
+       if (player->hurt_tilt_timer > 0.0f) {
+               player->hurt_tilt_timer -= dtime * 6.0f;
 
-       v2u32 screensize = driver->getScreenSize();
+               if (player->hurt_tilt_timer < 0.0f || g_settings->getBool("no_hurt_cam"))
+                       player->hurt_tilt_strength = 0.0f;
+       }
+
+       /*
+               Update minimap pos and rotation
+       */
+       if (mapper && m_game_ui->m_flags.show_hud) {
+               mapper->setPos(floatToInt(player->getPosition(), BS));
+               mapper->setAngle(player->getYaw());
+       }
+
+       /*
+               Get chat messages from client
+       */
 
-       updateChat(dtime, screensize);
+       updateChat(dtime);
 
        /*
                Inventory
@@ -3187,8 +3193,8 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
        v3f camera_direction = camera->getDirection();
        if (runData.update_draw_list_timer >= update_draw_list_delta
                        || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
-                       || m_camera_offset_changed) {
-
+                       || m_camera_offset_changed
+                       || client->getEnv().getClientMap().needsUpdateDrawList()) {
                runData.update_draw_list_timer = 0;
                client->getEnv().getClientMap().updateDrawList();
                runData.update_draw_list_last_cam_dir = camera_direction;
@@ -3229,11 +3235,11 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
        } while (false);
 
        /*
-               Drawing begins
+               ==================== Drawing begins ====================
        */
-       const video::SColor &skycolor = sky->getSkyColor();
+       const video::SColor skycolor = sky->getSkyColor();
 
-       TimeTaker tt_draw("Draw scene");
+       TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
        driver->beginScene(true, true, skycolor);
 
        bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
@@ -3254,6 +3260,8 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
        /*
                Profiler graph
        */
+       v2u32 screensize = driver->getScreenSize();
+
        if (m_game_ui->m_flags.show_profiler_graph)
                graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
 
@@ -3278,27 +3286,10 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
                runData.damage_flash -= 384.0f * dtime;
        }
 
-       /*
-               Damage camera tilt
-       */
-       if (player->hurt_tilt_timer > 0.0f) {
-               player->hurt_tilt_timer -= dtime * 6.0f;
-
-               if (player->hurt_tilt_timer < 0.0f || g_settings->getBool("no_hurt_cam"))
-                       player->hurt_tilt_strength = 0.0f;
-       }
-
-       /*
-               Update minimap pos and rotation
-       */
-       if (mapper && m_game_ui->m_flags.show_hud) {
-               mapper->setPos(floatToInt(player->getPosition(), BS));
-               mapper->setAngle(player->getYaw());
-       }
-
        /*
                End scene
        */
+#if IRRLICHT_VERSION_MT_REVISION < 5
        if (++m_reset_HW_buffer_counter > 500) {
                /*
                  Periodically remove all mesh HW buffers.
@@ -3320,11 +3311,13 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
                driver->removeAllHardwareBuffers();
                m_reset_HW_buffer_counter = 0;
        }
+#endif
+
        driver->endScene();
 
        stats->drawtime = tt_draw.stop(true);
-       g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
-       g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
+       g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
+       g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
 }
 
 /* Log times and stuff for visualization */
@@ -3346,7 +3339,12 @@ void Game::updateShadows()
 
        float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
 
-       float timeoftheday = fmod(getWickedTimeOfDay(in_timeofday) + 0.75f, 0.5f) + 0.25f;
+       float timeoftheday = getWickedTimeOfDay(in_timeofday);
+       bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
+       bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
+       shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
+
+       timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
        const float offset_constant = 10000.0f;
 
        v3f light(0.0f, 0.0f, -1.0f);
@@ -3368,47 +3366,46 @@ void Game::updateShadows()
  Misc
  ****************************************************************************/
 
-/* On some computers framerate doesn't seem to be automatically limited
- */
-inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
+void FpsControl::reset()
 {
-       // 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;
+       last_time = porting::getTimeUs();
+}
 
-       u32 frametime_min = 1000 / (
+/*
+ * On some computers framerate doesn't seem to be automatically limited
+ */
+void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
+{
+       const u64 frametime_min = 1000000.0f / (
                device->isWindowFocused() && !g_menumgr.pausesGame()
                        ? g_settings->getFloat("fps_max")
                        : g_settings->getFloat("fps_max_unfocused"));
 
-       if (fps_timings->busy_time < frametime_min) {
-               fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
-               device->sleep(fps_timings->sleep_time);
+       u64 time = porting::getTimeUs();
+
+       if (time > last_time) // Make sure time hasn't overflowed
+               busy_time = time - last_time;
+       else
+               busy_time = 0;
+
+       if (busy_time < frametime_min) {
+               sleep_time = frametime_min - busy_time;
+               if (sleep_time > 1000)
+                       sleep_ms(sleep_time / 1000);
        } else {
-               fps_timings->sleep_time = 0;
+               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();
+       // Read the timer again to accurately determine how long we actually slept,
+       // rather than calculating it by adding sleep_time to time.
+       time = porting::getTimeUs();
 
-       if (time > last_time)  // Make sure last_time hasn't overflowed
-               *dtime = (time - last_time) / 1000.0;
+       if (time > last_time) // Make sure last_time hasn't overflowed
+               *dtime = (time - last_time) / 1000000.0f;
        else
                *dtime = 0;
 
-       fps_timings->last_time = time;
+       last_time = time;
 }
 
 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
@@ -3522,7 +3519,7 @@ void Game::showDeathFormspec()
 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
 void Game::showPauseMenu()
 {
-#ifdef __ANDROID__
+#ifdef HAVE_TOUCHSCREENGUI
        static const std::string control_text = strgettext("Default Controls:\n"
                "No menu visible:\n"
                "- single tap: button activate\n"
@@ -3630,16 +3627,18 @@ void Game::showPauseMenu()
        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;
+               // Note: Status of enable_damage and creative_mode settings is intentionally
+               // NOT shown here because the game might roll its own damage system and/or do
+               // a per-player Creative Mode, in which case writing it here would mislead.
+               bool damage = g_settings->getBool("enable_damage");
                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;
-                       //~ PvP = Player versus Player
-                       os << strgettext("- PvP: ") << pvp << "\n"
-                                       << strgettext("- Public: ") << announced << "\n";
+                       if (damage) {
+                               const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
+                               //~ PvP = Player versus Player
+                               os << strgettext("- PvP: ") << pvp << "\n";
+                       }
+                       os << strgettext("- Public: ") << announced << "\n";
                        std::string server_name = g_settings->get("server_name");
                        str_formspec_escape(server_name);
                        if (announced == on && !server_name.empty())
@@ -3698,14 +3697,15 @@ void the_game(bool *kill,
                }
 
        } 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 ".";
+               const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
+               error_message = strgettext("A serialization error occurred:") +"\n"
+                               + e.what() + "\n\n" + ver_err;
                errorstream << error_message << std::endl;
        } catch (ServerError &e) {
                error_message = e.what();
                errorstream << "ServerError: " << error_message << std::endl;
        } catch (ModError &e) {
+               // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
                error_message = std::string("ModError: ") + e.what() +
                                strgettext("\nCheck debug.txt for details.");
                errorstream << error_message << std::endl;