]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - src/game.cpp
Put ChatEvent handler into own function
[dragonfireclient.git] / src / game.cpp
index 9836054488b058d4d3d9285d1dc8940d61fb1830..1738517b57022d2d3705d2a77f8d77e154eea1dc 100644 (file)
@@ -18,63 +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 "gettext.h"
+#include "guiChatConsole.h"
+#include "guiFormSpecMenu.h"
+#include "guiKeyChangeMenu.h"
+#include "guiPasswordChange.h"
+#include "guiVolumeChange.h"
+#include "hud.h"
+#include "mainmenumanager.h"
+#include "mapblock.h"
+#include "nodedef.h"         // Needed for determining pointing to nodes
 #include "nodemetadata.h"
-#include "main.h" // For g_settings
-#include "itemdef.h"
-#include "tile.h" // For TextureSource
-#include "shader.h" // For ShaderSource
-#include "logoutputbuffer.h"
-#include "subgame.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 "minimap.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"
 
 #ifdef HAVE_TOUCHSCREENGUI
-#include "touchscreengui.h"
+       #include "touchscreengui.h"
 #endif
 
+extern Settings *g_settings;
+extern Profiler *g_profiler;
+
 /*
        Text input system
 */
@@ -88,14 +84,14 @@ struct TextDestNodeMetadata : public TextDest {
        // This is deprecated I guess? -celeron55
        void gotText(std::wstring text)
        {
-               std::string ntext = wide_to_narrow(text);
+               std::string ntext = wide_to_utf8(text);
                infostream << "Submitting 'text' field of node at (" << m_p.X << ","
                           << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
-               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);
        }
@@ -115,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);
        }
@@ -142,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()) {
@@ -184,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(utf8_to_wide(it->second));
 
                                return;
                        }
@@ -214,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++;
                }
        }
