]> git.lizzy.rs Git - minetest.git/blobdiff - src/game.cpp
FormSpec: Add StaticTextSpec and superimpose over item image buttons
[minetest.git] / src / game.cpp
index 903e4f85c58cf5cafd3ffce27454326e993a8459..cae468d4cc72ecfbc3b67bddc33d99ab6113a049 100644 (file)
@@ -40,7 +40,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "guiPasswordChange.h"
 #include "guiVolumeChange.h"
 #include "hud.h"
-#include "logoutputbuffer.h"
 #include "mainmenumanager.h"
 #include "mapblock.h"
 #include "nodedef.h"         // Needed for determining pointing to nodes
@@ -57,6 +56,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "util/directiontables.h"
 #include "util/pointedthing.h"
 #include "version.h"
+#include "minimap.h"
+#include "mapblock_mesh.h"
 
 #include "sound.h"
 
@@ -84,7 +85,7 @@ 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;
                StringMap fields;
@@ -182,7 +183,7 @@ struct LocalFormspecHandler : public TextDest {
                                        (fields.find("quit") != fields.end())) {
                                StringMap::const_iterator it = fields.find("f_text");
                                if (it != fields.end())
-                                       m_client->typeChatMessage(narrow_to_wide(it->second));
+                                       m_client->typeChatMessage(utf8_to_wide(it->second));
 
                                return;
                        }
@@ -288,14 +289,16 @@ inline bool isPointableNode(const MapNode &n,
 /*
        Find what the player is pointing at
 */
-PointedThing getPointedThing(Client *client, v3f player_position,
-               v3f camera_direction, v3f camera_position, core::line3d<f32> shootline,
-               f32 d, bool liquids_pointable, bool look_for_object, v3s16 camera_offset,
-               std::vector<aabb3f> &hilightboxes, ClientActiveObject *&selected_object)
+PointedThing getPointedThing(Client *client, Hud *hud, const v3f &player_position,
+               const v3f &camera_direction, const v3f &camera_position,
+               core::line3d<f32> shootline, f32 d, bool liquids_pointable,
+               bool look_for_object, const v3s16 &camera_offset,
+               ClientActiveObject *&selected_object)
 {
        PointedThing result;
 
-       hilightboxes.clear();
+       std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
+       selectionboxes->clear();
        selected_object = NULL;
 
        INodeDefManager *nodedef = client->getNodeDefManager();
@@ -316,9 +319,9 @@ PointedThing getPointedThing(Client *client, v3f player_position,
                                assert(selection_box);
 
                                v3f pos = selected_object->getPosition();
-                               hilightboxes.push_back(aabb3f(
-                                                              selection_box->MinEdge + pos - intToFloat(camera_offset, BS),
-                                                              selection_box->MaxEdge + pos - intToFloat(camera_offset, BS)));
+                               selectionboxes->push_back(aabb3f(
+                                       selection_box->MinEdge, selection_box->MaxEdge));
+                               hud->setSelectionPos(pos, camera_offset);
                        }
 
                        mindistance = (selected_object->getPosition() - camera_position).getLength();
@@ -374,7 +377,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;
@@ -414,22 +417,56 @@ PointedThing getPointedThing(Client *client, v3f player_position,
                                                result.node_abovesurface = np_above;
                                                mindistance = distance;
 
-                                               hilightboxes.clear();
-
-                                               if (!g_settings->getBool("enable_node_highlighting")) {
-                                                       for (std::vector<aabb3f>::const_iterator
-                                                                       i2 = boxes.begin();
-                                                                       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);
-                                                               hilightboxes.push_back(box);
-                                                       }
+                                               selectionboxes->clear();
+                                               for (std::vector<aabb3f>::const_iterator
+                                                               i2 = boxes.begin();
+                                                               i2 != boxes.end(); ++i2) {
+                                                       aabb3f box = *i2;
+                                                       box.MinEdge += v3f(-d, -d, -d);
+                                                       box.MaxEdge += v3f(d, d, d);
+                                                       selectionboxes->push_back(box);
                                                }
+                                               hud->setSelectionPos(npf, camera_offset);
                                        }
                                }
                        } // for coords
 
