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