@@ -360,12 +358,11 @@ PointedThing getPointedThing(Client *client, v3f player_position,
                for (s16 z = zstart; z <= zend; z++)
                        for (s16 x = xstart; x <= xend; x++) {
                                MapNode n;
+                               bool is_valid_position;
 
-                               try {
-                                       n = map.getNode(v3s16(x, y, z));
-                               } catch (InvalidPositionException &e) {
+                               n = map.getNodeNoEx(v3s16(x, y, z), &is_valid_position);
+                               if (!is_valid_position)
                                        continue;
-                               }
 
                                if (!isPointableNode(n, client, liquids_pointable))
                                        continue;
@@ -377,7 +374,7 @@ PointedThing getPointedThing(Client *client, v3f player_position,
 
                                for (std::vector<aabb3f>::const_iterator
                                                i = boxes.begin();
-                                               i != boxes.end(); i++) {
+                                               i != boxes.end(); ++i) {
                                        aabb3f box = *i;
                                        box.MinEdge += npf;
                                        box.MaxEdge += npf;
@@ -422,7 +419,7 @@ PointedThing getPointedThing(Client *client, v3f player_position,
                                                if (!g_settings->getBool("enable_node_highlighting")) {
                                                        for (std::vector<aabb3f>::const_iterator
                                                                        i2 = boxes.begin();
-                                                                       i2 != boxes.end(); i2++) {
+                                                                       i2 != boxes.end(); ++i2) {
                                                                aabb3f box = *i2;
                                                                box.MinEdge += npf + v3f(-d, -d, -d) - intToFloat(camera_offset, BS);
                                                                box.MaxEdge += npf + v3f(d, d, d) - intToFloat(camera_offset, BS);
@@ -438,9 +435,8 @@ PointedThing getPointedThing(Client *client, v3f player_position,
 
 /* Profiler display */
 
-void update_profiler_gui(gui::IGUIStaticText *guitext_profiler,
-               gui::IGUIFont *font, u32 text_height, u32 show_profiler,
-               u32 show_profiler_max)
+void update_profiler_gui(gui::IGUIStaticText *guitext_profiler, FontEngine *fe,
+               u32 show_profiler, u32 show_profiler_max, s32 screen_height)
 {
        if (show_profiler == 0) {
                guitext_profiler->setVisible(false);
@@ -448,18 +444,29 @@ void update_profiler_gui(gui::IGUIStaticText *guitext_profiler,
 
                std::ostringstream os(std::ios_base::binary);
                g_profiler->printPage(os, show_profiler, show_profiler_max);
-               std::wstring text = narrow_to_wide(os.str());
+               std::wstring text = utf8_to_wide(os.str());
                guitext_profiler->setText(text.c_str());
                guitext_profiler->setVisible(true);
 
-               s32 w = font->getDimension(text.c_str()).Width;
+               s32 w = fe->getTextWidth(text.c_str());
 
                if (w < 400)
                        w = 400;
 
-               core::rect<s32> rect(6, 4 + (text_height + 5) * 2, 12 + w,
-                                    8 + (text_height + 5) * 2 +
-                                    font->getDimension(text.c_str()).Height);
+               unsigned text_height = fe->getTextHeight();
+
+               core::position2di upper_left, lower_right;
+
+               upper_left.X  = 6;
+               upper_left.Y  = (text_height + 5) * 2;
+               lower_right.X = 12 + w;
+               lower_right.Y = upper_left.Y + (text_height + 1) * MAX_PROFILER_TEXT_ROWS;
+
+               if (lower_right.Y > screen_height * 2 / 3)
+                       lower_right.Y = screen_height * 2 / 3;
+
+               core::rect<s32> rect(upper_left, lower_right);
+
                guitext_profiler->setRelativePosition(rect);
                guitext_profiler->setVisible(true);
        }
@@ -482,7 +489,7 @@ class ProfilerGraph
                        color(color)
                {}
        };
-       std::list<Piece> m_log;
+       std::deque<Piece> m_log;
 public:
        u32 m_log_max_size;
 
@@ -505,12 +512,12 @@ class ProfilerGraph
        {
                std::map<std::string, Meta> m_meta;
 
-               for (std::list<Piece>::const_iterator k = m_log.begin();
-                               k != m_log.end(); k++) {
+               for (std::deque<Piece>::const_iterator k = m_log.begin();
+                               k != m_log.end(); ++k) {
                        const Piece &piece = *k;
 
                        for (Profiler::GraphValues::const_iterator i = piece.values.begin();
-                                       i != piece.values.end(); i++) {
+                                       i != piece.values.end(); ++i) {
                                const std::string &id = i->first;
                                const float &value = i->second;
                                std::map<std::string, Meta>::iterator j =
@@ -542,7 +549,7 @@ class ProfilerGraph
                u32 next_color_i = 0;
 
                for (std::map<std::string, Meta>::iterator i = m_meta.begin();
-                               i != m_meta.end(); i++) {
+                               i != m_meta.end(); ++i) {
                        Meta &meta = i->second;
                        video::SColor color(255, 200, 200, 200);
 
@@ -555,20 +562,10 @@ 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();
-                               i != m_meta.end(); i++) {
+                               i != m_meta.end(); ++i) {
                        const std::string &id = i->first;
                        const Meta &meta = i->second;
                        s32 x = x_left;
@@ -584,16 +581,16 @@ class ProfilerGraph
                        s32 texth = 15;
                        char buf[10];
                        snprintf(buf, 10, "%.3g", show_max);
-                       font->draw(narrow_to_wide(buf).c_str(),
+                       font->draw(utf8_to_wide(buf).c_str(),
                                        core::rect<s32>(textx, y - graphh,
                                                   textx2, y - graphh + texth),
                                        meta.color);
                        snprintf(buf, 10, "%.3g", show_min);
-                       font->draw(narrow_to_wide(buf).c_str(),
+                       font->draw(utf8_to_wide(buf).c_str(),
                                        core::rect<s32>(textx, y - texth,
                                                   textx2, y),
                                        meta.color);
-                       font->draw(narrow_to_wide(id).c_str(),
+                       font->draw(utf8_to_wide(id).c_str(),
                                        core::rect<s32>(textx, y - graphh / 2 - texth / 2,
                                                   textx2, y - graphh / 2 + texth / 2),
                                        meta.color);
@@ -603,8 +600,8 @@ class ProfilerGraph
                        float lastscaledvalue = 0.0;
                        bool lastscaledvalue_exists = false;
 
-                       for (std::list<Piece>::const_iterator j = m_log.begin();
-                                       j != m_log.end(); j++) {
+                       for (std::deque<Piece>::const_iterator j = m_log.begin();
+                                       j != m_log.end(); ++j) {
                                const Piece &piece = *j;
                                float value = 0;
                                bool value_exists = false;
@@ -799,16 +796,35 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
        bool *m_force_fog_off;
        f32 *m_fog_range;
        Client *m_client;
+       bool m_fogEnabled;
 
 public:
+       void onSettingsChange(const std::string &name)
+       {
+               if (name == "enable_fog")
+                       m_fogEnabled = g_settings->getBool("enable_fog");
+       }
+
+       static void SettingsCallback(const std::string &name, void *userdata)
+       {
+               reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
+       }
+
        GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
                        f32 *fog_range, Client *client) :
                m_sky(sky),
                m_force_fog_off(force_fog_off),
                m_fog_range(fog_range),
                m_client(client)
-       {}
-       ~GameGlobalShaderConstantSetter() {}
+       {
+               g_settings->registerChangedCallback("enable_fog", SettingsCallback, this);
+               m_fogEnabled = g_settings->getBool("enable_fog");
+       }
+
+       ~GameGlobalShaderConstantSetter()
+       {
+               g_settings->deregisterChangedCallback("enable_fog", SettingsCallback, this);
+       }
 
        virtual void onSetConstants(video::IMaterialRendererServices *services,
                        bool is_highlevel)
@@ -830,7 +846,7 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
                // Fog distance
                float fog_distance = 10000 * BS;
 
-               if (g_settings->getBool("enable_fog") && !*m_force_fog_off)
+               if (m_fogEnabled && !*m_force_fog_off)
                        fog_distance = *m_fog_range;
 
                services->setPixelShaderConstant("fogDistance", &fog_distance, 1);
@@ -850,6 +866,9 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
                services->setPixelShaderConstant("eyePosition", (irr::f32 *)&eye_position, 3);
                services->setVertexShaderConstant("eyePosition", (irr::f32 *)&eye_position, 3);
 
+               v3f minimap_yaw_vec = m_client->getMapper()->getYawVec();
+               services->setPixelShaderConstant("yawVec", (irr::f32 *)&minimap_yaw_vec, 3);
+
                // Uniform sampler layers
                int layer0 = 0;
                int layer1 = 1;
@@ -858,11 +877,11 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
                services->setPixelShaderConstant("baseTexture" , (irr::f32 *)&layer0, 1);
                services->setPixelShaderConstant("normalTexture" , (irr::f32 *)&layer1, 1);
-               services->setPixelShaderConstant("useNormalmap" , (irr::f32 *)&layer2, 1);
+               services->setPixelShaderConstant("textureFlags" , (irr::f32 *)&layer2, 1);
 #else
                services->setPixelShaderConstant("baseTexture" , (irr::s32 *)&layer0, 1);
                services->setPixelShaderConstant("normalTexture" , (irr::s32 *)&layer1, 1);
-               services->setPixelShaderConstant("useNormalmap" , (irr::s32 *)&layer2, 1);
+               services->setPixelShaderConstant("textureFlags" , (irr::s32 *)&layer2, 1);
 #endif
        }
 };
@@ -873,22 +892,31 @@ bool nodePlacementPrediction(Client &client,
        std::string prediction = playeritem_def.node_placement_prediction;
        INodeDefManager *nodedef = client.ndef();
        ClientMap &map = client.getEnv().getClientMap();
+       MapNode node;
+       bool is_valid_position;
+
+       node = map.getNodeNoEx(nodepos, &is_valid_position);
+       if (!is_valid_position)
+               return false;
 
-       if (prediction != "" && !nodedef->get(map.getNode(nodepos)).rightclickable) {
+       if (prediction != "" && !nodedef->get(node).rightclickable) {
                verbosestream << "Node placement prediction for "
                              << playeritem_def.name << " is "
                              << prediction << std::endl;
                v3s16 p = neighbourpos;
 
                // Place inside node itself if buildable_to
-               try {
-                       MapNode n_under = map.getNode(nodepos);
-
+               MapNode n_under = map.getNodeNoEx(nodepos, &is_valid_position);
+               if (is_valid_position)
+               {
                        if (nodedef->get(n_under).buildable_to)
                                p = nodepos;
-                       else if (!nodedef->get(map.getNode(p)).buildable_to)
-                               return false;
-               } catch (InvalidPositionException &e) {}
+                       else {
+                               node = map.getNodeNoEx(p, &is_valid_position);
+                               if (is_valid_position &&!nodedef->get(node).buildable_to)
+                                       return false;
+                       }
+               }
 
                // Find id of predicted node
                content_t id;
@@ -946,7 +974,7 @@ bool nodePlacementPrediction(Client &client,
                        else
                                pp = p + v3s16(0, -1, 0);
 
-                       if (!nodedef->get(map.getNode(pp)).walkable)
+                       if (!nodedef->get(map.getNodeNoEx(pp)).walkable)
                                return false;
                }
 
@@ -959,6 +987,7 @@ bool nodePlacementPrediction(Client &client,
                        // Dont place node when player would be inside new node
                        // NOTE: This is to be eventually implemented by a mod as client-side Lua
                        if (!nodedef->get(n).walkable ||
+                                       g_settings->getBool("enable_build_where_you_stand") ||
                                        (client.checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
                                        (nodedef->get(n).walkable &&
                                         neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
@@ -1007,7 +1036,7 @@ 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]"
+#define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
 #endif
 
 static void show_chat_menu(GUIFormSpecMenu **cur_formspec,
@@ -1019,7 +1048,7 @@ static void show_chat_menu(GUIFormSpecMenu **cur_formspec,
                FORMSPEC_VERSION_STRING
                SIZE_TAG
                "field[3,2.35;6,0.5;f_text;;" + text + "]"
-               "button_exit[4,3;3,0.5;btn_send;" + wide_to_narrow(wstrgettext("Proceed")) + "]"
+               "button_exit[4,3;3,0.5;btn_send;" + strgettext("Proceed") + "]"
                ;
 
        /* Create menu */
@@ -1039,7 +1068,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") + "]"
                ;
 
@@ -1059,32 +1088,32 @@ static void show_pause_menu(GUIFormSpecMenu **cur_formspec,
                bool singleplayermode)
 {
 #ifdef __ANDROID__
-       std::string control_text = wide_to_narrow(wstrgettext("Default Controls:\n"
-                                  "No menu visible:\n"
-                                  "- single tap: button activate\n"
-                                  "- double tap: place/use\n"
-                                  "- slide finger: look around\n"
-                                  "Menu/Inventory visible:\n"
-                                  "- double tap (outside):\n"
-                                  " -->close\n"
-                                  "- touch stack, touch slot:\n"
-                                  " --> move stack\n"
-                                  "- touch&drag, tap 2nd finger\n"
-                                  " --> place single item to slot\n"
-                                                            ));
+       std::string control_text = strgettext("Default Controls:\n"
+               "No menu visible:\n"
+               "- single tap: button activate\n"
+               "- double tap: place/use\n"
+               "- slide finger: look around\n"
+               "Menu/Inventory visible:\n"
+               "- double tap (outside):\n"
+               " -->close\n"
+               "- touch stack, touch slot:\n"
+               " --> move stack\n"
+               "- touch&drag, tap 2nd finger\n"
+               " --> place single item to slot\n"
+               );
 #else
-       std::string control_text = wide_to_narrow(wstrgettext("Default Controls:\n"
-                                  "- WASD: move\n"
-                                  "- Space: jump/climb\n"
-                                  "- Shift: sneak/go down\n"
-                                  "- Q: drop item\n"
-                                  "- I: inventory\n"
-                                  "- Mouse: turn/look\n"
-                                  "- Mouse left: dig/punch\n"
-                                  "- Mouse right: place/use\n"
-                                  "- Mouse wheel: select item\n"
-                                  "- T: chat\n"
-                                                            ));
+       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"
+               "- Mouse: turn/look\n"
+               "- Mouse left: dig/punch\n"
+               "- Mouse right: place/use\n"
+               "- Mouse wheel: select item\n"
+               "- T: chat\n"
+               );
 #endif
 
        float ypos = singleplayermode ? 0.5 : 0.1;
@@ -1092,24 +1121,26 @@ static void show_pause_menu(GUIFormSpecMenu **cur_formspec,
 
        os << FORMSPEC_VERSION_STRING  << SIZE_TAG
           << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
-          << wide_to_narrow(wstrgettext("Continue"))     << "]";
+          << strgettext("Continue") << "]";
 
        if (!singleplayermode) {
                os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
-                  << wide_to_narrow(wstrgettext("Change Password")) << "]";
+                  << strgettext("Change Password") << "]";
        }
 
+#ifndef __ANDROID__
        os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
-                       << wide_to_narrow(wstrgettext("Sound Volume")) << "]";
+                       << strgettext("Sound Volume") << "]";
        os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
-                       << wide_to_narrow(wstrgettext("Change Keys"))  << "]";
+                       << strgettext("Change Keys")  << "]";
+#endif
        os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
-                       << wide_to_narrow(wstrgettext("Exit to Menu")) << "]";
+                       << strgettext("Exit to Menu") << "]";
        os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
-                       << wide_to_narrow(wstrgettext("Exit to OS"))   << "]"
+                       << strgettext("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;]";
 
@@ -1120,22 +1151,22 @@ static void show_pause_menu(GUIFormSpecMenu **cur_formspec,
        LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
 
        create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device,  fs_src, txt_dst, NULL);
-
+       std::string con("btn_continue");
+       (*cur_formspec)->setFocus(con);
        (*cur_formspec)->doPause = true;
 }
 
 /******************************************************************************/
 static void updateChat(Client &client, f32 dtime, bool show_debug,
                const v2u32 &screensize, bool show_chat, u32 show_profiler,
-               ChatBackend &chat_backend, gui::IGUIStaticText *guitext_chat,
-               gui::IGUIFont *font)
+               ChatBackend &chat_backend, gui::IGUIStaticText *guitext_chat)
 {
        // Add chat log output for errors to be shown in chat
-       static LogOutputBuffer chat_log_error_buf(LMT_ERROR);
+       static LogOutputBuffer chat_log_error_buf(g_logger, LL_ERROR);
 
        // Get new messages from error log buffer
        while (!chat_log_error_buf.empty()) {
-               chat_backend.addMessage(L"", narrow_to_wide(chat_log_error_buf.get()));
+               chat_backend.addMessage(L"", utf8_to_wide(chat_log_error_buf.get()));
        }
 
        // Get new messages from client
@@ -1151,9 +1182,7 @@ static void updateChat(Client &client, f32 dtime, bool show_debug,
        // Display all messages in a static text element
        unsigned int recent_chat_count = chat_backend.getRecentBuffer().getLineCount();
        std::wstring recent_chat       = chat_backend.getRecentChat();
-
-       // TODO replace by fontengine fcts
-       unsigned int line_height       = font->getDimension(L"Ay").Height + font->getKerningHeight();
+       unsigned int line_height       = g_fontengine->getLineHeight();
 
        guitext_chat->setText(recent_chat.c_str());
 
@@ -1164,7 +1193,7 @@ static void updateChat(Client &client, f32 dtime, bool show_debug,
                chat_y += line_height;
 
        // first pass to calculate height of text to be set
-       s32 width = std::min(font->getDimension(recent_chat.c_str()).Width + 10,
+       s32 width = std::min(g_fontengine->getTextWidth(recent_chat) + 10,
                             porting::getWindowSize().X - 20);
        core::rect<s32> rect(10, chat_y, width, chat_y + porting::getWindowSize().Y);
        guitext_chat->setRelativePosition(rect);
@@ -1181,9 +1210,126 @@ static void updateChat(Client &client, f32 dtime, bool show_debug,
 }
 
 
+/****************************************************************************
+ Fast key cache for main game loop
+ ****************************************************************************/
+
+/* This is faster than using getKeySetting with the tradeoff that functions
+ * using it must make sure that it's initialised before using it and there is
+ * no error handling (for example bounds checking). This is really intended for
+ * use only in the main running loop of the client (the_game()) where the faster
+ * (up to 10x faster) key lookup is an asset. Other parts of the codebase
+ * (e.g. formspecs) should continue using getKeySetting().
+ */
+struct KeyCache {
+
+       KeyCache() { populate(); }
+
+       enum {
+               // Player movement
+               KEYMAP_ID_FORWARD,
+               KEYMAP_ID_BACKWARD,
+               KEYMAP_ID_LEFT,
+               KEYMAP_ID_RIGHT,
+               KEYMAP_ID_JUMP,
+               KEYMAP_ID_SPECIAL1,
+               KEYMAP_ID_SNEAK,
+               KEYMAP_ID_AUTORUN,
+
+               // Other
+               KEYMAP_ID_DROP,
+               KEYMAP_ID_INVENTORY,
+               KEYMAP_ID_CHAT,
+               KEYMAP_ID_CMD,
+               KEYMAP_ID_CONSOLE,
+               KEYMAP_ID_MINIMAP,
+               KEYMAP_ID_FREEMOVE,
+               KEYMAP_ID_FASTMOVE,
+               KEYMAP_ID_NOCLIP,
+               KEYMAP_ID_CINEMATIC,
+               KEYMAP_ID_SCREENSHOT,
+               KEYMAP_ID_TOGGLE_HUD,
+               KEYMAP_ID_TOGGLE_CHAT,
+               KEYMAP_ID_TOGGLE_FORCE_FOG_OFF,
+               KEYMAP_ID_TOGGLE_UPDATE_CAMERA,
+               KEYMAP_ID_TOGGLE_DEBUG,
+               KEYMAP_ID_TOGGLE_PROFILER,
+               KEYMAP_ID_CAMERA_MODE,
+               KEYMAP_ID_INCREASE_VIEWING_RANGE,
+               KEYMAP_ID_DECREASE_VIEWING_RANGE,
+               KEYMAP_ID_RANGESELECT,
+
+               KEYMAP_ID_QUICKTUNE_NEXT,
+               KEYMAP_ID_QUICKTUNE_PREV,
+               KEYMAP_ID_QUICKTUNE_INC,
+               KEYMAP_ID_QUICKTUNE_DEC,
+
+               KEYMAP_ID_DEBUG_STACKS,
+
+               // Fake keycode for array size and internal checks
+               KEYMAP_INTERNAL_ENUM_COUNT
+
+
+       };
+
+       void populate();
+
+       KeyPress key[KEYMAP_INTERNAL_ENUM_COUNT];
+};
+
+void KeyCache::populate()
+{
+       key[KEYMAP_ID_FORWARD]      = getKeySetting("keymap_forward");
+       key[KEYMAP_ID_BACKWARD]     = getKeySetting("keymap_backward");
+       key[KEYMAP_ID_LEFT]         = getKeySetting("keymap_left");
+       key[KEYMAP_ID_RIGHT]        = getKeySetting("keymap_right");
+       key[KEYMAP_ID_JUMP]         = getKeySetting("keymap_jump");
+       key[KEYMAP_ID_SPECIAL1]     = getKeySetting("keymap_special1");
+       key[KEYMAP_ID_SNEAK]        = getKeySetting("keymap_sneak");
+
+       key[KEYMAP_ID_AUTORUN]      = getKeySetting("keymap_autorun");
+
+       key[KEYMAP_ID_DROP]         = getKeySetting("keymap_drop");
+       key[KEYMAP_ID_INVENTORY]    = getKeySetting("keymap_inventory");
+       key[KEYMAP_ID_CHAT]         = getKeySetting("keymap_chat");
+       key[KEYMAP_ID_CMD]          = getKeySetting("keymap_cmd");
+       key[KEYMAP_ID_CONSOLE]      = getKeySetting("keymap_console");
+       key[KEYMAP_ID_MINIMAP]      = getKeySetting("keymap_minimap");
+       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");
+       key[KEYMAP_ID_TOGGLE_FORCE_FOG_OFF]
+                       = getKeySetting("keymap_toggle_force_fog_off");
+       key[KEYMAP_ID_TOGGLE_UPDATE_CAMERA]
+                       = getKeySetting("keymap_toggle_update_camera");
+       key[KEYMAP_ID_TOGGLE_DEBUG]
+                       = getKeySetting("keymap_toggle_debug");
+       key[KEYMAP_ID_TOGGLE_PROFILER]
+                       = getKeySetting("keymap_toggle_profiler");
+       key[KEYMAP_ID_CAMERA_MODE]
+                       = getKeySetting("keymap_camera_mode");
+       key[KEYMAP_ID_INCREASE_VIEWING_RANGE]
+                       = getKeySetting("keymap_increase_viewing_range_min");
+       key[KEYMAP_ID_DECREASE_VIEWING_RANGE]
+                       = getKeySetting("keymap_decrease_viewing_range_min");
+       key[KEYMAP_ID_RANGESELECT]
+                       = getKeySetting("keymap_rangeselect");
+
+       key[KEYMAP_ID_QUICKTUNE_NEXT] = getKeySetting("keymap_quicktune_next");
+       key[KEYMAP_ID_QUICKTUNE_PREV] = getKeySetting("keymap_quicktune_prev");
+       key[KEYMAP_ID_QUICKTUNE_INC]  = getKeySetting("keymap_quicktune_inc");
+       key[KEYMAP_ID_QUICKTUNE_DEC]  = getKeySetting("keymap_quicktune_dec");
+
+       key[KEYMAP_ID_DEBUG_STACKS]   = getKeySetting("keymap_print_debug_stacks");
+}
+
 
 /****************************************************************************
- THE GAME
+
  ****************************************************************************/
 
 const float object_hit_delay = 0.2;
@@ -1198,20 +1344,20 @@ struct FpsControl {
  * many functions that do require objects of thse types do not modify them
  * (so they can be passed as a const qualified parameter)
  */
-
 struct CameraOrientation {
        f32 camera_yaw;    // "right/left"
        f32 camera_pitch;  // "up/down"
 };
 
-//TODO: Needs a better name because fog_range etc are included
-struct InteractParams {
+struct GameRunData {
        u16 dig_index;
        u16 new_playeritem;
        PointedThing pointed_old;
        bool digging;
        bool ldown_for_dig;
        bool left_punch;
+       bool update_wielded_item_trigger;
+       bool reset_jump_timer;
        float nodig_delay_timer;
        float dig_time;
        float dig_time_complete;
@@ -1231,6 +1377,9 @@ struct InteractParams {
 
        u32 profiler_current_page;
        u32 profiler_max_page;     // Number of pages
+
+       float time_of_day;
+       float time_of_day_smooth;
 };
 
 struct Jitter {
@@ -1251,6 +1400,7 @@ struct VolatileRunFlags {
        bool invert_mouse;
        bool show_chat;
        bool show_hud;
+       bool show_minimap;
        bool force_fog_off;
        bool show_debug;
        bool show_profiler_graph;
@@ -1260,29 +1410,32 @@ struct VolatileRunFlags {
 };
 
 
+/****************************************************************************
+ THE GAME
+ ****************************************************************************/
+
 /* This is not intended to be a public class. If a public class becomes
  * desirable then it may be better to create another 'wrapper' class that
  * hides most of the stuff in this class (nothing in this class is required
  * by any other file) but exposes the public methods/data only.
  */
-class MinetestApp
-{
+class Game {
 public:
-       MinetestApp();
-       ~MinetestApp();
+       Game();
+       ~Game();
 
        bool startup(bool *kill,
                        bool random_input,
                        InputHandler *input,
                        IrrlichtDevice *device,
-                       gui::IGUIFont *font,
                        const std::string &map_dir,
                        const std::string &playername,
                        const std::string &password,
                        // If address is "", local server is used and address is updated
                        std::string *address,
                        u16 port,
-                       std::wstring *error_message,
+                       std::string &error_message,
+                       bool *reconnect,
                        ChatBackend *chat_backend,
                        const SubgameSpec &gamespec,    // Used for local game
                        bool simple_singleplayer_mode);
@@ -1304,9 +1457,8 @@ class MinetestApp
 
        // 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,
@@ -1316,19 +1468,22 @@ class MinetestApp
 
        // Main loop
 
-       void updateInteractTimers(InteractParams *args, f32 dtime);
+       void updateInteractTimers(GameRunData *runData, f32 dtime);
        bool checkConnection();
        bool handleCallbacks();
        void processQueues();
+       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, InteractParams *interact_args,
+       void processUserInput(VolatileRunFlags *flags, GameRunData *runData,
                        f32 dtime);
        void processKeyboardInput(VolatileRunFlags *flags,
                        float *statustext_time,
                        float *jump_timer,
+                       bool *reset_jump_timer,
                        u32 *profiler_current_page,
                        u32 profiler_max_page);
        void processItemSelection(u16 *new_playeritem);
@@ -1340,9 +1495,12 @@ class MinetestApp
        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);
+       void toggleMinimap(float *statustext_time, bool *flag, bool show_hud,
+                       bool shift_pressed);
        void toggleFog(float *statustext_time, bool *flag);
        void toggleDebug(float *statustext_time, bool *show_debug,
                        bool *show_profiler_graph);
@@ -1355,6 +1513,8 @@ class MinetestApp
        void toggleFullViewRange(float *statustext_time);
 
        void updateCameraDirection(CameraOrientation *cam, VolatileRunFlags *flags);
+       void updateCameraOrientation(CameraOrientation *cam,
+                       const VolatileRunFlags &flags);
        void updatePlayerControl(const CameraOrientation &cam);
        void step(f32 *dtime);
        void processClientEvents(CameraOrientation *cam, float *damage_flash);
@@ -1362,38 +1522,40 @@ class MinetestApp
                        float time_from_last_punch);
        void updateSound(f32 dtime);
        void processPlayerInteraction(std::vector<aabb3f> &highlight_boxes,
-                       InteractParams *interactArgs, f32 dtime, bool show_hud,
+                       GameRunData *runData, f32 dtime, bool show_hud,
                        bool show_debug);
-       void handlePointingAtNode(InteractParams *interactArgs,
+       void handlePointingAtNode(GameRunData *runData,
                        const PointedThing &pointed, const ItemDefinition &playeritem_def,
                        const ToolCapabilities &playeritem_toolcap, f32 dtime);
-       void handlePointingAtObject(InteractParams *interactArgs,
+       void handlePointingAtObject(GameRunData *runData,
                        const PointedThing &pointed, const ItemStack &playeritem,
                        const v3f &player_position, bool show_debug);
-       void handleDigging(InteractParams *interactArgs, const PointedThing &pointed,
+       void handleDigging(GameRunData *runData, const PointedThing &pointed,
                        const v3s16 &nodepos, const ToolCapabilities &playeritem_toolcap,
                        f32 dtime);
        void updateFrame(std::vector<aabb3f> &highlight_boxes, ProfilerGraph *graph,
-                       RunStats *stats, InteractParams *interactArgs,
+                       RunStats *stats, GameRunData *runData,
                        f32 dtime, const VolatileRunFlags &flags, const CameraOrientation &cam);
-       void updateGui(float *statustext_time, const RunStats &stats, f32 dtime,
-                       const VolatileRunFlags &flags, const CameraOrientation &cam);
+       void updateGui(float *statustext_time, const RunStats &stats,
+                       const GameRunData& runData, f32 dtime, const VolatileRunFlags &flags,
+                       const CameraOrientation &cam);
        void updateProfilerGraphs(ProfilerGraph *graph);
 
        // Misc
-       void limitFps(FpsControl *params, f32 *dtime);
+       void limitFps(FpsControl *fps_timings, f32 *dtime);
 
-       void showOverlayMessage(const char *msg, float dtime, int percent,
+       void showOverlayMessage(const wchar_t *msg, float dtime, int percent,
                        bool draw_clouds = true);
 
+       static void settingChangedCallback(const std::string &setting_name, void *data);
+       void readSettings();
+
 private:
        InputHandler *input;
 
        Client *client;
        Server *server;
 
-       gui::IGUIFont *font;
-
        IWritableTextureSource *texture_src;
        IWritableShaderSource *shader_src;
 
@@ -1420,6 +1582,7 @@ class MinetestApp
        Sky *sky;                         // Free using ->Drop()
        Inventory *local_inventory;
        Hud *hud;
+       Mapper *mapper;
 
        /* 'cache'
           This class does take ownership/responsibily for cleaning up etc of any of
@@ -1428,9 +1591,9 @@ class MinetestApp
        IrrlichtDevice *device;
        video::IVideoDriver *driver;
        scene::ISceneManager *smgr;
-       u32 text_height;
        bool *kill;
-       std::wstring *error_message;
+       std::string *error_message;
+       bool *reconnect_requested;
        IGameDef *gamedef;                     // Convenience (same as *client)
        scene::ISceneNode *skybox;
 
@@ -1453,12 +1616,34 @@ class MinetestApp
 
        std::wstring infotext;
        std::wstring statustext;
+
+       KeyCache keycache;
+
+       IntervalLimiter profiler_interval;
+
+       /*
+        * TODO: Local caching of settings is not optimal and should at some stage
+        *       be updated to use a global settings object for getting thse values
+        *       (as opposed to the this local caching). This can be addressed in
+        *       a later release.
+        */
+       bool m_cache_doubletap_jump;
+       bool m_cache_enable_node_highlighting;
+       bool m_cache_enable_clouds;
+       bool m_cache_enable_particles;
+       bool m_cache_enable_fog;
+       f32  m_cache_mouse_sensitivity;
+       f32  m_repeat_right_click_time;
+
+#ifdef __ANDROID__
+       bool m_cache_hold_aux1;
+#endif
+
 };
 
-MinetestApp::MinetestApp() :
+Game::Game() :
        client(NULL),
        server(NULL),
-       font(NULL),
        texture_src(NULL),
        shader_src(NULL),
        itemdef_manager(NULL),
@@ -1476,8 +1661,29 @@ MinetestApp::MinetestApp() :
        clouds(NULL),
        sky(NULL),
        local_inventory(NULL),
-       hud(NULL)
+       hud(NULL),
+       mapper(NULL)
 {
+       g_settings->registerChangedCallback("doubletap_jump",
+               &settingChangedCallback, this);
+       g_settings->registerChangedCallback("enable_node_highlighting",
+               &settingChangedCallback, this);
+       g_settings->registerChangedCallback("enable_clouds",
+               &settingChangedCallback, this);
+       g_settings->registerChangedCallback("enable_particles",
+               &settingChangedCallback, this);
+       g_settings->registerChangedCallback("enable_fog",
+               &settingChangedCallback, this);
+       g_settings->registerChangedCallback("mouse_sensitivity",
+               &settingChangedCallback, this);
+       g_settings->registerChangedCallback("repeat_rightclick_time",
+               &settingChangedCallback, this);
+
+       readSettings();
+
+#ifdef __ANDROID__
+       m_cache_hold_aux1 = false;      // This is initialised properly later
+#endif
 
 }
 
@@ -1487,7 +1693,7 @@ MinetestApp::MinetestApp() :
  MinetestApp Public
  ****************************************************************************/
 
-MinetestApp::~MinetestApp()
+Game::~Game()
 {
        delete client;
        delete soundmaker;
@@ -1508,66 +1714,84 @@ MinetestApp::~MinetestApp()
        delete draw_control;
 
        extendedResourceCleanup();
+
+       g_settings->deregisterChangedCallback("doubletap_jump",
+               &settingChangedCallback, this);
+       g_settings->deregisterChangedCallback("enable_node_highlighting",
+               &settingChangedCallback, this);
+       g_settings->deregisterChangedCallback("enable_clouds",
+               &settingChangedCallback, this);
+       g_settings->deregisterChangedCallback("enable_particles",
+               &settingChangedCallback, this);
+       g_settings->deregisterChangedCallback("enable_fog",
+               &settingChangedCallback, this);
+       g_settings->deregisterChangedCallback("mouse_sensitivity",
+               &settingChangedCallback, this);
+       g_settings->deregisterChangedCallback("repeat_rightclick_time",
+               &settingChangedCallback, this);
 }
 
-bool MinetestApp::startup(bool *kill,
+bool Game::startup(bool *kill,
                bool random_input,
                InputHandler *input,
                IrrlichtDevice *device,
-               gui::IGUIFont *font,
                const std::string &map_dir,
                const std::string &playername,
                const std::string &password,
                std::string *address,     // can change if simple_singleplayer_mode
                u16 port,
-               std::wstring *error_message,
+               std::string &error_message,
+               bool *reconnect,
                ChatBackend *chat_backend,
                const SubgameSpec &gamespec,
                bool simple_singleplayer_mode)
 {
        // "cache"
-       this->device        = device;
-       this->font          = font;
-       this->kill          = kill;
-       this->error_message = error_message;
-       this->random_input  = random_input;
-       this->input         = input;
-       this->chat_backend  = chat_backend;
+       this->device              = device;
+       this->kill                = kill;
+       this->error_message       = &error_message;
+       this->reconnect_requested = reconnect;
+       this->random_input        = random_input;
+       this->input               = input;
+       this->chat_backend        = chat_backend;
        this->simple_singleplayer_mode = simple_singleplayer_mode;
 
        driver              = device->getVideoDriver();
        smgr                = device->getSceneManager();
-       text_height         = font->getDimension(L"Random test string").Height;
+
+       smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
 
        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;
 }
 
 
-void MinetestApp::run()
+void Game::run()
 {
        ProfilerGraph graph;
        RunStats stats              = { 0 };
+       CameraOrientation cam_view_target  = { 0 };
        CameraOrientation cam_view  = { 0 };
-       InteractParams interactArgs = { 0 };
+       GameRunData runData         = { 0 };
        FpsControl draw_times       = { 0 };
        VolatileRunFlags flags      = { 0 };
        f32 dtime; // in seconds
 
-       interactArgs.time_from_last_punch  = 10.0;
-       interactArgs.profiler_max_page = 3;
+       runData.time_from_last_punch  = 10.0;
+       runData.profiler_max_page = 3;
+       runData.update_wielded_item_trigger = true;
 
        flags.show_chat = true;
        flags.show_hud = true;
-
-       /* FIXME: This should be updated every iteration, or not stored locally
-                 now that key settings can be changed in-game */
+       flags.show_minimap = g_settings->getBool("enable_minimap");
+       flags.show_debug = g_settings->getBool("show_debug");
        flags.invert_mouse = g_settings->getBool("invert_mouse");
+       flags.first_loop_after_window_activation = true;
 
        /* Clear the profiler */
        Profiler::GraphValues dummyvalues;
@@ -1578,9 +1802,18 @@ void MinetestApp::run()
        shader_src->addGlobalConstantSetter(new GameGlobalShaderConstantSetter(
                        sky,
                        &flags.force_fog_off,
-                       &interactArgs.fog_range,
+                       &runData.fog_range,
                        client));
 
+       std::vector<aabb3f> highlight_boxes;
+
+       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
@@ -1589,7 +1822,7 @@ void MinetestApp::run()
                limitFps(&draw_times, &dtime);
 
                updateStats(&stats, draw_times, dtime);
-               updateInteractTimers(&interactArgs, dtime);
+               updateInteractTimers(&runData, dtime);
 
                if (!checkConnection())
                        break;
@@ -1598,32 +1831,44 @@ void MinetestApp::run()
 
                processQueues();
 
-               std::vector<aabb3f> highlight_boxes;
-
                infotext = L"";
                hud->resizeHotbar();
-               addProfilerGraphs(stats, draw_times, dtime);
-               processUserInput(&flags, &interactArgs, dtime);
+
+               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, &interactArgs.damage_flash);
+               processClientEvents(&cam_view_target, &runData.damage_flash);
                updateCamera(&flags, draw_times.busy_time, dtime,
-                               interactArgs.time_from_last_punch);
+                               runData.time_from_last_punch);
                updateSound(dtime);
-               processPlayerInteraction(highlight_boxes, &interactArgs, dtime,
+               processPlayerInteraction(highlight_boxes, &runData, dtime,
                                flags.show_hud, flags.show_debug);
-               updateFrame(highlight_boxes, &graph, &stats, &interactArgs, dtime,
+               updateFrame(highlight_boxes, &graph, &stats, &runData, dtime,
                                flags, cam_view);
                updateProfilerGraphs(&graph);
+
+               // Update if minimap has been disabled by the server
+               flags.show_minimap &= !client->isMinimapDisabledByServer();
        }
 }
 
 
-void MinetestApp::shutdown()
+void Game::shutdown()
 {
-       showOverlayMessage("Shutting down...", 0, 0, false);
+       showOverlayMessage(wgettext("Shutting down..."), 0, 0, false);
 
        if (clouds)
                clouds->drop();
@@ -1634,8 +1879,6 @@ void MinetestApp::shutdown()
        if (sky)
                sky->drop();
 
-       clear_particles();
-
        /* cleanup menus */
        while (g_menumgr.menuCount() > 0) {
                g_menumgr.m_stack.front()->setVisible(false);
@@ -1663,18 +1906,19 @@ void MinetestApp::shutdown()
 }
 
 
-
+/****************************************************************************/
 /****************************************************************************
  Startup
  ****************************************************************************/
+/****************************************************************************/
 
-bool MinetestApp::init(
+bool Game::init(
                const std::string &map_dir,
                std::string *address,
                u16 port,
                const SubgameSpec &gamespec)
 {
-       showOverlayMessage("Loading...", 0, 0);
+       showOverlayMessage(wgettext("Loading..."), 0, 0);
 
        texture_src = createTextureSource(device);
        shader_src = createShaderSource(device);
@@ -1701,7 +1945,7 @@ bool MinetestApp::init(
        return true;
 }
 
-bool MinetestApp::initSound()
+bool Game::initSound()
 {
 #if USE_SOUND
        if (g_settings->getBool("enable_sound")) {
@@ -1728,10 +1972,10 @@ bool MinetestApp::initSound()
        return true;
 }
 
-bool MinetestApp::createSingleplayerServer(const std::string map_dir,
+bool Game::createSingleplayerServer(const std::string map_dir,
                const SubgameSpec &gamespec, u16 port, std::string *address)
 {
-       showOverlayMessage("Creating server...", 0, 25);
+       showOverlayMessage(wgettext("Creating server..."), 0, 5);
 
        std::string bind_str = g_settings->get("bind_address");
        Address bind_addr(0, 0, 0, 0, port);
@@ -1742,7 +1986,6 @@ bool MinetestApp::createSingleplayerServer(const std::string map_dir,
 
        try {
                bind_addr.Resolve(bind_str.c_str());
-               *address = bind_str;
        } catch (ResolveError &e) {
                infostream << "Resolving bind address \"" << bind_str
                           << "\" failed: " << e.what()
@@ -1750,10 +1993,10 @@ bool MinetestApp::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;
        }
 
@@ -1765,11 +2008,10 @@ bool MinetestApp::createSingleplayerServer(const std::string map_dir,
        return true;
 }
 
-bool MinetestApp::createClient(const std::string &playername,
-               const std::string &password, std::string *address, u16 port,
-               std::wstring *error_message)
+bool Game::createClient(const std::string &playername,
+               const std::string &password, std::string *address, u16 port)
 {
-       showOverlayMessage("Creating client...", 0, 50);
+       showOverlayMessage(wgettext("Creating client..."), 0, 10);
 
        draw_control = new MapDrawControl;
        if (!draw_control)
@@ -1782,25 +2024,25 @@ bool MinetestApp::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, font);
+       client->afterContentReceived(device);
 
        /* Camera
         */
@@ -1810,27 +2052,25 @@ bool MinetestApp::createClient(const std::string &playername,
 
        /* Clouds
         */
-       if (g_settings->getBool("enable_clouds")) {
+       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;
                }
        }
 
        /* Skybox
         */
-       sky = new Sky(smgr->getRootSceneNode(), smgr, -1);
+       sky = new Sky(smgr->getRootSceneNode(), smgr, -1, texture_src);
        skybox = NULL;  // This is used/set later on in the main run loop
 
        local_inventory = new Inventory(itemdef_manager);
 
        if (!(sky && local_inventory)) {
-               *error_message = 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;
        }
 
@@ -1844,37 +2084,40 @@ bool MinetestApp::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 = utf8_to_wide(PROJECT_NAME_C);
+       str += L" [";
        str += driver->getName();
-       str += "]";
+       str += L"]";
        device->setWindowCaption(str.c_str());
 
        LocalPlayer *player = client->getEnv().getLocalPlayer();
        player->hurt_tilt_timer = 0;
        player->hurt_tilt_strength = 0;
 
-       hud = new Hud(driver, smgr, guienv, font, text_height, gamedef,
-                       player, local_inventory);
+       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;
        }
 
+       mapper = client->getMapper();
+       mapper->setMinimapMode(MINIMAP_MODE_OFF);
+
        return true;
 }
 
-bool MinetestApp::initGui(std::wstring *error_message)
+bool Game::initGui()
 {
        // First line of debug text
        guitext = guienv->addStaticText(
-                       L"Minetest",
+                       utf8_to_wide(PROJECT_NAME_C).c_str(),
                        core::rect<s32>(0, 0, 0, 0),
                        false, false, guiroot);
 
@@ -1888,7 +2131,7 @@ bool MinetestApp::initGui(std::wstring *error_message)
        // Object infos are shown in this
        guitext_info = guienv->addStaticText(
                        L"",
-                       core::rect<s32>(0, 0, 400, text_height * 5 + 5) + v2s32(100, 200),
+                       core::rect<s32>(0, 0, 400, g_fontengine->getTextHeight() * 5 + 5) + v2s32(100, 200),
                        false, true, guiroot);
 
        // Status text (displays info when showing and hiding GUI stuff, etc.)
@@ -1911,8 +2154,8 @@ bool MinetestApp::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;
        }
 
@@ -1928,18 +2171,22 @@ bool MinetestApp::initGui(std::wstring *error_message)
 #ifdef HAVE_TOUCHSCREENGUI
 
        if (g_touchscreengui)
-               g_touchscreengui->init(tsrc, porting::getDisplayDensity());
+               g_touchscreengui->init(texture_src, porting::getDisplayDensity());
 
 #endif
 
        return true;
 }
 
-bool MinetestApp::connectToServer(const std::string &playername,
+bool Game::connectToServer(const std::string &playername,
                const std::string &password, std::string *address, u16 port,
                bool *connect_ok, bool *aborted)
 {
-       showOverlayMessage("Resolving address...", 0, 75);
+       *connect_ok = false;    // Let's not be overly optimistic
+       *aborted = false;
+       bool local_server_mode = false;
+
+       showOverlayMessage(wgettext("Resolving address..."), 0, 15);
 
        Address connect_address(0, 0, 0, 0, port);
 
@@ -1955,37 +2202,39 @@ bool MinetestApp::connectToServer(const std::string &playername,
                        } else {
                                connect_address.setAddress(127, 0, 0, 1);
                        }
+                       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;
        }
 
-       client = new Client(device, playername.c_str(), password, *draw_control,
-                   texture_src, shader_src, itemdef_manager, nodedef_manager, sound,
-                       eventmgr, connect_address.isIPv6());
+       client = new Client(device,
+                       playername.c_str(), password,
+                       *draw_control, texture_src, shader_src,
+                       itemdef_manager, nodedef_manager, sound, eventmgr,
+                       connect_address.isIPv6());
 
        if (!client)
                return false;
 
        gamedef = client;       // Client acts as our GameDef
 
-
        infostream << "Connecting to server at ";
        connect_address.print(&infostream);
        infostream << std::endl;
 
-       client->connect(connect_address);
-
+       client->connect(connect_address, *address,
+               simple_singleplayer_mode || local_server_mode);
 
        /*
                Wait for server to accept connection
@@ -1995,7 +2244,10 @@ bool MinetestApp::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()) {
 
@@ -2015,9 +2267,10 @@ bool MinetestApp::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;
+                               *reconnect_requested = client->reconnectRequested();
+                               errorstream << *error_message << std::endl;
                                break;
                        }
 
@@ -2027,8 +2280,16 @@ bool MinetestApp::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("Connecting to server...", dtime, 100);
+                       showOverlayMessage(wgettext("Connecting to server..."), dtime, 20);
                }
        } catch (con::PeerNotFoundException &e) {
                // TODO: Should something be done here? At least an info/error
@@ -2039,13 +2300,15 @@ bool MinetestApp::connectToServer(const std::string &playername,
        return true;
 }
 
-bool MinetestApp::getServerContent(bool *aborted)
+bool Game::getServerContent(bool *aborted)
 {
        input->clear();
 
        FpsControl fps_control = { 0 };
        f32 dtime; // in seconds
 
+       fps_control.last_time = device->getTimer()->getTime();
+
        while (device->run()) {
 
                limitFps(&fps_control, &dtime);
@@ -2063,16 +2326,12 @@ bool MinetestApp::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;
                }
 
@@ -2083,17 +2342,17 @@ bool MinetestApp::getServerContent(bool *aborted)
                }
 
                // Display status
-               int progress = 0;
+               int progress = 25;
 
                if (!client->itemdefReceived()) {
-                       wchar_t *text = wgettext("Item definitions...");
-                       progress = 0;
-                       draw_load_screen(text, device, guienv, font, dtime, progress);
+                       const wchar_t *text = wgettext("Item definitions...");
+                       progress = 25;
+                       draw_load_screen(text, device, guienv, dtime, progress);
                        delete[] text;
                } else if (!client->nodedefReceived()) {
-                       wchar_t *text = wgettext("Node definitions...");
-                       progress = 25;
-                       draw_load_screen(text, device, guienv, font, dtime, progress);
+                       const wchar_t *text = wgettext("Node definitions...");
+                       progress = 30;
+                       draw_load_screen(text, device, guienv, dtime, progress);
                        delete[] text;
                } else {
                        std::stringstream message;
@@ -2103,19 +2362,19 @@ bool MinetestApp::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 = 50 + client->mediaReceiveProgress() * 50 + 0.5;
-                       draw_load_screen(narrow_to_wide(message.str().c_str()), device,
-                                       guienv, font, dtime, progress);
+                       progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
+                       draw_load_screen(utf8_to_wide(message.str()), device,
+                                       guienv, dtime, progress);
                }
        }
 
@@ -2123,31 +2382,33 @@ bool MinetestApp::getServerContent(bool *aborted)
 }
 
 
-
+/****************************************************************************/
 /****************************************************************************
  Run
  ****************************************************************************/
+/****************************************************************************/
 
-inline void MinetestApp::updateInteractTimers(InteractParams *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;
 }
 
 
-/* returns false if app should exit, otherwise true
+/* returns false if game should exit, otherwise true
  */
-inline bool MinetestApp::checkConnection()
+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;
+               *reconnect_requested = client->reconnectRequested();
+               errorstream << *error_message << std::endl;
                return false;
        }
 
@@ -2155,9 +2416,9 @@ inline bool MinetestApp::checkConnection()
 }
 
 
-/* returns false if app should exit, otherwise true
+/* returns false if game should exit, otherwise true
  */
-inline bool MinetestApp::handleCallbacks()
+inline bool Game::handleCallbacks()
 {
        if (g_gamecallback->disconnect_requested) {
                g_gamecallback->disconnect_requested = false;
@@ -2182,11 +2443,16 @@ inline bool MinetestApp::handleCallbacks()
                g_gamecallback->keyconfig_requested = false;
        }
 
+       if (g_gamecallback->keyconfig_changed) {
+               keycache.populate(); // update the cache with new settings
+               g_gamecallback->keyconfig_changed = false;
+       }
+
        return true;
 }
 
 
-void MinetestApp::processQueues()
+void Game::processQueues()
 {
        texture_src->processQueue();
        itemdef_manager->processQueue(gamedef);
@@ -2194,7 +2460,36 @@ void MinetestApp::processQueues()
 }
 
 
-void MinetestApp::addProfilerGraphs(const RunStats &stats,
+void Game::updateProfilers(const GameRunData &runData, const RunStats &stats,
+               const FpsControl &draw_times, f32 dtime)
+{
+       float profiler_print_interval =
+                       g_settings->getFloat("profiler_print_interval");
+       bool print_to_log = true;
+
+       if (profiler_print_interval == 0) {
+               print_to_log = false;
+               profiler_print_interval = 5;
+       }
+
+       if (profiler_interval.step(dtime, profiler_print_interval)) {
+               if (print_to_log) {
+                       infostream << "Profiler:" << std::endl;
+                       g_profiler->print(infostream);
+               }
+
+               update_profiler_gui(guitext_profiler, g_fontengine,
+                               runData.profiler_current_page, runData.profiler_max_page,
+                               driver->getScreenSize().Height);
+
+               g_profiler->clear();
+       }
+
+       addProfilerGraphs(stats, draw_times, dtime);
+}
+
+
+void Game::addProfilerGraphs(const RunStats &stats,
                const FpsControl &draw_times, f32 dtime)
 {
        g_profiler->graphAdd("mainloop_other",
@@ -2209,7 +2504,7 @@ void MinetestApp::addProfilerGraphs(const RunStats &stats,
 }
 
 
-void MinetestApp::updateStats(RunStats *stats, const FpsControl &draw_times,
+void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
                f32 dtime)
 {
 
@@ -2264,8 +2559,8 @@ void MinetestApp::updateStats(RunStats *stats, const FpsControl &draw_times,
  Input handling
  ****************************************************************************/
 
-void MinetestApp::processUserInput(VolatileRunFlags *flags,
-               InteractParams *interact_args, f32 dtime)
+void Game::processUserInput(VolatileRunFlags *flags,
+               GameRunData *runData, f32 dtime)
 {
        // Reset input if window not active or some menu is active
        if (device->isWindowActive() == false
@@ -2296,100 +2591,118 @@ void MinetestApp::processUserInput(VolatileRunFlags *flags,
 #endif
 
        // Increase timer for double tap of "keymap_jump"
-       if (g_settings->getBool("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->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);
 }
 
 
-void MinetestApp::processKeyboardInput(VolatileRunFlags *flags,
+void Game::processKeyboardInput(VolatileRunFlags *flags,
                float *statustext_time,
                float *jump_timer,
+               bool *reset_jump_timer,
                u32 *profiler_current_page,
                u32 profiler_max_page)
 {
-       if (input->wasKeyDown(getKeySetting("keymap_drop"))) {
+
+       //TimeTaker tt("process kybd input", NULL, PRECISION_NANO);
+
+       if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_DROP])) {
                dropSelectedItem();
-       } else if (input->wasKeyDown(getKeySetting("keymap_inventory"))) {
+       // Add WoW-style autorun by toggling continuous forward.
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_AUTORUN])) {
+               bool autorun_setting = g_settings->getBool("continuous_forward");
+               g_settings->setBool("continuous_forward", !autorun_setting);
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_INVENTORY])) {
                openInventory();
        } else if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) {
                show_pause_menu(&current_formspec, client, gamedef, texture_src, device,
                                simple_singleplayer_mode);
-       } else if (input->wasKeyDown(getKeySetting("keymap_chat"))) {
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CHAT])) {
                show_chat_menu(&current_formspec, client, gamedef, texture_src, device,
                                client, "");
-       } else if (input->wasKeyDown(getKeySetting("keymap_cmd"))) {
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CMD])) {
                show_chat_menu(&current_formspec, client, gamedef, texture_src, device,
                                client, "/");
-       } else if (input->wasKeyDown(getKeySetting("keymap_console"))) {
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CONSOLE])) {
                openConsole();
-       } else if (input->wasKeyDown(getKeySetting("keymap_freemove"))) {
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_FREEMOVE])) {
                toggleFreeMove(statustext_time);
-       } else if (input->wasKeyDown(getKeySetting("keymap_jump"))) {
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_JUMP])) {
                toggleFreeMoveAlt(statustext_time, jump_timer);
-       } else if (input->wasKeyDown(getKeySetting("keymap_fastmove"))) {
+               *reset_jump_timer = true;
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_FASTMOVE])) {
                toggleFast(statustext_time);
-       } else if (input->wasKeyDown(getKeySetting("keymap_noclip"))) {
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_NOCLIP])) {
                toggleNoClip(statustext_time);
-       } else if (input->wasKeyDown(getKeySetting("keymap_screenshot"))) {
+       } 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(getKeySetting("keymap_toggle_hud"))) {
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_HUD])) {
                toggleHud(statustext_time, &flags->show_hud);
-       } else if (input->wasKeyDown(getKeySetting("keymap_toggle_chat"))) {
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_MINIMAP])) {
+               toggleMinimap(statustext_time, &flags->show_minimap, flags->show_hud,
+                       input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SNEAK]));
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_CHAT])) {
                toggleChat(statustext_time, &flags->show_chat);
-       } else if (input->wasKeyDown(getKeySetting("keymap_toggle_force_fog_off"))) {
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_FORCE_FOG_OFF])) {
                toggleFog(statustext_time, &flags->force_fog_off);
-       } else if (input->wasKeyDown(getKeySetting("keymap_toggle_update_camera"))) {
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_UPDATE_CAMERA])) {
                toggleUpdateCamera(statustext_time, &flags->disable_camera_update);
-       } else if (input->wasKeyDown(getKeySetting("keymap_toggle_debug"))) {
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_DEBUG])) {
                toggleDebug(statustext_time, &flags->show_debug, &flags->show_profiler_graph);
-       } else if (input->wasKeyDown(getKeySetting("keymap_toggle_profiler"))) {
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_PROFILER])) {
                toggleProfiler(statustext_time, profiler_current_page, profiler_max_page);
-       } else if (input->wasKeyDown(getKeySetting("keymap_increase_viewing_range_min"))) {
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_INCREASE_VIEWING_RANGE])) {
                increaseViewRange(statustext_time);
-       } else if (input->wasKeyDown(getKeySetting("keymap_decrease_viewing_range_min"))) {
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_DECREASE_VIEWING_RANGE])) {
                decreaseViewRange(statustext_time);
-       } else if (input->wasKeyDown(getKeySetting("keymap_rangeselect"))) {
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_RANGESELECT])) {
                toggleFullViewRange(statustext_time);
-       }
-
-       // Handle QuicktuneShortcutter
-       if (input->wasKeyDown(getKeySetting("keymap_quicktune_next")))
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_NEXT]))
                quicktune->next();
-       else if (input->wasKeyDown(getKeySetting("keymap_quicktune_prev")))
+       else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_PREV]))
                quicktune->prev();
