For usages of assert() that are meant to persist in Release builds (when NDEBUG is...
[oweals/minetest.git] / src / game.cpp
index 70d526a9d7e5726c412e41b35427a5c3ca1408de..7b6d2a3c19d6f0305f1dce3a7adaad7cbc14d5dc 100644 (file)
@@ -51,7 +51,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "nodemetadata.h"
 #include "main.h" // For g_settings
 #include "itemdef.h"
-#include "tile.h" // For TextureSource
+#include "client/tile.h" // For TextureSource
 #include "shader.h" // For ShaderSource
 #include "logoutputbuffer.h"
 #include "subgame.h"
@@ -70,6 +70,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "util/pointedthing.h"
 #include "drawscene.h"
 #include "content_cao.h"
+#include "fontengine.h"
 
 #ifdef HAVE_TOUCHSCREENGUI
 #include "touchscreengui.h"
@@ -437,9 +438,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);
@@ -451,14 +451,25 @@ void update_profiler_gui(gui::IGUIStaticText *guitext_profiler,
                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);
        }
@@ -481,7 +492,7 @@ private:
                        color(color)
                {}
        };
-       std::list<Piece> m_log;
+       std::vector<Piece> m_log;
 public:
        u32 m_log_max_size;
 
@@ -504,7 +515,7 @@ public:
        {
                std::map<std::string, Meta> m_meta;
 
-               for (std::list<Piece>::const_iterator k = m_log.begin();
+               for (std::vector<Piece>::const_iterator k = m_log.begin();
                                k != m_log.end(); k++) {
                        const Piece &piece = *k;
 
@@ -602,7 +613,7 @@ public:
                        float lastscaledvalue = 0.0;
                        bool lastscaledvalue_exists = false;
 
-                       for (std::list<Piece>::const_iterator j = m_log.begin();
+                       for (std::vector<Piece>::const_iterator j = m_log.begin();
                                        j != m_log.end(); j++) {
                                const Piece &piece = *j;
                                float value = 0;
@@ -798,16 +809,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)
@@ -829,7 +859,7 @@ public:
                // 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);
@@ -967,6 +997,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) &&
@@ -1015,7 +1046,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,
@@ -1047,7 +1078,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") + "]"
                ;
 
@@ -1107,10 +1138,12 @@ static void show_pause_menu(GUIFormSpecMenu **cur_formspec,
                   << wide_to_narrow(wstrgettext("Change Password")) << "]";
        }
 
+#ifndef __ANDROID__
        os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
                        << wide_to_narrow(wstrgettext("Sound Volume")) << "]";
        os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
                        << wide_to_narrow(wstrgettext("Change Keys"))  << "]";
+#endif
        os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
                        << wide_to_narrow(wstrgettext("Exit to Menu")) << "]";
        os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
@@ -1128,15 +1161,14 @@ 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);
-
+       (*cur_formspec)->setFocus(L"btn_continue");
        (*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);
@@ -1159,9 +1191,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());
 
@@ -1172,7 +1202,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);
@@ -1223,6 +1253,7 @@ struct KeyCache {
                KEYMAP_ID_FREEMOVE,
                KEYMAP_ID_FASTMOVE,
                KEYMAP_ID_NOCLIP,
+               KEYMAP_ID_CINEMATIC,
                KEYMAP_ID_SCREENSHOT,
                KEYMAP_ID_TOGGLE_HUD,
                KEYMAP_ID_TOGGLE_CHAT,
@@ -1271,6 +1302,7 @@ void KeyCache::populate()
        key[KEYMAP_ID_FREEMOVE]     = getKeySetting("keymap_freemove");
        key[KEYMAP_ID_FASTMOVE]     = getKeySetting("keymap_fastmove");
        key[KEYMAP_ID_NOCLIP]       = getKeySetting("keymap_noclip");
+       key[KEYMAP_ID_CINEMATIC]    = getKeySetting("keymap_cinematic");
        key[KEYMAP_ID_SCREENSHOT]   = getKeySetting("keymap_screenshot");
        key[KEYMAP_ID_TOGGLE_HUD]   = getKeySetting("keymap_toggle_hud");
        key[KEYMAP_ID_TOGGLE_CHAT]  = getKeySetting("keymap_toggle_chat");
@@ -1316,7 +1348,6 @@ 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"
@@ -1401,7 +1432,6 @@ public:
                        bool random_input,
                        InputHandler *input,
                        IrrlichtDevice *device,
-                       gui::IGUIFont *font,
                        const std::string &map_dir,
                        const std::string &playername,
                        const std::string &password,
@@ -1469,6 +1499,7 @@ protected:
        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);
@@ -1484,6 +1515,8 @@ protected:
        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);
