]> git.lizzy.rs Git - dragonfireclient.git/blob - src/client/game.cpp
Merge branch 'master' of https://github.com/minetest/minetest
[dragonfireclient.git] / src / client / game.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "game.h"
21
22 #include <iomanip>
23 #include <cmath>
24 #include "client/renderingengine.h"
25 #include "camera.h"
26 #include "client.h"
27 #include "client/clientevent.h"
28 #include "client/gameui.h"
29 #include "client/inputhandler.h"
30 #include "client/tile.h"     // For TextureSource
31 #include "client/keys.h"
32 #include "client/joystick_controller.h"
33 #include "clientmap.h"
34 #include "clouds.h"
35 #include "config.h"
36 #include "content_cao.h"
37 #include "content/subgames.h"
38 #include "client/event_manager.h"
39 #include "fontengine.h"
40 #include "itemdef.h"
41 #include "log.h"
42 #include "filesys.h"
43 #include "gameparams.h"
44 #include "gettext.h"
45 #include "gui/guiChatConsole.h"
46 #include "gui/guiConfirmRegistration.h"
47 #include "gui/guiFormSpecMenu.h"
48 #include "gui/guiKeyChangeMenu.h"
49 #include "gui/guiPasswordChange.h"
50 #include "gui/guiVolumeChange.h"
51 #include "gui/mainmenumanager.h"
52 #include "gui/profilergraph.h"
53 #include "mapblock.h"
54 #include "minimap.h"
55 #include "nodedef.h"         // Needed for determining pointing to nodes
56 #include "nodemetadata.h"
57 #include "particles.h"
58 #include "porting.h"
59 #include "profiler.h"
60 #include "raycast.h"
61 #include "server.h"
62 #include "settings.h"
63 #include "shader.h"
64 #include "sky.h"
65 #include "translation.h"
66 #include "util/basic_macros.h"
67 #include "util/directiontables.h"
68 #include "util/pointedthing.h"
69 #include "util/quicktune_shortcutter.h"
70 #include "irrlicht_changes/static_text.h"
71 #include "irr_ptr.h"
72 #include "version.h"
73 #include "script/scripting_client.h"
74 #include "hud.h"
75
76 #if USE_SOUND
77         #include "client/sound_openal.h"
78 #else
79         #include "client/sound.h"
80 #endif
81
82 Game::Game() :
83         m_chat_log_buf(g_logger),
84         m_game_ui(new GameUI())
85 {
86         g_settings->registerChangedCallback("doubletap_jump",
87                 &settingChangedCallback, this);
88         g_settings->registerChangedCallback("enable_clouds",
89                 &settingChangedCallback, this);
90         g_settings->registerChangedCallback("doubletap_joysticks",
91                 &settingChangedCallback, this);
92         g_settings->registerChangedCallback("enable_particles",
93                 &settingChangedCallback, this);
94         g_settings->registerChangedCallback("enable_fog",
95                 &settingChangedCallback, this);
96         g_settings->registerChangedCallback("mouse_sensitivity",
97                 &settingChangedCallback, this);
98         g_settings->registerChangedCallback("joystick_frustum_sensitivity",
99                 &settingChangedCallback, this);
100         g_settings->registerChangedCallback("repeat_place_time",
101                 &settingChangedCallback, this);
102         g_settings->registerChangedCallback("noclip",
103                 &settingChangedCallback, this);
104         g_settings->registerChangedCallback("free_move",
105                 &settingChangedCallback, this);
106         g_settings->registerChangedCallback("cinematic",
107                 &settingChangedCallback, this);
108         g_settings->registerChangedCallback("cinematic_camera_smoothing",
109                 &settingChangedCallback, this);
110         g_settings->registerChangedCallback("camera_smoothing",
111                 &settingChangedCallback, this);
112         g_settings->registerChangedCallback("freecam",
113                 &freecamChangedCallback, this);
114         g_settings->registerChangedCallback("xray",
115                 &updateAllMapBlocksCallback, this);
116         g_settings->registerChangedCallback("xray_nodes",
117                 &updateAllMapBlocksCallback, this);
118         g_settings->registerChangedCallback("fullbright",
119                 &updateAllMapBlocksCallback, this);
120         g_settings->registerChangedCallback("node_esp_nodes",
121                 &updateAllMapBlocksCallback, this);
122
123         readSettings();
124
125 #ifdef HAVE_TOUCHSCREENGUI
126         m_cache_hold_aux1 = false;      // This is initialised properly later
127 #endif
128
129 }
130
131
132
133 /****************************************************************************
134  MinetestApp Public
135  ****************************************************************************/
136
137 Game::~Game()
138 {
139         delete client;
140         delete soundmaker;
141         if (!sound_is_dummy)
142                 delete sound;
143
144         delete server; // deleted first to stop all server threads
145
146         delete hud;
147         delete camera;
148         delete quicktune;
149         delete eventmgr;
150         delete texture_src;
151         delete shader_src;
152         delete nodedef_manager;
153         delete itemdef_manager;
154         delete draw_control;
155
156         clearTextureNameCache();
157
158         g_settings->deregisterChangedCallback("doubletap_jump",
159                 &settingChangedCallback, this);
160         g_settings->deregisterChangedCallback("enable_clouds",
161                 &settingChangedCallback, this);
162         g_settings->deregisterChangedCallback("enable_particles",
163                 &settingChangedCallback, this);
164         g_settings->deregisterChangedCallback("enable_fog",
165                 &settingChangedCallback, this);
166         g_settings->deregisterChangedCallback("mouse_sensitivity",
167                 &settingChangedCallback, this);
168         g_settings->deregisterChangedCallback("repeat_place_time",
169                 &settingChangedCallback, this);
170         g_settings->deregisterChangedCallback("noclip",
171                 &settingChangedCallback, this);
172         g_settings->deregisterChangedCallback("free_move",
173                 &settingChangedCallback, this);
174         g_settings->deregisterChangedCallback("cinematic",
175                 &settingChangedCallback, this);
176         g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
177                 &settingChangedCallback, this);
178         g_settings->deregisterChangedCallback("camera_smoothing",
179                 &settingChangedCallback, this);
180         g_settings->deregisterChangedCallback("freecam",
181                 &freecamChangedCallback, this);
182         g_settings->deregisterChangedCallback("xray",
183                 &updateAllMapBlocksCallback, this);
184         g_settings->deregisterChangedCallback("xray_nodes",
185                 &updateAllMapBlocksCallback, this);
186         g_settings->deregisterChangedCallback("fullbright",
187                 &updateAllMapBlocksCallback, this);
188         g_settings->deregisterChangedCallback("node_esp_nodes",
189                 &updateAllMapBlocksCallback, this);
190 }
191
192 bool Game::startup(bool *kill,
193                 InputHandler *input,
194                 RenderingEngine *rendering_engine,
195                 const GameStartData &start_data,
196                 std::string &error_message,
197                 bool *reconnect,
198                 ChatBackend *chat_backend)
199 {
200
201         // "cache"
202         m_rendering_engine        = rendering_engine;
203         device                    = m_rendering_engine->get_raw_device();
204         this->kill                = kill;
205         this->error_message       = &error_message;
206         reconnect_requested       = reconnect;
207         this->input               = input;
208         this->chat_backend        = chat_backend;
209         simple_singleplayer_mode  = start_data.isSinglePlayer();
210
211         input->keycache.populate();
212
213         driver = device->getVideoDriver();
214         smgr = m_rendering_engine->get_scene_manager();
215
216         smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
217
218         // Reinit runData
219         runData = GameRunData();
220         runData.time_from_last_punch = 10.0;
221
222         m_game_ui->initFlags();
223
224         m_invert_mouse = g_settings->getBool("invert_mouse");
225         m_first_loop_after_window_activation = true;
226
227         g_client_translations->clear();
228
229         // address can change if simple_singleplayer_mode
230         if (!init(start_data.world_spec.path, start_data.address,
231                         start_data.socket_port, start_data.game_spec))
232                 return false;
233
234         if (!createClient(start_data))
235                 return false;
236
237         m_rendering_engine->initialize(client, hud);
238
239         return true;
240 }
241
242
243 void Game::run()
244 {
245         ProfilerGraph graph;
246         RunStats stats = {};
247         FpsControl draw_times;
248         f32 dtime; // in seconds
249
250         /* Clear the profiler */
251         Profiler::GraphValues dummyvalues;
252         g_profiler->graphGet(dummyvalues);
253
254         draw_times.reset();
255
256         set_light_table(g_settings->getFloat("display_gamma"));
257
258 #ifdef HAVE_TOUCHSCREENGUI
259         m_cache_hold_aux1 = g_settings->getBool("fast_move")
260                         && client->checkPrivilege("fast");
261 #endif
262
263         irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
264                 g_settings->getU16("screen_h"));
265
266         while (m_rendering_engine->run()
267                         && !(*kill || g_gamecallback->shutdown_requested
268                         || (server && server->isShutdownRequested()))) {
269
270                 const irr::core::dimension2d<u32> &current_screen_size =
271                         m_rendering_engine->get_video_driver()->getScreenSize();
272                 // Verify if window size has changed and save it if it's the case
273                 // Ensure evaluating settings->getBool after verifying screensize
274                 // First condition is cheaper
275                 if (previous_screen_size != current_screen_size &&
276                                 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
277                                 g_settings->getBool("autosave_screensize")) {
278                         g_settings->setU16("screen_w", current_screen_size.Width);
279                         g_settings->setU16("screen_h", current_screen_size.Height);
280                         previous_screen_size = current_screen_size;
281                 }
282
283                 // Calculate dtime =
284                 //    m_rendering_engine->run() from this iteration
285                 //  + Sleep time until the wanted FPS are reached
286                 draw_times.limit(device, &dtime);
287
288                 // Prepare render data for next iteration
289
290                 updateStats(&stats, draw_times, dtime);
291                 updateInteractTimers(dtime);
292
293                 if (!checkConnection())
294                         break;
295                 if (!handleCallbacks())
296                         break;
297
298                 processQueues();
299
300                 m_game_ui->clearInfoText();
301                 hud->resizeHotbar();
302
303
304                 updateProfilers(stats, draw_times, dtime);
305                 processUserInput(dtime);
306                 // Update camera before player movement to avoid camera lag of one frame
307                 updateCameraDirection(&cam_view_target, dtime);
308                 cam_view.camera_yaw += (cam_view_target.camera_yaw -
309                                 cam_view.camera_yaw) * m_cache_cam_smoothing;
310                 cam_view.camera_pitch += (cam_view_target.camera_pitch -
311                                 cam_view.camera_pitch) * m_cache_cam_smoothing;
312                 updatePlayerControl(cam_view);
313                 step(&dtime);
314                 processClientEvents(&cam_view_target);
315                 updateDebugState();
316                 updateCamera(dtime);
317                 updateSound(dtime);
318                 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
319                 updateFrame(&graph, &stats, dtime, cam_view);
320                 updateProfilerGraphs(&graph);
321
322                 // Update if minimap has been disabled by the server
323                 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
324
325                 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
326                         showPauseMenu();
327                 }
328         }
329 }
330
331
332 void Game::shutdown()
333 {
334         m_rendering_engine->finalize();
335 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
336         if (g_settings->get("3d_mode") == "pageflip") {
337                 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
338         }
339 #endif
340         auto formspec = m_game_ui->getFormspecGUI();
341         if (formspec)
342                 formspec->quitMenu();
343
344 #ifdef HAVE_TOUCHSCREENGUI
345         g_touchscreengui->hide();
346 #endif
347
348         showOverlayMessage(N_("Shutting down..."), 0, 0, false);
349
350         if (clouds)
351                 clouds->drop();
352
353         if (gui_chat_console)
354                 gui_chat_console->drop();
355
356         if (m_cheat_menu)
357                 delete m_cheat_menu;
358
359         if (sky)
360                 sky->drop();
361
362         /* cleanup menus */
363         while (g_menumgr.menuCount() > 0) {
364                 g_menumgr.m_stack.front()->setVisible(false);
365                 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
366         }
367
368         m_game_ui->deleteFormspec();
369
370         chat_backend->addMessage(L"", L"# Disconnected.");
371         chat_backend->addMessage(L"", L"");
372         m_chat_log_buf.clear();
373
374         if (client) {
375                 client->Stop();
376                 while (!client->isShutdown()) {
377                         assert(texture_src != NULL);
378                         assert(shader_src != NULL);
379                         texture_src->processQueue();
380                         shader_src->processQueue();
381                         sleep_ms(100);
382                 }
383         }
384 }
385
386
387 /****************************************************************************/
388 /****************************************************************************
389  Startup
390  ****************************************************************************/
391 /****************************************************************************/
392
393 bool Game::init(
394                 const std::string &map_dir,
395                 const std::string &address,
396                 u16 port,
397                 const SubgameSpec &gamespec)
398 {
399         texture_src = createTextureSource();
400
401         showOverlayMessage(N_("Loading..."), 0, 0);
402
403         shader_src = createShaderSource();
404
405         itemdef_manager = createItemDefManager();
406         nodedef_manager = createNodeDefManager();
407
408         eventmgr = new EventManager();
409         quicktune = new QuicktuneShortcutter();
410
411         if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
412                         && eventmgr && quicktune))
413                 return false;
414
415         if (!initSound())
416                 return false;
417
418         // Create a server if not connecting to an existing one
419         if (address.empty()) {
420                 if (!createSingleplayerServer(map_dir, gamespec, port))
421                         return false;
422         }
423
424         return true;
425 }
426
427 bool Game::initSound()
428 {
429 #if USE_SOUND
430         if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
431                 infostream << "Attempting to use OpenAL audio" << std::endl;
432                 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
433                 if (!sound)
434                         infostream << "Failed to initialize OpenAL audio" << std::endl;
435         } else
436                 infostream << "Sound disabled." << std::endl;
437 #endif
438
439         if (!sound) {
440                 infostream << "Using dummy audio." << std::endl;
441                 sound = &dummySoundManager;
442                 sound_is_dummy = true;
443         }
444
445         soundmaker = new SoundMaker(sound, nodedef_manager);
446         if (!soundmaker)
447                 return false;
448
449         soundmaker->registerReceiver(eventmgr);
450
451         return true;
452 }
453
454 bool Game::createSingleplayerServer(const std::string &map_dir,
455                 const SubgameSpec &gamespec, u16 port)
456 {
457         showOverlayMessage(N_("Creating server..."), 0, 5);
458
459         std::string bind_str = g_settings->get("bind_address");
460         Address bind_addr(0, 0, 0, 0, port);
461
462         if (g_settings->getBool("ipv6_server")) {
463                 bind_addr.setAddress((IPv6AddressBytes *) NULL);
464         }
465
466         try {
467                 bind_addr.Resolve(bind_str.c_str());
468         } catch (ResolveError &e) {
469                 infostream << "Resolving bind address \"" << bind_str
470                            << "\" failed: " << e.what()
471                            << " -- Listening on all addresses." << std::endl;
472         }
473
474         if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
475                 *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
476                         bind_addr.serializeString().c_str());
477                 errorstream << *error_message << std::endl;
478                 return false;
479         }
480
481         server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
482                         false, nullptr, error_message);
483         server->start();
484
485         return true;
486 }
487
488 bool Game::createClient(const GameStartData &start_data)
489 {
490         showOverlayMessage(N_("Creating client..."), 0, 10);
491
492         draw_control = new MapDrawControl();
493         if (!draw_control)
494                 return false;
495
496         bool could_connect, connect_aborted;
497 #ifdef HAVE_TOUCHSCREENGUI
498         if (g_touchscreengui) {
499                 g_touchscreengui->init(texture_src);
500                 g_touchscreengui->hide();
501         }
502 #endif
503         if (!connectToServer(start_data, &could_connect, &connect_aborted))
504                 return false;
505
506         if (!could_connect) {
507                 if (error_message->empty() && !connect_aborted) {
508                         // Should not happen if error messages are set properly
509                         *error_message = gettext("Connection failed for unknown reason");
510                         errorstream << *error_message << std::endl;
511                 }
512                 return false;
513         }
514
515         if (!getServerContent(&connect_aborted)) {
516                 if (error_message->empty() && !connect_aborted) {
517                         // Should not happen if error messages are set properly
518                         *error_message = gettext("Connection failed for unknown reason");
519                         errorstream << *error_message << std::endl;
520                 }
521                 return false;
522         }
523
524         auto *scsf = new GameGlobalShaderConstantSetterFactory(
525                         &m_flags.force_fog_off, &runData.fog_range, client);
526         shader_src->addShaderConstantSetterFactory(scsf);
527
528         // Update cached textures, meshes and materials
529         client->afterContentReceived();
530
531         /* Camera
532          */
533         camera = new Camera(*draw_control, client, m_rendering_engine);
534         if (client->modsLoaded())
535                 client->getScript()->on_camera_ready(camera);
536         client->setCamera(camera);
537
538         /* Clouds
539          */
540         if (m_cache_enable_clouds)
541                 clouds = new Clouds(smgr, -1, time(0));
542
543         /* Skybox
544          */
545         sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
546         scsf->setSky(sky);
547         skybox = NULL;  // This is used/set later on in the main run loop
548
549         /* Pre-calculated values
550          */
551         video::ITexture *t = texture_src->getTexture("crack_anylength.png");
552         if (t) {
553                 v2u32 size = t->getOriginalSize();
554                 crack_animation_length = size.Y / size.X;
555         } else {
556                 crack_animation_length = 5;
557         }
558
559         if (!initGui())
560                 return false;
561
562         /* Set window caption
563          */
564         std::wstring str = utf8_to_wide(PROJECT_NAME_C);
565         str += L" ";
566         str += utf8_to_wide(g_version_hash);
567         {
568                 const wchar_t *text = nullptr;
569                 if (simple_singleplayer_mode)
570                         text = wgettext("Singleplayer");
571                 else
572                         text = wgettext("Multiplayer");
573                 str += L" [";
574                 str += text;
575                 str += L"]";
576                 delete[] text;
577         }
578         str += L" [";
579         str += L"Minetest Hackclient";
580         str += L"]";
581
582         device->setWindowCaption(str.c_str());
583
584         LocalPlayer *player = client->getEnv().getLocalPlayer();
585         player->hurt_tilt_timer = 0;
586         player->hurt_tilt_strength = 0;
587
588         hud = new Hud(client, player, &player->inventory);
589
590         mapper = client->getMinimap();
591
592         if (mapper && client->modsLoaded())
593                 client->getScript()->on_minimap_ready(mapper);
594
595         return true;
596 }
597
598 bool Game::initGui()
599 {
600         m_game_ui->init();
601
602         // Remove stale "recent" chat messages from previous connections
603         chat_backend->clearRecentChat();
604
605         // Make sure the size of the recent messages buffer is right
606         chat_backend->applySettings();
607
608         // Chat backend and console
609         gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
610                         -1, chat_backend, client, &g_menumgr);
611
612         if (!gui_chat_console) {
613                 *error_message = "Could not allocate memory for chat console";
614                 errorstream << *error_message << std::endl;
615                 return false;
616         }
617
618         m_cheat_menu = new CheatMenu(client);
619
620         if (!m_cheat_menu) {
621                 *error_message = "Could not allocate memory for cheat menu";
622                 errorstream << *error_message << std::endl;
623                 return false;
624         }
625
626 #ifdef HAVE_TOUCHSCREENGUI
627
628         if (g_touchscreengui)
629                 g_touchscreengui->show();
630
631 #endif
632
633         return true;
634 }
635
636 bool Game::connectToServer(const GameStartData &start_data,
637                 bool *connect_ok, bool *connection_aborted)
638 {
639         *connect_ok = false;    // Let's not be overly optimistic
640         *connection_aborted = false;
641         bool local_server_mode = false;
642
643         showOverlayMessage(N_("Resolving address..."), 0, 15);
644
645         Address connect_address(0, 0, 0, 0, start_data.socket_port);
646
647         try {
648                 connect_address.Resolve(start_data.address.c_str());
649
650                 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
651                         if (connect_address.isIPv6()) {
652                                 IPv6AddressBytes addr_bytes;
653                                 addr_bytes.bytes[15] = 1;
654                                 connect_address.setAddress(&addr_bytes);
655                         } else {
656                                 connect_address.setAddress(127, 0, 0, 1);
657                         }
658                         local_server_mode = true;
659                 }
660         } catch (ResolveError &e) {
661                 *error_message = fmtgettext("Couldn't resolve address: %s", e.what());
662
663                 errorstream << *error_message << std::endl;
664                 return false;
665         }
666
667         if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
668                 *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
669                 errorstream << *error_message << std::endl;
670                 return false;
671         }
672
673         try {
674                 client = new Client(start_data.name.c_str(),
675                                 start_data.password, start_data.address,
676                                 *draw_control, texture_src, shader_src,
677                                 itemdef_manager, nodedef_manager, sound, eventmgr,
678                                 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get());
679                 client->migrateModStorage();
680         } catch (const BaseException &e) {
681                 *error_message = fmtgettext("Error creating client: %s", e.what());
682                 errorstream << *error_message << std::endl;
683                 return false;
684         }
685
686         client->m_simple_singleplayer_mode = simple_singleplayer_mode;
687
688         infostream << "Connecting to server at ";
689         connect_address.print(infostream);
690         infostream << std::endl;
691
692         client->connect(connect_address,
693                 simple_singleplayer_mode || local_server_mode);
694
695         /*
696                 Wait for server to accept connection
697         */
698
699         try {
700                 input->clear();
701
702                 FpsControl fps_control;
703                 f32 dtime;
704                 f32 wait_time = 0; // in seconds
705
706                 fps_control.reset();
707
708                 while (m_rendering_engine->run()) {
709
710                         fps_control.limit(device, &dtime);
711
712                         // Update client and server
713                         client->step(dtime);
714
715                         if (server != NULL)
716                                 server->step(dtime);
717
718                         // End condition
719                         if (client->getState() == LC_Init) {
720                                 *connect_ok = true;
721                                 break;
722                         }
723
724                         // Break conditions
725                         if (*connection_aborted)
726                                 break;
727
728                         if (client->accessDenied()) {
729                                 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
730                                 *reconnect_requested = client->reconnectRequested();
731                                 errorstream << *error_message << std::endl;
732                                 break;
733                         }
734
735                         if (input->cancelPressed()) {
736                                 *connection_aborted = true;
737                                 infostream << "Connect aborted [Escape]" << std::endl;
738                                 break;
739                         }
740
741                         if (client->m_is_registration_confirmation_state) {
742                                 if (registration_confirmation_shown) {
743                                         // Keep drawing the GUI
744                                         m_rendering_engine->draw_menu_scene(guienv, dtime, true);
745                                 } else {
746                                         registration_confirmation_shown = true;
747                                         (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
748                                                    &g_menumgr, client, start_data.name, start_data.password,
749                                                    connection_aborted, texture_src))->drop();
750                                 }
751                         } else {
752                                 wait_time += dtime;
753                                 // Only time out if we aren't waiting for the server we started
754                                 if (!start_data.address.empty() && wait_time > 10) {
755                                         *error_message = gettext("Connection timed out.");
756                                         errorstream << *error_message << std::endl;
757                                         break;
758                                 }
759
760                                 // Update status
761                                 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
762                         }
763                 }
764         } catch (con::PeerNotFoundException &e) {
765                 // TODO: Should something be done here? At least an info/error
766                 // message?
767                 return false;
768         }
769
770         return true;
771 }
772
773 bool Game::getServerContent(bool *aborted)
774 {
775         input->clear();
776
777         FpsControl fps_control;
778         f32 dtime; // in seconds
779
780         fps_control.reset();
781
782         while (m_rendering_engine->run()) {
783
784                 fps_control.limit(device, &dtime);
785
786                 // Update client and server
787                 client->step(dtime);
788
789                 if (server != NULL)
790                         server->step(dtime);
791
792                 // End condition
793                 if (client->mediaReceived() && client->itemdefReceived() &&
794                                 client->nodedefReceived()) {
795                         break;
796                 }
797
798                 // Error conditions
799                 if (!checkConnection())
800                         return false;
801
802                 if (client->getState() < LC_Init) {
803                         *error_message = gettext("Client disconnected");
804                         errorstream << *error_message << std::endl;
805                         return false;
806                 }
807
808                 if (input->cancelPressed()) {
809                         *aborted = true;
810                         infostream << "Connect aborted [Escape]" << std::endl;
811                         return false;
812                 }
813
814                 // Display status
815                 int progress = 25;
816
817                 if (!client->itemdefReceived()) {
818                         const wchar_t *text = wgettext("Item definitions...");
819                         progress = 25;
820                         m_rendering_engine->draw_load_screen(text, guienv, texture_src,
821                                 dtime, progress);
822                         delete[] text;
823                 } else if (!client->nodedefReceived()) {
824                         const wchar_t *text = wgettext("Node definitions...");
825                         progress = 30;
826                         m_rendering_engine->draw_load_screen(text, guienv, texture_src,
827                                 dtime, progress);
828                         delete[] text;
829                 } else {
830                         std::ostringstream message;
831                         std::fixed(message);
832                         message.precision(0);
833                         float receive = client->mediaReceiveProgress() * 100;
834                         message << gettext("Media...");
835                         if (receive > 0)
836                                 message << " " << receive << "%";
837                         message.precision(2);
838
839                         if ((USE_CURL == 0) ||
840                                         (!g_settings->getBool("enable_remote_media_server"))) {
841                                 float cur = client->getCurRate();
842                                 std::string cur_unit = gettext("KiB/s");
843
844                                 if (cur > 900) {
845                                         cur /= 1024.0;
846                                         cur_unit = gettext("MiB/s");
847                                 }
848
849                                 message << " (" << cur << ' ' << cur_unit << ")";
850                         }
851
852                         progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
853                         m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
854                                 texture_src, dtime, progress);
855                 }
856         }
857
858         return true;
859 }
860
861
862 /****************************************************************************/
863 /****************************************************************************
864  Run
865  ****************************************************************************/
866 /****************************************************************************/
867
868 inline void Game::updateInteractTimers(f32 dtime)
869 {
870         if (runData.nodig_delay_timer >= 0)
871                 runData.nodig_delay_timer -= dtime;
872
873         if (runData.object_hit_delay_timer >= 0)
874                 runData.object_hit_delay_timer -= dtime;
875
876         runData.time_from_last_punch += dtime;
877 }
878
879
880 /* returns false if game should exit, otherwise true
881  */
882 inline bool Game::checkConnection()
883 {
884         if (client->accessDenied()) {
885                 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
886                 *reconnect_requested = client->reconnectRequested();
887                 errorstream << *error_message << std::endl;
888                 return false;
889         }
890
891         return true;
892 }
893
894
895 /* returns false if game should exit, otherwise true
896  */
897 inline bool Game::handleCallbacks()
898 {
899         if (g_gamecallback->disconnect_requested) {
900                 g_gamecallback->disconnect_requested = false;
901                 return false;
902         }
903
904         if (g_gamecallback->changepassword_requested) {
905                 (new GUIPasswordChange(guienv, guiroot, -1,
906                                        &g_menumgr, client, texture_src))->drop();
907                 g_gamecallback->changepassword_requested = false;
908         }
909
910         if (g_gamecallback->changevolume_requested) {
911                 (new GUIVolumeChange(guienv, guiroot, -1,
912                                      &g_menumgr, texture_src))->drop();
913                 g_gamecallback->changevolume_requested = false;
914         }
915
916         if (g_gamecallback->keyconfig_requested) {
917                 (new GUIKeyChangeMenu(guienv, guiroot, -1,
918                                       &g_menumgr, texture_src))->drop();
919                 g_gamecallback->keyconfig_requested = false;
920         }
921
922         if (g_gamecallback->keyconfig_changed) {
923                 input->keycache.populate(); // update the cache with new settings
924                 g_gamecallback->keyconfig_changed = false;
925         }
926
927         return true;
928 }
929
930
931 void Game::processQueues()
932 {
933         texture_src->processQueue();
934         itemdef_manager->processQueue(client);
935         shader_src->processQueue();
936 }
937
938 void Game::updateDebugState()
939 {
940         LocalPlayer *player = client->getEnv().getLocalPlayer();
941         bool has_debug = client->checkPrivilege("debug");
942         bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
943
944         if (m_game_ui->m_flags.show_basic_debug) {
945                 if (!has_basic_debug)
946                         m_game_ui->m_flags.show_basic_debug = false;
947         } else if (m_game_ui->m_flags.show_minimal_debug) {
948                 if (has_basic_debug)
949                         m_game_ui->m_flags.show_basic_debug = true;
950         }
951         if (!has_basic_debug)
952                 hud->disableBlockBounds();
953         if (!has_debug)
954                 draw_control->show_wireframe = false;
955 }
956
957 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
958                 f32 dtime)
959 {
960         float profiler_print_interval =
961                         g_settings->getFloat("profiler_print_interval");
962         bool print_to_log = true;
963
964         if (profiler_print_interval == 0) {
965                 print_to_log = false;
966                 profiler_print_interval = 3;
967         }
968
969         if (profiler_interval.step(dtime, profiler_print_interval)) {
970                 if (print_to_log) {
971                         infostream << "Profiler:" << std::endl;
972                         g_profiler->print(infostream);
973                 }
974
975                 m_game_ui->updateProfiler();
976                 g_profiler->clear();
977         }
978
979         // Update update graphs
980         g_profiler->graphAdd("Time non-rendering [us]",
981                 draw_times.busy_time - stats.drawtime);
982
983         g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
984         g_profiler->graphAdd("FPS", 1.0f / dtime);
985 }
986
987 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
988                 f32 dtime)
989 {
990
991         f32 jitter;
992         Jitter *jp;
993
994         /* Time average and jitter calculation
995          */
996         jp = &stats->dtime_jitter;
997         jp->avg = jp->avg * 0.96 + dtime * 0.04;
998
999         jitter = dtime - jp->avg;
1000
1001         if (jitter > jp->max)
1002                 jp->max = jitter;
1003
1004         jp->counter += dtime;
1005
1006         if (jp->counter > 0.0) {
1007                 jp->counter -= 3.0;
1008                 jp->max_sample = jp->max;
1009                 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1010                 jp->max = 0.0;
1011         }
1012
1013         /* Busytime average and jitter calculation
1014          */
1015         jp = &stats->busy_time_jitter;
1016         jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
1017
1018         jitter = draw_times.getBusyMs() - jp->avg;
1019
1020         if (jitter > jp->max)
1021                 jp->max = jitter;
1022         if (jitter < jp->min)
1023                 jp->min = jitter;
1024
1025         jp->counter += dtime;
1026
1027         if (jp->counter > 0.0) {
1028                 jp->counter -= 3.0;
1029                 jp->max_sample = jp->max;
1030                 jp->min_sample = jp->min;
1031                 jp->max = 0.0;
1032                 jp->min = 0.0;
1033         }
1034 }
1035
1036
1037
1038 /****************************************************************************
1039  Input handling
1040  ****************************************************************************/
1041
1042 void Game::processUserInput(f32 dtime)
1043 {
1044         // Reset input if window not active or some menu is active
1045         if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1046                 input->clear();
1047 #ifdef HAVE_TOUCHSCREENGUI
1048                 g_touchscreengui->hide();
1049 #endif
1050         }
1051 #ifdef HAVE_TOUCHSCREENGUI
1052         else if (g_touchscreengui) {
1053                 /* on touchscreengui step may generate own input events which ain't
1054                  * what we want in case we just did clear them */
1055                 g_touchscreengui->show();
1056                 g_touchscreengui->step(dtime);
1057         }
1058 #endif
1059
1060         if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1061                 gui_chat_console->closeConsoleAtOnce();
1062         }
1063
1064         // Input handler step() (used by the random input generator)
1065         input->step(dtime);
1066
1067 #ifdef __ANDROID__
1068         auto formspec = m_game_ui->getFormspecGUI();
1069         if (formspec)
1070                 formspec->getAndroidUIInput();
1071         else
1072                 handleAndroidChatInput();
1073 #endif
1074
1075         // Increase timer for double tap of "keymap_jump"
1076         if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1077                 runData.jump_timer += dtime;
1078
1079         processKeyInput();
1080         processItemSelection(&runData.new_playeritem);
1081 }
1082
1083
1084 void Game::processKeyInput()
1085 {
1086         if (wasKeyDown(KeyType::SELECT_UP)) {
1087                 m_cheat_menu->selectUp();
1088         } else if (wasKeyDown(KeyType::SELECT_DOWN)) {
1089                 m_cheat_menu->selectDown();
1090         } else if (wasKeyDown(KeyType::SELECT_LEFT)) {
1091                 m_cheat_menu->selectLeft();
1092         } else if (wasKeyDown(KeyType::SELECT_RIGHT)) {
1093                 m_cheat_menu->selectRight();
1094         } else if (wasKeyDown(KeyType::SELECT_CONFIRM)) {
1095                 m_cheat_menu->selectConfirm();
1096         }
1097
1098         if (wasKeyDown(KeyType::DROP)) {
1099                 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1100         } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1101                 toggleAutoforward();
1102         } else if (wasKeyDown(KeyType::BACKWARD)) {
1103                 if (g_settings->getBool("continuous_forward"))
1104                         toggleAutoforward();
1105         } else if (wasKeyDown(KeyType::INVENTORY)) {
1106                 openInventory();
1107         } else if (wasKeyDown(KeyType::ENDERCHEST)) {
1108                 openEnderchest();
1109         } else if (input->cancelPressed()) {
1110 #ifdef __ANDROID__
1111                 m_android_chat_open = false;
1112 #endif
1113                 if (!gui_chat_console->isOpenInhibited()) {
1114                         showPauseMenu();
1115                 }
1116         } else if (wasKeyDown(KeyType::CHAT)) {
1117                 openConsole(0.2, L"");
1118         } else if (wasKeyDown(KeyType::CMD)) {
1119                 openConsole(0.2, L"/");
1120         } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1121                 if (client->modsLoaded())
1122                         openConsole(0.2, L".");
1123                 else
1124                         m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1125         } else if (wasKeyDown(KeyType::CONSOLE)) {
1126                 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1127         } else if (wasKeyDown(KeyType::FREEMOVE)) {
1128                 toggleFreeMove();
1129         } else if (wasKeyDown(KeyType::JUMP)) {
1130                 toggleFreeMoveAlt();
1131         } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1132                 togglePitchMove();
1133         } else if (wasKeyDown(KeyType::FASTMOVE)) {
1134                 toggleFast();
1135         } else if (wasKeyDown(KeyType::NOCLIP)) {
1136                 toggleNoClip();
1137         } else if (wasKeyDown(KeyType::KILLAURA)) {
1138                 toggleKillaura();
1139         } else if (wasKeyDown(KeyType::FREECAM)) {
1140                 toggleFreecam();
1141         } else if (wasKeyDown(KeyType::SCAFFOLD)) {
1142                 toggleScaffold();
1143 #if USE_SOUND
1144         } else if (wasKeyDown(KeyType::MUTE)) {
1145                 if (g_settings->getBool("enable_sound")) {
1146                         bool new_mute_sound = !g_settings->getBool("mute_sound");
1147                         g_settings->setBool("mute_sound", new_mute_sound);
1148                         if (new_mute_sound)
1149                                 m_game_ui->showTranslatedStatusText("Sound muted");
1150                         else
1151                                 m_game_ui->showTranslatedStatusText("Sound unmuted");
1152                 } else {
1153                         m_game_ui->showTranslatedStatusText("Sound system is disabled");
1154                 }
1155         } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1156                 if (g_settings->getBool("enable_sound")) {
1157                         float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1158                         g_settings->setFloat("sound_volume", new_volume);
1159                         std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1160                         m_game_ui->showStatusText(msg);
1161                 } else {
1162                         m_game_ui->showTranslatedStatusText("Sound system is disabled");
1163                 }
1164         } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1165                 if (g_settings->getBool("enable_sound")) {
1166                         float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1167                         g_settings->setFloat("sound_volume", new_volume);
1168                         std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1169                         m_game_ui->showStatusText(msg);
1170                 } else {
1171                         m_game_ui->showTranslatedStatusText("Sound system is disabled");
1172                 }
1173 #else
1174         } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1175                         || wasKeyDown(KeyType::DEC_VOLUME)) {
1176                 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1177 #endif
1178         } else if (wasKeyDown(KeyType::CINEMATIC)) {
1179                 toggleCinematic();
1180         } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1181                 client->makeScreenshot();
1182         } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
1183                 toggleBlockBounds();
1184         } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1185                 m_game_ui->toggleHud();
1186         } else if (wasKeyDown(KeyType::MINIMAP)) {
1187                 toggleMinimap(isKeyDown(KeyType::SNEAK));
1188         } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1189                 m_game_ui->toggleChat();
1190         } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1191                 toggleFog();
1192         } else if (wasKeyDown(KeyType::TOGGLE_CHEAT_MENU)) {
1193                 m_game_ui->toggleCheatMenu();
1194         } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1195                 toggleUpdateCamera();
1196         } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1197                 toggleDebug();
1198         } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1199                 m_game_ui->toggleProfiler();
1200         } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1201                 increaseViewRange();
1202         } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1203                 decreaseViewRange();
1204         } else if (wasKeyDown(KeyType::RANGESELECT)) {
1205                 toggleFullViewRange();
1206         } else if (wasKeyDown(KeyType::ZOOM)) {
1207                 checkZoomEnabled();
1208         } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1209                 quicktune->next();
1210         } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1211                 quicktune->prev();
1212         } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1213                 quicktune->inc();
1214         } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1215                 quicktune->dec();
1216         }
1217
1218         if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1219                 runData.reset_jump_timer = false;
1220                 runData.jump_timer = 0.0f;
1221         }
1222
1223         if (quicktune->hasMessage()) {
1224                 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
1225         }
1226 }
1227
1228 void Game::processItemSelection(u16 *new_playeritem)
1229 {
1230         LocalPlayer *player = client->getEnv().getLocalPlayer();
1231
1232         /* Item selection using mouse wheel
1233          */
1234         *new_playeritem = player->getWieldIndex();
1235
1236         s32 wheel = input->getMouseWheel();
1237         u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
1238                     player->hud_hotbar_itemcount - 1);
1239
1240         s32 dir = wheel;
1241
1242         if (wasKeyDown(KeyType::HOTBAR_NEXT))
1243                 dir = -1;
1244
1245         if (wasKeyDown(KeyType::HOTBAR_PREV))
1246                 dir = 1;
1247
1248         if (dir < 0)
1249                 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
1250         else if (dir > 0)
1251                 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
1252         // else dir == 0
1253
1254         /* Item selection using hotbar slot keys
1255          */
1256         for (u16 i = 0; i <= max_item; i++) {
1257                 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
1258                         *new_playeritem = i;
1259                         break;
1260                 }
1261         }
1262 }
1263
1264
1265 void Game::dropSelectedItem(bool single_item)
1266 {
1267         IDropAction *a = new IDropAction();
1268         a->count = single_item ? 1 : 0;
1269         a->from_inv.setCurrentPlayer();
1270         a->from_list = "main";
1271         a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
1272         client->inventoryAction(a);
1273 }
1274
1275
1276 void Game::openInventory()
1277 {
1278         /*
1279          * Don't permit to open inventory is CAO or player doesn't exists.
1280          * This prevent showing an empty inventory at player load
1281          */
1282
1283         LocalPlayer *player = client->getEnv().getLocalPlayer();
1284         if (!player || !player->getCAO())
1285                 return;
1286
1287         infostream << "Game: Launching inventory" << std::endl;
1288
1289         PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
1290
1291         InventoryLocation inventoryloc;
1292         inventoryloc.setCurrentPlayer();
1293
1294         if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
1295                 delete fs_src;
1296                 return;
1297         }
1298
1299         if (fs_src->getForm().empty()) {
1300                 delete fs_src;
1301                 return;
1302         }
1303
1304         TextDest *txt_dst = new TextDestPlayerInventory(client);
1305         auto *&formspec = m_game_ui->updateFormspec("");
1306         GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
1307                 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
1308
1309         formspec->setFormSpec(fs_src->getForm(), inventoryloc);
1310 }
1311
1312 void Game::openEnderchest()
1313 {
1314         LocalPlayer *player = client->getEnv().getLocalPlayer();
1315         if (!player || !player->getCAO())
1316                 return;
1317
1318         infostream << "Game: Launching special inventory" << std::endl;
1319
1320         if (client->modsLoaded())
1321                 client->getScript()->open_enderchest();
1322 }
1323
1324
1325 void Game::openConsole(float scale, const wchar_t *line)
1326 {
1327         assert(scale > 0.0f && scale <= 1.0f);
1328
1329 #ifdef __ANDROID__
1330         porting::showInputDialog(gettext("ok"), "", "", 2);
1331         m_android_chat_open = true;
1332 #else
1333         if (gui_chat_console->isOpenInhibited())
1334                 return;
1335         gui_chat_console->openConsole(scale);
1336         if (line) {
1337                 gui_chat_console->setCloseOnEnter(true);
1338                 gui_chat_console->replaceAndAddToHistory(line);
1339         }
1340 #endif
1341 }
1342
1343 #ifdef __ANDROID__
1344 void Game::handleAndroidChatInput()
1345 {
1346         if (m_android_chat_open && porting::getInputDialogState() == 0) {
1347                 std::string text = porting::getInputDialogValue();
1348                 client->typeChatMessage(utf8_to_wide(text));
1349                 m_android_chat_open = false;
1350         }
1351 }
1352 #endif
1353
1354
1355 void Game::toggleFreeMove()
1356 {
1357         bool free_move = !g_settings->getBool("free_move");
1358         g_settings->set("free_move", bool_to_cstr(free_move));
1359
1360         if (free_move) {
1361                 if (client->checkPrivilege("fly")) {
1362                         m_game_ui->showTranslatedStatusText("Fly mode enabled");
1363                 } else {
1364                         m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
1365                 }
1366         } else {
1367                 m_game_ui->showTranslatedStatusText("Fly mode disabled");
1368         }
1369 }
1370
1371 void Game::toggleFreeMoveAlt()
1372 {
1373         if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
1374                 toggleFreeMove();
1375
1376         runData.reset_jump_timer = true;
1377 }
1378
1379
1380 void Game::togglePitchMove()
1381 {
1382         bool pitch_move = !g_settings->getBool("pitch_move");
1383         g_settings->set("pitch_move", bool_to_cstr(pitch_move));
1384
1385         if (pitch_move) {
1386                 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
1387         } else {
1388                 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
1389         }
1390 }
1391
1392
1393 void Game::toggleFast()
1394 {
1395         bool fast_move = !g_settings->getBool("fast_move");
1396         bool has_fast_privs = client->checkPrivilege("fast");
1397         g_settings->set("fast_move", bool_to_cstr(fast_move));
1398
1399         if (fast_move) {
1400                 if (has_fast_privs) {
1401                         m_game_ui->showTranslatedStatusText("Fast mode enabled");
1402                 } else {
1403                         m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
1404                 }
1405         } else {
1406                 m_game_ui->showTranslatedStatusText("Fast mode disabled");
1407         }
1408
1409 #ifdef HAVE_TOUCHSCREENGUI
1410         m_cache_hold_aux1 = fast_move && has_fast_privs;
1411 #endif
1412 }
1413
1414
1415 void Game::toggleNoClip()
1416 {
1417         bool noclip = !g_settings->getBool("noclip");
1418         g_settings->set("noclip", bool_to_cstr(noclip));
1419
1420         if (noclip) {
1421                 if (client->checkPrivilege("noclip")) {
1422                         m_game_ui->showTranslatedStatusText("Noclip mode enabled");
1423                 } else {
1424                         m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
1425                 }
1426         } else {
1427                 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
1428         }
1429 }
1430
1431 void Game::toggleKillaura()
1432 {
1433         bool killaura = ! g_settings->getBool("killaura");
1434         g_settings->set("killaura", bool_to_cstr(killaura));
1435
1436         if (killaura) {
1437                 m_game_ui->showTranslatedStatusText("Killaura enabled");
1438         } else {
1439                 m_game_ui->showTranslatedStatusText("Killaura disabled");
1440         }
1441 }
1442
1443 void Game::toggleFreecam()
1444 {
1445         bool freecam = ! g_settings->getBool("freecam");
1446         g_settings->set("freecam", bool_to_cstr(freecam));
1447
1448         if (freecam) {
1449                 m_game_ui->showTranslatedStatusText("Freecam enabled");
1450         } else {
1451                 m_game_ui->showTranslatedStatusText("Freecam disabled");
1452         }
1453 }
1454
1455 void Game::toggleScaffold()
1456 {
1457         bool scaffold = ! g_settings->getBool("scaffold");
1458         g_settings->set("scaffold", bool_to_cstr(scaffold));
1459
1460         if (scaffold) {
1461                 m_game_ui->showTranslatedStatusText("Scaffold enabled");
1462         } else {
1463                 m_game_ui->showTranslatedStatusText("Scaffold disabled");
1464         }
1465 }
1466
1467 void Game::toggleCinematic()
1468 {
1469         bool cinematic = !g_settings->getBool("cinematic");
1470         g_settings->set("cinematic", bool_to_cstr(cinematic));
1471
1472         if (cinematic)
1473                 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
1474         else
1475                 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
1476 }
1477
1478 void Game::toggleBlockBounds()
1479 {
1480         LocalPlayer *player = client->getEnv().getLocalPlayer();
1481         if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
1482                 m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
1483                 return;
1484         }
1485         enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
1486         switch (newmode) {
1487                 case Hud::BLOCK_BOUNDS_OFF:
1488                         m_game_ui->showTranslatedStatusText("Block bounds hidden");
1489                         break;
1490                 case Hud::BLOCK_BOUNDS_CURRENT:
1491                         m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
1492                         break;
1493                 case Hud::BLOCK_BOUNDS_NEAR:
1494                         m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
1495                         break;
1496                 case Hud::BLOCK_BOUNDS_MAX:
1497                         m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
1498                         break;
1499                 default:
1500                         break;
1501         }
1502 }
1503
1504 // Autoforward by toggling continuous forward.
1505 void Game::toggleAutoforward()
1506 {
1507         bool autorun_enabled = !g_settings->getBool("continuous_forward");
1508         g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
1509
1510         if (autorun_enabled)
1511                 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
1512         else
1513                 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
1514 }
1515
1516 void Game::toggleMinimap(bool shift_pressed)
1517 {
1518         if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
1519                 return;
1520
1521         if (shift_pressed)
1522                 mapper->toggleMinimapShape();
1523         else
1524                 mapper->nextMode();
1525
1526         // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
1527
1528         // Not so satisying code to keep compatibility with old fixed mode system
1529         // -->
1530         u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
1531
1532         if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
1533                 m_game_ui->m_flags.show_minimap = false;
1534         } else {
1535
1536         // If radar is disabled, try to find a non radar mode or fall back to 0
1537                 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
1538                         while (mapper->getModeIndex() &&
1539                                         mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
1540                                 mapper->nextMode();
1541
1542                 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
1543                                 MINIMAP_TYPE_OFF;
1544         }
1545         // <--
1546         // End of 'not so satifying code'
1547         if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
1548                         (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
1549                 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
1550         else
1551                 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
1552 }
1553
1554 void Game::toggleFog()
1555 {
1556         bool fog_enabled = g_settings->getBool("enable_fog");
1557         g_settings->setBool("enable_fog", !fog_enabled);
1558         if (fog_enabled)
1559                 m_game_ui->showTranslatedStatusText("Fog disabled");
1560         else
1561                 m_game_ui->showTranslatedStatusText("Fog enabled");
1562 }
1563
1564
1565 void Game::toggleDebug()
1566 {
1567         LocalPlayer *player = client->getEnv().getLocalPlayer();
1568         bool has_debug = client->checkPrivilege("debug");
1569         bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
1570         // Initial: No debug info
1571         // 1x toggle: Debug text
1572         // 2x toggle: Debug text with profiler graph
1573         // 3x toggle: Debug text and wireframe (needs "debug" priv)
1574         // Next toggle: Back to initial
1575         //
1576         // The debug text can be in 2 modes: minimal and basic.
1577         // * Minimal: Only technical client info that not gameplay-relevant
1578         // * Basic: Info that might give gameplay advantage, e.g. pos, angle
1579         // Basic mode is used when player has the debug HUD flag set,
1580         // otherwise the Minimal mode is used.
1581         if (!m_game_ui->m_flags.show_minimal_debug) {
1582                 m_game_ui->m_flags.show_minimal_debug = true;
1583                 if (has_basic_debug)
1584                         m_game_ui->m_flags.show_basic_debug = true;
1585                 m_game_ui->m_flags.show_profiler_graph = false;
1586                 draw_control->show_wireframe = false;
1587                 m_game_ui->showTranslatedStatusText("Debug info shown");
1588         } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
1589                 if (has_basic_debug)
1590                         m_game_ui->m_flags.show_basic_debug = true;
1591                 m_game_ui->m_flags.show_profiler_graph = true;
1592                 m_game_ui->showTranslatedStatusText("Profiler graph shown");
1593         } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
1594                 if (has_basic_debug)
1595                         m_game_ui->m_flags.show_basic_debug = true;
1596                 m_game_ui->m_flags.show_profiler_graph = false;
1597                 draw_control->show_wireframe = true;
1598                 m_game_ui->showTranslatedStatusText("Wireframe shown");
1599         } else {
1600                 m_game_ui->m_flags.show_minimal_debug = false;
1601                 m_game_ui->m_flags.show_basic_debug = false;
1602                 m_game_ui->m_flags.show_profiler_graph = false;
1603                 draw_control->show_wireframe = false;
1604                 if (has_debug) {
1605                         m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
1606                 } else {
1607                         m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
1608                 }
1609         }
1610 }
1611
1612
1613 void Game::toggleUpdateCamera()
1614 {
1615         if (g_settings->getBool("freecam"))
1616                 return;
1617         m_flags.disable_camera_update = !m_flags.disable_camera_update;
1618         if (m_flags.disable_camera_update)
1619                 m_game_ui->showTranslatedStatusText("Camera update disabled");
1620         else
1621                 m_game_ui->showTranslatedStatusText("Camera update enabled");
1622 }
1623
1624
1625 void Game::increaseViewRange()
1626 {
1627         s16 range = g_settings->getS16("viewing_range");
1628         s16 range_new = range + 10;
1629
1630         if (range_new > 4000) {
1631                 range_new = 4000;
1632                 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
1633                 m_game_ui->showStatusText(msg);
1634         } else {
1635                 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
1636                 m_game_ui->showStatusText(msg);
1637         }
1638         g_settings->set("viewing_range", itos(range_new));
1639 }
1640
1641
1642 void Game::decreaseViewRange()
1643 {
1644         s16 range = g_settings->getS16("viewing_range");
1645         s16 range_new = range - 10;
1646
1647         if (range_new < 20) {
1648                 range_new = 20;
1649                 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
1650                 m_game_ui->showStatusText(msg);
1651         } else {
1652                 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
1653                 m_game_ui->showStatusText(msg);
1654         }
1655         g_settings->set("viewing_range", itos(range_new));
1656 }
1657
1658
1659 void Game::toggleFullViewRange()
1660 {
1661         draw_control->range_all = !draw_control->range_all;
1662         if (draw_control->range_all)
1663                 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
1664         else
1665                 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
1666 }
1667
1668
1669 void Game::checkZoomEnabled()
1670 {
1671         LocalPlayer *player = client->getEnv().getLocalPlayer();
1672         if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
1673                 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
1674 }
1675
1676 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
1677 {
1678         if ((device->isWindowActive() && device->isWindowFocused()
1679                         && !isMenuActive()) || input->isRandom()) {
1680
1681 #ifndef __ANDROID__
1682                 if (!input->isRandom()) {
1683                         // Mac OSX gets upset if this is set every frame
1684                         if (device->getCursorControl()->isVisible())
1685                                 device->getCursorControl()->setVisible(false);
1686                 }
1687 #endif
1688
1689                 if (m_first_loop_after_window_activation) {
1690                         m_first_loop_after_window_activation = false;
1691
1692                         input->setMousePos(driver->getScreenSize().Width / 2,
1693                                 driver->getScreenSize().Height / 2);
1694                 } else {
1695                         updateCameraOrientation(cam, dtime);
1696                 }
1697
1698         } else {
1699
1700 #ifndef ANDROID
1701                 // Mac OSX gets upset if this is set every frame
1702                 if (!device->getCursorControl()->isVisible())
1703                         device->getCursorControl()->setVisible(true);
1704 #endif
1705
1706                 m_first_loop_after_window_activation = true;
1707
1708         }
1709 }
1710
1711 // Get the factor to multiply with sensitivity to get the same mouse/joystick
1712 // responsiveness independently of FOV.
1713 f32 Game::getSensitivityScaleFactor() const
1714 {
1715         f32 fov_y = client->getCamera()->getFovY();
1716
1717         // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
1718         // 16:9 aspect ratio to minimize disruption of existing sensitivity
1719         // settings.
1720         return tan(fov_y / 2.0f) * 1.3763818698f;
1721 }
1722
1723 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
1724 {
1725 #ifdef HAVE_TOUCHSCREENGUI
1726         if (g_touchscreengui) {
1727                 cam->camera_yaw   += g_touchscreengui->getYawChange();
1728                 cam->camera_pitch  = g_touchscreengui->getPitch();
1729         } else {
1730 #endif
1731                 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
1732                 v2s32 dist = input->getMousePos() - center;
1733
1734                 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
1735                         dist.Y = -dist.Y;
1736                 }
1737
1738                 f32 sens_scale = getSensitivityScaleFactor();
1739                 cam->camera_yaw   -= dist.X * m_cache_mouse_sensitivity * sens_scale;
1740                 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
1741
1742                 if (dist.X != 0 || dist.Y != 0)
1743                         input->setMousePos(center.X, center.Y);
1744 #ifdef HAVE_TOUCHSCREENGUI
1745         }
1746 #endif
1747
1748         if (m_cache_enable_joysticks) {
1749                 f32 sens_scale = getSensitivityScaleFactor();
1750                 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
1751                 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
1752                 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
1753         }
1754
1755         cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
1756 }
1757
1758
1759 void Game::updatePlayerControl(const CameraOrientation &cam)
1760 {
1761         LocalPlayer *player = client->getEnv().getLocalPlayer();
1762
1763         //TimeTaker tt("update player control", NULL, PRECISION_NANO);
1764
1765         PlayerControl control(
1766                 isKeyDown(KeyType::FORWARD),
1767                 isKeyDown(KeyType::BACKWARD),
1768                 isKeyDown(KeyType::LEFT),
1769                 isKeyDown(KeyType::RIGHT),
1770                 isKeyDown(KeyType::JUMP) || player->getAutojump(),
1771                 isKeyDown(KeyType::AUX1),
1772                 isKeyDown(KeyType::SNEAK),
1773                 isKeyDown(KeyType::ZOOM),
1774                 isKeyDown(KeyType::DIG),
1775                 isKeyDown(KeyType::PLACE),
1776                 cam.camera_pitch,
1777                 cam.camera_yaw,
1778                 input->getMovementSpeed(),
1779                 input->getMovementDirection()
1780         );
1781
1782         // autoforward if set: move towards pointed position at maximum speed
1783         if (player->getPlayerSettings().continuous_forward &&
1784                         client->activeObjectsReceived() && !player->isDead()) {
1785                 control.movement_speed = 1.0f;
1786                 control.movement_direction = 0.0f;
1787         }
1788
1789 #ifdef HAVE_TOUCHSCREENGUI
1790         /* For touch, simulate holding down AUX1 (fast move) if the user has
1791          * the fast_move setting toggled on. If there is an aux1 key defined for
1792          * touch then its meaning is inverted (i.e. holding aux1 means walk and
1793          * not fast)
1794          */
1795         if (m_cache_hold_aux1) {
1796                 control.aux1 = control.aux1 ^ true;
1797         }
1798 #endif
1799
1800         client->setPlayerControl(control);
1801
1802         //tt.stop();
1803 }
1804
1805
1806 inline void Game::step(f32 *dtime)
1807 {
1808         bool can_be_and_is_paused =
1809                         (simple_singleplayer_mode && g_menumgr.pausesGame());
1810
1811         if (can_be_and_is_paused) { // This is for a singleplayer server
1812                 *dtime = 0;             // No time passes
1813         } else {
1814                 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
1815                         resumeAnimation();
1816
1817                 if (server)
1818                         server->step(*dtime);
1819
1820                 client->step(*dtime);
1821         }
1822 }
1823
1824 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
1825         if (!node)
1826                 return;
1827         for (auto &&child: node->getChildren())
1828                 pauseNodeAnimation(paused, child);
1829         if (node->getType() != scene::ESNT_ANIMATED_MESH)
1830                 return;
1831         auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
1832         float speed = animated_node->getAnimationSpeed();
1833         if (!speed)
1834                 return;
1835         paused.push_back({grab(animated_node), speed});
1836         animated_node->setAnimationSpeed(0.0f);
1837 }
1838
1839 void Game::pauseAnimation()
1840 {
1841         pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
1842 }
1843
1844 void Game::resumeAnimation()
1845 {
1846         for (auto &&pair: paused_animated_nodes)
1847                 pair.first->setAnimationSpeed(pair.second);
1848         paused_animated_nodes.clear();
1849 }
1850
1851 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
1852         {&Game::handleClientEvent_None},
1853         {&Game::handleClientEvent_PlayerDamage},
1854         {&Game::handleClientEvent_PlayerForceMove},
1855         {&Game::handleClientEvent_Deathscreen},
1856         {&Game::handleClientEvent_ShowFormSpec},
1857         {&Game::handleClientEvent_ShowLocalFormSpec},
1858         {&Game::handleClientEvent_HandleParticleEvent},
1859         {&Game::handleClientEvent_HandleParticleEvent},
1860         {&Game::handleClientEvent_HandleParticleEvent},
1861         {&Game::handleClientEvent_HudAdd},
1862         {&Game::handleClientEvent_HudRemove},
1863         {&Game::handleClientEvent_HudChange},
1864         {&Game::handleClientEvent_SetSky},
1865         {&Game::handleClientEvent_SetSun},
1866         {&Game::handleClientEvent_SetMoon},
1867         {&Game::handleClientEvent_SetStars},
1868         {&Game::handleClientEvent_OverrideDayNigthRatio},
1869         {&Game::handleClientEvent_CloudParams},
1870 };
1871
1872 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
1873 {
1874         FATAL_ERROR("ClientEvent type None received");
1875 }
1876
1877 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
1878 {
1879         if (client->modsLoaded())
1880                 client->getScript()->on_damage_taken(event->player_damage.amount);
1881
1882         // Damage flash and hurt tilt are not used at death
1883         if (client->getHP() > 0) {
1884                 LocalPlayer *player = client->getEnv().getLocalPlayer();
1885
1886                 f32 hp_max = player->getCAO() ?
1887                         player->getCAO()->getProperties()->hp_max : PLAYER_MAX_HP_DEFAULT;
1888                 f32 damage_ratio = event->player_damage.amount / hp_max;
1889
1890                 runData.damage_flash += 95.0f + 64.f * damage_ratio;
1891                 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
1892
1893                 player->hurt_tilt_timer = 1.5f;
1894                 player->hurt_tilt_strength =
1895                         rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
1896         }
1897
1898         // Play damage sound
1899         client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
1900 }
1901
1902 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
1903 {
1904         cam->camera_yaw = event->player_force_move.yaw;
1905         cam->camera_pitch = event->player_force_move.pitch;
1906 }
1907
1908 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
1909 {
1910         // If client scripting is enabled, deathscreen is handled by CSM code in
1911         // builtin/client/init.lua
1912         if (client->modsLoaded())
1913                 client->getScript()->on_death();
1914         else
1915                 showDeathFormspec();
1916
1917         /* Handle visualization */
1918         LocalPlayer *player = client->getEnv().getLocalPlayer();
1919         runData.damage_flash = 0;
1920         player->hurt_tilt_timer = 0;
1921         player->hurt_tilt_strength = 0;
1922 }
1923
1924 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
1925 {
1926         if (event->show_formspec.formspec->empty()) {
1927                 auto formspec = m_game_ui->getFormspecGUI();
1928                 if (formspec && (event->show_formspec.formname->empty()
1929                                 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
1930                         formspec->quitMenu();
1931                 }
1932         } else {
1933                 FormspecFormSource *fs_src =
1934                         new FormspecFormSource(*(event->show_formspec.formspec));
1935                 TextDestPlayerInventory *txt_dst =
1936                         new TextDestPlayerInventory(client, *(event->show_formspec.formname));
1937
1938                 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
1939                 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
1940                         &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
1941         }
1942
1943         delete event->show_formspec.formspec;
1944         delete event->show_formspec.formname;
1945 }
1946
1947 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
1948 {
1949         if (event->show_formspec.formspec->empty()) {
1950                 auto formspec = m_game_ui->getFormspecGUI();
1951                 if (formspec && (event->show_formspec.formname->empty()
1952                                 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
1953                         formspec->quitMenu();
1954                 }
1955         } else {
1956                 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
1957                 LocalFormspecHandler *txt_dst =
1958                         new LocalFormspecHandler(*event->show_formspec.formname, client);
1959                 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(), &input->joystick,
1960                         fs_src, txt_dst, client->getFormspecPrepend(), sound);
1961         }
1962
1963         delete event->show_formspec.formspec;
1964         delete event->show_formspec.formname;
1965 }
1966
1967 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
1968                 CameraOrientation *cam)
1969 {
1970         LocalPlayer *player = client->getEnv().getLocalPlayer();
1971         client->getParticleManager()->handleParticleEvent(event, client, player);
1972 }
1973
1974 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
1975 {
1976         LocalPlayer *player = client->getEnv().getLocalPlayer();
1977
1978         u32 server_id = event->hudadd->server_id;
1979         // ignore if we already have a HUD with that ID
1980         auto i = m_hud_server_to_client.find(server_id);
1981         if (i != m_hud_server_to_client.end()) {
1982                 delete event->hudadd;
1983                 return;
1984         }
1985
1986         HudElement *e = new HudElement;
1987         e->type   = static_cast<HudElementType>(event->hudadd->type);
1988         e->pos    = event->hudadd->pos;
1989         e->name   = event->hudadd->name;
1990         e->scale  = event->hudadd->scale;
1991         e->text   = event->hudadd->text;
1992         e->number = event->hudadd->number;
1993         e->item   = event->hudadd->item;
1994         e->dir    = event->hudadd->dir;
1995         e->align  = event->hudadd->align;
1996         e->offset = event->hudadd->offset;
1997         e->world_pos = event->hudadd->world_pos;
1998         e->size      = event->hudadd->size;
1999         e->z_index   = event->hudadd->z_index;
2000         e->text2     = event->hudadd->text2;
2001         e->style     = event->hudadd->style;
2002         m_hud_server_to_client[server_id] = player->addHud(e);
2003
2004         delete event->hudadd;
2005 }
2006
2007 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2008 {
2009         LocalPlayer *player = client->getEnv().getLocalPlayer();
2010
2011         auto i = m_hud_server_to_client.find(event->hudrm.id);
2012         if (i != m_hud_server_to_client.end()) {
2013                 HudElement *e = player->removeHud(i->second);
2014                 delete e;
2015                 m_hud_server_to_client.erase(i);
2016         }
2017
2018 }
2019
2020 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2021 {
2022         LocalPlayer *player = client->getEnv().getLocalPlayer();
2023
2024         HudElement *e = nullptr;
2025
2026         auto i = m_hud_server_to_client.find(event->hudchange->id);
2027         if (i != m_hud_server_to_client.end()) {
2028                 e = player->getHud(i->second);
2029         }
2030
2031         if (e == nullptr) {
2032                 delete event->hudchange;
2033                 return;
2034         }
2035
2036 #define CASE_SET(statval, prop, dataprop) \
2037         case statval: \
2038                 e->prop = event->hudchange->dataprop; \
2039                 break
2040
2041         switch (event->hudchange->stat) {
2042                 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2043
2044                 CASE_SET(HUD_STAT_NAME, name, sdata);
2045
2046                 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2047
2048                 CASE_SET(HUD_STAT_TEXT, text, sdata);
2049
2050                 CASE_SET(HUD_STAT_NUMBER, number, data);
2051
2052                 CASE_SET(HUD_STAT_ITEM, item, data);
2053
2054                 CASE_SET(HUD_STAT_DIR, dir, data);
2055
2056                 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2057
2058                 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2059
2060                 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2061
2062                 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2063
2064                 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2065
2066                 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2067
2068                 CASE_SET(HUD_STAT_STYLE, style, data);
2069         }
2070
2071 #undef CASE_SET
2072
2073         delete event->hudchange;
2074 }
2075
2076 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2077 {
2078         sky->setVisible(false);
2079         // Whether clouds are visible in front of a custom skybox.
2080         sky->setCloudsEnabled(event->set_sky->clouds);
2081
2082         if (skybox) {
2083                 skybox->remove();
2084                 skybox = NULL;
2085         }
2086         // Clear the old textures out in case we switch rendering type.
2087         sky->clearSkyboxTextures();
2088         // Handle according to type
2089         if (event->set_sky->type == "regular") {
2090                 // Shows the mesh skybox
2091                 sky->setVisible(true);
2092                 // Update mesh based skybox colours if applicable.
2093                 sky->setSkyColors(event->set_sky->sky_color);
2094                 sky->setHorizonTint(
2095                         event->set_sky->fog_sun_tint,
2096                         event->set_sky->fog_moon_tint,
2097                         event->set_sky->fog_tint_type
2098                 );
2099         } else if (event->set_sky->type == "skybox" &&
2100                         event->set_sky->textures.size() == 6) {
2101                 // Disable the dyanmic mesh skybox:
2102                 sky->setVisible(false);
2103                 // Set fog colors:
2104                 sky->setFallbackBgColor(event->set_sky->bgcolor);
2105                 // Set sunrise and sunset fog tinting:
2106                 sky->setHorizonTint(
2107                         event->set_sky->fog_sun_tint,
2108                         event->set_sky->fog_moon_tint,
2109                         event->set_sky->fog_tint_type
2110                 );
2111                 // Add textures to skybox.
2112                 for (int i = 0; i < 6; i++)
2113                         sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2114         } else {
2115                 // Handle everything else as plain color.
2116                 if (event->set_sky->type != "plain")
2117                         infostream << "Unknown sky type: "
2118                                 << (event->set_sky->type) << std::endl;
2119                 sky->setVisible(false);
2120                 sky->setFallbackBgColor(event->set_sky->bgcolor);
2121                 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2122                 sky->setHorizonTint(
2123                         event->set_sky->bgcolor,
2124                         event->set_sky->bgcolor,
2125                         "custom"
2126                 );
2127         }
2128
2129         delete event->set_sky;
2130 }
2131
2132 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2133 {
2134         sky->setSunVisible(event->sun_params->visible);
2135         sky->setSunTexture(event->sun_params->texture,
2136                 event->sun_params->tonemap, texture_src);
2137         sky->setSunScale(event->sun_params->scale);
2138         sky->setSunriseVisible(event->sun_params->sunrise_visible);
2139         sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2140         delete event->sun_params;
2141 }
2142
2143 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2144 {
2145         sky->setMoonVisible(event->moon_params->visible);
2146         sky->setMoonTexture(event->moon_params->texture,
2147                 event->moon_params->tonemap, texture_src);
2148         sky->setMoonScale(event->moon_params->scale);
2149         delete event->moon_params;
2150 }
2151
2152 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2153 {
2154         sky->setStarsVisible(event->star_params->visible);
2155         sky->setStarCount(event->star_params->count);
2156         sky->setStarColor(event->star_params->starcolor);
2157         sky->setStarScale(event->star_params->scale);
2158         delete event->star_params;
2159 }
2160
2161 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2162                 CameraOrientation *cam)
2163 {
2164         client->getEnv().setDayNightRatioOverride(
2165                 event->override_day_night_ratio.do_override,
2166                 event->override_day_night_ratio.ratio_f * 1000.0f);
2167 }
2168
2169 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2170 {
2171         if (!clouds)
2172                 return;
2173
2174         clouds->setDensity(event->cloud_params.density);
2175         clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2176         clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2177         clouds->setHeight(event->cloud_params.height);
2178         clouds->setThickness(event->cloud_params.thickness);
2179         clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2180 }
2181
2182 void Game::processClientEvents(CameraOrientation *cam)
2183 {
2184         while (client->hasClientEvents()) {
2185                 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2186                 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2187                 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2188                 (this->*evHandler.handler)(event.get(), cam);
2189         }
2190 }
2191
2192 void Game::updateChat(f32 dtime)
2193 {
2194         // Get new messages from error log buffer
2195         while (!m_chat_log_buf.empty())
2196                 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2197
2198         // Get new messages from client
2199         std::wstring message;
2200         while (client->getChatMessage(message)) {
2201                 chat_backend->addUnparsedMessage(message);
2202         }
2203
2204         // Remove old messages
2205         chat_backend->step(dtime);
2206
2207         // Display all messages in a static text element
2208         auto &buf = chat_backend->getRecentBuffer();
2209         if (buf.getLinesModified()) {
2210                 buf.resetLinesModified();
2211                 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
2212         }
2213
2214         // Make sure that the size is still correct
2215         m_game_ui->updateChatSize();
2216 }
2217
2218 void Game::updateCamera(f32 dtime)
2219 {
2220         LocalPlayer *player = client->getEnv().getLocalPlayer();
2221
2222         /*
2223                 For interaction purposes, get info about the held item
2224                 - What item is it?
2225                 - Is it a usable item?
2226                 - Can it point to liquids?
2227         */
2228         ItemStack playeritem;
2229         {
2230                 ItemStack selected, hand;
2231                 playeritem = player->getWieldedItem(&selected, &hand);
2232         }
2233
2234         ToolCapabilities playeritem_toolcap =
2235                 playeritem.getToolCapabilities(itemdef_manager);
2236
2237         v3s16 old_camera_offset = camera->getOffset();
2238
2239         if (wasKeyDown(KeyType::CAMERA_MODE) && ! g_settings->getBool("freecam")) {
2240                 camera->toggleCameraMode();
2241                 updatePlayerCAOVisibility();
2242         }
2243
2244         float full_punch_interval = playeritem_toolcap.full_punch_interval;
2245         float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2246
2247         tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2248         camera->update(player, dtime, tool_reload_ratio);
2249         camera->step(dtime);
2250
2251         v3f camera_position = camera->getPosition();
2252         v3f camera_direction = camera->getDirection();
2253         f32 camera_fov = camera->getFovMax();
2254         v3s16 camera_offset = camera->getOffset();
2255
2256         m_camera_offset_changed = (camera_offset != old_camera_offset);
2257
2258         if (!m_flags.disable_camera_update) {
2259                 client->getEnv().getClientMap().updateCamera(camera_position,
2260                                 camera_direction, camera_fov, camera_offset);
2261
2262                 if (m_camera_offset_changed) {
2263                         client->updateCameraOffset(camera_offset);
2264                         client->getEnv().updateCameraOffset(camera_offset);
2265
2266                         if (clouds)
2267                                 clouds->updateCameraOffset(camera_offset);
2268                 }
2269         }
2270 }
2271
2272 void Game::updatePlayerCAOVisibility()
2273 {
2274         // Make the player visible depending on camera mode.
2275         LocalPlayer *player = client->getEnv().getLocalPlayer();
2276         GenericCAO *playercao = player->getCAO();
2277         if (!playercao)
2278                 return;
2279         playercao->updateMeshCulling();
2280         bool is_visible = camera->getCameraMode() > CAMERA_MODE_FIRST || g_settings->getBool("freecam");
2281         playercao->setChildrenVisible(is_visible);
2282 }
2283
2284 void Game::updateSound(f32 dtime)
2285 {
2286         // Update sound listener
2287         v3s16 camera_offset = camera->getOffset();
2288         sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
2289                               v3f(0, 0, 0), // velocity
2290                               camera->getDirection(),
2291                               camera->getCameraNode()->getUpVector());
2292
2293         bool mute_sound = g_settings->getBool("mute_sound");
2294         if (mute_sound) {
2295                 sound->setListenerGain(0.0f);
2296         } else {
2297                 // Check if volume is in the proper range, else fix it.
2298                 float old_volume = g_settings->getFloat("sound_volume");
2299                 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
2300                 sound->setListenerGain(new_volume);
2301
2302                 if (old_volume != new_volume) {
2303                         g_settings->setFloat("sound_volume", new_volume);
2304                 }
2305         }
2306
2307         LocalPlayer *player = client->getEnv().getLocalPlayer();
2308
2309         // Tell the sound maker whether to make footstep sounds
2310         soundmaker->makes_footstep_sound = player->makes_footstep_sound;
2311
2312         //      Update sound maker
2313         if (player->makes_footstep_sound)
2314                 soundmaker->step(dtime);
2315
2316         ClientMap &map = client->getEnv().getClientMap();
2317         MapNode n = map.getNode(player->getFootstepNodePos());
2318         soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
2319 }
2320
2321
2322 void Game::processPlayerInteraction(f32 dtime, bool show_hud)
2323 {
2324         LocalPlayer *player = client->getEnv().getLocalPlayer();
2325
2326         const v3f camera_direction = camera->getDirection();
2327         const v3s16 camera_offset  = camera->getOffset();
2328
2329         /*
2330                 Calculate what block is the crosshair pointing to
2331         */
2332
2333         ItemStack selected_item, hand_item;
2334         const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
2335
2336         const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
2337         f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
2338
2339
2340         if (g_settings->getBool("reach"))
2341                 d += g_settings->getU16("tool_range");
2342
2343         core::line3d<f32> shootline;
2344
2345         switch (camera->getCameraMode()) {
2346         case CAMERA_MODE_FIRST:
2347                 // Shoot from camera position, with bobbing
2348                 shootline.start = camera->getPosition();
2349                 break;
2350         case CAMERA_MODE_THIRD:
2351                 // Shoot from player head, no bobbing
2352                 shootline.start = camera->getHeadPosition();
2353                 break;
2354         case CAMERA_MODE_THIRD_FRONT:
2355                 shootline.start = camera->getHeadPosition();
2356                 // prevent player pointing anything in front-view
2357                 d = 0;
2358                 break;
2359         }
2360         shootline.end = shootline.start + camera_direction * BS * d;
2361
2362 #ifdef HAVE_TOUCHSCREENGUI
2363
2364         if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
2365                 shootline = g_touchscreengui->getShootline();
2366                 // Scale shootline to the acual distance the player can reach
2367                 shootline.end = shootline.start
2368                         + shootline.getVector().normalize() * BS * d;
2369                 shootline.start += intToFloat(camera_offset, BS);
2370                 shootline.end += intToFloat(camera_offset, BS);
2371         }
2372
2373 #endif
2374
2375         PointedThing pointed = updatePointedThing(shootline,
2376                         selected_def.liquids_pointable,
2377                         !runData.btn_down_for_dig,
2378                         camera_offset);
2379
2380         if (pointed != runData.pointed_old)
2381                 infostream << "Pointing at " << pointed.dump() << std::endl;
2382
2383         // Note that updating the selection mesh every frame is not particularly efficient,
2384         // but the halo rendering code is already inefficient so there's no point in optimizing it here
2385         hud->updateSelectionMesh(camera_offset);
2386
2387         // Allow digging again if button is not pressed
2388         if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
2389                 runData.digging_blocked = false;
2390
2391         /*
2392                 Stop digging when
2393                 - releasing dig button
2394                 - pointing away from node
2395         */
2396         if (runData.digging) {
2397                 if (wasKeyReleased(KeyType::DIG)) {
2398                         infostream << "Dig button released (stopped digging)" << std::endl;
2399                         runData.digging = false;
2400                 } else if (pointed != runData.pointed_old) {
2401                         if (pointed.type == POINTEDTHING_NODE
2402                                         && runData.pointed_old.type == POINTEDTHING_NODE
2403                                         && pointed.node_undersurface
2404                                                         == runData.pointed_old.node_undersurface) {
2405                                 // Still pointing to the same node, but a different face.
2406                                 // Don't reset.
2407                         } else {
2408                                 infostream << "Pointing away from node (stopped digging)" << std::endl;
2409                                 runData.digging = false;
2410                                 hud->updateSelectionMesh(camera_offset);
2411                         }
2412                 }
2413
2414                 if (!runData.digging) {
2415                         client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
2416                         client->setCrack(-1, v3s16(0, 0, 0));
2417                         runData.dig_time = 0.0;
2418                 }
2419         } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
2420                 // Remove e.g. torches faster when clicking instead of holding dig button
2421                 runData.nodig_delay_timer = 0;
2422                 runData.dig_instantly = false;
2423         }
2424
2425         if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
2426                 runData.btn_down_for_dig = false;
2427
2428         runData.punching = false;
2429
2430         soundmaker->m_player_leftpunch_sound.name = "";
2431
2432         // Prepare for repeating, unless we're not supposed to
2433         if ((isKeyDown(KeyType::PLACE) || g_settings->getBool("autoplace")) && !g_settings->getBool("safe_dig_and_place"))
2434                 runData.repeat_place_timer += dtime;
2435         else
2436                 runData.repeat_place_timer = 0;
2437
2438         if (selected_def.usable && isKeyDown(KeyType::DIG)) {
2439                 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
2440                                 !client->getScript()->on_item_use(selected_item, pointed)))
2441                         client->interact(INTERACT_USE, pointed);
2442         } else if (pointed.type == POINTEDTHING_NODE) {
2443                 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
2444         } else if (pointed.type == POINTEDTHING_OBJECT) {
2445                 v3f player_position  = player->getPosition();
2446                 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
2447                 handlePointingAtObject(pointed, tool_item, player_position,
2448                                 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
2449         } else if (isKeyDown(KeyType::DIG)) {
2450                 // When button is held down in air, show continuous animation
2451                 runData.punching = true;
2452                 // Run callback even though item is not usable
2453                 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
2454                         client->getScript()->on_item_use(selected_item, pointed);
2455         } else if (wasKeyPressed(KeyType::PLACE)) {
2456                 handlePointingAtNothing(selected_item);
2457         }
2458
2459         runData.pointed_old = pointed;
2460
2461         if (runData.punching || wasKeyPressed(KeyType::DIG))
2462                 camera->setDigging(0); // dig animation
2463
2464         input->clearWasKeyPressed();
2465         input->clearWasKeyReleased();
2466         // Ensure DIG & PLACE are marked as handled
2467         wasKeyDown(KeyType::DIG);
2468         wasKeyDown(KeyType::PLACE);
2469
2470         input->joystick.clearWasKeyPressed(KeyType::DIG);
2471         input->joystick.clearWasKeyPressed(KeyType::PLACE);
2472
2473         input->joystick.clearWasKeyReleased(KeyType::DIG);
2474         input->joystick.clearWasKeyReleased(KeyType::PLACE);
2475 }
2476
2477
2478 PointedThing Game::updatePointedThing(
2479         const core::line3d<f32> &shootline,
2480         bool liquids_pointable,
2481         bool look_for_object,
2482         const v3s16 &camera_offset)
2483 {
2484         std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
2485         selectionboxes->clear();
2486         hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
2487         static thread_local const bool show_entity_selectionbox = g_settings->getBool(
2488                 "show_entity_selectionbox");
2489
2490         ClientEnvironment &env = client->getEnv();
2491         ClientMap &map = env.getClientMap();
2492         const NodeDefManager *nodedef = map.getNodeDefManager();
2493
2494         runData.selected_object = NULL;
2495         hud->pointing_at_object = false;
2496         RaycastState s(shootline, look_for_object, liquids_pointable, ! g_settings->getBool("dont_point_nodes"));
2497         PointedThing result;
2498         env.continueRaycast(&s, &result);
2499         if (result.type == POINTEDTHING_OBJECT) {
2500                 hud->pointing_at_object = true;
2501
2502                 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
2503                 aabb3f selection_box;
2504                 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
2505                                 runData.selected_object->getSelectionBox(&selection_box)) {
2506                         v3f pos = runData.selected_object->getPosition();
2507                         selectionboxes->push_back(aabb3f(selection_box));
2508                         hud->setSelectionPos(pos, camera_offset);
2509                 }
2510         } else if (result.type == POINTEDTHING_NODE) {
2511                 // Update selection boxes
2512                 MapNode n = map.getNode(result.node_undersurface);
2513                 std::vector<aabb3f> boxes;
2514                 n.getSelectionBoxes(nodedef, &boxes,
2515                         n.getNeighbors(result.node_undersurface, &map));
2516
2517                 f32 d = 0.002 * BS;
2518                 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
2519                         i != boxes.end(); ++i) {
2520                         aabb3f box = *i;
2521                         box.MinEdge -= v3f(d, d, d);
2522                         box.MaxEdge += v3f(d, d, d);
2523                         selectionboxes->push_back(box);
2524                 }
2525                 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
2526                         camera_offset);
2527                 hud->setSelectedFaceNormal(v3f(
2528                         result.intersection_normal.X,
2529                         result.intersection_normal.Y,
2530                         result.intersection_normal.Z));
2531         }
2532
2533         // Update selection mesh light level and vertex colors
2534         if (!selectionboxes->empty()) {
2535                 v3f pf = hud->getSelectionPos();
2536                 v3s16 p = floatToInt(pf, BS);
2537
2538                 // Get selection mesh light level
2539                 MapNode n = map.getNode(p);
2540                 u16 node_light = getInteriorLight(n, -1, nodedef);
2541                 u16 light_level = node_light;
2542
2543                 for (const v3s16 &dir : g_6dirs) {
2544                         n = map.getNode(p + dir);
2545                         node_light = getInteriorLight(n, -1, nodedef);
2546                         if (node_light > light_level)
2547                                 light_level = node_light;
2548                 }
2549
2550                 u32 daynight_ratio = client->getEnv().getDayNightRatio();
2551                 video::SColor c;
2552                 final_color_blend(&c, light_level, daynight_ratio);
2553
2554                 // Modify final color a bit with time
2555                 u32 timer = porting::getTimeMs() % 5000;
2556                 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
2557                 float sin_r = 0.08f * std::sin(timerf);
2558                 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
2559                 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
2560                 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
2561                 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
2562                 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
2563
2564                 // Set mesh final color
2565                 hud->setSelectionMeshColor(c);
2566         }
2567         return result;
2568 }
2569
2570 void Game::handlePointingAtNothing(const ItemStack &playerItem)
2571 {
2572         infostream << "Attempted to place item while pointing at nothing" << std::endl;
2573         PointedThing fauxPointed;
2574         fauxPointed.type = POINTEDTHING_NOTHING;
2575         client->interact(INTERACT_ACTIVATE, fauxPointed);
2576 }
2577
2578
2579 void Game::handlePointingAtNode(const PointedThing &pointed,
2580         const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
2581 {
2582         v3s16 nodepos = pointed.node_undersurface;
2583         v3s16 neighbourpos = pointed.node_abovesurface;
2584
2585         /*
2586                 Check information text of node
2587         */
2588
2589         ClientMap &map = client->getEnv().getClientMap();
2590
2591         if (((runData.nodig_delay_timer <= 0.0 || g_settings->getBool("fastdig")) && (isKeyDown(KeyType::DIG) || g_settings->getBool("autodig"))
2592                         && !runData.digging_blocked
2593                         && client->checkPrivilege("interact"))
2594                 ) {
2595                 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
2596         }
2597
2598         // This should be done after digging handling
2599         NodeMetadata *meta = map.getNodeMetadata(nodepos);
2600
2601         if (meta) {
2602                 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
2603                         meta->getString("infotext"))));
2604         } else {
2605                 MapNode n = map.getNode(nodepos);
2606
2607                 if (nodedef_manager->get(n).name == "unknown") {
2608                         m_game_ui->setInfoText(L"Unknown node");
2609                 }
2610         }
2611
2612         if ((wasKeyPressed(KeyType::PLACE) ||
2613                         (runData.repeat_place_timer >= (g_settings->getBool("fastplace") ? 0.001 : m_repeat_place_time))) &&
2614                         client->checkPrivilege("interact")) {
2615                 runData.repeat_place_timer = 0;
2616                 infostream << "Place button pressed while looking at ground" << std::endl;
2617
2618                 // Placing animation (always shown for feedback)
2619                 camera->setDigging(1);
2620
2621                 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
2622
2623                 // If the wielded item has node placement prediction,
2624                 // make that happen
2625                 // And also set the sound and send the interact
2626                 // But first check for meta formspec and rightclickable
2627                 auto &def = selected_item.getDefinition(itemdef_manager);
2628                 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
2629                         pointed, meta);
2630
2631                 if (placed && client->modsLoaded())
2632                         client->getScript()->on_placenode(pointed, def);
2633         }
2634 }
2635
2636 bool Game::nodePlacement(const ItemDefinition &selected_def,
2637         const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
2638         const PointedThing &pointed, const NodeMetadata *meta, bool force)
2639 {
2640         const auto &prediction = selected_def.node_placement_prediction;
2641
2642         const NodeDefManager *nodedef = client->ndef();
2643         ClientMap &map = client->getEnv().getClientMap();
2644         MapNode node;
2645         bool is_valid_position;
2646
2647         node = map.getNode(nodepos, &is_valid_position);
2648         if (!is_valid_position) {
2649                 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2650                 return false;
2651         }
2652
2653         // formspec in meta
2654         if (meta && !meta->getString("formspec").empty() && !input->isRandom()
2655                         && !isKeyDown(KeyType::SNEAK) && !force) {
2656                 // on_rightclick callbacks are called anyway
2657                 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
2658                         client->interact(INTERACT_PLACE, pointed);
2659
2660                 infostream << "Launching custom inventory view" << std::endl;
2661
2662                 InventoryLocation inventoryloc;
2663                 inventoryloc.setNodeMeta(nodepos);
2664
2665                 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
2666                         &client->getEnv().getClientMap(), nodepos);
2667                 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
2668
2669                 auto *&formspec = m_game_ui->updateFormspec("");
2670                 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2671                         &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2672
2673                 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
2674                 return false;
2675         }
2676
2677         // on_rightclick callback
2678         if (prediction.empty() || (nodedef->get(node).rightclickable &&
2679                         !isKeyDown(KeyType::SNEAK) && !force)) {
2680                 // Report to server
2681                 client->interact(INTERACT_PLACE, pointed);
2682                 return false;
2683         }
2684
2685         verbosestream << "Node placement prediction for "
2686                 << selected_def.name << " is " << prediction << std::endl;
2687         v3s16 p = neighbourpos;
2688
2689         // Place inside node itself if buildable_to
2690         MapNode n_under = map.getNode(nodepos, &is_valid_position);
2691         if (is_valid_position) {
2692                 if (nodedef->get(n_under).buildable_to) {
2693                         p = nodepos;
2694                 } else {
2695                         node = map.getNode(p, &is_valid_position);
2696                         if (is_valid_position && !nodedef->get(node).buildable_to) {
2697                                 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2698                                 // Report to server
2699                                 client->interact(INTERACT_PLACE, pointed);
2700                                 return false;
2701                         }
2702                 }
2703         }
2704
2705         // Find id of predicted node
2706         content_t id;
2707         bool found = nodedef->getId(prediction, id);
2708
2709         if (!found) {
2710                 errorstream << "Node placement prediction failed for "
2711                         << selected_def.name << " (places " << prediction
2712                         << ") - Name not known" << std::endl;
2713                 // Handle this as if prediction was empty
2714                 // Report to server
2715                 client->interact(INTERACT_PLACE, pointed);
2716                 return false;
2717         }
2718
2719         const ContentFeatures &predicted_f = nodedef->get(id);
2720
2721         // Predict param2 for facedir and wallmounted nodes
2722         // Compare core.item_place_node() for what the server does
2723         u8 param2 = 0;
2724
2725         const u8 place_param2 = selected_def.place_param2;
2726
2727         if (place_param2) {
2728                 param2 = place_param2;
2729         } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
2730                         predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
2731                 v3s16 dir = nodepos - neighbourpos;
2732
2733                 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
2734                         param2 = dir.Y < 0 ? 1 : 0;
2735                 } else if (abs(dir.X) > abs(dir.Z)) {
2736                         param2 = dir.X < 0 ? 3 : 2;
2737                 } else {
2738                         param2 = dir.Z < 0 ? 5 : 4;
2739                 }
2740         } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
2741                         predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
2742                 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
2743
2744                 if (abs(dir.X) > abs(dir.Z)) {
2745                         param2 = dir.X < 0 ? 3 : 1;
2746                 } else {
2747                         param2 = dir.Z < 0 ? 2 : 0;
2748                 }
2749         }
2750
2751         // Check attachment if node is in group attached_node
2752         if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
2753                 const static v3s16 wallmounted_dirs[8] = {
2754                         v3s16(0, 1, 0),
2755                         v3s16(0, -1, 0),
2756                         v3s16(1, 0, 0),
2757                         v3s16(-1, 0, 0),
2758                         v3s16(0, 0, 1),
2759                         v3s16(0, 0, -1),
2760                 };
2761                 v3s16 pp;
2762
2763                 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
2764                                 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
2765                         pp = p + wallmounted_dirs[param2];
2766                 else
2767                         pp = p + v3s16(0, -1, 0);
2768
2769                 if (!nodedef->get(map.getNode(pp)).walkable) {
2770                         soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2771                         // Report to server
2772                         client->interact(INTERACT_PLACE, pointed);
2773                         return false;
2774                 }
2775         }
2776
2777         // Apply color
2778         if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
2779                         || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
2780                         || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
2781                 const auto &indexstr = selected_item.metadata.
2782                         getString("palette_index", 0);
2783                 if (!indexstr.empty()) {
2784                         s32 index = mystoi(indexstr);
2785                         if (predicted_f.param_type_2 == CPT2_COLOR) {
2786                                 param2 = index;
2787                         } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
2788                                 // param2 = pure palette index + other
2789                                 param2 = (index & 0xf8) | (param2 & 0x07);
2790                         } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
2791                                 // param2 = pure palette index + other
2792                                 param2 = (index & 0xe0) | (param2 & 0x1f);
2793                         }
2794                 }
2795         }
2796
2797         // Add node to client map
2798         MapNode n(id, 0, param2);
2799
2800         try {
2801                 LocalPlayer *player = client->getEnv().getLocalPlayer();
2802
2803                 // Dont place node when player would be inside new node
2804                 // NOTE: This is to be eventually implemented by a mod as client-side Lua
2805                 if (!nodedef->get(n).walkable ||
2806                                 g_settings->getBool("enable_build_where_you_stand") ||
2807                                 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
2808                                 (nodedef->get(n).walkable &&
2809                                         neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
2810                                         neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
2811                         // This triggers the required mesh update too
2812                         client->addNode(p, n);
2813                         // Report to server
2814                         client->interact(INTERACT_PLACE, pointed);
2815                         // A node is predicted, also play a sound
2816                         soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
2817                         return true;
2818                 } else {
2819                         soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2820                         return false;
2821                 }
2822         } catch (const InvalidPositionException &e) {
2823                 errorstream << "Node placement prediction failed for "
2824                         << selected_def.name << " (places "
2825                         << prediction << ") - Position not loaded" << std::endl;
2826                 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2827                 return false;
2828         }
2829 }
2830
2831 void Game::handlePointingAtObject(const PointedThing &pointed,
2832                 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
2833 {
2834         std::wstring infotext = unescape_translate(
2835                 utf8_to_wide(runData.selected_object->infoText()));
2836
2837         if (show_debug) {
2838                 if (!infotext.empty()) {
2839                         infotext += L"\n";
2840                 }
2841                 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
2842         }
2843
2844         m_game_ui->setInfoText(infotext);
2845
2846         if (isKeyDown(KeyType::DIG) || g_settings->getBool("autohit")) {
2847                 bool do_punch = false;
2848                 bool do_punch_damage = false;
2849
2850                 if (runData.object_hit_delay_timer <= 0.0 || g_settings->getBool("spamclick")) {
2851                         do_punch = true;
2852                         do_punch_damage = true;
2853                         runData.object_hit_delay_timer = object_hit_delay;
2854                 }
2855
2856                 if (wasKeyPressed(KeyType::DIG))
2857                         do_punch = true;
2858
2859                 if (do_punch) {
2860                         infostream << "Punched object" << std::endl;
2861                         runData.punching = true;
2862                 }
2863
2864                 if (do_punch_damage) {
2865                         // Report direct punch
2866                         v3f objpos = runData.selected_object->getPosition();
2867                         v3f dir = (objpos - player_position).normalize();
2868
2869                         bool disable_send = runData.selected_object->directReportPunch(
2870                                         dir, &tool_item, runData.time_from_last_punch);
2871                         runData.time_from_last_punch = 0;
2872
2873                         if (!disable_send) {
2874                                 client->interact(INTERACT_START_DIGGING, pointed);
2875                         }
2876                 }
2877         } else if (wasKeyDown(KeyType::PLACE)) {
2878                 infostream << "Pressed place button while pointing at object" << std::endl;
2879                 client->interact(INTERACT_PLACE, pointed);  // place
2880         }
2881 }
2882
2883
2884 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
2885                 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
2886 {
2887         // See also: serverpackethandle.cpp, action == 2
2888         LocalPlayer *player = client->getEnv().getLocalPlayer();
2889         ClientMap &map = client->getEnv().getClientMap();
2890         MapNode n = client->getEnv().getClientMap().getNode(nodepos);
2891
2892         // NOTE: Similar piece of code exists on the server side for
2893         // cheat detection.
2894         // Get digging parameters
2895         DigParams params = getDigParams(nodedef_manager->get(n).groups,
2896                         &selected_item.getToolCapabilities(itemdef_manager),
2897                         selected_item.wear);
2898
2899         // If can't dig, try hand
2900         if (!params.diggable) {
2901                 params = getDigParams(nodedef_manager->get(n).groups,
2902                                 &hand_item.getToolCapabilities(itemdef_manager));
2903         }
2904
2905         if (!params.diggable) {
2906                 // I guess nobody will wait for this long
2907                 runData.dig_time_complete = 10000000.0;
2908         } else {
2909                 runData.dig_time_complete = params.time;
2910
2911                 if (m_cache_enable_particles) {
2912                         const ContentFeatures &features = client->getNodeDefManager()->get(n);
2913                         client->getParticleManager()->addNodeParticle(client,
2914                                         player, nodepos, n, features);
2915                 }
2916         }
2917
2918         if(g_settings->getBool("instant_break")) {
2919                 runData.dig_time_complete = 0;
2920                 runData.dig_instantly = true;
2921         }
2922         if (!runData.digging) {
2923                 infostream << "Started digging" << std::endl;
2924                 runData.dig_instantly = runData.dig_time_complete == 0;
2925                 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
2926                         return;
2927                 client->interact(INTERACT_START_DIGGING, pointed);
2928                 runData.digging = true;
2929                 runData.btn_down_for_dig = true;
2930         }
2931
2932         if (!runData.dig_instantly) {
2933                 runData.dig_index = (float)crack_animation_length
2934                                 * runData.dig_time
2935                                 / runData.dig_time_complete;
2936         } else {
2937                 // This is for e.g. torches
2938                 runData.dig_index = crack_animation_length;
2939         }
2940
2941         SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
2942
2943         if (sound_dig.exists() && params.diggable) {
2944                 if (sound_dig.name == "__group") {
2945                         if (!params.main_group.empty()) {
2946                                 soundmaker->m_player_leftpunch_sound.gain = 0.5;
2947                                 soundmaker->m_player_leftpunch_sound.name =
2948                                                 std::string("default_dig_") +
2949                                                 params.main_group;
2950                         }
2951                 } else {
2952                         soundmaker->m_player_leftpunch_sound = sound_dig;
2953                 }
2954         }
2955
2956         // Don't show cracks if not diggable
2957         if (runData.dig_time_complete >= 100000.0) {
2958         } else if (runData.dig_index < crack_animation_length) {
2959                 //TimeTaker timer("client.setTempMod");
2960                 //infostream<<"dig_index="<<dig_index<<std::endl;
2961                 client->setCrack(runData.dig_index, nodepos);
2962         } else {
2963                 infostream << "Digging completed" << std::endl;
2964                 client->setCrack(-1, v3s16(0, 0, 0));
2965
2966                 runData.dig_time = 0;
2967                 runData.digging = false;
2968                 // we successfully dug, now block it from repeating if we want to be safe
2969                 if (g_settings->getBool("safe_dig_and_place"))
2970                         runData.digging_blocked = true;
2971
2972                 runData.nodig_delay_timer =
2973                                 runData.dig_time_complete / (float)crack_animation_length;
2974
2975                 // We don't want a corresponding delay to very time consuming nodes
2976                 // and nodes without digging time (e.g. torches) get a fixed delay.
2977                 if (runData.nodig_delay_timer > 0.3)
2978                         runData.nodig_delay_timer = 0.3;
2979                 else if (runData.dig_instantly)
2980                         runData.nodig_delay_timer = 0.15;
2981
2982                 bool is_valid_position;
2983                 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
2984                 if (is_valid_position) {
2985                         if (client->modsLoaded() &&
2986                                         client->getScript()->on_dignode(nodepos, wasnode)) {
2987                                 return;
2988                         }
2989
2990                         const ContentFeatures &f = client->ndef()->get(wasnode);
2991                         if (f.node_dig_prediction == "air") {
2992                                 client->removeNode(nodepos);
2993                         } else if (!f.node_dig_prediction.empty()) {
2994                                 content_t id;
2995                                 bool found = client->ndef()->getId(f.node_dig_prediction, id);
2996                                 if (found)
2997                                         client->addNode(nodepos, id, true);
2998                         }
2999                         // implicit else: no prediction
3000                 }
3001
3002                 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3003
3004                 if (m_cache_enable_particles) {
3005                         const ContentFeatures &features =
3006                                 client->getNodeDefManager()->get(wasnode);
3007                         client->getParticleManager()->addDiggingParticles(client,
3008                                 player, nodepos, wasnode, features);
3009                 }
3010
3011
3012                 // Send event to trigger sound
3013                 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3014         }
3015
3016         if (runData.dig_time_complete < 100000.0) {
3017                 runData.dig_time += dtime;
3018         } else {
3019                 runData.dig_time = 0;
3020                 client->setCrack(-1, nodepos);
3021         }
3022
3023         camera->setDigging(0);  // Dig animation
3024 }
3025
3026 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3027                 const CameraOrientation &cam)
3028 {
3029         TimeTaker tt_update("Game::updateFrame()");
3030         LocalPlayer *player = client->getEnv().getLocalPlayer();
3031
3032         /*
3033                 Fog range
3034         */
3035
3036         if (draw_control->range_all) {
3037                 runData.fog_range = 100000 * BS;
3038         } else {
3039                 runData.fog_range = draw_control->wanted_range * BS;
3040         }
3041
3042         /*
3043                 Calculate general brightness
3044         */
3045         u32 daynight_ratio = client->getEnv().getDayNightRatio();
3046         float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3047         float direct_brightness;
3048         bool sunlight_seen;
3049
3050         if ((m_cache_enable_noclip && m_cache_enable_free_move) || g_settings->getBool("freecam")) {
3051                 direct_brightness = time_brightness;
3052                 sunlight_seen = true;
3053         } else {
3054                 float old_brightness = sky->getBrightness();
3055                 direct_brightness = client->getEnv().getClientMap()
3056                                 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3057                                         daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3058                                     / 255.0;
3059         }
3060
3061         float time_of_day_smooth = runData.time_of_day_smooth;
3062         float time_of_day = client->getEnv().getTimeOfDayF();
3063
3064         static const float maxsm = 0.05f;
3065         static const float todsm = 0.05f;
3066
3067         if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3068                         std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3069                         std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3070                 time_of_day_smooth = time_of_day;
3071
3072         if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3073                 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3074                                 + (time_of_day + 1.0) * todsm;
3075         else
3076                 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3077                                 + time_of_day * todsm;
3078
3079         runData.time_of_day_smooth = time_of_day_smooth;
3080
3081         sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3082                         sunlight_seen, camera->getCameraMode(), player->getYaw(),
3083                         player->getPitch());
3084
3085         /*
3086                 Update clouds
3087         */
3088         if (clouds) {
3089                 if (sky->getCloudsVisible()) {
3090                         clouds->setVisible(true);
3091                         clouds->step(dtime);
3092                         // camera->getPosition is not enough for 3rd person views
3093                         v3f camera_node_position = camera->getCameraNode()->getPosition();
3094                         v3s16 camera_offset      = camera->getOffset();
3095                         camera_node_position.X   = camera_node_position.X + camera_offset.X * BS;
3096                         camera_node_position.Y   = camera_node_position.Y + camera_offset.Y * BS;
3097                         camera_node_position.Z   = camera_node_position.Z + camera_offset.Z * BS;
3098                         clouds->update(camera_node_position,
3099                                         sky->getCloudColor());
3100                         if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3101                                 // if inside clouds, and fog enabled, use that as sky
3102                                 // color(s)
3103                                 video::SColor clouds_dark = clouds->getColor()
3104                                                 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3105                                 sky->overrideColors(clouds_dark, clouds->getColor());
3106                                 sky->setInClouds(true);
3107                                 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3108                                 // do not draw clouds after all
3109                                 clouds->setVisible(false);
3110                         }
3111                 } else {
3112                         clouds->setVisible(false);
3113                 }
3114         }
3115
3116         /*
3117                 Update particles
3118         */
3119         client->getParticleManager()->step(dtime);
3120
3121         /*
3122                 Fog
3123         */
3124
3125         if (m_cache_enable_fog) {
3126                 driver->setFog(
3127                                 sky->getBgColor(),
3128                                 video::EFT_FOG_LINEAR,
3129                                 runData.fog_range * m_cache_fog_start,
3130                                 runData.fog_range * 1.0,
3131                                 0.01,
3132                                 false, // pixel fog
3133                                 true // range fog
3134                 );
3135         } else {
3136                 driver->setFog(
3137                                 sky->getBgColor(),
3138                                 video::EFT_FOG_LINEAR,
3139                                 100000 * BS,
3140                                 110000 * BS,
3141                                 0.01f,
3142                                 false, // pixel fog
3143                                 false // range fog
3144                 );
3145         }
3146
3147         /*
3148                 Damage camera tilt
3149         */
3150         if (player->hurt_tilt_timer > 0.0f) {
3151                 player->hurt_tilt_timer -= dtime * 6.0f;
3152
3153                 if (player->hurt_tilt_timer < 0.0f || g_settings->getBool("no_hurt_cam"))
3154                         player->hurt_tilt_strength = 0.0f;
3155         }
3156
3157         /*
3158                 Update minimap pos and rotation
3159         */
3160         if (mapper && m_game_ui->m_flags.show_hud) {
3161                 mapper->setPos(floatToInt(player->getPosition(), BS));
3162                 mapper->setAngle(player->getYaw());
3163         }
3164
3165         /*
3166                 Get chat messages from client
3167         */
3168
3169         updateChat(dtime);
3170
3171         /*
3172                 Inventory
3173         */
3174
3175         if (player->getWieldIndex() != runData.new_playeritem)
3176                 client->setPlayerItem(runData.new_playeritem);
3177
3178         if (client->updateWieldedItem()) {
3179                 // Update wielded tool
3180                 ItemStack selected_item, hand_item;
3181                 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3182                 camera->wield(tool_item);
3183         }
3184
3185         /*
3186                 Update block draw list every 200ms or when camera direction has
3187                 changed much
3188         */
3189         runData.update_draw_list_timer += dtime;
3190
3191         float update_draw_list_delta = 0.2f;
3192
3193         v3f camera_direction = camera->getDirection();
3194         if (runData.update_draw_list_timer >= update_draw_list_delta
3195                         || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3196                         || m_camera_offset_changed
3197                         || client->getEnv().getClientMap().needsUpdateDrawList()) {
3198                 runData.update_draw_list_timer = 0;
3199                 client->getEnv().getClientMap().updateDrawList();
3200                 runData.update_draw_list_last_cam_dir = camera_direction;
3201         }
3202
3203         if (RenderingEngine::get_shadow_renderer()) {
3204                 updateShadows();
3205         }
3206
3207         m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3208
3209         /*
3210            make sure menu is on top
3211            1. Delete formspec menu reference if menu was removed
3212            2. Else, make sure formspec menu is on top
3213         */
3214         auto formspec = m_game_ui->getFormspecGUI();
3215         do { // breakable. only runs for one iteration
3216                 if (!formspec)
3217                         break;
3218
3219                 if (formspec->getReferenceCount() == 1) {
3220                         m_game_ui->deleteFormspec();
3221                         break;
3222                 }
3223
3224                 auto &loc = formspec->getFormspecLocation();
3225                 if (loc.type == InventoryLocation::NODEMETA) {
3226                         NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3227                         if (!meta || meta->getString("formspec").empty()) {
3228                                 formspec->quitMenu();
3229                                 break;
3230                         }
3231                 }
3232
3233                 if (isMenuActive())
3234                         guiroot->bringToFront(formspec);
3235         } while (false);
3236
3237         /*
3238                 ==================== Drawing begins ====================
3239         */
3240         const video::SColor skycolor = sky->getSkyColor();
3241
3242         TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
3243         driver->beginScene(true, true, skycolor);
3244
3245         bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3246                         (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3247                         (camera->getCameraMode() == CAMERA_MODE_FIRST));
3248         bool draw_crosshair = (
3249                         (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3250                         (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3251 #ifdef HAVE_TOUCHSCREENGUI
3252         try {
3253                 draw_crosshair = !g_settings->getBool("touchtarget");
3254         } catch (SettingNotFoundException) {
3255         }
3256 #endif
3257         m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3258                         m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3259
3260         /*
3261                 Profiler graph
3262         */
3263         v2u32 screensize = driver->getScreenSize();
3264
3265         if (m_game_ui->m_flags.show_profiler_graph)
3266                 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3267
3268         /*
3269                 Cheat menu
3270         */
3271
3272         if (! gui_chat_console->isOpen()) {
3273                 if (m_game_ui->m_flags.show_cheat_menu)
3274                         m_cheat_menu->draw(driver, m_game_ui->m_flags.show_minimal_debug);
3275                 if (g_settings->getBool("cheat_hud"))
3276                         m_cheat_menu->drawHUD(driver, dtime);
3277         }
3278         /*
3279                 Damage flash
3280         */
3281         if (runData.damage_flash > 0.0f) {
3282                 video::SColor color(runData.damage_flash, 180, 0, 0);
3283                 if (! g_settings->getBool("no_hurt_cam"))
3284                         driver->draw2DRectangle(color, core::rect<s32>(0, 0, screensize.X, screensize.Y), NULL);
3285
3286                 runData.damage_flash -= 384.0f * dtime;
3287         }
3288
3289         /*
3290                 End scene
3291         */
3292 #if IRRLICHT_VERSION_MT_REVISION < 5
3293         if (++m_reset_HW_buffer_counter > 500) {
3294                 /*
3295                   Periodically remove all mesh HW buffers.
3296
3297                   Work around for a quirk in Irrlicht where a HW buffer is only
3298                   released after 20000 iterations (triggered from endScene()).
3299
3300                   Without this, all loaded but unused meshes will retain their HW
3301                   buffers for at least 5 minutes, at which point looking up the HW buffers
3302                   becomes a bottleneck and the framerate drops (as much as 30%).
3303
3304                   Tests showed that numbers between 50 and 1000 are good, so picked 500.
3305                   There are no other public Irrlicht APIs that allow interacting with the
3306                   HW buffers without tracking the status of every individual mesh.
3307
3308                   The HW buffers for _visible_ meshes will be reinitialized in the next frame.
3309                 */
3310                 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
3311                 driver->removeAllHardwareBuffers();
3312                 m_reset_HW_buffer_counter = 0;
3313         }
3314 #endif
3315
3316         driver->endScene();
3317
3318         stats->drawtime = tt_draw.stop(true);
3319         g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
3320         g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
3321 }
3322
3323 /* Log times and stuff for visualization */
3324 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
3325 {
3326         Profiler::GraphValues values;
3327         g_profiler->graphGet(values);
3328         graph->put(values);
3329 }
3330
3331 /****************************************************************************
3332  * Shadows
3333  *****************************************************************************/
3334 void Game::updateShadows()
3335 {
3336         ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
3337         if (!shadow)
3338                 return;
3339
3340         float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
3341
3342         float timeoftheday = getWickedTimeOfDay(in_timeofday);
3343         bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
3344         bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
3345         shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
3346
3347         timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
3348         const float offset_constant = 10000.0f;
3349
3350         v3f light(0.0f, 0.0f, -1.0f);
3351         light.rotateXZBy(90);
3352         light.rotateXYBy(timeoftheday * 360 - 90);
3353         light.rotateYZBy(sky->getSkyBodyOrbitTilt());
3354
3355         v3f sun_pos = light * offset_constant;
3356
3357         if (shadow->getDirectionalLightCount() == 0)
3358                 shadow->addDirectionalLight();
3359         shadow->getDirectionalLight().setDirection(sun_pos);
3360         shadow->setTimeOfDay(in_timeofday);
3361
3362         shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
3363 }
3364
3365 /****************************************************************************
3366  Misc
3367  ****************************************************************************/
3368
3369 void FpsControl::reset()
3370 {
3371         last_time = porting::getTimeUs();
3372 }
3373
3374 /*
3375  * On some computers framerate doesn't seem to be automatically limited
3376  */
3377 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
3378 {
3379         const u64 frametime_min = 1000000.0f / (
3380                 device->isWindowFocused() && !g_menumgr.pausesGame()
3381                         ? g_settings->getFloat("fps_max")
3382                         : g_settings->getFloat("fps_max_unfocused"));
3383
3384         u64 time = porting::getTimeUs();
3385
3386         if (time > last_time) // Make sure time hasn't overflowed
3387                 busy_time = time - last_time;
3388         else
3389                 busy_time = 0;
3390
3391         if (busy_time < frametime_min) {
3392                 sleep_time = frametime_min - busy_time;
3393                 if (sleep_time > 1000)
3394                         sleep_ms(sleep_time / 1000);
3395         } else {
3396                 sleep_time = 0;
3397         }
3398
3399         // Read the timer again to accurately determine how long we actually slept,
3400         // rather than calculating it by adding sleep_time to time.
3401         time = porting::getTimeUs();
3402
3403         if (time > last_time) // Make sure last_time hasn't overflowed
3404                 *dtime = (time - last_time) / 1000000.0f;
3405         else
3406                 *dtime = 0;
3407
3408         last_time = time;
3409 }
3410
3411 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
3412 {
3413         const wchar_t *wmsg = wgettext(msg);
3414         m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
3415                 draw_clouds);
3416         delete[] wmsg;
3417 }
3418
3419 void Game::settingChangedCallback(const std::string &setting_name, void *data)
3420 {
3421         ((Game *)data)->readSettings();
3422 }
3423
3424 void Game::updateAllMapBlocksCallback(const std::string &setting_name, void *data)
3425 {
3426         ((Game *) data)->client->updateAllMapBlocks();
3427 }
3428
3429 void Game::freecamChangedCallback(const std::string &setting_name, void *data)
3430 {
3431         Game *game = (Game *) data;
3432         LocalPlayer *player = game->client->getEnv().getLocalPlayer();
3433         if (g_settings->getBool("freecam")) {
3434                 game->camera->setCameraMode(CAMERA_MODE_FIRST);
3435                 player->freecamEnable();
3436         } else {
3437                 player->freecamDisable();
3438         }
3439         game->updatePlayerCAOVisibility();
3440 }
3441
3442 void Game::readSettings()
3443 {
3444         m_cache_doubletap_jump               = g_settings->getBool("doubletap_jump");
3445         m_cache_enable_clouds                = g_settings->getBool("enable_clouds");
3446         m_cache_enable_joysticks             = g_settings->getBool("enable_joysticks");
3447         m_cache_enable_particles             = g_settings->getBool("enable_particles");
3448         m_cache_enable_fog                   = g_settings->getBool("enable_fog");
3449         m_cache_mouse_sensitivity            = g_settings->getFloat("mouse_sensitivity");
3450         m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
3451         m_repeat_place_time                  = g_settings->getFloat("repeat_place_time");
3452
3453         m_cache_enable_noclip                = g_settings->getBool("noclip");
3454         m_cache_enable_free_move             = g_settings->getBool("free_move");
3455
3456         m_cache_fog_start                    = g_settings->getFloat("fog_start");
3457
3458         m_cache_cam_smoothing = 0;
3459         if (g_settings->getBool("cinematic"))
3460                 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
3461         else
3462                 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
3463
3464         m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
3465         m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
3466         m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
3467
3468         m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
3469 }
3470
3471 bool Game::isKeyDown(GameKeyType k)
3472 {
3473         return input->isKeyDown(k);
3474 }
3475
3476 bool Game::wasKeyDown(GameKeyType k)
3477 {
3478         return input->wasKeyDown(k);
3479 }
3480
3481 bool Game::wasKeyPressed(GameKeyType k)
3482 {
3483         return input->wasKeyPressed(k);
3484 }
3485
3486 bool Game::wasKeyReleased(GameKeyType k)
3487 {
3488         return input->wasKeyReleased(k);
3489 }
3490
3491 /****************************************************************************/
3492 /****************************************************************************
3493  Shutdown / cleanup
3494  ****************************************************************************/
3495 /****************************************************************************/
3496
3497 void Game::showDeathFormspec()
3498 {
3499         static std::string formspec_str =
3500                 std::string("formspec_version[1]") +
3501                 SIZE_TAG
3502                 "bgcolor[#320000b4;true]"
3503                 "label[4.85,1.35;" + gettext("You died") + "]"
3504                 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
3505                 ;
3506
3507         /* Create menu */
3508         /* Note: FormspecFormSource and LocalFormspecHandler  *
3509          * are deleted by guiFormSpecMenu                     */
3510         FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
3511         LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
3512
3513         auto *&formspec = m_game_ui->getFormspecGUI();
3514         GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3515                 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3516         formspec->setFocus("btn_respawn");
3517 }
3518
3519 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
3520 void Game::showPauseMenu()
3521 {
3522 #ifdef HAVE_TOUCHSCREENGUI
3523         static const std::string control_text = strgettext("Default Controls:\n"
3524                 "No menu visible:\n"
3525                 "- single tap: button activate\n"
3526                 "- double tap: place/use\n"
3527                 "- slide finger: look around\n"
3528                 "Menu/Inventory visible:\n"
3529                 "- double tap (outside):\n"
3530                 " -->close\n"
3531                 "- touch stack, touch slot:\n"
3532                 " --> move stack\n"
3533                 "- touch&drag, tap 2nd finger\n"
3534                 " --> place single item to slot\n"
3535                 );
3536 #else
3537         static const std::string control_text_template = strgettext("Controls:\n"
3538                 "- %s: move forwards\n"
3539                 "- %s: move backwards\n"
3540                 "- %s: move left\n"
3541                 "- %s: move right\n"
3542                 "- %s: jump/climb up\n"
3543                 "- %s: dig/punch\n"
3544                 "- %s: place/use\n"
3545                 "- %s: sneak/climb down\n"
3546                 "- %s: drop item\n"
3547                 "- %s: inventory\n"
3548                 "- %s: enderchest\n"
3549                 "- Mouse: turn/look\n"
3550                 "- Mouse wheel: select item\n"
3551                 "- %s: chat\n"
3552                 "- %s: Killaura\n"
3553                 "- %s: Freecam\n"
3554                 "- %s: Scaffold\n"
3555         );
3556
3557          char control_text_buf[600];
3558
3559          porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
3560                         GET_KEY_NAME(keymap_forward),
3561                         GET_KEY_NAME(keymap_backward),
3562                         GET_KEY_NAME(keymap_left),
3563                         GET_KEY_NAME(keymap_right),
3564                         GET_KEY_NAME(keymap_jump),
3565                         GET_KEY_NAME(keymap_dig),
3566                         GET_KEY_NAME(keymap_place),
3567                         GET_KEY_NAME(keymap_sneak),
3568                         GET_KEY_NAME(keymap_drop),
3569                         GET_KEY_NAME(keymap_inventory),
3570                         GET_KEY_NAME(keymap_enderchest),
3571                         GET_KEY_NAME(keymap_chat),
3572                         GET_KEY_NAME(keymap_toggle_killaura),
3573                         GET_KEY_NAME(keymap_toggle_freecam),
3574                         GET_KEY_NAME(keymap_toggle_scaffold)
3575                         );
3576
3577         std::string control_text = std::string(control_text_buf);
3578         str_formspec_escape(control_text);
3579 #endif
3580
3581         float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
3582         std::ostringstream os;
3583
3584         os << "formspec_version[1]" << SIZE_TAG
3585                 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
3586                 << strgettext("Continue") << "]";
3587
3588         if (!simple_singleplayer_mode) {
3589                 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
3590                         << strgettext("Change Password") << "]";
3591         } else {
3592                 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
3593         }
3594
3595 #ifndef __ANDROID__
3596 #if USE_SOUND
3597         if (g_settings->getBool("enable_sound")) {
3598                 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
3599                         << strgettext("Sound Volume") << "]";
3600         }
3601 #endif
3602         os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
3603                 << strgettext("Change Keys")  << "]";
3604 #endif
3605         os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
3606                 << strgettext("Exit to Menu") << "]";
3607         os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
3608                 << strgettext("Exit to OS")   << "]"
3609                 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
3610                 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
3611                 << "\n"
3612                 <<  strgettext("Game info:") << "\n";
3613         const std::string &address = client->getAddressName();
3614         static const std::string mode = strgettext("- Mode: ");
3615         if (!simple_singleplayer_mode) {
3616                 Address serverAddress = client->getServerAddress();
3617                 if (!address.empty()) {
3618                         os << mode << strgettext("Remote server") << "\n"
3619                                         << strgettext("- Address: ") << address;
3620                 } else {
3621                         os << mode << strgettext("Hosting server");
3622                 }
3623                 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
3624         } else {
3625                 os << mode << strgettext("Singleplayer") << "\n";
3626         }
3627         if (simple_singleplayer_mode || address.empty()) {
3628                 static const std::string on = strgettext("On");
3629                 static const std::string off = strgettext("Off");
3630                 // Note: Status of enable_damage and creative_mode settings is intentionally
3631                 // NOT shown here because the game might roll its own damage system and/or do
3632                 // a per-player Creative Mode, in which case writing it here would mislead.
3633                 bool damage = g_settings->getBool("enable_damage");
3634                 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
3635                 if (!simple_singleplayer_mode) {
3636                         if (damage) {
3637                                 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
3638                                 //~ PvP = Player versus Player
3639                                 os << strgettext("- PvP: ") << pvp << "\n";
3640                         }
3641                         os << strgettext("- Public: ") << announced << "\n";
3642                         std::string server_name = g_settings->get("server_name");
3643                         str_formspec_escape(server_name);
3644                         if (announced == on && !server_name.empty())
3645                                 os << strgettext("- Server Name: ") << server_name;
3646
3647                 }
3648         }
3649         os << ";]";
3650
3651         /* Create menu */
3652         /* Note: FormspecFormSource and LocalFormspecHandler  *
3653          * are deleted by guiFormSpecMenu                     */
3654         FormspecFormSource *fs_src = new FormspecFormSource(os.str());
3655         LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
3656
3657         auto *&formspec = m_game_ui->getFormspecGUI();
3658         GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3659                         &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3660         formspec->setFocus("btn_continue");
3661         formspec->doPause = true;
3662
3663         if (simple_singleplayer_mode)
3664                 pauseAnimation();
3665 }
3666
3667 /****************************************************************************/
3668 /****************************************************************************
3669  extern function for launching the game
3670  ****************************************************************************/
3671 /****************************************************************************/
3672
3673 Game *g_game;
3674
3675 void the_game(bool *kill,
3676                 InputHandler *input,
3677                 RenderingEngine *rendering_engine,
3678                 const GameStartData &start_data,
3679                 std::string &error_message,
3680                 ChatBackend &chat_backend,
3681                 bool *reconnect_requested) // Used for local game
3682 {
3683         Game game;
3684
3685         g_game = &game;
3686
3687         /* Make a copy of the server address because if a local singleplayer server
3688          * is created then this is updated and we don't want to change the value
3689          * passed to us by the calling function
3690          */
3691
3692         try {
3693
3694                 if (game.startup(kill, input, rendering_engine, start_data,
3695                                 error_message, reconnect_requested, &chat_backend)) {
3696                         game.run();
3697                 }
3698
3699         } catch (SerializationError &e) {
3700                 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
3701                 error_message = strgettext("A serialization error occurred:") +"\n"
3702                                 + e.what() + "\n\n" + ver_err;
3703                 errorstream << error_message << std::endl;
3704         } catch (ServerError &e) {
3705                 error_message = e.what();
3706                 errorstream << "ServerError: " << error_message << std::endl;
3707         } catch (ModError &e) {
3708                 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
3709                 error_message = std::string("ModError: ") + e.what() +
3710                                 strgettext("\nCheck debug.txt for details.");
3711                 errorstream << error_message << std::endl;
3712         }
3713         game.shutdown();
3714 }