]> git.lizzy.rs Git - minetest.git/blobdiff - src/game.cpp
Minimal: Add snow biome and jungleleaves nodes. Add mapgen aliases
[minetest.git] / src / game.cpp
index ba28aa7897935ee949f67ee560801708f1b88461..125fc7c740441e11734c9de04e9dd9f53a2524d8 100644 (file)
@@ -18,64 +18,59 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 */
 
 #include "game.h"
-#include "irrlichttypes_extrabloated.h"
-#include <IGUICheckBox.h>
-#include <IGUIEditBox.h>
-#include <IGUIButton.h>
-#include <IGUIStaticText.h>
-#include <IGUIFont.h>
-#include <IMaterialRendererServices.h>
-#include "IMeshCache.h"
+
+#include <iomanip>
+#include "camera.h"
 #include "client.h"
-#include "server.h"
-#include "guiPasswordChange.h"
-#include "guiVolumeChange.h"
-#include "guiKeyChangeMenu.h"
-#include "guiFormSpecMenu.h"
-#include "tool.h"
-#include "guiChatConsole.h"
-#include "config.h"
-#include "version.h"
+#include "client/tile.h"     // For TextureSource
+#include "clientmap.h"
 #include "clouds.h"
-#include "particles.h"
-#include "camera.h"
-#include "mapblock.h"
-#include "settings.h"
-#include "profiler.h"
-#include "mainmenumanager.h"
-#include "gettext.h"
+#include "config.h"
+#include "content_cao.h"
+#include "drawscene.h"
+#include "event_manager.h"
+#include "fontengine.h"
+#include "itemdef.h"
 #include "log.h"
 #include "filesys.h"
-// Needed for determining pointing to nodes
-#include "nodedef.h"
-#include "nodemetadata.h"
-#include "main.h" // For g_settings
-#include "itemdef.h"
-#include "tile.h" // For TextureSource
-#include "shader.h" // For ShaderSource
+#include "gettext.h"
+#include "guiChatConsole.h"
+#include "guiFormSpecMenu.h"
+#include "guiKeyChangeMenu.h"
+#include "guiPasswordChange.h"
+#include "guiVolumeChange.h"
+#include "hud.h"
 #include "logoutputbuffer.h"
-#include "subgame.h"
+#include "mainmenumanager.h"
+#include "mapblock.h"
+#include "nodedef.h"         // Needed for determining pointing to nodes
+#include "nodemetadata.h"
+#include "particles.h"
+#include "profiler.h"
 #include "quicktune_shortcutter.h"
-#include "clientmap.h"
-#include "hud.h"
+#include "server.h"
+#include "settings.h"
+#include "shader.h"          // For ShaderSource
 #include "sky.h"
+#include "subgame.h"
+#include "tool.h"
+#include "util/directiontables.h"
+#include "util/pointedthing.h"
+#include "version.h"
+
 #include "sound.h"
+
 #if USE_SOUND
-#include "sound_openal.h"
+       #include "sound_openal.h"
 #endif
-#include "event_manager.h"
-#include <iomanip>
-#include <list>
-#include "util/directiontables.h"
-#include "util/pointedthing.h"
-#include "drawscene.h"
-#include "content_cao.h"
-#include "fontengine.h"
 
 #ifdef HAVE_TOUCHSCREENGUI
-#include "touchscreengui.h"
+       #include "touchscreengui.h"
 #endif
 
+extern Settings *g_settings;
+extern Profiler *g_profiler;
+
 /*
        Text input system
 */
@@ -92,11 +87,11 @@ struct TextDestNodeMetadata : public TextDest {
                std::string ntext = wide_to_narrow(text);
                infostream << "Submitting 'text' field of node at (" << m_p.X << ","
                           << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
-               std::map<std::string, std::string> fields;
+               StringMap fields;
                fields["text"] = ntext;
                m_client->sendNodemetaFields(m_p, "", fields);
        }
-       void gotText(std::map<std::string, std::string> fields)
+       void gotText(const StringMap &fields)
        {
                m_client->sendNodemetaFields(m_p, "", fields);
        }
@@ -116,7 +111,7 @@ struct TextDestPlayerInventory : public TextDest {
                m_client = client;
                m_formname = formname;
        }
-       void gotText(std::map<std::string, std::string> fields)
+       void gotText(const StringMap &fields)
        {
                m_client->sendInventoryFields(m_formname, fields);
        }
@@ -143,7 +138,7 @@ struct LocalFormspecHandler : public TextDest {
                errorstream << "LocalFormspecHandler::gotText old style message received" << std::endl;
        }
 