-       else if (input->wasKeyDown(getKeySetting("keymap_quicktune_inc")))
+       else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_INC]))
                quicktune->inc();
-       else if (input->wasKeyDown(getKeySetting("keymap_quicktune_dec")))
+       else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_DEC]))
                quicktune->dec();
-
-       std::string msg = quicktune->getMessage();
-       if (msg != "") {
-               statustext = narrow_to_wide(msg);
-               *statustext_time = 0;
-       }
-
-       // Print debug stacks
-       if (input->wasKeyDown(getKeySetting("keymap_print_debug_stacks"))) {
+       else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_DEBUG_STACKS])) {
+               // Print debug stacks
                dstream << "-----------------------------------------"
                        << std::endl;
-               dstream << DTIME << "Printing debug stacks:" << std::endl;
+               dstream << "Printing debug stacks:" << std::endl;
                dstream << "-----------------------------------------"
                        << std::endl;
                debug_stacks_print();
        }
+
+       if (!input->isKeyDown(getKeySetting("keymap_jump")) && *reset_jump_timer) {
+               *reset_jump_timer = false;
+               *jump_timer = 0.0;
+       }
+
+       //tt.stop();
+
+       if (quicktune->hasMessage()) {
+               std::string msg = quicktune->getMessage();
+               statustext = utf8_to_wide(msg);
+               *statustext_time = 0;
+       }
 }
 
 