+       // Update selection mesh light level and vertex colors
+       if (selectionboxes->size() > 0) {
+               v3f pf = hud->getSelectionPos();
+               v3s16 p = floatToInt(pf, BS);  
+
+               // Get selection mesh light level
+               MapNode n = map.getNodeNoEx(p); 
+               u16 node_light = getInteriorLight(n, -1, nodedef);
+               u16 light_level = node_light;
+
+               for (u8 i = 0; i < 6; i++) {
+                       n = map.getNodeNoEx(p + g_6dirs[i]);
+                       node_light = getInteriorLight(n, -1, nodedef);
+                       if (node_light > light_level)
+                               light_level = node_light;
+               }
+
+               video::SColor c = MapBlock_LightColor(255, light_level, 0);
+               u8 day = c.getRed();
+               u8 night = c.getGreen();
+               u32 daynight_ratio = client->getEnv().getDayNightRatio();
+               finalColorBlend(c, day, night, daynight_ratio);
+
+               // Modify final color a bit with time
+               u32 timer = porting::getTimeMs() % 5000;
+               float timerf = (float)(irr::core::PI * ((timer / 2500.0) - 0.5));
+               float sin_r = 0.08 * sin(timerf);
+               float sin_g = 0.08 * sin(timerf + irr::core::PI * 0.5);
+               float sin_b = 0.08 * sin(timerf + irr::core::PI);
+               c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
+               c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
+               c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
+
+               // Set mesh final color
+               hud->setSelectionMeshColor(c);
+       }
        return result;
 }
 
@@ -444,7 +481,7 @@ void update_profiler_gui(gui::IGUIStaticText *guitext_profiler, FontEngine *fe,
 
                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);
 
@@ -489,7 +526,7 @@ class ProfilerGraph
                        color(color)
                {}
        };
-       std::vector<Piece> m_log;
+       std::deque<Piece> m_log;
 public:
        u32 m_log_max_size;
 
@@ -512,12 +549,12 @@ class ProfilerGraph
        {
                std::map<std::string, Meta> m_meta;
 
-               for (std::vector<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 =
@@ -549,7 +586,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);
 
@@ -565,7 +602,7 @@ class ProfilerGraph
                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;
@@ -581,16 +618,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);
@@ -600,8 +637,8 @@ class ProfilerGraph
                        float lastscaledvalue = 0.0;
                        bool lastscaledvalue_exists = false;
 
-                       for (std::vector<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;
@@ -805,7 +842,7 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
                        m_fogEnabled = g_settings->getBool("enable_fog");
        }
 
-       static void SettingsCallback(const std::string name, void *userdata)
+       static void SettingsCallback(const std::string &name, void *userdata)
        {
                reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
        }
@@ -866,6 +903,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;
@@ -874,11 +914,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
        }
 };
@@ -1045,7 +1085,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 */
@@ -1085,32 +1125,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;
@@ -1118,23 +1158,23 @@ 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;;" << PROJECT_NAME_C "\n"
                        << g_build_info << "\n"