@@ -1505,14 +1538,15 @@ protected:
        void updateFrame(std::vector<aabb3f> &highlight_boxes, ProfilerGraph *graph,
                        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 *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);
 
 private:
@@ -1521,8 +1555,6 @@ private:
        Client *client;
        Server *server;
 
-       gui::IGUIFont *font;
-
        IWritableTextureSource *texture_src;
        IWritableShaderSource *shader_src;
 
@@ -1557,7 +1589,6 @@ private:
        IrrlichtDevice *device;
        video::IVideoDriver *driver;
        scene::ISceneManager *smgr;
-       u32 text_height;
        bool *kill;
        std::wstring *error_message;
        IGameDef *gamedef;                     // Convenience (same as *client)
@@ -1586,12 +1617,28 @@ private:
        KeyCache keycache;
 
        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;
+       f32  m_cache_mouse_sensitivity;
+       f32  m_repeat_right_click_time;
 };
 
 Game::Game() :
        client(NULL),
        server(NULL),
-       font(NULL),
        texture_src(NULL),
        shader_src(NULL),
        itemdef_manager(NULL),
@@ -1611,7 +1658,15 @@ Game::Game() :
        local_inventory(NULL),
        hud(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);
 }
 
 
@@ -1647,7 +1702,6 @@ 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,
@@ -1660,7 +1714,6 @@ bool Game::startup(bool *kill,
 {
        // "cache"
        this->device        = device;
-       this->font          = font;
        this->kill          = kill;
        this->error_message = error_message;
        this->random_input  = random_input;
@@ -1670,7 +1723,8 @@ bool Game::startup(bool *kill,
 
        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;
@@ -1686,6 +1740,7 @@ void Game::run()
 {
        ProfilerGraph graph;
        RunStats stats              = { 0 };
+       CameraOrientation cam_view_target  = { 0 };
        CameraOrientation cam_view  = { 0 };
        GameRunData runData         = { 0 };
        FpsControl draw_times       = { 0 };
@@ -1700,6 +1755,7 @@ void Game::run()
        flags.show_hud = true;
        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;
@@ -1715,6 +1771,8 @@ void Game::run()
 
        std::vector<aabb3f> highlight_boxes;
 
+       set_light_table(g_settings->getFloat("display_gamma"));
+
        while (device->run() && !(*kill || g_gamecallback->shutdown_requested)) {
 
                /* Must be called immediately after a device->run() call because it
@@ -1738,7 +1796,17 @@ void Game::run()
                updateProfilers(runData, stats, draw_times, dtime);
                processUserInput(&flags, &runData, dtime);
                // Update camera before player movement to avoid camera lag of one frame
-               updateCameraDirection(&cam_view, &flags);
+               updateCameraDirection(&cam_view_target, &flags);
+               float cam_smoothing = 0;
+               if (g_settings->getBool("cinematic"))
+                       cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
+               else
+                       cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
+               cam_smoothing = rangelim(cam_smoothing, 0.01f, 1.0f);
+               cam_view.camera_yaw += (cam_view_target.camera_yaw -
+                               cam_view.camera_yaw) * cam_smoothing;
+               cam_view.camera_pitch += (cam_view_target.camera_pitch -
+                               cam_view.camera_pitch) * cam_smoothing;
                updatePlayerControl(cam_view);
                step(&dtime);
                processClientEvents(&cam_view, &runData.damage_flash);
@@ -1756,7 +1824,7 @@ void Game::run()
 
 void Game::shutdown()
 {
-       showOverlayMessage("Shutting down...", 0, 0, false);
+       showOverlayMessage(wgettext("Shutting down..."), 0, 0, false);
 
        if (clouds)
                clouds->drop();
@@ -1767,8 +1835,6 @@ void Game::shutdown()
        if (sky)
                sky->drop();
 
-       clear_particles();
-
        /* cleanup menus */
        while (g_menumgr.menuCount() > 0) {
                g_menumgr.m_stack.front()->setVisible(false);
@@ -1807,7 +1873,7 @@ bool Game::init(
                u16 port,
                const SubgameSpec &gamespec)
 {
-       showOverlayMessage("Loading...", 0, 0);
+       showOverlayMessage(wgettext("Loading..."), 0, 0);
 
        texture_src = createTextureSource(device);
        shader_src = createShaderSource(device);
@@ -1864,7 +1930,7 @@ bool Game::initSound()
 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);
@@ -1875,7 +1941,6 @@ bool Game::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()
@@ -1902,7 +1967,7 @@ bool Game::createClient(const std::string &playername,
                const std::string &password, std::string *address, u16 port,
                std::wstring *error_message)
 {
-       showOverlayMessage("Creating client...", 0, 50);
+       showOverlayMessage(wgettext("Creating client..."), 0, 10);
 
        draw_control = new MapDrawControl;
        if (!draw_control)
@@ -1933,7 +1998,7 @@ bool Game::createClient(const std::string &playername,
        }
 
        // Update cached textures, meshes and materials
-       client->afterContentReceived(device, font);
+       client->afterContentReceived(device, g_fontengine->getFont());
 
        /* Camera
         */
@@ -1943,7 +2008,7 @@ bool Game::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";
@@ -1955,7 +2020,7 @@ bool Game::createClient(const std::string &playername,
 
        /* 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);
@@ -1991,8 +2056,7 @@ bool Game::createClient(const std::string &playername,
        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";
@@ -2021,7 +2085,7 @@ bool Game::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.)
@@ -2072,7 +2136,11 @@ 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);
 
@@ -2088,6 +2156,7 @@ bool Game::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());
@@ -2104,7 +2173,7 @@ bool Game::connectToServer(const std::string &playername,
        }
 
        client = new Client(device,
-                       playername.c_str(), password, simple_singleplayer_mode,
+                       playername.c_str(), password,
                        *draw_control, texture_src, shader_src,
                        itemdef_manager, nodedef_manager, sound, eventmgr,
                        connect_address.isIPv6());
@@ -2114,13 +2183,12 @@ bool Game::connectToServer(const std::string &playername,
 
        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
@@ -2163,7 +2231,7 @@ bool Game::connectToServer(const std::string &playername,
                        }
 
                        // 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
@@ -2218,17 +2286,17 @@ bool Game::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;
@@ -2248,9 +2316,9 @@ bool Game::getServerContent(bool *aborted)
                                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(narrow_to_wide(message.str()), device,
+                                       guienv, dtime, progress);
                }
        }
 
@@ -2352,8 +2420,9 @@ void Game::updateProfilers(const GameRunData &run_data, const RunStats &stats,
                        g_profiler->print(infostream);
                }
 
-               update_profiler_gui(guitext_profiler, font, text_height,
-                               run_data.profiler_current_page, run_data.profiler_max_page);
+               update_profiler_gui(guitext_profiler, g_fontengine,
+                               run_data.profiler_current_page, run_data.profiler_max_page,
+                               driver->getScreenSize().Height);
 
                g_profiler->clear();
        }
@@ -2464,7 +2533,7 @@ void Game::processUserInput(VolatileRunFlags *flags,
 #endif
 
        // Increase timer for double tap of "keymap_jump"
-       if (g_settings->getBool("doubletap_jump") && interact_args->jump_timer <= 0.2)
+       if (m_cache_doubletap_jump && interact_args->jump_timer <= 0.2)
                interact_args->jump_timer += dtime;
 
        processKeyboardInput(
@@ -2513,6 +2582,8 @@ void Game::processKeyboardInput(VolatileRunFlags *flags,
                toggleFast(statustext_time);
        } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_NOCLIP])) {
                toggleNoClip(statustext_time);
+       } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CINEMATIC])) {
+               toggleCinematic(statustext_time);
        } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_SCREENSHOT])) {
                client->makeScreenshot(device);
        } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_HUD])) {
@@ -2618,6 +2689,15 @@ void Game::dropSelectedItem()
 
 void Game::openInventory()
 {
+       /*
+        * Don't permit to open inventory is CAO or player doesn't exists.
+        * This prevent showing an empty inventory at player load
+        */
+
+       LocalPlayer *player = client->getEnv().getLocalPlayer();
+       if (player == NULL || player->getCAO() == NULL)
+               return;
+
        infostream << "the_game: " << "Launching inventory" << std::endl;
 
        PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
@@ -2658,7 +2738,7 @@ void Game::toggleFreeMove(float *statustext_time)
 
 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);
 }
 
@@ -2690,6 +2770,16 @@ void Game::toggleNoClip(float *statustext_time)
                statustext += L" (note: no 'noclip' privilege)";
 }
 
+void Game::toggleCinematic(float *statustext_time)
+{
+       static const wchar_t *msg[] = { L"cinematic disabled", L"cinematic enabled" };
+       bool cinematic = !g_settings->getBool("cinematic");
+       g_settings->set("cinematic", bool_to_cstr(cinematic));
+
+       *statustext_time = 0;
+       statustext = msg[cinematic];
+}
+
 
 void Game::toggleChat(float *statustext_time, bool *flag)
 {
@@ -2708,7 +2798,8 @@ void Game::toggleHud(float *statustext_time, bool *flag)
        *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);
 }
 
 
@@ -2763,8 +2854,8 @@ void Game::toggleProfiler(float *statustext_time, u32 *profiler_current_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;
@@ -2821,11 +2912,24 @@ void Game::toggleFullViewRange(float *statustext_time)
 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
@@ -2833,63 +2937,38 @@ void Game::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) {
-                       cam->camera_yaw   = g_touchscreengui->getYaw();
-                       cam->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);
 }
 
 
@@ -2994,44 +3073,11 @@ void Game::processClientEvents(CameraOrientation *cam, float *damage_flash)
 
                        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;
 
@@ -3066,7 +3112,7 @@ void Game::processClientEvents(CameraOrientation *cam, float *damage_flash)
 
                        u32 new_id = player->addHud(e);
                        //if this isn't true our huds aren't consistent
-                       assert(new_id == id);
+                       sanity_check(new_id == id);
 
                        delete event.hudadd.pos;
                        delete event.hudadd.name;
@@ -3211,10 +3257,13 @@ void Game::updateCamera(VolatileRunFlags *flags, u32 busy_time,
        v3s16 old_camera_offset = camera->getOffset();
 
        if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CAMERA_MODE])) {
-               camera->toggleCameraMode();
                GenericCAO *playercao = player->getCAO();
 
-               assert(playercao != NULL);
+               // If playercao not loaded, don't change camera
+               if (playercao == NULL)
+                       return;
+
+               camera->toggleCameraMode();
 
                playercao->setVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
        }
@@ -3342,7 +3391,7 @@ void Game::processPlayerInteraction(std::vector<aabb3f> &highlight_boxes,
        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 {
@@ -3455,8 +3504,7 @@ void Game::handlePointingAtNode(GameRunData *runData,
        }
 
        if ((input->getRightClicked() ||
-                       runData->repeat_rightclick_timer >=
-                       g_settings->getFloat("repeat_rightclick_time")) &&
+                       runData->repeat_rightclick_timer >= m_repeat_right_click_time) &&
                        client->checkPrivilege("interact")) {
                runData->repeat_rightclick_timer = 0;
                infostream << "Ground right-clicked" << std::endl;
@@ -3591,11 +3639,11 @@ void Game::handleDigging(GameRunData *runData,
        } else {
                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);
                }
        }
 
@@ -3638,12 +3686,11 @@ void Game::handleDigging(GameRunData *runData,
                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);
                }
 
                runData->dig_time = 0;