-void MinetestApp::processItemSelection(u16 *new_playeritem)
+void Game::processItemSelection(u16 *new_playeritem)
 {
        LocalPlayer *player = client->getEnv().getLocalPlayer();
 
@@ -2428,7 +2741,7 @@ void MinetestApp::processItemSelection(u16 *new_playeritem)
 }
 
 
-void MinetestApp::dropSelectedItem()
+void Game::dropSelectedItem()
 {
        IDropAction *a = new IDropAction();
        a->count = 0;
@@ -2439,8 +2752,17 @@ void MinetestApp::dropSelectedItem()
 }
 
 
-void MinetestApp::openInventory()
+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);
@@ -2455,7 +2777,7 @@ void MinetestApp::openInventory()
 }
 
 
-void MinetestApp::openConsole()
+void Game::openConsole()
 {
        if (!gui_chat_console->isOpenInhibited()) {
                // Open up to over half of the screen
@@ -2465,7 +2787,7 @@ void MinetestApp::openConsole()
 }
 
 
-void MinetestApp::toggleFreeMove(float *statustext_time)
+void Game::toggleFreeMove(float *statustext_time)
 {
        static const wchar_t *msg[] = { L"free_move disabled", L"free_move enabled" };
 
@@ -2479,16 +2801,14 @@ void MinetestApp::toggleFreeMove(float *statustext_time)
 }
 
 
-void MinetestApp::toggleFreeMoveAlt(float *statustext_time, float *jump_timer)
+void Game::toggleFreeMoveAlt(float *statustext_time, float *jump_timer)
 {
-       if (g_settings->getBool("doubletap_jump") && *jump_timer < 0.2f) {
+       if (m_cache_doubletap_jump && *jump_timer < 0.2f)
                toggleFreeMove(statustext_time);
-               *jump_timer = 0;
-       }
 }
 
 
-void MinetestApp::toggleFast(float *statustext_time)
+void Game::toggleFast(float *statustext_time)
 {
        static const wchar_t *msg[] = { L"fast_move disabled", L"fast_move enabled" };
        bool fast_move = !g_settings->getBool("fast_move");
@@ -2497,12 +2817,18 @@ void MinetestApp::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
 }
 
 
-void MinetestApp::toggleNoClip(float *statustext_time)
+void Game::toggleNoClip(float *statustext_time)
 {
        static const wchar_t *msg[] = { L"noclip disabled", L"noclip enabled" };
        bool noclip = !g_settings->getBool("noclip");
@@ -2515,8 +2841,18 @@ void MinetestApp::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));
 