@@ -1159,11 +1199,11 @@ static void updateChat(Client &client, f32 dtime, bool show_debug,
                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
@@ -1231,6 +1271,7 @@ struct KeyCache {
                KEYMAP_ID_JUMP,
                KEYMAP_ID_SPECIAL1,
                KEYMAP_ID_SNEAK,
+               KEYMAP_ID_AUTORUN,
 
                // Other
                KEYMAP_ID_DROP,
@@ -1238,6 +1279,7 @@ struct KeyCache {
                KEYMAP_ID_CHAT,
                KEYMAP_ID_CMD,
                KEYMAP_ID_CONSOLE,
+               KEYMAP_ID_MINIMAP,
                KEYMAP_ID_FREEMOVE,
                KEYMAP_ID_FASTMOVE,
                KEYMAP_ID_NOCLIP,
@@ -1282,11 +1324,14 @@ void KeyCache::populate()
        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");
@@ -1392,6 +1437,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;
@@ -1410,8 +1456,7 @@ struct VolatileRunFlags {
  * 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 Game
-{
+class Game {
 public:
        Game();
        ~Game();
@@ -1427,6 +1472,7 @@ class Game
                        std::string *address,
                        u16 port,
                        std::string &error_message,
+                       bool *reconnect,
                        ChatBackend *chat_backend,
                        const SubgameSpec &gamespec,    // Used for local game
                        bool simple_singleplayer_mode);
@@ -1487,9 +1533,12 @@ class Game
        void toggleFast(float *statustext_time);
        void toggleNoClip(float *statustext_time);
        void toggleCinematic(float *statustext_time);
+       void toggleAutorun(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);
@@ -1510,9 +1559,9 @@ class Game
        void updateCamera(VolatileRunFlags *flags, u32 busy_time, f32 dtime,
                        float time_from_last_punch);
        void updateSound(f32 dtime);
-       void processPlayerInteraction(std::vector<aabb3f> &highlight_boxes,
-                       GameRunData *runData, f32 dtime, bool show_hud,
+       void processPlayerInteraction(GameRunData *runData, f32 dtime, bool show_hud,
                        bool show_debug);
+       void handlePointingAtNothing(GameRunData *runData, const ItemStack &playerItem);
        void handlePointingAtNode(GameRunData *runData,
                        const PointedThing &pointed, const ItemDefinition &playeritem_def,
                        const ToolCapabilities &playeritem_toolcap, f32 dtime);
@@ -1522,8 +1571,7 @@ class Game
        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, GameRunData *runData,
+       void updateFrame(ProfilerGraph *graph, RunStats *stats, GameRunData *runData,
                        f32 dtime, const VolatileRunFlags &flags, const CameraOrientation &cam);
        void updateGui(float *statustext_time, const RunStats &stats,
                        const GameRunData& runData, f32 dtime, const VolatileRunFlags &flags,
@@ -1536,6 +1584,9 @@ class Game
        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;
 
@@ -1568,6 +1619,7 @@ class Game
        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
@@ -1578,6 +1630,7 @@ class Game
        scene::ISceneManager *smgr;
        bool *kill;
        std::string *error_message;
+       bool *reconnect_requested;
        IGameDef *gamedef;                     // Convenience (same as *client)
        scene::ISceneNode *skybox;
 
@@ -1605,17 +1658,13 @@ class Game
 
        IntervalLimiter profiler_interval;
 
-       /* TODO: Add a callback function so these can be updated when a setting
-        *       changes.  At this point in time it doesn't matter (e.g. /set
-        *       is documented to change server settings only)
-        *
+       /*
         * 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;
@@ -1648,17 +1697,23 @@ Game::Game() :
        clouds(NULL),
        sky(NULL),
        local_inventory(NULL),
-       hud(NULL)
+       hud(NULL),
+       mapper(NULL)
 {
-       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);
+       g_settings->registerChangedCallback("doubletap_jump",
+               &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
@@ -1693,6 +1748,19 @@ Game::~Game()
        delete draw_control;
 
        extendedResourceCleanup();
+
+       g_settings->deregisterChangedCallback("doubletap_jump",
+               &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 Game::startup(bool *kill,
@@ -1705,17 +1773,19 @@ bool Game::startup(bool *kill,
                std::string *address,     // can change if simple_singleplayer_mode
                u16 port,
                std::string &error_message,
+               bool *reconnect,
                ChatBackend *chat_backend,
                const SubgameSpec &gamespec,
                bool simple_singleplayer_mode)
 {
        // "cache"
-       this->device        = device;
-       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();
@@ -1750,6 +1820,7 @@ void Game::run()
 
        flags.show_chat = true;
        flags.show_hud = true;
+       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;
@@ -1766,8 +1837,6 @@ void Game::run()
                        &runData.fog_range,
                        client));
 
-       std::vector<aabb3f> highlight_boxes;
-
        set_light_table(g_settings->getFloat("display_gamma"));
 
 #ifdef __ANDROID__
@@ -1775,7 +1844,9 @@ void Game::run()
                        && client->checkPrivilege("fast");
 #endif
 
-       while (device->run() && !(*kill || g_gamecallback->shutdown_requested)) {
+       while (device->run()
+                       && !(*kill || g_gamecallback->shutdown_requested
+                       || (server && server->getShutdownRequested()))) {
 
                /* Must be called immediately after a device->run() call because it
                 * uses device->getTimer()->getTime()
@@ -1815,17 +1886,23 @@ void Game::run()
                updateCamera(&flags, draw_times.busy_time, dtime,
                                runData.time_from_last_punch);
                updateSound(dtime);
-               processPlayerInteraction(highlight_boxes, &runData, dtime,
-                               flags.show_hud, flags.show_debug);
-               updateFrame(highlight_boxes, &graph, &stats, &runData, dtime,
-                               flags, cam_view);
+               processPlayerInteraction(&runData, dtime, flags.show_hud,
+                               flags.show_debug);
+               updateFrame(&graph, &stats, &runData, dtime, flags, cam_view);
                updateProfilerGraphs(&graph);
+
+               // Update if minimap has been disabled by the server
+               flags.show_minimap &= !client->isMinimapDisabledByServer();
        }
 }
 
 
 void Game::shutdown()
 {
+       if (g_settings->get("3d_mode") == "pageflip") {
+               driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
+       }
+
        showOverlayMessage(wgettext("Shutting down..."), 0, 0, false);
 
        if (clouds)
@@ -2047,7 +2124,7 @@ bool Game::createClient(const std::string &playername,
 
        /* Set window caption
         */
-       std::wstring str = narrow_to_wide(PROJECT_NAME_C);
+       std::wstring str = utf8_to_wide(PROJECT_NAME_C);
        str += L" [";
        str += driver->getName();
        str += L"]";
@@ -2065,6 +2142,9 @@ bool Game::createClient(const std::string &playername,
                return false;
        }
 
+       mapper = client->getMapper();
+       mapper->setMinimapMode(MINIMAP_MODE_OFF);
+
        return true;
 }
 
@@ -2072,7 +2152,7 @@ bool Game::initGui()
 {
        // First line of debug text
        guitext = guienv->addStaticText(
-                       narrow_to_wide(PROJECT_NAME_C).c_str(),
+                       utf8_to_wide(PROJECT_NAME_C).c_str(),
                        core::rect<s32>(0, 0, 0, 0),
                        false, false, guiroot);
 
@@ -2126,7 +2206,7 @@ bool Game::initGui()
 #ifdef HAVE_TOUCHSCREENGUI
 
        if (g_touchscreengui)
-               g_touchscreengui->init(texture_src, porting::getDisplayDensity());
+               g_touchscreengui->init(texture_src);
 
 #endif
 
@@ -2224,6 +2304,7 @@ bool Game::connectToServer(const std::string &playername,
                        if (client->accessDenied()) {
                                *error_message = "Access denied. Reason: "
                                                + client->accessDeniedReason();
+                               *reconnect_requested = client->reconnectRequested();
                                errorstream << *error_message << std::endl;
                                break;
                        }
@@ -2327,7 +2408,7 @@ bool Game::getServerContent(bool *aborted)
                        }
 
                        progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
-                       draw_load_screen(narrow_to_wide(message.str()), device,
+                       draw_load_screen(utf8_to_wide(message.str()), device,
                                        guienv, dtime, progress);
                }
        }
@@ -2361,6 +2442,7 @@ inline bool Game::checkConnection()
        if (client->accessDenied()) {
                *error_message = "Access denied. Reason: "
                                + client->accessDeniedReason();
+               *reconnect_requested = client->reconnectRequested();
                errorstream << *error_message << std::endl;
                return false;
        }
@@ -2520,7 +2602,17 @@ void Game::processUserInput(VolatileRunFlags *flags,
                        || noMenuActive() == false
                        || guienv->hasFocus(gui_chat_console)) {
                input->clear();
+#ifdef HAVE_TOUCHSCREENGUI
+               g_touchscreengui->hide();
+#endif
+       }
+#ifdef HAVE_TOUCHSCREENGUI
+       else if (g_touchscreengui) {
+               /* on touchscreengui step may generate own input events which ain't
+                * what we want in case we just did clear them */
+               g_touchscreengui->step(dtime);
        }
+#endif
 
        if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
                gui_chat_console->closeConsoleAtOnce();
@@ -2529,13 +2621,6 @@ void Game::processUserInput(VolatileRunFlags *flags,
        // Input handler step() (used by the random input generator)
        input->step(dtime);
 
-#ifdef HAVE_TOUCHSCREENGUI
-
-       if (g_touchscreengui) {
-               g_touchscreengui->step(dtime);
-       }
-
-#endif
 #ifdef __ANDROID__
 
        if (current_formspec != 0)
@@ -2571,6 +2656,8 @@ void Game::processKeyboardInput(VolatileRunFlags *flags,
 
        if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_DROP])) {
                dropSelectedItem();
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_AUTORUN])) {
+               toggleAutorun(statustext_time);
        } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_INVENTORY])) {
                openInventory();
        } else if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) {
@@ -2599,6 +2686,9 @@ void Game::processKeyboardInput(VolatileRunFlags *flags,
                client->makeScreenshot(device);
        } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_HUD])) {
                toggleHud(statustext_time, &flags->show_hud);
+       } 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(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_FORCE_FOG_OFF])) {
@@ -2627,7 +2717,7 @@ void Game::processKeyboardInput(VolatileRunFlags *flags,
                // 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();
@@ -2642,7 +2732,7 @@ void Game::processKeyboardInput(VolatileRunFlags *flags,
 
        if (quicktune->hasMessage()) {
                std::string msg = quicktune->getMessage();
-               statustext = narrow_to_wide(msg);
+               statustext = utf8_to_wide(msg);
                *statustext_time = 0;
        }
 }
@@ -2797,6 +2887,16 @@ void Game::toggleCinematic(float *statustext_time)
        statustext = msg[cinematic];
 }
 
+// Add WoW-style autorun by toggling continuous forward.
+void Game::toggleAutorun(float *statustext_time)
+{
+       static const wchar_t *msg[] = { L"autorun disabled", L"autorun enabled" };
+       bool autorun_enabled = !g_settings->getBool("continuous_forward");
+       g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
+
+       *statustext_time = 0;
+       statustext = msg[autorun_enabled ? 1 : 0];
+}
 
 void Game::toggleChat(float *statustext_time, bool *flag)
 {
@@ -2815,10 +2915,57 @@ void Game::toggleHud(float *statustext_time, bool *flag)
        *flag = !*flag;
        *statustext_time = 0;
        statustext = msg[*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";
+       }
+
+       *statustext_time = 0;
+       mapper->setMinimapMode(mode);
+}
 
 void Game::toggleFog(float *statustext_time, bool *flag)
 {
@@ -2891,7 +3038,7 @@ 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;
 }
@@ -2906,7 +3053,7 @@ void Game::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;
 }
