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