-void MinetestApp::toggleChat(float *statustext_time, bool *flag)
+       *statustext_time = 0;
+       statustext = msg[cinematic];
+}
+
+
+void Game::toggleChat(float *statustext_time, bool *flag)
 {
        static const wchar_t *msg[] = { L"Chat hidden", L"Chat shown" };
 
@@ -2526,18 +2862,68 @@ void MinetestApp::toggleChat(float *statustext_time, bool *flag)
 }
 
 
-void MinetestApp::toggleHud(float *statustext_time, bool *flag)
+void Game::toggleHud(float *statustext_time, bool *flag)
 {
        static const wchar_t *msg[] = { L"HUD hidden", L"HUD shown" };
 
        *flag = !*flag;
        *statustext_time = 0;
        statustext = msg[*flag];
-       client->setHighlighted(client->getHighlighted(), *flag);
+       if (g_settings->getBool("enable_node_highlighting"))
+               client->setHighlighted(client->getHighlighted(), *flag);
 }
 
+void Game::toggleMinimap(float *statustext_time, bool *flag,
+       bool show_hud, bool shift_pressed)
+{
+       if (!show_hud || !g_settings->getBool("enable_minimap"))
+               return;
+
+       if (shift_pressed) {
+               mapper->toggleMinimapShape();
+               return;
+       }
+
+       u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
+
+       MinimapMode mode = MINIMAP_MODE_OFF;
+       if (hud_flags & HUD_FLAG_MINIMAP_VISIBLE) {
+               mode = mapper->getMinimapMode();
+               mode = (MinimapMode)((int)mode + 1);
+       }
+
+       *flag = true;
+       switch (mode) {
+               case MINIMAP_MODE_SURFACEx1:
+                       statustext = L"Minimap in surface mode, Zoom x1";
+                       break;
+               case MINIMAP_MODE_SURFACEx2:
+                       statustext = L"Minimap in surface mode, Zoom x2";
+                       break;
+               case MINIMAP_MODE_SURFACEx4:
+                       statustext = L"Minimap in surface mode, Zoom x4";
+                       break;
+               case MINIMAP_MODE_RADARx1:
+                       statustext = L"Minimap in radar mode, Zoom x1";
+                       break;
+               case MINIMAP_MODE_RADARx2:
+                       statustext = L"Minimap in radar mode, Zoom x2";
+                       break;
+               case MINIMAP_MODE_RADARx4:
+                       statustext = L"Minimap in radar mode, Zoom x4";
+                       break;
+               default:
+                       mode = MINIMAP_MODE_OFF;
+                       *flag = false;
+                       statustext = (hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ?
+                               L"Minimap hidden" : L"Minimap disabled by server";
+       }
 
-void MinetestApp::toggleFog(float *statustext_time, bool *flag)
+       *statustext_time = 0;
+       mapper->setMinimapMode(mode);
+}
+
+void Game::toggleFog(float *statustext_time, bool *flag)
 {
        static const wchar_t *msg[] = { L"Fog enabled", L"Fog disabled" };
 
@@ -2547,7 +2933,7 @@ void MinetestApp::toggleFog(float *statustext_time, bool *flag)
 }
 
 
-void MinetestApp::toggleDebug(float *statustext_time, bool *show_debug,
+void Game::toggleDebug(float *statustext_time, bool *show_debug,
                bool *show_profiler_graph)
 {
        // Initial / 3x toggle: Chat only
@@ -2569,7 +2955,7 @@ void MinetestApp::toggleDebug(float *statustext_time, bool *show_debug,
 }
 
 
-void MinetestApp::toggleUpdateCamera(float *statustext_time, bool *flag)
+void Game::toggleUpdateCamera(float *statustext_time, bool *flag)
 {
        static const wchar_t *msg[] = {
                L"Camera update enabled",
@@ -2582,14 +2968,14 @@ void MinetestApp::toggleUpdateCamera(float *statustext_time, bool *flag)
 }
 
 
-void MinetestApp::toggleProfiler(float *statustext_time, u32 *profiler_current_page,
+void Game::toggleProfiler(float *statustext_time, u32 *profiler_current_page,
                u32 profiler_max_page)
 {
        *profiler_current_page = (*profiler_current_page + 1) % (profiler_max_page + 1);
 
        // FIXME: This updates the profiler with incomplete values
-       update_profiler_gui(guitext_profiler, font, text_height,
-                           *profiler_current_page, profiler_max_page);
+       update_profiler_gui(guitext_profiler, g_fontengine, *profiler_current_page,
+                       profiler_max_page, driver->getScreenSize().Height);
 
        if (*profiler_current_page != 0) {
                std::wstringstream sstr;
@@ -2603,18 +2989,18 @@ void MinetestApp::toggleProfiler(float *statustext_time, u32 *profiler_current_p
 }
 
 
-void MinetestApp::increaseViewRange(float *statustext_time)
+void Game::increaseViewRange(float *statustext_time)
 {
        s16 range = g_settings->getS16("viewing_range_nodes_min");
        s16 range_new = range + 10;
        g_settings->set("viewing_range_nodes_min", itos(range_new));
-       statustext = narrow_to_wide("Minimum viewing range changed to "
+       statustext = utf8_to_wide("Minimum viewing range changed to "
                        + itos(range_new));
        *statustext_time = 0;
 }
 
 
-void MinetestApp::decreaseViewRange(float *statustext_time)
+void Game::decreaseViewRange(float *statustext_time)
 {
        s16 range = g_settings->getS16("viewing_range_nodes_min");
        s16 range_new = range - 10;
@@ -2623,13 +3009,13 @@ void MinetestApp::decreaseViewRange(float *statustext_time)
                range_new = range;
 
        g_settings->set("viewing_range_nodes_min", itos(range_new));
-       statustext = narrow_to_wide("Minimum viewing range changed to "
+       statustext = utf8_to_wide("Minimum viewing range changed to "
                        + itos(range_new));
        *statustext_time = 0;
 }
 
 
-void MinetestApp::toggleFullViewRange(float *statustext_time)
+void Game::toggleFullViewRange(float *statustext_time)
 {
        static const wchar_t *msg[] = {
                L"Disabled full viewing range",
@@ -2643,14 +3029,27 @@ void MinetestApp::toggleFullViewRange(float *statustext_time)
 }
 
 
-void MinetestApp::updateCameraDirection(CameraOrientation *cam,
+void Game::updateCameraDirection(CameraOrientation *cam,
                VolatileRunFlags *flags)
 {
-       // float turn_amount = 0;       // Deprecated?
+       if ((device->isWindowActive() && noMenuActive()) || random_input) {
 
-       if (!(device->isWindowActive() && noMenuActive()) || random_input) {
+#ifndef __ANDROID__
+               if (!random_input) {
+                       // Mac OSX gets upset if this is set every frame
+                       if (device->getCursorControl()->isVisible())
+                               device->getCursorControl()->setVisible(false);
+               }
+#endif
 
-       // FIXME: Clean this up
+               if (flags->first_loop_after_window_activation)
+                       flags->first_loop_after_window_activation = false;
+               else
+                       updateCameraOrientation(cam, *flags);
+
+               input->setMousePos((driver->getScreenSize().Width / 2),
+                               (driver->getScreenSize().Height / 2));
+       } else {
 
 #ifndef ANDROID
                // Mac OSX gets upset if this is set every frame
@@ -2658,99 +3057,92 @@ void MinetestApp::updateCameraDirection(CameraOrientation *cam,
                        device->getCursorControl()->setVisible(true);
 #endif
 
-               //infostream<<"window inactive"<<std::endl;
-               flags->first_loop_after_window_activation = true;
-               return;
-       }
+               if (!flags->first_loop_after_window_activation)
+                       flags->first_loop_after_window_activation = true;
 
-#ifndef __ANDROID__
-       if (!random_input) {
-               // Mac OSX gets upset if this is set every frame
-               if (device->getCursorControl()->isVisible())
-                       device->getCursorControl()->setVisible(false);
        }
-#endif
+}
 
-       if (flags->first_loop_after_window_activation) {
-               //infostream<<"window active, first loop"<<std::endl;
-               flags->first_loop_after_window_activation = false;
-       } else {
 
+void Game::updateCameraOrientation(CameraOrientation *cam,
+               const VolatileRunFlags &flags)
+{
 #ifdef HAVE_TOUCHSCREENGUI
-
-               if (g_touchscreengui) {
-                       camera_yaw   = g_touchscreengui->getYaw();
-                       camera_pitch = g_touchscreengui->getPitch();
-               } else {
+       if (g_touchscreengui) {
+               cam->camera_yaw   = g_touchscreengui->getYaw();
+               cam->camera_pitch = g_touchscreengui->getPitch();
+       } else {
 #endif
-                       s32 dx = input->getMousePos().X - (driver->getScreenSize().Width / 2);
-                       s32 dy = input->getMousePos().Y - (driver->getScreenSize().Height / 2);
+               s32 dx = input->getMousePos().X - (driver->getScreenSize().Width / 2);
+               s32 dy = input->getMousePos().Y - (driver->getScreenSize().Height / 2);
 
-                       if (flags->invert_mouse
-                                       || (camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT)) {
-                               dy = -dy;
-                       }
-
-                       //infostream<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
+               if (flags.invert_mouse
+                               || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
+                       dy = -dy;
+               }
 
-                       float d = g_settings->getFloat("mouse_sensitivity");
-                       d = rangelim(d, 0.01, 100.0);
-                       cam->camera_yaw -= dx * d;
-                       cam->camera_pitch += dy * d;
-                       // turn_amount = v2f(dx, dy).getLength() * d; // deprecated?
+               cam->camera_yaw   -= dx * m_cache_mouse_sensitivity;
+               cam->camera_pitch += dy * m_cache_mouse_sensitivity;
 
 #ifdef HAVE_TOUCHSCREENGUI
-                       }
-#endif
-
-               if (cam->camera_pitch < -89.5)
-                       cam->camera_pitch = -89.5;
-               else if (cam->camera_pitch > 89.5)
-                       cam->camera_pitch = 89.5;
        }
+#endif
 
-       input->setMousePos(driver->getScreenSize().Width / 2,
-                       driver->getScreenSize().Height / 2);
-
-       // Deprecated? Not used anywhere else
-       // recent_turn_speed = recent_turn_speed * 0.9 + turn_amount * 0.1;
-       // std::cerr<<"recent_turn_speed = "<<recent_turn_speed<<std::endl;
+       cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
 }
 
 
-void MinetestApp::updatePlayerControl(const CameraOrientation &cam)
+void Game::updatePlayerControl(const CameraOrientation &cam)
 {
+       //TimeTaker tt("update player control", NULL, PRECISION_NANO);
+
        PlayerControl control(
-               input->isKeyDown(getKeySetting("keymap_forward")),
-               input->isKeyDown(getKeySetting("keymap_backward")),
-               input->isKeyDown(getKeySetting("keymap_left")),
-               input->isKeyDown(getKeySetting("keymap_right")),
-               input->isKeyDown(getKeySetting("keymap_jump")),
-               input->isKeyDown(getKeySetting("keymap_special1")),
-               input->isKeyDown(getKeySetting("keymap_sneak")),
+               input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_FORWARD]),
+               input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_BACKWARD]),
+               input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_LEFT]),
+               input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_RIGHT]),
+               input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_JUMP]),
+               input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SPECIAL1]),
+               input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SNEAK]),
                input->getLeftState(),
                input->getRightState(),
                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(getKeySetting("keymap_forward"))  & 0x1) << 0) |