-       void gotText(std::map<std::string, std::string> fields)
+       void gotText(const StringMap &fields)
        {
                if (m_formname == "MT_PAUSE_MENU") {
                        if (fields.find("btn_sound") != fields.end()) {
@@ -185,9 +180,9 @@ struct LocalFormspecHandler : public TextDest {
 
                        if ((fields.find("btn_send") != fields.end()) ||
                                        (fields.find("quit") != fields.end())) {
-                               if (fields.find("f_text") != fields.end()) {
-                                       m_client->typeChatMessage(narrow_to_wide(fields["f_text"]));
-                               }
+                               StringMap::const_iterator it = fields.find("f_text");
+                               if (it != fields.end())
+                                       m_client->typeChatMessage(narrow_to_wide(it->second));
 
                                return;
                        }
@@ -215,12 +210,14 @@ struct LocalFormspecHandler : public TextDest {
                        return;
                }
 
-               errorstream << "LocalFormspecHandler::gotText unhandled >" << m_formname << "< event" << std::endl;
-               int i = 0;
+               errorstream << "LocalFormspecHandler::gotText unhandled >"
+                       << m_formname << "< event" << std::endl;
 
-               for (std::map<std::string, std::string>::iterator iter = fields.begin();
-                               iter != fields.end(); iter++) {
-                       errorstream << "\t" << i << ": " << iter->first << "=" << iter->second << std::endl;
+               int i = 0;
+               StringMap::const_iterator it;
+               for (it = fields.begin(); it != fields.end(); ++it) {
+                       errorstream << "\t" << i << ": " << it->first
+                               << "=" << it->second << std::endl;
                        i++;
                }
        }
@@ -492,7 +489,7 @@ class ProfilerGraph
                        color(color)
                {}
        };
-       std::list<Piece> m_log;
+       std::vector<Piece> m_log;
 public:
        u32 m_log_max_size;
 
@@ -515,7 +512,7 @@ class ProfilerGraph
        {
                std::map<std::string, Meta> m_meta;
 
-               for (std::list<Piece>::const_iterator k = m_log.begin();
+               for (std::vector<Piece>::const_iterator k = m_log.begin();
                                k != m_log.end(); k++) {
                        const Piece &piece = *k;
 
@@ -565,16 +562,6 @@ class ProfilerGraph
                s32 graphh = 50;
                s32 textx = x_left + m_log_max_size + 15;
                s32 textx2 = textx + 200 - 15;
-
-               // Draw background
-               /*{
-                       u32 num_graphs = m_meta.size();
-                       core::rect<s32> rect(x_left, y_bottom - num_graphs*graphh,
-                                       textx2, y_bottom);
-                       video::SColor bgcolor(120,0,0,0);
-                       driver->draw2DRectangle(bgcolor, rect, NULL);
-               }*/
-
                s32 meta_i = 0;
 
                for (std::map<std::string, Meta>::const_iterator i = m_meta.begin();
@@ -613,7 +600,7 @@ class ProfilerGraph
                        float lastscaledvalue = 0.0;
                        bool lastscaledvalue_exists = false;
 
-                       for (std::list<Piece>::const_iterator j = m_log.begin();
+                       for (std::vector<Piece>::const_iterator j = m_log.begin();
                                        j != m_log.end(); j++) {
                                const Piece &piece = *j;
                                float value = 0;
@@ -1043,7 +1030,11 @@ static inline void create_formspec_menu(GUIFormSpecMenu **cur_formspec,
        }
 }
 
+#ifdef __ANDROID__
 #define SIZE_TAG "size[11,5.5]"
+#else
+#define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
+#endif
 
 static void show_chat_menu(GUIFormSpecMenu **cur_formspec,
                InventoryManager *invmgr, IGameDef *gamedef,
@@ -1074,7 +1065,7 @@ static void show_deathscreen(GUIFormSpecMenu **cur_formspec,
                std::string(FORMSPEC_VERSION_STRING) +
                SIZE_TAG
                "bgcolor[#320000b4;true]"
-               "label[4.85,1.35;You died.]"
+               "label[4.85,1.35;" + gettext("You died.") + "]"
                "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
                ;
 
@@ -1133,7 +1124,7 @@ static void show_pause_menu(GUIFormSpecMenu **cur_formspec,
                os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
                   << wide_to_narrow(wstrgettext("Change Password")) << "]";
        }
-       
+
 #ifndef __ANDROID__
        os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
                        << wide_to_narrow(wstrgettext("Sound Volume")) << "]";
@@ -1145,8 +1136,8 @@ static void show_pause_menu(GUIFormSpecMenu **cur_formspec,
        os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
                        << wide_to_narrow(wstrgettext("Exit to OS"))   << "]"
                        << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
-                       << "textarea[0.4,0.25;3.5,6;;" << "Minetest\n"
-                       << minetest_build_info << "\n"
+                       << "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;]";
 
@@ -1249,6 +1240,7 @@ struct KeyCache {
                KEYMAP_ID_FREEMOVE,
                KEYMAP_ID_FASTMOVE,
                KEYMAP_ID_NOCLIP,
+               KEYMAP_ID_CINEMATIC,
                KEYMAP_ID_SCREENSHOT,
                KEYMAP_ID_TOGGLE_HUD,
                KEYMAP_ID_TOGGLE_CHAT,
@@ -1297,6 +1289,7 @@ void KeyCache::populate()
        key[KEYMAP_ID_FREEMOVE]     = getKeySetting("keymap_freemove");
        key[KEYMAP_ID_FASTMOVE]     = getKeySetting("keymap_fastmove");
        key[KEYMAP_ID_NOCLIP]       = getKeySetting("keymap_noclip");
+       key[KEYMAP_ID_CINEMATIC]    = getKeySetting("keymap_cinematic");
        key[KEYMAP_ID_SCREENSHOT]   = getKeySetting("keymap_screenshot");
        key[KEYMAP_ID_TOGGLE_HUD]   = getKeySetting("keymap_toggle_hud");
        key[KEYMAP_ID_TOGGLE_CHAT]  = getKeySetting("keymap_toggle_chat");
@@ -1432,7 +1425,7 @@ class Game
                        // If address is "", local server is used and address is updated
                        std::string *address,
                        u16 port,
-                       std::wstring *error_message,
+                       std::string &error_message,
                        ChatBackend *chat_backend,
                        const SubgameSpec &gamespec,    // Used for local game
                        bool simple_singleplayer_mode);
@@ -1454,9 +1447,8 @@ class Game
 
        // Client creation
        bool createClient(const std::string &playername,
-                       const std::string &password, std::string *address, u16 port,
-                       std::wstring *error_message);
-       bool initGui(std::wstring *error_message);
+                       const std::string &password, std::string *address, u16 port);
+       bool initGui();
 
        // Client connection
        bool connectToServer(const std::string &playername,
@@ -1466,17 +1458,17 @@ class Game
 
        // Main loop
 
-       void updateInteractTimers(GameRunData *args, f32 dtime);
+       void updateInteractTimers(GameRunData *runData, f32 dtime);
        bool checkConnection();
        bool handleCallbacks();
        void processQueues();
-       void updateProfilers(const GameRunData &run_data, const RunStats &stats,
+       void updateProfilers(const GameRunData &runData, const RunStats &stats,
                        const FpsControl &draw_times, f32 dtime);
        void addProfilerGraphs(const RunStats &stats, const FpsControl &draw_times,
                        f32 dtime);
        void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
 
-       void processUserInput(VolatileRunFlags *flags, GameRunData *interact_args,
+       void processUserInput(VolatileRunFlags *flags, GameRunData *runData,
                        f32 dtime);
        void processKeyboardInput(VolatileRunFlags *flags,
                        float *statustext_time,
@@ -1493,6 +1485,7 @@ class Game
        void toggleFreeMoveAlt(float *statustext_time, float *jump_timer);
        void toggleFast(float *statustext_time);
        void toggleNoClip(float *statustext_time);
+       void toggleCinematic(float *statustext_time);
 
        void toggleChat(float *statustext_time, bool *flag);
        void toggleHud(float *statustext_time, bool *flag);
@@ -1583,7 +1576,7 @@ class Game
        video::IVideoDriver *driver;
        scene::ISceneManager *smgr;
        bool *kill;
-       std::wstring *error_message;
+       std::string *error_message;
        IGameDef *gamedef;                     // Convenience (same as *client)
        scene::ISceneNode *skybox;
 
@@ -1627,6 +1620,11 @@ class Game
        bool m_cache_enable_fog;
        f32  m_cache_mouse_sensitivity;
        f32  m_repeat_right_click_time;
+
+#ifdef __ANDROID__
+       bool m_cache_hold_aux1;
+#endif
+
 };
 
 Game::Game() :
@@ -1660,6 +1658,11 @@ Game::Game() :
        m_repeat_right_click_time         = g_settings->getFloat("repeat_rightclick_time");
 
        m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
+
+#ifdef __ANDROID__
+       m_cache_hold_aux1 = false;      // This is initialised properly later
+#endif
+
 }
 
 
@@ -1700,7 +1703,7 @@ bool Game::startup(bool *kill,
                const std::string &password,
                std::string *address,     // can change if simple_singleplayer_mode
                u16 port,
-               std::wstring *error_message,
+               std::string &error_message,
                ChatBackend *chat_backend,
                const SubgameSpec &gamespec,
                bool simple_singleplayer_mode)
@@ -1708,7 +1711,7 @@ bool Game::startup(bool *kill,
        // "cache"
        this->device        = device;
        this->kill          = kill;
-       this->error_message = error_message;
+       this->error_message = &error_message;
        this->random_input  = random_input;
        this->input         = input;
        this->chat_backend  = chat_backend;
@@ -1722,7 +1725,7 @@ bool Game::startup(bool *kill,
        if (!init(map_dir, address, port, gamespec))
                return false;
 
-       if (!createClient(playername, password, address, port, error_message))
+       if (!createClient(playername, password, address, port))
                return false;
 
        return true;
@@ -1733,6 +1736,7 @@ void Game::run()
 {
        ProfilerGraph graph;
        RunStats stats              = { 0 };
+       CameraOrientation cam_view_target  = { 0 };
        CameraOrientation cam_view  = { 0 };
        GameRunData runData         = { 0 };
        FpsControl draw_times       = { 0 };
@@ -1765,6 +1769,11 @@ void Game::run()
 
        set_light_table(g_settings->getFloat("display_gamma"));
 
+#ifdef __ANDROID__
+       m_cache_hold_aux1 = g_settings->getBool("fast_move")
+                       && client->checkPrivilege("fast");
+#endif
+
        while (device->run() && !(*kill || g_gamecallback->shutdown_requested)) {
 
                /* Must be called immediately after a device->run() call because it
@@ -1788,10 +1797,20 @@ void Game::run()
                updateProfilers(runData, stats, draw_times, dtime);
                processUserInput(&flags, &runData, dtime);
                // Update camera before player movement to avoid camera lag of one frame
-               updateCameraDirection(&cam_view, &flags);
+               updateCameraDirection(&cam_view_target, &flags);
+               float cam_smoothing = 0;
+               if (g_settings->getBool("cinematic"))
+                       cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
+               else
+                       cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
+               cam_smoothing = rangelim(cam_smoothing, 0.01f, 1.0f);
+               cam_view.camera_yaw += (cam_view_target.camera_yaw -
+                               cam_view.camera_yaw) * cam_smoothing;
+               cam_view.camera_pitch += (cam_view_target.camera_pitch -
+                               cam_view.camera_pitch) * cam_smoothing;
                updatePlayerControl(cam_view);
                step(&dtime);
-               processClientEvents(&cam_view, &runData.damage_flash);
+               processClientEvents(&cam_view_target, &runData.damage_flash);
                updateCamera(&flags, draw_times.busy_time, dtime,
                                runData.time_from_last_punch);
                updateSound(dtime);
@@ -1844,10 +1863,11 @@ void Game::shutdown()
 }
 
 
-
+/****************************************************************************/
 /****************************************************************************
  Startup
  ****************************************************************************/
+/****************************************************************************/
 
 bool Game::init(
                const std::string &map_dir,
@@ -1930,10 +1950,10 @@ bool Game::createSingleplayerServer(const std::string map_dir,
        }
 
        if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
-               *error_message = L"Unable to listen on " +
-                               narrow_to_wide(bind_addr.serializeString()) +
-                               L" because IPv6 is disabled";
-               errorstream << wide_to_narrow(*error_message) << std::endl;
+               *error_message = "Unable to listen on " +
+                               bind_addr.serializeString() +
+                               " because IPv6 is disabled";
+               errorstream << *error_message << std::endl;
                return false;
        }
 
@@ -1946,8 +1966,7 @@ bool Game::createSingleplayerServer(const std::string map_dir,
 }
 
 bool Game::createClient(const std::string &playername,
-               const std::string &password, std::string *address, u16 port,
-               std::wstring *error_message)
+               const std::string &password, std::string *address, u16 port)
 {
        showOverlayMessage(wgettext("Creating client..."), 0, 10);
 
@@ -1962,25 +1981,25 @@ bool Game::createClient(const std::string &playername,
                return false;
 
        if (!could_connect) {
-               if (*error_message == L"" && !connect_aborted) {
+               if (error_message->empty() && !connect_aborted) {
                        // Should not happen if error messages are set properly
-                       *error_message = L"Connection failed for unknown reason";
-                       errorstream << wide_to_narrow(*error_message) << std::endl;
+                       *error_message = "Connection failed for unknown reason";
+                       errorstream << *error_message << std::endl;
                }
                return false;
        }
 
        if (!getServerContent(&connect_aborted)) {
-               if (*error_message == L"" && !connect_aborted) {
+               if (error_message->empty() && !connect_aborted) {
                        // Should not happen if error messages are set properly
-                       *error_message = L"Connection failed for unknown reason";
-                       errorstream << wide_to_narrow(*error_message) << std::endl;
+                       *error_message = "Connection failed for unknown reason";
+                       errorstream << *error_message << std::endl;
                }
                return false;
        }
 
        // Update cached textures, meshes and materials
-       client->afterContentReceived(device, g_fontengine->getFont());
+       client->afterContentReceived(device);
 
        /* Camera
         */
@@ -1993,9 +2012,8 @@ bool Game::createClient(const std::string &playername,
        if (m_cache_enable_clouds) {
                clouds = new Clouds(smgr->getRootSceneNode(), smgr, -1, time(0));
                if (!clouds) {
-                       *error_message = L"Memory allocation error";
-                       *error_message += narrow_to_wide(" (clouds)");
-                       errorstream << wide_to_narrow(*error_message) << std::endl;
+                       *error_message = "Memory allocation error (clouds)";
+                       errorstream << *error_message << std::endl;
                        return false;
                }
        }
@@ -2008,9 +2026,8 @@ bool Game::createClient(const std::string &playername,
        local_inventory = new Inventory(itemdef_manager);
 
        if (!(sky && local_inventory)) {
-               *error_message = L"Memory allocation error";
-               *error_message += narrow_to_wide(" (sky or local inventory)");
-               errorstream << wide_to_narrow(*error_message) << std::endl;
+               *error_message = "Memory allocation error (sky or local inventory)";
+               errorstream << *error_message << std::endl;
                return false;
        }
 
@@ -2024,14 +2041,15 @@ bool Game::createClient(const std::string &playername,
                crack_animation_length = 5;
        }
 
-       if (!initGui(error_message))
+       if (!initGui())
                return false;
 
        /* Set window caption
         */
-       core::stringw str = L"Minetest [";
+       std::wstring str = narrow_to_wide(PROJECT_NAME_C);
+       str += L" [";
        str += driver->getName();
-       str += "]";
+       str += L"]";
        device->setWindowCaption(str.c_str());
 
        LocalPlayer *player = client->getEnv().getLocalPlayer();
@@ -2041,19 +2059,19 @@ bool Game::createClient(const std::string &playername,
        hud = new Hud(driver, smgr, guienv, gamedef, player, local_inventory);
 
        if (!hud) {
-               *error_message = L"Memory error: could not create HUD";
-               errorstream << wide_to_narrow(*error_message) << std::endl;
+               *error_message = "Memory error: could not create HUD";
+               errorstream << *error_message << std::endl;
                return false;
        }
 
        return true;
 }
 
-bool Game::initGui(std::wstring *error_message)
+bool Game::initGui()
 {
        // First line of debug text
        guitext = guienv->addStaticText(
-                       L"Minetest",
+                       narrow_to_wide(PROJECT_NAME_C).c_str(),
                        core::rect<s32>(0, 0, 0, 0),
                        false, false, guiroot);
 
@@ -2090,8 +2108,8 @@ bool Game::initGui(std::wstring *error_message)
        gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
                        -1, chat_backend, client);
        if (!gui_chat_console) {
-               *error_message = L"Could not allocate memory for chat console";
-               errorstream << wide_to_narrow(*error_message) << std::endl;
+               *error_message = "Could not allocate memory for chat console";
+               errorstream << *error_message << std::endl;
                return false;
        }
 
@@ -2141,16 +2159,16 @@ bool Game::connectToServer(const std::string &playername,
                        local_server_mode = true;
                }
        } catch (ResolveError &e) {
-               *error_message = L"Couldn't resolve address: " + narrow_to_wide(e.what());
-               errorstream << wide_to_narrow(*error_message) << std::endl;
+               *error_message = std::string("Couldn't resolve address: ") + e.what();
+               errorstream << *error_message << std::endl;
                return false;
        }
 
        if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
-               *error_message = L"Unable to connect to " +
-                               narrow_to_wide(connect_address.serializeString()) +
-                               L" because IPv6 is disabled";
-               errorstream << wide_to_narrow(*error_message) << std::endl;
+               *error_message = "Unable to connect to " +
+                               connect_address.serializeString() +
+                               " because IPv6 is disabled";
+               errorstream << *error_message << std::endl;
                return false;
        }
 
@@ -2180,7 +2198,10 @@ bool Game::connectToServer(const std::string &playername,
                input->clear();
 
                FpsControl fps_control = { 0 };
-               f32 dtime; // in seconds
+               f32 dtime;
+               f32 wait_time = 0; // in seconds
+
+               fps_control.last_time = device->getTimer()->getTime();
 
                while (device->run()) {
 
@@ -2200,9 +2221,9 @@ bool Game::connectToServer(const std::string &playername,
 
                        // Break conditions
                        if (client->accessDenied()) {
-                               *error_message = L"Access denied. Reason: "
+                               *error_message = "Access denied. Reason: "
                                                + client->accessDeniedReason();
-                               errorstream << wide_to_narrow(*error_message) << std::endl;
+                               errorstream << *error_message << std::endl;
                                break;
                        }
 
@@ -2212,6 +2233,14 @@ bool Game::connectToServer(const std::string &playername,
                                break;
                        }
 
+                       wait_time += dtime;
+                       // Only time out if we aren't waiting for the server we started
+                       if ((*address != "") && (wait_time > 10)) {
+                               *error_message = "Connection timed out.";
+                               errorstream << *error_message << std::endl;
+                               break;
+                       }
+
                        // Update status
                        showOverlayMessage(wgettext("Connecting to server..."), dtime, 20);
                }
@@ -2231,6 +2260,8 @@ bool Game::getServerContent(bool *aborted)
        FpsControl fps_control = { 0 };
        f32 dtime; // in seconds
 
+       fps_control.last_time = device->getTimer()->getTime();
+
        while (device->run()) {
 
                limitFps(&fps_control, &dtime);
@@ -2248,16 +2279,12 @@ bool Game::getServerContent(bool *aborted)
                }
 
                // Error conditions
-               if (client->accessDenied()) {
-                       *error_message = L"Access denied. Reason: "
-                                       + client->accessDeniedReason();
-                       errorstream << wide_to_narrow(*error_message) << std::endl;
+               if (!checkConnection())
                        return false;
-               }
 
                if (client->getState() < LC_Init) {
-                       *error_message = L"Client disconnected";
-                       errorstream << wide_to_narrow(*error_message) << std::endl;
+                       *error_message = "Client disconnected";
+                       errorstream << *error_message << std::endl;
                        return false;
                }
 
@@ -2288,14 +2315,14 @@ bool Game::getServerContent(bool *aborted)
                        if ((USE_CURL == 0) ||
                                        (!g_settings->getBool("enable_remote_media_server"))) {
                                float cur = client->getCurRate();
-                               std::string cur_unit = gettext(" KB/s");
+                               std::string cur_unit = gettext("KiB/s");
 
                                if (cur > 900) {
                                        cur /= 1024.0;
-                                       cur_unit = gettext(" MB/s");
+                                       cur_unit = gettext("MiB/s");
                                }
 
-                               message << " ( " << cur << cur_unit << " )";
+                               message << " (" << cur << ' ' << cur_unit << ")";
                        }
 
                        progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
@@ -2308,20 +2335,21 @@ bool Game::getServerContent(bool *aborted)
 }
 
 
-
+/****************************************************************************/
 /****************************************************************************
  Run
  ****************************************************************************/
+/****************************************************************************/
 
-inline void Game::updateInteractTimers(GameRunData *args, f32 dtime)
+inline void Game::updateInteractTimers(GameRunData *runData, f32 dtime)
 {
-       if (args->nodig_delay_timer >= 0)
-               args->nodig_delay_timer -= dtime;
+       if (runData->nodig_delay_timer >= 0)
+               runData->nodig_delay_timer -= dtime;
 
-       if (args->object_hit_delay_timer >= 0)
-               args->object_hit_delay_timer -= dtime;
+       if (runData->object_hit_delay_timer >= 0)
+               runData->object_hit_delay_timer -= dtime;
 
-       args->time_from_last_punch += dtime;
+       runData->time_from_last_punch += dtime;
 }
 
 
@@ -2330,9 +2358,9 @@ inline void Game::updateInteractTimers(GameRunData *args, f32 dtime)
 inline bool Game::checkConnection()
 {
        if (client->accessDenied()) {
-               *error_message = L"Access denied. Reason: "
+               *error_message = "Access denied. Reason: "
                                + client->accessDeniedReason();
-               errorstream << wide_to_narrow(*error_message) << std::endl;
+               errorstream << *error_message << std::endl;
                return false;
        }
 
@@ -2384,7 +2412,7 @@ void Game::processQueues()
 }
 
 
-void Game::updateProfilers(const GameRunData &run_data, const RunStats &stats,
+void Game::updateProfilers(const GameRunData &runData, const RunStats &stats,
                const FpsControl &draw_times, f32 dtime)
 {
        float profiler_print_interval =
@@ -2403,7 +2431,7 @@ void Game::updateProfilers(const GameRunData &run_data, const RunStats &stats,
                }
 
                update_profiler_gui(guitext_profiler, g_fontengine,
-                               run_data.profiler_current_page, run_data.profiler_max_page,
+                               runData.profiler_current_page, runData.profiler_max_page,
                                driver->getScreenSize().Height);
 
                g_profiler->clear();
@@ -2484,7 +2512,7 @@ void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
  ****************************************************************************/
 
 void Game::processUserInput(VolatileRunFlags *flags,
-               GameRunData *interact_args, f32 dtime)
+               GameRunData *runData, f32 dtime)
 {
        // Reset input if window not active or some menu is active
        if (device->isWindowActive() == false
@@ -2515,18 +2543,18 @@ void Game::processUserInput(VolatileRunFlags *flags,
 #endif
 
        // Increase timer for double tap of "keymap_jump"
-       if (m_cache_doubletap_jump && interact_args->jump_timer <= 0.2)
-               interact_args->jump_timer += dtime;
+       if (m_cache_doubletap_jump && runData->jump_timer <= 0.2)
+               runData->jump_timer += dtime;
 
        processKeyboardInput(
                        flags,
-                       &interact_args->statustext_time,
-                       &interact_args->jump_timer,
-                       &interact_args->reset_jump_timer,
-                       &interact_args->profiler_current_page,
-                       interact_args->profiler_max_page);
+                       &runData->statustext_time,
+                       &runData->jump_timer,
+                       &runData->reset_jump_timer,
+                       &runData->profiler_current_page,
+                       runData->profiler_max_page);
 
-       processItemSelection(&interact_args->new_playeritem);
+       processItemSelection(&runData->new_playeritem);
 }
 
 
@@ -2564,6 +2592,8 @@ void Game::processKeyboardInput(VolatileRunFlags *flags,
                toggleFast(statustext_time);
        } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_NOCLIP])) {
                toggleNoClip(statustext_time);
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CINEMATIC])) {
+               toggleCinematic(statustext_time);
        } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_SCREENSHOT])) {
                client->makeScreenshot(device);
        } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_HUD])) {
@@ -2669,6 +2699,15 @@ void Game::dropSelectedItem()
 
 void Game::openInventory()
 {
+       /*
+        * Don't permit to open inventory is CAO or player doesn't exists.
+        * This prevent showing an empty inventory at player load
+        */
+
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
+       if (player == NULL || player->getCAO() == NULL)
+               return;
+
        infostream << "the_game: " << "Launching inventory" << std::endl;
 
        PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
@@ -2723,8 +2762,14 @@ void Game::toggleFast(float *statustext_time)
        *statustext_time = 0;
        statustext = msg[fast_move];
 
-       if (fast_move && !client->checkPrivilege("fast"))
+       bool has_fast_privs = client->checkPrivilege("fast");
+
+       if (fast_move && !has_fast_privs)
                statustext += L" (note: no 'fast' privilege)";
+
+#ifdef __ANDROID__
+       m_cache_hold_aux1 = fast_move && has_fast_privs;
+#endif
 }
 
 
@@ -2741,6 +2786,16 @@ void Game::toggleNoClip(float *statustext_time)
                statustext += L" (note: no 'noclip' privilege)";
 }
 
+void Game::toggleCinematic(float *statustext_time)
+{
+       static const wchar_t *msg[] = { L"cinematic disabled", L"cinematic enabled" };
+       bool cinematic = !g_settings->getBool("cinematic");
+       g_settings->set("cinematic", bool_to_cstr(cinematic));
+
+       *statustext_time = 0;
+       statustext = msg[cinematic];
+}
+
 
 void Game::toggleChat(float *statustext_time, bool *flag)
 {
@@ -2950,19 +3005,34 @@ void Game::updatePlayerControl(const CameraOrientation &cam)
                cam.camera_pitch,
                cam.camera_yaw
        );
+
+       u32 keypress_bits =
+                       ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_FORWARD])  & 0x1) << 0) |
+                       ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_BACKWARD]) & 0x1) << 1) |
+                       ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_LEFT])     & 0x1) << 2) |
+                       ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_RIGHT])    & 0x1) << 3) |
+                       ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_JUMP])     & 0x1) << 4) |
+                       ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SPECIAL1]) & 0x1) << 5) |
+                       ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SNEAK])    & 0x1) << 6) |
+                       ( (u32)(input->getLeftState()                                        & 0x1) << 7) |
+                       ( (u32)(input->getRightState()                                       & 0x1) << 8
+               );
+
+#ifdef ANDROID
+       /* For Android, simulate holding down AUX1 (fast move) if the user has
+        * the fast_move setting toggled on. If there is an aux1 key defined for
+        * Android then its meaning is inverted (i.e. holding aux1 means walk and
+        * not fast)
+        */
+       if (m_cache_hold_aux1) {
+               control.aux1 = control.aux1 ^ true;
+               keypress_bits ^= ((u32)(1U << 5));
+       }
+#endif
+
        client->setPlayerControl(control);
        LocalPlayer *player = client->getEnv().getLocalPlayer();