@@ -3298,6 +3445,7 @@ void Game::updateCamera(VolatileRunFlags *flags, u32 busy_time,
                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;
@@ -3352,8 +3500,8 @@ void Game::updateSound(f32 dtime)
 }
 
 
-void Game::processPlayerInteraction(std::vector<aabb3f> &highlight_boxes,
-               GameRunData *runData, f32 dtime, bool show_hud, bool show_debug)
+void Game::processPlayerInteraction(GameRunData *runData,
+               f32 dtime, bool show_hud, bool show_debug)
 {
        LocalPlayer *player = client->getEnv().getLocalPlayer();
 
@@ -3411,25 +3559,17 @@ void Game::processPlayerInteraction(std::vector<aabb3f> &highlight_boxes,
 
        PointedThing pointed = getPointedThing(
                        // input
-                       client, player_position, camera_direction,
+                       client, hud, player_position, camera_direction,
                        camera_position, shootline, d,
                        playeritem_def.liquids_pointable,
                        !runData->ldown_for_dig,
                        camera_offset,
                        // output
-                       highlight_boxes,
                        runData->selected_object);
 
        if (pointed != runData->pointed_old) {
                infostream << "Pointing at " << pointed.dump() << std::endl;
-
-               if (m_cache_enable_node_highlighting) {
-                       if (pointed.type == POINTEDTHING_NODE) {
-                               client->setHighlighted(pointed.node_undersurface, show_hud);
-                       } else {
-                               client->setHighlighted(pointed.node_undersurface, false);
-                       }
-               }
+               hud->updateSelectionMesh(camera_offset);
        }
 
        /*
@@ -3453,6 +3593,7 @@ void Game::processPlayerInteraction(std::vector<aabb3f> &highlight_boxes,
                                infostream << "Pointing away from node"
                                           << " (stopped digging)" << std::endl;
                                runData->digging = false;
+                               hud->updateSelectionMesh(camera_offset);
                        }
                }
 
@@ -3490,6 +3631,8 @@ void Game::processPlayerInteraction(std::vector<aabb3f> &highlight_boxes,
        } else if (input->getLeftState()) {
                // When button is held down in air, show continuous animation
                runData->left_punch = true;
+       } else if (input->getRightClicked()) {
+               handlePointingAtNothing(runData, playeritem);
        }
 
        runData->pointed_old = pointed;
@@ -3505,6 +3648,15 @@ void Game::processPlayerInteraction(std::vector<aabb3f> &highlight_boxes,
 }
 
 
+void Game::handlePointingAtNothing(GameRunData *runData, const ItemStack &playerItem)
+{
+       infostream << "Right Clicked in Air" << std::endl;
+       PointedThing fauxPointed;
+       fauxPointed.type = POINTEDTHING_NOTHING;
+       client->interact(5, fauxPointed);
+}
+
+
 void Game::handlePointingAtNode(GameRunData *runData,
                const PointedThing &pointed, const ItemDefinition &playeritem_def,
                const ToolCapabilities &playeritem_toolcap, f32 dtime)
@@ -3520,13 +3672,13 @@ void Game::handlePointingAtNode(GameRunData *runData,
        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.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);
                }
        }
 
@@ -3576,11 +3728,15 @@ void Game::handlePointingAtNode(GameRunData *runData,
                        } else {
                                soundmaker->m_player_rightpunch_sound =
                                                SimpleSoundSpec();
-                       }
 
-                       if (playeritem_def.node_placement_prediction == "" ||
-                                       nodedef_manager->get(map.getNodeNoEx(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;
+                               }
+                       }
                }
        }
 }
@@ -3592,10 +3748,13 @@ void Game::handlePointingAtObject(GameRunData *runData,
                const v3f &player_position,
                bool show_debug)
 {
-       infotext = narrow_to_wide(runData->selected_object->infoText());
+       infotext = utf8_to_wide(runData->selected_object->infoText());
 
-       if (infotext == L"" && show_debug) {
-               infotext = narrow_to_wide(runData->selected_object->debugInfoText());
+       if (show_debug) {
+               if (infotext != L"") {
+                       infotext += L"\n";
+               }
+               infotext += utf8_to_wide(runData->selected_object->debugInfoText());
        }
 
        if (input->getLeftState()) {
@@ -3759,9 +3918,9 @@ void Game::handleDigging(GameRunData *runData,
 }
 
 
-void Game::updateFrame(std::vector<aabb3f> &highlight_boxes,
-               ProfilerGraph *graph, RunStats *stats, GameRunData *runData,
-               f32 dtime, const VolatileRunFlags &flags, const CameraOrientation &cam)
+void Game::updateFrame(ProfilerGraph *graph, RunStats *stats,
+               GameRunData *runData, f32 dtime, const VolatileRunFlags &flags,
+               const CameraOrientation &cam)
 {
        LocalPlayer *player = client->getEnv().getLocalPlayer();
 
@@ -3952,8 +4111,9 @@ void Game::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, screensize, skycolor, flags.show_hud,
+                       flags.show_minimap);
 
        /*
                Profiler graph
@@ -3986,6 +4146,14 @@ void Game::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
        */
@@ -4051,12 +4219,12 @@ void Game::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 << PROJECT_NAME_C " " << g_version_hash;
-               guitext->setText(narrow_to_wide(os.str()).c_str());
+               guitext->setText(utf8_to_wide(os.str()).c_str());
                guitext->setVisible(true);
        } else {
                guitext->setVisible(false);
@@ -4093,7 +4261,7 @@ void Game::updateGui(float *statustext_time, const RunStats &stats,
                        }
                }
 
-               guitext2->setText(narrow_to_wide(os.str()).c_str());
+               guitext2->setText(utf8_to_wide(os.str()).c_str());
                guitext2->setVisible(true);
 
                core::rect<s32> rect(
@@ -4216,6 +4384,22 @@ void Game::showOverlayMessage(const wchar_t *msg, float dtime,
        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_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);
+}
 
 /****************************************************************************/
 /****************************************************************************
@@ -4264,6 +4448,7 @@ void the_game(bool *kill,
 
                std::string &error_message,
                ChatBackend &chat_backend,
+               bool *reconnect_requested,
                const SubgameSpec &gamespec,        // Used for local game
                bool simple_singleplayer_mode)
 {
@@ -4278,8 +4463,8 @@ void the_game(bool *kill,
        try {
 
                if (game.startup(kill, random_input, input, device, map_dir,
-                               playername, password, &server_address, port,
-                               error_message, &chat_backend, gamespec,
+                               playername, password, &server_address, port, error_message,
+                               reconnect_requested, &chat_backend, gamespec,
                                simple_singleplayer_mode)) {
                        game.run();
                        game.shutdown();
@@ -4298,4 +4483,3 @@ void the_game(bool *kill,
                errorstream << "ModError: " << error_message << std::endl;
        }
 }
-