@@ -3766,15 +3813,13 @@ void Game::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,
@@ -3804,7 +3849,7 @@ void Game::updateFrame(std::vector<aabb3f> &highlight_boxes,
 
        updateChat(*client, dtime, flags.show_debug, screensize,
                        flags.show_chat, runData->profiler_current_page,
-                       *chat_backend, guitext_chat, font);
+                       *chat_backend, guitext_chat);
 
        /*
                Inventory
@@ -3846,7 +3891,7 @@ void Game::updateFrame(std::vector<aabb3f> &highlight_boxes,
                runData->update_draw_list_last_cam_dir = camera_direction;
        }
 
-       updateGui(&runData->statustext_time, *stats, dtime, flags, cam);
+       updateGui(&runData->statustext_time, *stats, *runData, dtime, flags, cam);
 
        /*
           make sure menu is on top
@@ -3882,7 +3927,7 @@ void Game::updateFrame(std::vector<aabb3f> &highlight_boxes,
                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
@@ -3923,8 +3968,31 @@ void Game::updateFrame(std::vector<aabb3f> &highlight_boxes,
 }
 
 
-void Game::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();
@@ -3965,7 +4033,7 @@ void Game::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);
        }
@@ -3977,14 +4045,28 @@ void Game::updateGui(float *statustext_time, const RunStats& stats,
                   << ", " << (player_position.Y / BS)
                   << ", " << (player_position.Z / BS)
                   << ") (yaw=" << (wrapDegrees_0_360(cam.camera_yaw))
+                  << " " << yawToDirectionString(cam.camera_yaw)
                   << ") (seed = " << ((u64)client->getMapSeed())
                   << ")";
+
+               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(narrow_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 {
@@ -4009,10 +4091,13 @@ void Game::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);
 
@@ -4090,13 +4175,14 @@ inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
        fps_timings->last_time = time;
 }
 
-
-void Game::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;
 }
 
 
@@ -4135,7 +4221,6 @@ void the_game(bool *kill,
                bool random_input,
                InputHandler *input,
                IrrlichtDevice *device,
-               gui::IGUIFont *font,
 
                const std::string &map_dir,
                const std::string &playername,
@@ -4158,7 +4243,7 @@ void the_game(bool *kill,
 
        try {
 
-               if (game.startup(kill, random_input, input, device, font, map_dir,
+               if (game.startup(kill, random_input, input, device, map_dir,
                                        playername, password, &server_address, port,
                                        &error_message, &chat_backend, gamespec,
                                        simple_singleplayer_mode)) {
@@ -4177,6 +4262,6 @@ void the_game(bool *kill,
                errorstream << "ServerError: " << e.what() << 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 = narrow_to_wide(e.what()) + wstrgettext("\nCheck debug.txt for details.");
        }
 }