-       player->keyPressed =
-               ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_FORWARD])  & 0x1) << 0) |
-               ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_BACKWARD]) & 0x1) << 1) |
-               ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_LEFT])     & 0x1) << 2) |
-               ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_RIGHT])    & 0x1) << 3) |
-               ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_JUMP])     & 0x1) << 4) |
-               ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SPECIAL1]) & 0x1) << 5) |
-               ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SNEAK])    & 0x1) << 6) |
-               ( (u32)(input->getLeftState()                                        & 0x1) << 7) |
-               ( (u32)(input->getRightState()                                       & 0x1) << 8
-       );
+       player->keyPressed = keypress_bits;
 
        //tt.stop();
 }
@@ -3073,7 +3143,7 @@ void Game::processClientEvents(CameraOrientation *cam, float *damage_flash)
 
                        u32 new_id = player->addHud(e);
                        //if this isn't true our huds aren't consistent
-                       assert(new_id == id);
+                       sanity_check(new_id == id);
 
                        delete event.hudadd.pos;
                        delete event.hudadd.name;
@@ -3165,12 +3235,12 @@ void Game::processClientEvents(CameraOrientation *cam, float *damage_flash)
                                        event.set_sky.params->size() == 6) {
                                sky->setFallbackBgColor(*event.set_sky.bgcolor);
                                skybox = smgr->addSkyBoxSceneNode(
-                                                texture_src->getTexture((*event.set_sky.params)[0]),
-                                                texture_src->getTexture((*event.set_sky.params)[1]),
-                                                texture_src->getTexture((*event.set_sky.params)[2]),
-                                                texture_src->getTexture((*event.set_sky.params)[3]),
-                                                texture_src->getTexture((*event.set_sky.params)[4]),
-                                                texture_src->getTexture((*event.set_sky.params)[5]));
+                                                texture_src->getTextureForMesh((*event.set_sky.params)[0]),
+                                                texture_src->getTextureForMesh((*event.set_sky.params)[1]),
+                                                texture_src->getTextureForMesh((*event.set_sky.params)[2]),
+                                                texture_src->getTextureForMesh((*event.set_sky.params)[3]),
+                                                texture_src->getTextureForMesh((*event.set_sky.params)[4]),
+                                                texture_src->getTextureForMesh((*event.set_sky.params)[5]));
                        }
                        // Handle everything else as plain color
                        else {
@@ -3218,10 +3288,13 @@ void Game::updateCamera(VolatileRunFlags *flags, u32 busy_time,
        v3s16 old_camera_offset = camera->getOffset();
 
        if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CAMERA_MODE])) {
-               camera->toggleCameraMode();
                GenericCAO *playercao = player->getCAO();
 
-               assert(playercao != NULL);
+               // If playercao not loaded, don't change camera
+               if (playercao == NULL)
+                       return;
+
+               camera->toggleCameraMode();
 
                playercao->setVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
        }