-                       ( (u32)(input->isKeyDown(getKeySetting("keymap_backward")) & 0x1) << 1) |
-                       ( (u32)(input->isKeyDown(getKeySetting("keymap_left"))     & 0x1) << 2) |
-                       ( (u32)(input->isKeyDown(getKeySetting("keymap_right"))    & 0x1) << 3) |
-                       ( (u32)(input->isKeyDown(getKeySetting("keymap_jump"))     & 0x1) << 4) |
-                       ( (u32)(input->isKeyDown(getKeySetting("keymap_special1")) & 0x1) << 5) |
-                       ( (u32)(input->isKeyDown(getKeySetting("keymap_sneak"))    & 0x1) << 6) |
-                       ( (u32)(input->getLeftState()                              & 0x1) << 7) |
-                       ( (u32)(input->getRightState()                             & 0x1) << 8
-       );
+       player->keyPressed = keypress_bits;
 
+       //tt.stop();
 }
 
 
-inline void MinetestApp::step(f32 *dtime)
+inline void Game::step(f32 *dtime)
 {
        bool can_be_and_is_paused =
                        (simple_singleplayer_mode && g_menumgr.pausesGame());
@@ -2769,7 +3161,7 @@ inline void MinetestApp::step(f32 *dtime)
 }
 
 
