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