@@ -3926,6 +3999,28 @@ void Game::updateFrame(std::vector<aabb3f> &highlight_boxes,
 }
 
 
+inline static const char *yawToDirectionString(int yaw)
+{
+       // NOTE: TODO: This can be done mathematically without the else/else-if
+       // cascade.
+
+       const char *player_direction;
+
+       yaw = wrapDegrees_0_360(yaw);
+
+       if (yaw >= 45 && yaw < 135)
+               player_direction = "West [-X]";
+       else if (yaw >= 135 && yaw < 225)
+               player_direction = "South [-Z]";
+       else if (yaw >= 225 && yaw < 315)
+               player_direction = "East [+X]";
+       else
+               player_direction = "North [+Z]";
+
+       return player_direction;
+}
+
+
 void Game::updateGui(float *statustext_time, const RunStats &stats,
                const GameRunData& runData, f32 dtime, const VolatileRunFlags &flags,
                const CameraOrientation &cam)
@@ -3943,7 +4038,7 @@ void Game::updateGui(float *statustext_time, const RunStats &stats,
 
                std::ostringstream os(std::ios_base::binary);
                os << std::fixed
-                  << "Minetest " << minetest_version_hash
+                  << PROJECT_NAME_C " " << g_version_hash
                   << " FPS = " << fps
                   << " (R: range_all=" << draw_control->range_all << ")"
                   << std::setprecision(0)
@@ -3959,7 +4054,7 @@ void Game::updateGui(float *statustext_time, const RunStats &stats,
                guitext->setVisible(true);
        } else if (flags.show_hud || flags.show_chat) {
                std::ostringstream os(std::ios_base::binary);
-               os << "Minetest " << minetest_version_hash;
+               os << PROJECT_NAME_C " " << g_version_hash;
                guitext->setText(narrow_to_wide(os.str()).c_str());
                guitext->setVisible(true);
        } else {
@@ -3981,6 +4076,7 @@ void Game::updateGui(float *statustext_time, const RunStats &stats,
                   << ", " << (player_position.Y / BS)
                   << ", " << (player_position.Z / BS)
                   << ") (yaw=" << (wrapDegrees_0_360(cam.camera_yaw))
+                  << " " << yawToDirectionString(cam.camera_yaw)
                   << ") (seed = " << ((u64)client->getMapSeed())
                   << ")";
 
@@ -4074,7 +4170,6 @@ inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
        // not using getRealTime is necessary for wine
        device->getTimer()->tick(); // Maker sure device time is up-to-date
        u32 time = device->getTimer()->getTime();
-
        u32 last_time = fps_timings->last_time;
 
        if (time > last_time)  // Make sure time hasn't overflowed
@@ -4110,7 +4205,9 @@ inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
        fps_timings->last_time = time;
 }
 
-
+// Note: This will free (using delete[])! \p msg. If you want to use it later,
+// pass a copy of it to this function
+// Note: \p msg must be allocated using new (not malloc())
 void Game::showOverlayMessage(const wchar_t *msg, float dtime,
                int percent, bool draw_clouds)
 {
@@ -4119,9 +4216,11 @@ void Game::showOverlayMessage(const wchar_t *msg, float dtime,
 }
 
 
+/****************************************************************************/
 /****************************************************************************
  Shutdown / cleanup
  ****************************************************************************/
+/****************************************************************************/
 
 void Game::extendedResourceCleanup()
 {
@@ -4145,10 +4244,11 @@ void Game::extendedResourceCleanup()
 }
 
 
-
+/****************************************************************************/
 /****************************************************************************
  extern function for launching the game
  ****************************************************************************/
+/****************************************************************************/
 
 void the_game(bool *kill,
                bool random_input,
@@ -4161,7 +4261,7 @@ void the_game(bool *kill,
                const std::string &address,         // If empty local server is created
                u16 port,
 
-               std::wstring &error_message,
+               std::string &error_message,
                ChatBackend &chat_backend,
                const SubgameSpec &gamespec,        // Used for local game
                bool simple_singleplayer_mode)
@@ -4177,25 +4277,24 @@ void the_game(bool *kill,
        try {
 
                if (game.startup(kill, random_input, input, device, map_dir,
-                                       playername, password, &server_address, port,
-                                       &error_message, &chat_backend, gamespec,
-                                       simple_singleplayer_mode)) {
-
+                               playername, password, &server_address, port,
+                               error_message, &chat_backend, gamespec,
+                               simple_singleplayer_mode)) {
                        game.run();
                        game.shutdown();
                }
 
        } catch (SerializationError &e) {
-               error_message = L"A serialization error occurred:\n"
-                               + narrow_to_wide(e.what()) + L"\n\nThe server is probably "
-                               L" running a different version of Minetest.";
-               errorstream << wide_to_narrow(error_message) << std::endl;
+               error_message = std::string("A serialization error occurred:\n")
+                               + e.what() + "\n\nThe server is probably "
+                               " running a different version of " PROJECT_NAME_C ".";
+               errorstream << error_message << std::endl;
        } catch (ServerError &e) {
-               error_message = narrow_to_wide(e.what());
-               errorstream << "ServerError: " << e.what() << std::endl;
+               error_message = e.what();
+               errorstream << "ServerError: " << error_message << std::endl;
        } catch (ModError &e) {
-               errorstream << "ModError: " << e.what() << std::endl;
-               error_message = narrow_to_wide(e.what()) + wstrgettext("\nCheck debug.txt for details.");
+               error_message = e.what() + strgettext("\nCheck debug.txt for details.");
+               errorstream << "ModError: " << error_message << std::endl;
        }
 }