-void MinetestApp::processClientEvents(CameraOrientation *cam, float *damage_flash)
+void Game::processClientEvents(CameraOrientation *cam, float *damage_flash)
 {
        ClientEvent event = client->getClientEvent();
 
@@ -2816,44 +3208,11 @@ void MinetestApp::processClientEvents(CameraOrientation *cam, float *damage_flas
 
                        delete(event.show_formspec.formspec);
                        delete(event.show_formspec.formname);
-               } else if (event.type == CE_SPAWN_PARTICLE) {
-                       video::ITexture *texture =
-                               gamedef->tsrc()->getTexture(*(event.spawn_particle.texture));
-
-                       new Particle(gamedef, smgr, player, client->getEnv(),
-                                       *event.spawn_particle.pos,
-                                       *event.spawn_particle.vel,
-                                       *event.spawn_particle.acc,
-                                       event.spawn_particle.expirationtime,
-                                       event.spawn_particle.size,
-                                       event.spawn_particle.collisiondetection,
-                                       event.spawn_particle.vertical,
-                                       texture,
-                                       v2f(0.0, 0.0),
-                                       v2f(1.0, 1.0));
-               } else if (event.type == CE_ADD_PARTICLESPAWNER) {
-                       video::ITexture *texture =
-                               gamedef->tsrc()->getTexture(*(event.add_particlespawner.texture));
-
-                       new ParticleSpawner(gamedef, smgr, player,
-                                       event.add_particlespawner.amount,
-                                       event.add_particlespawner.spawntime,
-                                       *event.add_particlespawner.minpos,
-                                       *event.add_particlespawner.maxpos,
-                                       *event.add_particlespawner.minvel,
-                                       *event.add_particlespawner.maxvel,
-                                       *event.add_particlespawner.minacc,
-                                       *event.add_particlespawner.maxacc,
-                                       event.add_particlespawner.minexptime,
-                                       event.add_particlespawner.maxexptime,
-                                       event.add_particlespawner.minsize,
-                                       event.add_particlespawner.maxsize,
-                                       event.add_particlespawner.collisiondetection,
-                                       event.add_particlespawner.vertical,
-                                       texture,
-                                       event.add_particlespawner.id);
-               } else if (event.type == CE_DELETE_PARTICLESPAWNER) {
-                       delete_particlespawner(event.delete_particlespawner.id);
+               } else if ((event.type == CE_SPAWN_PARTICLE) ||
+                               (event.type == CE_ADD_PARTICLESPAWNER) ||
+                               (event.type == CE_DELETE_PARTICLESPAWNER)) {
+                       client->getParticleManager()->handleParticleEvent(&event, gamedef,
+                                       smgr, player);
                } else if (event.type == CE_HUDADD) {
                        u32 id = event.hudadd.id;
 
@@ -2888,7 +3247,7 @@ void MinetestApp::processClientEvents(CameraOrientation *cam, float *damage_flas
 
                        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;
@@ -2980,12 +3339,12 @@ void MinetestApp::processClientEvents(CameraOrientation *cam, float *damage_flas
                                        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 {
@@ -3008,7 +3367,7 @@ void MinetestApp::processClientEvents(CameraOrientation *cam, float *damage_flas
 }
 
 
-void MinetestApp::updateCamera(VolatileRunFlags *flags, u32 busy_time,
+void Game::updateCamera(VolatileRunFlags *flags, u32 busy_time,
                f32 dtime, float time_from_last_punch)
 {
        LocalPlayer *player = client->getEnv().getLocalPlayer();
@@ -3032,13 +3391,17 @@ void MinetestApp::updateCamera(VolatileRunFlags *flags, u32 busy_time,
 
        v3s16 old_camera_offset = camera->getOffset();
 
-       if (input->wasKeyDown(getKeySetting("keymap_camera_mode"))) {
-               camera->toggleCameraMode();
+       if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CAMERA_MODE])) {
                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);
+               playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
        }
 
        float full_punch_interval = playeritem_toolcap.full_punch_interval;
@@ -3071,7 +3434,7 @@ void MinetestApp::updateCamera(VolatileRunFlags *flags, u32 busy_time,
 }
 
 
-void MinetestApp::updateSound(f32 dtime)
+void Game::updateSound(f32 dtime)
 {
        // Update sound listener
        v3s16 camera_offset = camera->getOffset();
@@ -3093,8 +3456,8 @@ void MinetestApp::updateSound(f32 dtime)
 }
 
 
-void MinetestApp::processPlayerInteraction(std::vector<aabb3f> &highlight_boxes,
-               InteractParams *interactArgs, f32 dtime, bool show_hud, bool show_debug)
+void Game::processPlayerInteraction(std::vector<aabb3f> &highlight_boxes,
+               GameRunData *runData, f32 dtime, bool show_hud, bool show_debug)
 {
        LocalPlayer *player = client->getEnv().getLocalPlayer();
 
@@ -3155,16 +3518,16 @@ void MinetestApp::processPlayerInteraction(std::vector<aabb3f> &highlight_boxes,
                        client, player_position, camera_direction,
                        camera_position, shootline, d,
                        playeritem_def.liquids_pointable,
-                       !interactArgs->ldown_for_dig,
+                       !runData->ldown_for_dig,
                        camera_offset,
                        // output
                        highlight_boxes,
-                       interactArgs->selected_object);
+                       runData->selected_object);
 
-       if (pointed != interactArgs->pointed_old) {
+       if (pointed != runData->pointed_old) {
                infostream << "Pointing at " << pointed.dump() << std::endl;
 
-               if (g_settings->getBool("enable_node_highlighting")) {
+               if (m_cache_enable_node_highlighting) {
                        if (pointed.type == POINTEDTHING_NODE) {
                                client->setHighlighted(pointed.node_undersurface, show_hud);
                        } else {
@@ -3178,44 +3541,44 @@ void MinetestApp::processPlayerInteraction(std::vector<aabb3f> &highlight_boxes,
                - releasing left mouse button
                - pointing away from node
        */
-       if (interactArgs->digging) {
+       if (runData->digging) {
                if (input->getLeftReleased()) {
                        infostream << "Left button released"
                                   << " (stopped digging)" << std::endl;
-                       interactArgs->digging = false;
-               } else if (pointed != interactArgs->pointed_old) {
+                       runData->digging = false;
+               } else if (pointed != runData->pointed_old) {
                        if (pointed.type == POINTEDTHING_NODE
-                                       && interactArgs->pointed_old.type == POINTEDTHING_NODE
+                                       && runData->pointed_old.type == POINTEDTHING_NODE
                                        && pointed.node_undersurface
-                                                       == interactArgs->pointed_old.node_undersurface) {
+                                                       == runData->pointed_old.node_undersurface) {
                                // Still pointing to the same node, but a different face.
                                // Don't reset.
                        } else {
                                infostream << "Pointing away from node"
                                           << " (stopped digging)" << std::endl;
-                               interactArgs->digging = false;
+                               runData->digging = false;
                        }
                }
 
-               if (!interactArgs->digging) {
-                       client->interact(1, interactArgs->pointed_old);
+               if (!runData->digging) {
+                       client->interact(1, runData->pointed_old);
                        client->setCrack(-1, v3s16(0, 0, 0));
-                       interactArgs->dig_time = 0.0;
+                       runData->dig_time = 0.0;
                }
        }
 
-       if (!interactArgs->digging && interactArgs->ldown_for_dig && !input->getLeftState()) {
-               interactArgs->ldown_for_dig = false;
+       if (!runData->digging && runData->ldown_for_dig && !input->getLeftState()) {
+               runData->ldown_for_dig = false;
        }
 
-       interactArgs->left_punch = false;
+       runData->left_punch = false;
 
        soundmaker->m_player_leftpunch_sound.name = "";
 
        if (input->getRightState())
-               interactArgs->repeat_rightclick_timer += dtime;
+               runData->repeat_rightclick_timer += dtime;
        else
-               interactArgs->repeat_rightclick_timer = 0;
+               runData->repeat_rightclick_timer = 0;
 
        if (playeritem_def.usable && input->getLeftState()) {
                if (input->getLeftClicked())
@@ -3223,19 +3586,19 @@ void MinetestApp::processPlayerInteraction(std::vector<aabb3f> &highlight_boxes,
        } else if (pointed.type == POINTEDTHING_NODE) {
                ToolCapabilities playeritem_toolcap =
                                playeritem.getToolCapabilities(itemdef_manager);
-               handlePointingAtNode(interactArgs, pointed, playeritem_def,
+               handlePointingAtNode(runData, pointed, playeritem_def,
                                playeritem_toolcap, dtime);
        } else if (pointed.type == POINTEDTHING_OBJECT) {
-               handlePointingAtObject(interactArgs, pointed, playeritem,
+               handlePointingAtObject(runData, pointed, playeritem,
                                player_position, show_debug);
        } else if (input->getLeftState()) {
                // When button is held down in air, show continuous animation
-               interactArgs->left_punch = true;
+               runData->left_punch = true;
        }
 
-       interactArgs->pointed_old = pointed;
+       runData->pointed_old = pointed;
 
-       if (interactArgs->left_punch || input->getLeftClicked())
+       if (runData->left_punch || input->getLeftClicked())
                camera->setDigging(0); // left click animation
 
        input->resetLeftClicked();
@@ -3246,7 +3609,7 @@ void MinetestApp::processPlayerInteraction(std::vector<aabb3f> &highlight_boxes,
 }
 
 
-void MinetestApp::handlePointingAtNode(InteractParams *interactArgs,
+void Game::handlePointingAtNode(GameRunData *runData,
                const PointedThing &pointed, const ItemDefinition &playeritem_def,
                const ToolCapabilities &playeritem_toolcap, f32 dtime)
 {
@@ -3261,26 +3624,25 @@ void MinetestApp::handlePointingAtNode(InteractParams *interactArgs,
        NodeMetadata *meta = map.getNodeMetadata(nodepos);
 
        if (meta) {
-               infotext = narrow_to_wide(meta->getString("infotext"));
+               infotext = utf8_to_wide(meta->getString("infotext"));
        } else {
-               MapNode n = map.getNode(nodepos);
+               MapNode n = map.getNodeNoEx(nodepos);
 
                if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
                        infotext = L"Unknown node: ";
-                       infotext += narrow_to_wide(nodedef_manager->get(n).name);
+                       infotext += utf8_to_wide(nodedef_manager->get(n).name);
                }
        }
 
-       if (interactArgs->nodig_delay_timer <= 0.0 && input->getLeftState()
+       if (runData->nodig_delay_timer <= 0.0 && input->getLeftState()
                        && client->checkPrivilege("interact")) {
-               handleDigging(interactArgs, pointed, nodepos, playeritem_toolcap, dtime);
+               handleDigging(runData, pointed, nodepos, playeritem_toolcap, dtime);
        }
 
        if ((input->getRightClicked() ||
-                       interactArgs->repeat_rightclick_timer >=
-                       g_settings->getFloat("repeat_rightclick_time")) &&
+                       runData->repeat_rightclick_timer >= m_repeat_right_click_time) &&
                        client->checkPrivilege("interact")) {
-               interactArgs->repeat_rightclick_timer = 0;
+               runData->repeat_rightclick_timer = 0;
                infostream << "Ground right-clicked" << std::endl;
 
                if (meta && meta->getString("formspec") != "" && !random_input
@@ -3318,36 +3680,40 @@ void MinetestApp::handlePointingAtNode(InteractParams *interactArgs,
                        } else {
                                soundmaker->m_player_rightpunch_sound =
                                                SimpleSoundSpec();
-                       }
 
-                       if (playeritem_def.node_placement_prediction == "" ||
-                                       nodedef_manager->get(map.getNode(nodepos)).rightclickable)
-                               client->interact(3, pointed); // Report to server
+                               if (playeritem_def.node_placement_prediction == "" ||
+                                               nodedef_manager->get(map.getNodeNoEx(nodepos)).rightclickable) {
+                                       client->interact(3, pointed); // Report to server
+                               } else {
+                                       soundmaker->m_player_rightpunch_sound =
+                                               playeritem_def.sound_place_failed;
+                               }
+                       }
                }
        }
 }
 
 
-void MinetestApp::handlePointingAtObject(InteractParams *interactArgs,
+void Game::handlePointingAtObject(GameRunData *runData,
                const PointedThing &pointed,
                const ItemStack &playeritem,
                const v3f &player_position,
                bool show_debug)
 {
-       infotext = narrow_to_wide(interactArgs->selected_object->infoText());
+       infotext = utf8_to_wide(runData->selected_object->infoText());
 
        if (infotext == L"" && show_debug) {
-               infotext = narrow_to_wide(interactArgs->selected_object->debugInfoText());
+               infotext = utf8_to_wide(runData->selected_object->debugInfoText());
        }
 
        if (input->getLeftState()) {
                bool do_punch = false;
                bool do_punch_damage = false;
 
-               if (interactArgs->object_hit_delay_timer <= 0.0) {
+               if (runData->object_hit_delay_timer <= 0.0) {
                        do_punch = true;
                        do_punch_damage = true;
-                       interactArgs->object_hit_delay_timer = object_hit_delay;
+                       runData->object_hit_delay_timer = object_hit_delay;
                }
 
                if (input->getLeftClicked())
@@ -3355,17 +3721,17 @@ void MinetestApp::handlePointingAtObject(InteractParams *interactArgs,
 
                if (do_punch) {
                        infostream << "Left-clicked object" << std::endl;
-                       interactArgs->left_punch = true;
+                       runData->left_punch = true;
                }
 
                if (do_punch_damage) {
                        // Report direct punch
-                       v3f objpos = interactArgs->selected_object->getPosition();
+                       v3f objpos = runData->selected_object->getPosition();
                        v3f dir = (objpos - player_position).normalize();
 
-                       bool disable_send = interactArgs->selected_object->directReportPunch(
-                                       dir, &playeritem, interactArgs->time_from_last_punch);
-                       interactArgs->time_from_last_punch = 0;
+                       bool disable_send = runData->selected_object->directReportPunch(
+                                       dir, &playeritem, runData->time_from_last_punch);
+                       runData->time_from_last_punch = 0;
 
                        if (!disable_send)
                                client->interact(0, pointed);
@@ -3377,20 +3743,20 @@ void MinetestApp::handlePointingAtObject(InteractParams *interactArgs,
 }
 
 
-void MinetestApp::handleDigging(InteractParams *interactArgs,
+void Game::handleDigging(GameRunData *runData,
                const PointedThing &pointed, const v3s16 &nodepos,
                const ToolCapabilities &playeritem_toolcap, f32 dtime)
 {
-       if (!interactArgs->digging) {
+       if (!runData->digging) {
                infostream << "Started digging" << std::endl;
                client->interact(0, pointed);
-               interactArgs->digging = true;
-               interactArgs->ldown_for_dig = true;
+               runData->digging = true;
+               runData->ldown_for_dig = true;
        }
 
        LocalPlayer *player = client->getEnv().getLocalPlayer();
        ClientMap &map = client->getEnv().getClientMap();
-       MapNode n = client->getEnv().getClientMap().getNode(nodepos);
+       MapNode n = client->getEnv().getClientMap().getNodeNoEx(nodepos);
 
        // NOTE: Similar piece of code exists on the server side for
        // cheat detection.
@@ -3409,25 +3775,25 @@ void MinetestApp::handleDigging(InteractParams *interactArgs,
 
        if (params.diggable == false) {
                // I guess nobody will wait for this long
-               interactArgs->dig_time_complete = 10000000.0;
+               runData->dig_time_complete = 10000000.0;
        } else {
-               interactArgs->dig_time_complete = params.time;
+               runData->dig_time_complete = params.time;
 
-               if (g_settings->getBool("enable_particles")) {
+               if (m_cache_enable_particles) {
                        const ContentFeatures &features =
                                        client->getNodeDefManager()->get(n);
-                       addPunchingParticles(gamedef, smgr, player,
-                                       client->getEnv(), nodepos, features.tiles);
+                       client->getParticleManager()->addPunchingParticles(gamedef, smgr,
+                                       player, nodepos, features.tiles);
                }
        }
 
-       if (interactArgs->dig_time_complete >= 0.001) {
-               interactArgs->dig_index = (float)crack_animation_length
-                               * interactArgs->dig_time
-                               / interactArgs->dig_time_complete;
+       if (runData->dig_time_complete >= 0.001) {
+               runData->dig_index = (float)crack_animation_length
+                               * runData->dig_time
+                               / runData->dig_time_complete;
        } else {
                // This is for torches
-               interactArgs->dig_index = crack_animation_length;
+               runData->dig_index = crack_animation_length;
        }
 
        SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
@@ -3446,53 +3812,54 @@ void MinetestApp::handleDigging(InteractParams *interactArgs,
        }
 
        // Don't show cracks if not diggable
-       if (interactArgs->dig_time_complete >= 100000.0) {
-       } else if (interactArgs->dig_index < crack_animation_length) {
+       if (runData->dig_time_complete >= 100000.0) {
+       } else if (runData->dig_index < crack_animation_length) {
                //TimeTaker timer("client.setTempMod");
                //infostream<<"dig_index="<<dig_index<<std::endl;
-               client->setCrack(interactArgs->dig_index, nodepos);
+               client->setCrack(runData->dig_index, nodepos);
        } else {
                infostream << "Digging completed" << std::endl;
                client->interact(2, pointed);
                client->setCrack(-1, v3s16(0, 0, 0));
-               MapNode wasnode = map.getNode(nodepos);
-               client->removeNode(nodepos);
+               bool is_valid_position;
+               MapNode wasnode = map.getNodeNoEx(nodepos, &is_valid_position);
+               if (is_valid_position)
+                       client->removeNode(nodepos);
 
-               if (g_settings->getBool("enable_particles")) {
+               if (m_cache_enable_particles) {
                        const ContentFeatures &features =
                                client->getNodeDefManager()->get(wasnode);
-                       addDiggingParticles
-                       (gamedef, smgr, player, client->getEnv(),
-                        nodepos, features.tiles);
+                       client->getParticleManager()->addDiggingParticles(gamedef, smgr,
+                                       player, nodepos, features.tiles);
                }
 
-               interactArgs->dig_time = 0;
-               interactArgs->digging = false;
+               runData->dig_time = 0;
+               runData->digging = false;
 
-               interactArgs->nodig_delay_timer =
-                               interactArgs->dig_time_complete / (float)crack_animation_length;
+               runData->nodig_delay_timer =
+                               runData->dig_time_complete / (float)crack_animation_length;
 
                // We don't want a corresponding delay to
                // very time consuming nodes
-               if (interactArgs->nodig_delay_timer > 0.3)
-                       interactArgs->nodig_delay_timer = 0.3;
+               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 (interactArgs->nodig_delay_timer < mindelay)
-                       interactArgs->nodig_delay_timer = mindelay;
+               if (runData->nodig_delay_timer < mindelay)
+                       runData->nodig_delay_timer = mindelay;
 
                // Send event to trigger sound
                MtEvent *e = new NodeDugEvent(nodepos, wasnode);
                gamedef->event()->put(e);
        }
 
-       if (interactArgs->dig_time_complete < 100000.0) {
-               interactArgs->dig_time += dtime;
+       if (runData->dig_time_complete < 100000.0) {
+               runData->dig_time += dtime;
        } else {
-               interactArgs->dig_time = 0;
+               runData->dig_time = 0;
                client->setCrack(-1, nodepos);
        }
 
@@ -3500,8 +3867,8 @@ void MinetestApp::handleDigging(InteractParams *interactArgs,
 }
 
 
-void MinetestApp::updateFrame(std::vector<aabb3f> &highlight_boxes,
-               ProfilerGraph *graph, RunStats *stats, InteractParams *interactArgs,
+void Game::updateFrame(std::vector<aabb3f> &highlight_boxes,
+               ProfilerGraph *graph, RunStats *stats, GameRunData *runData,
                f32 dtime, const VolatileRunFlags &flags, const CameraOrientation &cam)
 {
        LocalPlayer *player = client->getEnv().getLocalPlayer();
@@ -3511,14 +3878,14 @@ void MinetestApp::updateFrame(std::vector<aabb3f> &highlight_boxes,
        */
 
        if (draw_control->range_all) {
-               interactArgs->fog_range = 100000 * BS;
+               runData->fog_range = 100000 * BS;
        } else {
-               interactArgs->fog_range = draw_control->wanted_range * BS
+               runData->fog_range = draw_control->wanted_range * BS
                                + 0.0 * MAP_BLOCKSIZE * BS;
-               interactArgs->fog_range = MYMIN(
-                               interactArgs->fog_range,
+               runData->fog_range = MYMIN(
+                               runData->fog_range,
                                (draw_control->farthest_drawn + 20) * BS);
-               interactArgs->fog_range *= 0.9;
+               runData->fog_range *= 0.9;
        }
 
        /*
@@ -3526,8 +3893,8 @@ void MinetestApp::updateFrame(std::vector<aabb3f> &highlight_boxes,
        */
        u32 daynight_ratio = client->getEnv().getDayNightRatio();
        float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
-       float direct_brightness = 0;
-       bool sunlight_seen = false;
+       float direct_brightness;
+       bool sunlight_seen;
 
        if (g_settings->getBool("free_move")) {
                direct_brightness = time_brightness;
@@ -3536,25 +3903,24 @@ void MinetestApp::updateFrame(std::vector<aabb3f> &highlight_boxes,
                ScopeProfiler sp(g_profiler, "Detecting background light", SPT_AVG);
                float old_brightness = sky->getBrightness();
                direct_brightness = client->getEnv().getClientMap()
-                               .getBackgroundBrightness(MYMIN(interactArgs->fog_range * 1.2, 60 * BS),
+                               .getBackgroundBrightness(MYMIN(runData->fog_range * 1.2, 60 * BS),
                                        daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
                                    / 255.0;
        }
 
-       float time_of_day = 0;
-       float time_of_day_smooth = 0;
+       float time_of_day = runData->time_of_day;
+       float time_of_day_smooth = runData->time_of_day_smooth;
 
        time_of_day = client->getEnv().getTimeOfDayF();
 
        const float maxsm = 0.05;
+       const float todsm = 0.05;
 
        if (fabs(time_of_day - time_of_day_smooth) > maxsm &&
                        fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
                        fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
                time_of_day_smooth = time_of_day;
 
-       const float todsm = 0.05;
-
        if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
                time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
                                + (time_of_day + 1.0) * todsm;
@@ -3562,6 +3928,9 @@ void MinetestApp::updateFrame(std::vector<aabb3f> &highlight_boxes,
                time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
                                + time_of_day * todsm;
 
+       runData->time_of_day = time_of_day;
+       runData->time_of_day_smooth = time_of_day_smooth;
+
        sky->update(time_of_day_smooth, time_brightness, direct_brightness,
                        sunlight_seen, camera->getCameraMode(), player->getYaw(),
                        player->getPitch());
@@ -3584,20 +3953,18 @@ void MinetestApp::updateFrame(std::vector<aabb3f> &highlight_boxes,
        /*
                Update particles
        */
-
-       allparticles_step(dtime);
-       allparticlespawners_step(dtime, client->getEnv());
+       client->getParticleManager()->step(dtime);
 
        /*
                Fog
        */
 
-       if (g_settings->getBool("enable_fog") && !flags.force_fog_off) {
+       if (m_cache_enable_fog && !flags.force_fog_off) {
                driver->setFog(
                                sky->getBgColor(),
                                video::EFT_FOG_LINEAR,
-                               interactArgs->fog_range * 0.4,
-                               interactArgs->fog_range * 1.0,
+                               runData->fog_range * 0.4,
+                               runData->fog_range * 1.0,
                                0.01,
                                false, // pixel fog
                                false // range fog
@@ -3621,54 +3988,50 @@ void MinetestApp::updateFrame(std::vector<aabb3f> &highlight_boxes,
        v2u32 screensize = driver->getScreenSize();
 
        updateChat(*client, dtime, flags.show_debug, screensize,
-                       flags.show_chat, interactArgs->profiler_current_page,
-                       *chat_backend, guitext_chat, font);
+                       flags.show_chat, runData->profiler_current_page,
+                       *chat_backend, guitext_chat);
 
        /*
                Inventory
        */
 
-       bool update_wielded_item_trigger = true;
-
-       if (client->getPlayerItem() != interactArgs->new_playeritem) {
-               client->selectPlayerItem(interactArgs->new_playeritem);
-       }
+       if (client->getPlayerItem() != runData->new_playeritem)
+               client->selectPlayerItem(runData->new_playeritem);
 
+       // Update local inventory if it has changed
        if (client->getLocalInventoryUpdated()) {
                //infostream<<"Updating local inventory"<<std::endl;
                client->getLocalInventory(*local_inventory);
-
-               update_wielded_item_trigger = true;
+               runData->update_wielded_item_trigger = true;
        }
 
-       if (update_wielded_item_trigger) {
-               update_wielded_item_trigger = false;
+       if (runData->update_wielded_item_trigger) {
                // Update wielded tool
                InventoryList *mlist = local_inventory->getList("main");
-               ItemStack item;
-
-               if (mlist  && (client->getPlayerItem() < mlist->getSize()))
-                       item = mlist->getItem(client->getPlayerItem());
 
-               camera->wield(item, client->getPlayerItem());
+               if (mlist && (client->getPlayerItem() < mlist->getSize())) {
+                       ItemStack item = mlist->getItem(client->getPlayerItem());
+                       camera->wield(item);
+               }
+               runData->update_wielded_item_trigger = false;
        }
 
        /*
                Update block draw list every 200ms or when camera direction has
                changed much
        */
-       interactArgs->update_draw_list_timer += dtime;
+       runData->update_draw_list_timer += dtime;
 
        v3f camera_direction = camera->getDirection();
-       if (interactArgs->update_draw_list_timer >= 0.2
-                       || interactArgs->update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
+       if (runData->update_draw_list_timer >= 0.2
+                       || runData->update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
                        || flags.camera_offset_changed) {
-               interactArgs->update_draw_list_timer = 0;
+               runData->update_draw_list_timer = 0;
                client->getEnv().getClientMap().updateDrawList(driver);
-               interactArgs->update_draw_list_last_cam_dir = camera_direction;
+               runData->update_draw_list_last_cam_dir = camera_direction;
        }
 
-       updateGui(&interactArgs->statustext_time, *stats, dtime, flags, cam);
+       updateGui(&runData->statustext_time, *stats, *runData, dtime, flags, cam);
 
        /*
           make sure menu is on top
@@ -3697,20 +4060,21 @@ void MinetestApp::updateFrame(std::vector<aabb3f> &highlight_boxes,
                stats->beginscenetime = timer.stop(true);
        }
 
-       draw_scene(driver, smgr, *camera, *client, player, *hud, guienv,
-                       highlight_boxes, screensize, skycolor, flags.show_hud);
+       draw_scene(driver, smgr, *camera, *client, player, *hud, *mapper,
+                       guienv, highlight_boxes, screensize, skycolor, flags.show_hud,
+                       flags.show_minimap);
 
        /*
                Profiler graph
        */
        if (flags.show_profiler_graph)
-               graph->draw(10, screensize.Y - 10, driver, font);
+               graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
 
        /*
                Damage flash
        */
-       if (interactArgs->damage_flash > 0.0) {
-               video::SColor color(std::min(interactArgs->damage_flash, 180.0f),
+       if (runData->damage_flash > 0.0) {
+               video::SColor color(std::min(runData->damage_flash, 180.0f),
                                180,
                                0,
                                0);
@@ -3718,7 +4082,7 @@ void MinetestApp::updateFrame(std::vector<aabb3f> &highlight_boxes,
                                        core::rect<s32>(0, 0, screensize.X, screensize.Y),
                                        NULL);
 
-               interactArgs->damage_flash -= 100.0 * dtime;
+               runData->damage_flash -= 100.0 * dtime;
        }
 
        /*
@@ -3731,6 +4095,14 @@ void MinetestApp::updateFrame(std::vector<aabb3f> &highlight_boxes,
                        player->hurt_tilt_strength = 0;
        }
 
+       /*
+               Update minimap pos and rotation
+       */
+       if (flags.show_minimap && flags.show_hud) {
+               mapper->setPos(floatToInt(player->getPosition(), BS));
+               mapper->setAngle(player->getYaw());
+       }
+
        /*
                End scene
        */
@@ -3745,8 +4117,31 @@ void MinetestApp::updateFrame(std::vector<aabb3f> &highlight_boxes,
 }
 
 
-void MinetestApp::updateGui(float *statustext_time, const RunStats& stats,
-               f32 dtime, const VolatileRunFlags &flags, const CameraOrientation &cam)
+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)
 {
        v2u32 screensize = driver->getScreenSize();
        LocalPlayer *player = client->getEnv().getLocalPlayer();
@@ -3757,10 +4152,11 @@ void MinetestApp::updateGui(float *statustext_time, const RunStats& stats,
                drawtime_avg = drawtime_avg * 0.95 + stats.drawtime * 0.05;
 
                u16 fps = 1.0 / stats.dtime_jitter.avg;
+               //s32 fps = driver->getFPS();
 
                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)
@@ -3772,12 +4168,12 @@ void MinetestApp::updateGui(float *statustext_time, const RunStats& stats,
                   << ", v_range = " << draw_control->wanted_range
                   << std::setprecision(3)
                   << ", RTT = " << client->getRTT();
-               guitext->setText(narrow_to_wide(os.str()).c_str());
+               guitext->setText(utf8_to_wide(os.str()).c_str());
                guitext->setVisible(true);
        } else if (flags.show_hud || flags.show_chat) {
                std::ostringstream os(std::ios_base::binary);
-               os << "Minetest " << minetest_version_hash;
-               guitext->setText(narrow_to_wide(os.str()).c_str());
+               os << PROJECT_NAME_C " " << g_version_hash;
+               guitext->setText(utf8_to_wide(os.str()).c_str());
                guitext->setVisible(true);
        } else {
                guitext->setVisible(false);
@@ -3786,7 +4182,7 @@ void MinetestApp::updateGui(float *statustext_time, const RunStats& stats,
        if (guitext->isVisible()) {
                core::rect<s32> rect(
                                5,              5,
-                               screensize.X,   5 + text_height
+                               screensize.X,   5 + g_fontengine->getTextHeight()
                );
                guitext->setRelativePosition(rect);
        }
@@ -3798,14 +4194,28 @@ void MinetestApp::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())
                   << ")";
-               guitext2->setText(narrow_to_wide(os.str()).c_str());
+
+               if (runData.pointed_old.type == POINTEDTHING_NODE) {
+                       ClientMap &map = client->getEnv().getClientMap();
+                       const INodeDefManager *nodedef = client->getNodeDefManager();
+                       MapNode n = map.getNodeNoEx(runData.pointed_old.node_undersurface);
+                       if (n.getContent() != CONTENT_IGNORE && nodedef->get(n).name != "unknown") {
+                               const ContentFeatures &features = nodedef->get(n);
+                               os << " (pointing_at = " << nodedef->get(n).name
+                                  << " - " << features.tiledef[0].name.c_str()
+                                  << ")";
+                       }
+               }
+
+               guitext2->setText(utf8_to_wide(os.str()).c_str());
                guitext2->setVisible(true);
 
                core::rect<s32> rect(
-                               5,             5 + text_height,
-                               screensize.X,  5 + text_height * 2
+                               5,             5 + g_fontengine->getTextHeight(),
+                               screensize.X,  5 + g_fontengine->getTextHeight() * 2
                );
                guitext2->setRelativePosition(rect);
        } else {
@@ -3830,10 +4240,13 @@ void MinetestApp::updateGui(float *statustext_time, const RunStats& stats,
        guitext_status->setVisible(!statustext.empty());
 
        if (!statustext.empty()) {
-               s32 status_y = screensize.Y - 130;
+               s32 status_width  = guitext_status->getTextWidth();
+               s32 status_height = guitext_status->getTextHeight();
+               s32 status_y = screensize.Y - 150;
+               s32 status_x = (screensize.X - status_width) / 2;
                core::rect<s32> rect(
-                               10, status_y - guitext_status->getTextHeight(),
-                               10 + guitext_status->getTextWidth(), status_y
+                               status_x , status_y - status_height,
+                               status_x + status_width, status_y
                );
                guitext_status->setRelativePosition(rect);
 
@@ -3855,7 +4268,7 @@ void MinetestApp::updateGui(float *statustext_time, const RunStats& stats,
 
 
 /* Log times and stuff for visualization */
-inline void MinetestApp::updateProfilerGraphs(ProfilerGraph *graph)
+inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
 {
        Profiler::GraphValues values;
        g_profiler->graphGet(values);
@@ -3869,78 +4282,82 @@ inline void MinetestApp::updateProfilerGraphs(ProfilerGraph *graph)
  ****************************************************************************/
 
 /* On some computers framerate doesn't seem to be automatically limited
- *
- * *Must* be called after device->run() so that device->getTimer()->getTime();
- * is correct
  */
-inline void MinetestApp::limitFps(FpsControl *params, f32 *dtime)
+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;
 
-       u32 last_time = params->last_time;
-
-       if (time > last_time) // Make sure time hasn't overflowed
-               params->busy_time = time - last_time;
+       if (time > last_time)  // Make sure time hasn't overflowed
+               fps_timings->busy_time = time - last_time;
        else
-               params->busy_time = 0;
+               fps_timings->busy_time = 0;
 
        u32 frametime_min = 1000 / (g_menumgr.pausesGame()
                        ? g_settings->getFloat("pause_fps_max")
                        : g_settings->getFloat("fps_max"));
 
-       if (params->busy_time < frametime_min) {
-               params->sleep_time = frametime_min - params->busy_time;
-               device->sleep(params->sleep_time);
-               time += params->sleep_time;
+       if (fps_timings->busy_time < frametime_min) {
+               fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
+               device->sleep(fps_timings->sleep_time);
        } else {
-               params->sleep_time = 0;
+               fps_timings->sleep_time = 0;
        }
 
-       if (time > last_time) // Checking for overflow
-               *dtime = (time - last_time) / 1000.0;
-       else
-               *dtime = 0.03; // Choose 30fps as fallback in overflow case
-
-       params->last_time = time;
-
-#if 0
-
-       /* This is the old method for calculating new_time and dtime, and seems
-        * like overkill considering timings are messed up by expected variation
-        * in execution speed in other places anyway. (This has nothing to do with
-        * WINE... the new method above calculates dtime based on sleep_time)
+       /* 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.
         */
 
-       // Necessary for device->getTimer()->getTime()
-       device->run();
+       device->getTimer()->tick(); // Update device timer
        time = device->getTimer()->getTime();
 
-       if (time > last_time)   // Make sure last_time hasn't overflowed
+       if (time > last_time)  // Make sure last_time hasn't overflowed
                *dtime = (time - last_time) / 1000.0;
        else
-               *dtime = 0.033;
+               *dtime = 0;
 
-       params->last_time = time;
-#endif
+       fps_timings->last_time = time;
 }
 
-
-void MinetestApp::showOverlayMessage(const char *msg, float dtime,
+// 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)
 {
-       wchar_t *text = wgettext(msg);
-       draw_load_screen(text, device, guienv, font, dtime, percent, draw_clouds);
-       delete[] text;
+       draw_load_screen(msg, device, guienv, dtime, percent, draw_clouds);
+       delete[] msg;
 }
 
+void Game::settingChangedCallback(const std::string &setting_name, void *data)
+{
+       ((Game *)data)->readSettings();
+}
 
+void Game::readSettings()
+{
+       m_cache_doubletap_jump            = g_settings->getBool("doubletap_jump");
+       m_cache_enable_node_highlighting  = g_settings->getBool("enable_node_highlighting");
+       m_cache_enable_clouds             = g_settings->getBool("enable_clouds");
+       m_cache_enable_particles          = g_settings->getBool("enable_particles");
+       m_cache_enable_fog                = g_settings->getBool("enable_fog");
+       m_cache_mouse_sensitivity         = g_settings->getFloat("mouse_sensitivity");
+       m_repeat_right_click_time         = g_settings->getFloat("repeat_rightclick_time");
+
+       m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
+}
 
+/****************************************************************************/
 /****************************************************************************
  Shutdown / cleanup
  ****************************************************************************/
+/****************************************************************************/
 
-void MinetestApp::extendedResourceCleanup()
+void Game::extendedResourceCleanup()
 {
        // Extended resource accounting
        infostream << "Irrlicht resources after cleanup:" << std::endl;
@@ -3962,16 +4379,16 @@ void MinetestApp::extendedResourceCleanup()
 }
 
 
-
+/****************************************************************************/
 /****************************************************************************
  extern function for launching the game
  ****************************************************************************/
+/****************************************************************************/
 
 void the_game(bool *kill,
                bool random_input,
                InputHandler *input,
                IrrlichtDevice *device,
-               gui::IGUIFont *font,
 
                const std::string &map_dir,
                const std::string &playername,
@@ -3979,12 +4396,13 @@ 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,
+               bool *reconnect_requested,
                const SubgameSpec &gamespec,        // Used for local game
                bool simple_singleplayer_mode)
 {
-       MinetestApp app;
+       Game game;
 
        /* Make a copy of the server address because if a local singleplayer server
         * is created then this is updated and we don't want to change the value
@@ -3994,26 +4412,24 @@ void the_game(bool *kill,
 
        try {
 
-               if (app.startup(kill, random_input, input, device, font, map_dir,
-                                       playername, password, &server_address, port,
-                                       &error_message, &chat_backend, gamespec,
-                                       simple_singleplayer_mode)) {
-
-                       //std::cout << "App started" << std::endl;
-                       app.run();
-                       app.shutdown();
+               if (game.startup(kill, random_input, input, device, map_dir,
+                               playername, password, &server_address, port, error_message,
+                               reconnect_requested, &chat_backend, gamespec,
+                               simple_singleplayer_mode)) {
+                       game.run();
+                       game.shutdown();
                }
 
        } catch (SerializationError &e) {
-               error_message = 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()) + wgettext("\nCheck debug.txt for details.");
+               error_message = e.what() + strgettext("\nCheck debug.txt for details.");
+               errorstream << "ModError: " << error_message << std::endl;
        }
 }