3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
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.
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.
24 #include "client/renderingengine.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"
36 #include "content_cao.h"
37 #include "content/subgames.h"
38 #include "client/event_manager.h"
39 #include "fontengine.h"
43 #include "gameparams.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"
55 #include "nodedef.h" // Needed for determining pointing to nodes
56 #include "nodemetadata.h"
57 #include "particles.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"
73 #include "script/scripting_client.h"
77 #include "client/sound_openal.h"
79 #include "client/sound.h"
83 m_chat_log_buf(g_logger),
84 m_game_ui(new GameUI())
86 g_settings->registerChangedCallback("doubletap_jump",
87 &settingChangedCallback, this);
88 g_settings->registerChangedCallback("enable_clouds",
89 &settingChangedCallback, this);
90 g_settings->registerChangedCallback("doubletap_joysticks",
91 &settingChangedCallback, this);
92 g_settings->registerChangedCallback("enable_particles",
93 &settingChangedCallback, this);
94 g_settings->registerChangedCallback("enable_fog",
95 &settingChangedCallback, this);
96 g_settings->registerChangedCallback("mouse_sensitivity",
97 &settingChangedCallback, this);
98 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
99 &settingChangedCallback, this);
100 g_settings->registerChangedCallback("repeat_place_time",
101 &settingChangedCallback, this);
102 g_settings->registerChangedCallback("noclip",
103 &settingChangedCallback, this);
104 g_settings->registerChangedCallback("free_move",
105 &settingChangedCallback, this);
106 g_settings->registerChangedCallback("cinematic",
107 &settingChangedCallback, this);
108 g_settings->registerChangedCallback("cinematic_camera_smoothing",
109 &settingChangedCallback, this);
110 g_settings->registerChangedCallback("camera_smoothing",
111 &settingChangedCallback, this);
112 g_settings->registerChangedCallback("freecam",
113 &freecamChangedCallback, this);
114 g_settings->registerChangedCallback("xray",
115 &updateAllMapBlocksCallback, this);
116 g_settings->registerChangedCallback("xray_nodes",
117 &updateAllMapBlocksCallback, this);
118 g_settings->registerChangedCallback("fullbright",
119 &updateAllMapBlocksCallback, this);
120 g_settings->registerChangedCallback("node_esp_nodes",
121 &updateAllMapBlocksCallback, this);
126 m_cache_hold_aux1 = false; // This is initialised properly later
133 /****************************************************************************
135 ****************************************************************************/
144 delete server; // deleted first to stop all server threads
152 delete nodedef_manager;
153 delete itemdef_manager;
156 clearTextureNameCache();
158 g_settings->deregisterChangedCallback("doubletap_jump",
159 &settingChangedCallback, this);
160 g_settings->deregisterChangedCallback("enable_clouds",
161 &settingChangedCallback, this);
162 g_settings->deregisterChangedCallback("enable_particles",
163 &settingChangedCallback, this);
164 g_settings->deregisterChangedCallback("enable_fog",
165 &settingChangedCallback, this);
166 g_settings->deregisterChangedCallback("mouse_sensitivity",
167 &settingChangedCallback, this);
168 g_settings->deregisterChangedCallback("repeat_place_time",
169 &settingChangedCallback, this);
170 g_settings->deregisterChangedCallback("noclip",
171 &settingChangedCallback, this);
172 g_settings->deregisterChangedCallback("free_move",
173 &settingChangedCallback, this);
174 g_settings->deregisterChangedCallback("cinematic",
175 &settingChangedCallback, this);
176 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
177 &settingChangedCallback, this);
178 g_settings->deregisterChangedCallback("camera_smoothing",
179 &settingChangedCallback, this);
180 g_settings->deregisterChangedCallback("freecam",
181 &freecamChangedCallback, this);
182 g_settings->deregisterChangedCallback("xray",
183 &updateAllMapBlocksCallback, this);
184 g_settings->deregisterChangedCallback("xray_nodes",
185 &updateAllMapBlocksCallback, this);
186 g_settings->deregisterChangedCallback("fullbright",
187 &updateAllMapBlocksCallback, this);
188 g_settings->deregisterChangedCallback("node_esp_nodes",
189 &updateAllMapBlocksCallback, this);
192 bool Game::startup(bool *kill,
194 RenderingEngine *rendering_engine,
195 const GameStartData &start_data,
196 std::string &error_message,
198 ChatBackend *chat_backend)
202 m_rendering_engine = rendering_engine;
203 device = m_rendering_engine->get_raw_device();
205 this->error_message = &error_message;
206 reconnect_requested = reconnect;
208 this->chat_backend = chat_backend;
209 simple_singleplayer_mode = start_data.isSinglePlayer();
211 input->keycache.populate();
213 driver = device->getVideoDriver();
214 smgr = m_rendering_engine->get_scene_manager();
216 smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
219 runData = GameRunData();
220 runData.time_from_last_punch = 10.0;
222 m_game_ui->initFlags();
224 m_invert_mouse = g_settings->getBool("invert_mouse");
225 m_first_loop_after_window_activation = true;
227 g_client_translations->clear();
229 // address can change if simple_singleplayer_mode
230 if (!init(start_data.world_spec.path, start_data.address,
231 start_data.socket_port, start_data.game_spec))
234 if (!createClient(start_data))
237 m_rendering_engine->initialize(client, hud);
246 RunStats stats = { 0 };
247 FpsControl draw_times = { 0 };
248 f32 dtime; // in seconds
250 /* Clear the profiler */
251 Profiler::GraphValues dummyvalues;
252 g_profiler->graphGet(dummyvalues);
254 draw_times.last_time = m_rendering_engine->get_timer_time();
256 set_light_table(g_settings->getFloat("display_gamma"));
259 m_cache_hold_aux1 = g_settings->getBool("fast_move")
260 && client->checkPrivilege("fast");
263 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
264 g_settings->getU16("screen_h"));
266 while (m_rendering_engine->run()
267 && !(*kill || g_gamecallback->shutdown_requested
268 || (server && server->isShutdownRequested()))) {
270 const irr::core::dimension2d<u32> ¤t_screen_size =
271 m_rendering_engine->get_video_driver()->getScreenSize();
272 // Verify if window size has changed and save it if it's the case
273 // Ensure evaluating settings->getBool after verifying screensize
274 // First condition is cheaper
275 if (previous_screen_size != current_screen_size &&
276 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
277 g_settings->getBool("autosave_screensize")) {
278 g_settings->setU16("screen_w", current_screen_size.Width);
279 g_settings->setU16("screen_h", current_screen_size.Height);
280 previous_screen_size = current_screen_size;
284 // m_rendering_engine->run() from this iteration
285 // + Sleep time until the wanted FPS are reached
286 limitFps(&draw_times, &dtime);
288 // Prepare render data for next iteration
290 updateStats(&stats, draw_times, dtime);
291 updateInteractTimers(dtime);
293 if (!checkConnection())
295 if (!handleCallbacks())
300 m_game_ui->clearInfoText();
304 updateProfilers(stats, draw_times, dtime);
305 processUserInput(dtime);
306 // Update camera before player movement to avoid camera lag of one frame
307 updateCameraDirection(&cam_view_target, dtime);
308 cam_view.camera_yaw += (cam_view_target.camera_yaw -
309 cam_view.camera_yaw) * m_cache_cam_smoothing;
310 cam_view.camera_pitch += (cam_view_target.camera_pitch -
311 cam_view.camera_pitch) * m_cache_cam_smoothing;
312 updatePlayerControl(cam_view);
314 processClientEvents(&cam_view_target);
316 updateCamera(draw_times.busy_time, dtime);
318 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
319 m_game_ui->m_flags.show_basic_debug);
320 updateFrame(&graph, &stats, dtime, cam_view);
321 updateProfilerGraphs(&graph);
323 // Update if minimap has been disabled by the server
324 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
326 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
333 void Game::shutdown()
335 m_rendering_engine->finalize();
336 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
337 if (g_settings->get("3d_mode") == "pageflip") {
338 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
341 auto formspec = m_game_ui->getFormspecGUI();
343 formspec->quitMenu();
345 #ifdef HAVE_TOUCHSCREENGUI
346 g_touchscreengui->hide();
349 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
354 if (gui_chat_console)
355 gui_chat_console->drop();
364 while (g_menumgr.menuCount() > 0) {
365 g_menumgr.m_stack.front()->setVisible(false);
366 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
369 m_game_ui->deleteFormspec();
371 chat_backend->addMessage(L"", L"# Disconnected.");
372 chat_backend->addMessage(L"", L"");
373 m_chat_log_buf.clear();
377 while (!client->isShutdown()) {
378 assert(texture_src != NULL);
379 assert(shader_src != NULL);
380 texture_src->processQueue();
381 shader_src->processQueue();
388 /****************************************************************************/
389 /****************************************************************************
391 ****************************************************************************/
392 /****************************************************************************/
395 const std::string &map_dir,
396 const std::string &address,
398 const SubgameSpec &gamespec)
400 texture_src = createTextureSource();
402 showOverlayMessage(N_("Loading..."), 0, 0);
404 shader_src = createShaderSource();
406 itemdef_manager = createItemDefManager();
407 nodedef_manager = createNodeDefManager();
409 eventmgr = new EventManager();
410 quicktune = new QuicktuneShortcutter();
412 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
413 && eventmgr && quicktune))
419 // Create a server if not connecting to an existing one
420 if (address.empty()) {
421 if (!createSingleplayerServer(map_dir, gamespec, port))
428 bool Game::initSound()
431 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
432 infostream << "Attempting to use OpenAL audio" << std::endl;
433 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
435 infostream << "Failed to initialize OpenAL audio" << std::endl;
437 infostream << "Sound disabled." << std::endl;
441 infostream << "Using dummy audio." << std::endl;
442 sound = &dummySoundManager;
443 sound_is_dummy = true;
446 soundmaker = new SoundMaker(sound, nodedef_manager);
450 soundmaker->registerReceiver(eventmgr);
455 bool Game::createSingleplayerServer(const std::string &map_dir,
456 const SubgameSpec &gamespec, u16 port)
458 showOverlayMessage(N_("Creating server..."), 0, 5);
460 std::string bind_str = g_settings->get("bind_address");
461 Address bind_addr(0, 0, 0, 0, port);
463 if (g_settings->getBool("ipv6_server")) {
464 bind_addr.setAddress((IPv6AddressBytes *) NULL);
468 bind_addr.Resolve(bind_str.c_str());
469 } catch (ResolveError &e) {
470 infostream << "Resolving bind address \"" << bind_str
471 << "\" failed: " << e.what()
472 << " -- Listening on all addresses." << std::endl;
475 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
476 *error_message = "Unable to listen on " +
477 bind_addr.serializeString() +
478 " because IPv6 is disabled";
479 errorstream << *error_message << std::endl;
483 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
484 false, nullptr, error_message);
490 bool Game::createClient(const GameStartData &start_data)
492 showOverlayMessage(N_("Creating client..."), 0, 10);
494 draw_control = new MapDrawControl();
498 bool could_connect, connect_aborted;
499 #ifdef HAVE_TOUCHSCREENGUI
500 if (g_touchscreengui) {
501 g_touchscreengui->init(texture_src);
502 g_touchscreengui->hide();
505 if (!connectToServer(start_data, &could_connect, &connect_aborted))
508 if (!could_connect) {
509 if (error_message->empty() && !connect_aborted) {
510 // Should not happen if error messages are set properly
511 *error_message = "Connection failed for unknown reason";
512 errorstream << *error_message << std::endl;
517 if (!getServerContent(&connect_aborted)) {
518 if (error_message->empty() && !connect_aborted) {
519 // Should not happen if error messages are set properly
520 *error_message = "Connection failed for unknown reason";
521 errorstream << *error_message << std::endl;
526 auto *scsf = new GameGlobalShaderConstantSetterFactory(
527 &m_flags.force_fog_off, &runData.fog_range, client);
528 shader_src->addShaderConstantSetterFactory(scsf);
530 // Update cached textures, meshes and materials
531 client->afterContentReceived();
535 camera = new Camera(*draw_control, client, m_rendering_engine);
536 if (!camera->successfullyCreated(*error_message))
538 client->setCamera(camera);
542 if (m_cache_enable_clouds)
543 clouds = new Clouds(smgr, -1, time(0));
547 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
549 skybox = NULL; // This is used/set later on in the main run loop
551 /* Pre-calculated values
553 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
555 v2u32 size = t->getOriginalSize();
556 crack_animation_length = size.Y / size.X;
558 crack_animation_length = 5;
564 /* Set window caption
566 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
568 str += utf8_to_wide(g_version_hash);
570 const wchar_t *text = nullptr;
571 if (simple_singleplayer_mode)
572 text = wgettext("Singleplayer");
574 text = wgettext("Multiplayer");
581 str += L"Minetest Hackclient";
584 device->setWindowCaption(str.c_str());
586 LocalPlayer *player = client->getEnv().getLocalPlayer();
587 player->hurt_tilt_timer = 0;
588 player->hurt_tilt_strength = 0;
590 hud = new Hud(client, player, &player->inventory);
592 mapper = client->getMinimap();
594 if (mapper && client->modsLoaded())
595 client->getScript()->on_minimap_ready(mapper);
604 // Remove stale "recent" chat messages from previous connections
605 chat_backend->clearRecentChat();
607 // Make sure the size of the recent messages buffer is right
608 chat_backend->applySettings();
610 // Chat backend and console
611 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
612 -1, chat_backend, client, &g_menumgr);
614 if (!gui_chat_console) {
615 *error_message = "Could not allocate memory for chat console";
616 errorstream << *error_message << std::endl;
620 m_cheat_menu = new CheatMenu(client);
623 *error_message = "Could not allocate memory for cheat menu";
624 errorstream << *error_message << std::endl;
628 #ifdef HAVE_TOUCHSCREENGUI
630 if (g_touchscreengui)
631 g_touchscreengui->show();
638 bool Game::connectToServer(const GameStartData &start_data,
639 bool *connect_ok, bool *connection_aborted)
641 *connect_ok = false; // Let's not be overly optimistic
642 *connection_aborted = false;
643 bool local_server_mode = false;
645 showOverlayMessage(N_("Resolving address..."), 0, 15);
647 Address connect_address(0, 0, 0, 0, start_data.socket_port);
650 connect_address.Resolve(start_data.address.c_str());
652 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
653 //connect_address.Resolve("localhost");
654 if (connect_address.isIPv6()) {
655 IPv6AddressBytes addr_bytes;
656 addr_bytes.bytes[15] = 1;
657 connect_address.setAddress(&addr_bytes);
659 connect_address.setAddress(127, 0, 0, 1);
661 local_server_mode = true;
663 } catch (ResolveError &e) {
664 *error_message = std::string("Couldn't resolve address: ") + e.what();
665 errorstream << *error_message << std::endl;
669 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
670 *error_message = "Unable to connect to " +
671 connect_address.serializeString() +
672 " because IPv6 is disabled";
673 errorstream << *error_message << std::endl;
677 client = new Client(start_data.name.c_str(),
678 start_data.password, start_data.address,
679 *draw_control, texture_src, shader_src,
680 itemdef_manager, nodedef_manager, sound, eventmgr,
681 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get());
683 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
685 infostream << "Connecting to server at ";
686 connect_address.print(&infostream);
687 infostream << std::endl;
689 client->connect(connect_address,
690 simple_singleplayer_mode || local_server_mode);
693 Wait for server to accept connection
699 FpsControl fps_control = { 0 };
701 f32 wait_time = 0; // in seconds
703 fps_control.last_time = m_rendering_engine->get_timer_time();
705 while (m_rendering_engine->run()) {
707 limitFps(&fps_control, &dtime);
709 // Update client and server
716 if (client->getState() == LC_Init) {
722 if (*connection_aborted)
725 if (client->accessDenied()) {
726 *error_message = "Access denied. Reason: "
727 + client->accessDeniedReason();
728 *reconnect_requested = client->reconnectRequested();
729 errorstream << *error_message << std::endl;
733 if (input->cancelPressed()) {
734 *connection_aborted = true;
735 infostream << "Connect aborted [Escape]" << std::endl;
739 if (client->m_is_registration_confirmation_state) {
740 if (registration_confirmation_shown) {
741 // Keep drawing the GUI
742 m_rendering_engine->draw_menu_scene(guienv, dtime, true);
744 registration_confirmation_shown = true;
745 (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
746 &g_menumgr, client, start_data.name, start_data.password,
747 connection_aborted, texture_src))->drop();
751 // Only time out if we aren't waiting for the server we started
752 if (!start_data.address.empty() && wait_time > 10) {
753 *error_message = "Connection timed out.";
754 errorstream << *error_message << std::endl;
759 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
762 } catch (con::PeerNotFoundException &e) {
763 // TODO: Should something be done here? At least an info/error
771 bool Game::getServerContent(bool *aborted)
775 FpsControl fps_control = { 0 };
776 f32 dtime; // in seconds
778 fps_control.last_time = m_rendering_engine->get_timer_time();
780 while (m_rendering_engine->run()) {
782 limitFps(&fps_control, &dtime);
784 // Update client and server
791 if (client->mediaReceived() && client->itemdefReceived() &&
792 client->nodedefReceived()) {
797 if (!checkConnection())
800 if (client->getState() < LC_Init) {
801 *error_message = "Client disconnected";
802 errorstream << *error_message << std::endl;
806 if (input->cancelPressed()) {
808 infostream << "Connect aborted [Escape]" << std::endl;
815 if (!client->itemdefReceived()) {
816 const wchar_t *text = wgettext("Item definitions...");
818 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
821 } else if (!client->nodedefReceived()) {
822 const wchar_t *text = wgettext("Node definitions...");
824 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
828 std::ostringstream message;
830 message.precision(0);
831 float receive = client->mediaReceiveProgress() * 100;
832 message << gettext("Media...");
834 message << " " << receive << "%";
835 message.precision(2);
837 if ((USE_CURL == 0) ||
838 (!g_settings->getBool("enable_remote_media_server"))) {
839 float cur = client->getCurRate();
840 std::string cur_unit = gettext("KiB/s");
844 cur_unit = gettext("MiB/s");
847 message << " (" << cur << ' ' << cur_unit << ")";
850 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
851 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
852 texture_src, dtime, progress);
860 /****************************************************************************/
861 /****************************************************************************
863 ****************************************************************************/
864 /****************************************************************************/
866 inline void Game::updateInteractTimers(f32 dtime)
868 if (runData.nodig_delay_timer >= 0)
869 runData.nodig_delay_timer -= dtime;
871 if (runData.object_hit_delay_timer >= 0)
872 runData.object_hit_delay_timer -= dtime;
874 runData.time_from_last_punch += dtime;
878 /* returns false if game should exit, otherwise true
880 inline bool Game::checkConnection()
882 if (client->accessDenied()) {
883 *error_message = "Access denied. Reason: "
884 + client->accessDeniedReason();
885 *reconnect_requested = client->reconnectRequested();
886 errorstream << *error_message << std::endl;
894 /* returns false if game should exit, otherwise true
896 inline bool Game::handleCallbacks()
898 if (g_gamecallback->disconnect_requested) {
899 g_gamecallback->disconnect_requested = false;
903 if (g_gamecallback->changepassword_requested) {
904 (new GUIPasswordChange(guienv, guiroot, -1,
905 &g_menumgr, client, texture_src))->drop();
906 g_gamecallback->changepassword_requested = false;
909 if (g_gamecallback->changevolume_requested) {
910 (new GUIVolumeChange(guienv, guiroot, -1,
911 &g_menumgr, texture_src))->drop();
912 g_gamecallback->changevolume_requested = false;
915 if (g_gamecallback->keyconfig_requested) {
916 (new GUIKeyChangeMenu(guienv, guiroot, -1,
917 &g_menumgr, texture_src))->drop();
918 g_gamecallback->keyconfig_requested = false;
921 if (g_gamecallback->keyconfig_changed) {
922 input->keycache.populate(); // update the cache with new settings
923 g_gamecallback->keyconfig_changed = false;
930 void Game::processQueues()
932 texture_src->processQueue();
933 itemdef_manager->processQueue(client);
934 shader_src->processQueue();
937 void Game::updateDebugState()
939 bool has_basic_debug = client->checkPrivilege("basic_debug");
940 bool has_debug = client->checkPrivilege("debug");
942 if (m_game_ui->m_flags.show_basic_debug) {
943 if (!has_basic_debug) {
944 m_game_ui->m_flags.show_basic_debug = false;
946 } else if (m_game_ui->m_flags.show_minimal_debug) {
947 if (has_basic_debug) {
948 m_game_ui->m_flags.show_basic_debug = true;
951 if (!has_basic_debug)
952 hud->disableBlockBounds();
954 draw_control->show_wireframe = false;
957 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
960 float profiler_print_interval =
961 g_settings->getFloat("profiler_print_interval");
962 bool print_to_log = true;
964 if (profiler_print_interval == 0) {
965 print_to_log = false;
966 profiler_print_interval = 3;
969 if (profiler_interval.step(dtime, profiler_print_interval)) {
971 infostream << "Profiler:" << std::endl;
972 g_profiler->print(infostream);
975 m_game_ui->updateProfiler();
979 // Update update graphs
980 g_profiler->graphAdd("Time non-rendering [ms]",
981 draw_times.busy_time - stats.drawtime);
983 g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time);
984 g_profiler->graphAdd("FPS", 1.0f / dtime);
987 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
994 /* Time average and jitter calculation
996 jp = &stats->dtime_jitter;
997 jp->avg = jp->avg * 0.96 + dtime * 0.04;
999 jitter = dtime - jp->avg;
1001 if (jitter > jp->max)
1004 jp->counter += dtime;
1006 if (jp->counter > 0.0) {
1008 jp->max_sample = jp->max;
1009 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1013 /* Busytime average and jitter calculation
1015 jp = &stats->busy_time_jitter;
1016 jp->avg = jp->avg + draw_times.busy_time * 0.02;
1018 jitter = draw_times.busy_time - jp->avg;
1020 if (jitter > jp->max)
1022 if (jitter < jp->min)
1025 jp->counter += dtime;
1027 if (jp->counter > 0.0) {
1029 jp->max_sample = jp->max;
1030 jp->min_sample = jp->min;
1038 /****************************************************************************
1040 ****************************************************************************/
1042 void Game::processUserInput(f32 dtime)
1044 // Reset input if window not active or some menu is active
1045 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1047 #ifdef HAVE_TOUCHSCREENGUI
1048 g_touchscreengui->hide();
1051 #ifdef HAVE_TOUCHSCREENGUI
1052 else if (g_touchscreengui) {
1053 /* on touchscreengui step may generate own input events which ain't
1054 * what we want in case we just did clear them */
1055 g_touchscreengui->step(dtime);
1059 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1060 gui_chat_console->closeConsoleAtOnce();
1063 // Input handler step() (used by the random input generator)
1067 auto formspec = m_game_ui->getFormspecGUI();
1069 formspec->getAndroidUIInput();
1071 handleAndroidChatInput();
1074 // Increase timer for double tap of "keymap_jump"
1075 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1076 runData.jump_timer += dtime;
1079 processItemSelection(&runData.new_playeritem);
1083 void Game::processKeyInput()
1085 if (wasKeyDown(KeyType::SELECT_UP)) {
1086 m_cheat_menu->selectUp();
1087 } else if (wasKeyDown(KeyType::SELECT_DOWN)) {
1088 m_cheat_menu->selectDown();
1089 } else if (wasKeyDown(KeyType::SELECT_LEFT)) {
1090 m_cheat_menu->selectLeft();
1091 } else if (wasKeyDown(KeyType::SELECT_RIGHT)) {
1092 m_cheat_menu->selectRight();
1093 } else if (wasKeyDown(KeyType::SELECT_CONFIRM)) {
1094 m_cheat_menu->selectConfirm();
1097 if (wasKeyDown(KeyType::DROP)) {
1098 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1099 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1100 toggleAutoforward();
1101 } else if (wasKeyDown(KeyType::BACKWARD)) {
1102 if (g_settings->getBool("continuous_forward"))
1103 toggleAutoforward();
1104 } else if (wasKeyDown(KeyType::INVENTORY)) {
1106 } else if (wasKeyDown(KeyType::ENDERCHEST)) {
1108 } else if (input->cancelPressed()) {
1110 m_android_chat_open = false;
1112 if (!gui_chat_console->isOpenInhibited()) {
1115 } else if (wasKeyDown(KeyType::CHAT)) {
1116 openConsole(0.2, L"");
1117 } else if (wasKeyDown(KeyType::CMD)) {
1118 openConsole(0.2, L"/");
1119 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1120 if (client->modsLoaded())
1121 openConsole(0.2, L".");
1123 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1124 } else if (wasKeyDown(KeyType::CONSOLE)) {
1125 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1126 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1128 } else if (wasKeyDown(KeyType::JUMP)) {
1129 toggleFreeMoveAlt();
1130 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1132 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1134 } else if (wasKeyDown(KeyType::NOCLIP)) {
1136 } else if (wasKeyDown(KeyType::KILLAURA)) {
1138 } else if (wasKeyDown(KeyType::FREECAM)) {
1140 } else if (wasKeyDown(KeyType::SCAFFOLD)) {
1143 } else if (wasKeyDown(KeyType::MUTE)) {
1144 if (g_settings->getBool("enable_sound")) {
1145 bool new_mute_sound = !g_settings->getBool("mute_sound");
1146 g_settings->setBool("mute_sound", new_mute_sound);
1148 m_game_ui->showTranslatedStatusText("Sound muted");
1150 m_game_ui->showTranslatedStatusText("Sound unmuted");
1152 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1154 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1155 if (g_settings->getBool("enable_sound")) {
1156 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1157 g_settings->setFloat("sound_volume", new_volume);
1158 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1159 m_game_ui->showStatusText(msg);
1161 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1163 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1164 if (g_settings->getBool("enable_sound")) {
1165 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1166 g_settings->setFloat("sound_volume", new_volume);
1167 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1168 m_game_ui->showStatusText(msg);
1170 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1173 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1174 || wasKeyDown(KeyType::DEC_VOLUME)) {
1175 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1177 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1179 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1180 client->makeScreenshot();
1181 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
1182 toggleBlockBounds();
1183 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1184 m_game_ui->toggleHud();
1185 } else if (wasKeyDown(KeyType::MINIMAP)) {
1186 toggleMinimap(isKeyDown(KeyType::SNEAK));
1187 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1188 m_game_ui->toggleChat();
1189 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1191 } else if (wasKeyDown(KeyType::TOGGLE_CHEAT_MENU)) {
1192 m_game_ui->toggleCheatMenu();
1193 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1194 toggleUpdateCamera();
1195 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1197 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1198 m_game_ui->toggleProfiler();
1199 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1200 increaseViewRange();
1201 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1202 decreaseViewRange();
1203 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1204 toggleFullViewRange();
1205 } else if (wasKeyDown(KeyType::ZOOM)) {
1207 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1209 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1211 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1213 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1217 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1218 runData.reset_jump_timer = false;
1219 runData.jump_timer = 0.0f;
1222 if (quicktune->hasMessage()) {
1223 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
1227 void Game::processItemSelection(u16 *new_playeritem)
1229 LocalPlayer *player = client->getEnv().getLocalPlayer();
1231 /* Item selection using mouse wheel
1233 *new_playeritem = player->getWieldIndex();
1235 s32 wheel = input->getMouseWheel();
1236 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
1237 player->hud_hotbar_itemcount - 1);
1241 if (wasKeyDown(KeyType::HOTBAR_NEXT))
1244 if (wasKeyDown(KeyType::HOTBAR_PREV))
1248 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
1250 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
1253 /* Item selection using hotbar slot keys
1255 for (u16 i = 0; i <= max_item; i++) {
1256 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
1257 *new_playeritem = i;
1264 void Game::dropSelectedItem(bool single_item)
1266 IDropAction *a = new IDropAction();
1267 a->count = single_item ? 1 : 0;
1268 a->from_inv.setCurrentPlayer();
1269 a->from_list = "main";
1270 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
1271 client->inventoryAction(a);
1275 void Game::openInventory()
1278 * Don't permit to open inventory is CAO or player doesn't exists.
1279 * This prevent showing an empty inventory at player load
1282 LocalPlayer *player = client->getEnv().getLocalPlayer();
1283 if (!player || !player->getCAO())
1286 infostream << "Game: Launching inventory" << std::endl;
1288 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
1290 InventoryLocation inventoryloc;
1291 inventoryloc.setCurrentPlayer();
1293 if (!client->modsLoaded()
1294 || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
1295 TextDest *txt_dst = new TextDestPlayerInventory(client);
1296 auto *&formspec = m_game_ui->updateFormspec("");
1297 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
1298 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
1300 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
1304 void Game::openEnderchest()
1306 LocalPlayer *player = client->getEnv().getLocalPlayer();
1307 if (!player || !player->getCAO())
1310 infostream << "Game: Launching special inventory" << std::endl;
1312 if (client->modsLoaded())
1313 client->getScript()->open_enderchest();
1317 void Game::openConsole(float scale, const wchar_t *line)
1319 assert(scale > 0.0f && scale <= 1.0f);
1322 porting::showInputDialog(gettext("ok"), "", "", 2);
1323 m_android_chat_open = true;
1325 if (gui_chat_console->isOpenInhibited())
1327 gui_chat_console->openConsole(scale);
1329 gui_chat_console->setCloseOnEnter(true);
1330 gui_chat_console->replaceAndAddToHistory(line);
1336 void Game::handleAndroidChatInput()
1338 if (m_android_chat_open && porting::getInputDialogState() == 0) {
1339 std::string text = porting::getInputDialogValue();
1340 client->typeChatMessage(utf8_to_wide(text));
1341 m_android_chat_open = false;
1347 void Game::toggleFreeMove()
1349 bool free_move = !g_settings->getBool("free_move");
1350 g_settings->set("free_move", bool_to_cstr(free_move));
1353 if (client->checkPrivilege("fly")) {
1354 m_game_ui->showTranslatedStatusText("Fly mode enabled");
1356 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
1359 m_game_ui->showTranslatedStatusText("Fly mode disabled");
1363 void Game::toggleFreeMoveAlt()
1365 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
1368 runData.reset_jump_timer = true;
1372 void Game::togglePitchMove()
1374 bool pitch_move = !g_settings->getBool("pitch_move");
1375 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
1378 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
1380 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
1385 void Game::toggleFast()
1387 bool fast_move = !g_settings->getBool("fast_move");
1388 bool has_fast_privs = client->checkPrivilege("fast");
1389 g_settings->set("fast_move", bool_to_cstr(fast_move));
1392 if (has_fast_privs) {
1393 m_game_ui->showTranslatedStatusText("Fast mode enabled");
1395 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
1398 m_game_ui->showTranslatedStatusText("Fast mode disabled");
1402 m_cache_hold_aux1 = fast_move && has_fast_privs;
1407 void Game::toggleNoClip()
1409 bool noclip = !g_settings->getBool("noclip");
1410 g_settings->set("noclip", bool_to_cstr(noclip));
1413 if (client->checkPrivilege("noclip")) {
1414 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
1416 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
1419 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
1423 void Game::toggleKillaura()
1425 bool killaura = ! g_settings->getBool("killaura");
1426 g_settings->set("killaura", bool_to_cstr(killaura));
1429 m_game_ui->showTranslatedStatusText("Killaura enabled");
1431 m_game_ui->showTranslatedStatusText("Killaura disabled");
1435 void Game::toggleFreecam()
1437 bool freecam = ! g_settings->getBool("freecam");
1438 g_settings->set("freecam", bool_to_cstr(freecam));
1441 m_game_ui->showTranslatedStatusText("Freecam enabled");
1443 m_game_ui->showTranslatedStatusText("Freecam disabled");
1447 void Game::toggleScaffold()
1449 bool scaffold = ! g_settings->getBool("scaffold");
1450 g_settings->set("scaffold", bool_to_cstr(scaffold));
1453 m_game_ui->showTranslatedStatusText("Scaffold enabled");
1455 m_game_ui->showTranslatedStatusText("Scaffold disabled");
1459 void Game::toggleCinematic()
1461 bool cinematic = !g_settings->getBool("cinematic");
1462 g_settings->set("cinematic", bool_to_cstr(cinematic));
1465 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
1467 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
1470 void Game::toggleBlockBounds()
1472 if (client->checkPrivilege("basic_debug")) {
1473 enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
1475 case Hud::BLOCK_BOUNDS_OFF:
1476 m_game_ui->showTranslatedStatusText("Block bounds hidden");
1478 case Hud::BLOCK_BOUNDS_CURRENT:
1479 m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
1481 case Hud::BLOCK_BOUNDS_NEAR:
1482 m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
1484 case Hud::BLOCK_BOUNDS_MAX:
1485 m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
1492 m_game_ui->showTranslatedStatusText("Can't show block bounds (need 'basic_debug' privilege)");
1496 // Autoforward by toggling continuous forward.
1497 void Game::toggleAutoforward()
1499 bool autorun_enabled = !g_settings->getBool("continuous_forward");
1500 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
1502 if (autorun_enabled)
1503 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
1505 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
1508 void Game::toggleMinimap(bool shift_pressed)
1510 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
1514 mapper->toggleMinimapShape();
1518 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
1520 // Not so satisying code to keep compatibility with old fixed mode system
1522 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
1524 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
1525 m_game_ui->m_flags.show_minimap = false;
1528 // If radar is disabled, try to find a non radar mode or fall back to 0
1529 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
1530 while (mapper->getModeIndex() &&
1531 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
1534 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
1538 // End of 'not so satifying code'
1539 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
1540 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
1541 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
1543 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
1546 void Game::toggleFog()
1548 bool fog_enabled = g_settings->getBool("enable_fog");
1549 g_settings->setBool("enable_fog", !fog_enabled);
1551 m_game_ui->showTranslatedStatusText("Fog disabled");
1553 m_game_ui->showTranslatedStatusText("Fog enabled");
1557 void Game::toggleDebug()
1559 // Initial: No debug info
1560 // 1x toggle: Debug text
1561 // 2x toggle: Debug text with profiler graph
1562 // 3x toggle: Debug text and wireframe (needs "debug" priv)
1563 // Next toggle: Back to initial
1565 // The debug text can be in 2 modes: minimal and basic.
1566 // * Minimal: Only technical client info that not gameplay-relevant
1567 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
1568 // Basic mode is used when player has "basic_debug" priv,
1569 // otherwise the Minimal mode is used.
1570 if (!m_game_ui->m_flags.show_minimal_debug) {
1571 m_game_ui->m_flags.show_minimal_debug = true;
1572 if (client->checkPrivilege("basic_debug")) {
1573 m_game_ui->m_flags.show_basic_debug = true;
1575 m_game_ui->m_flags.show_profiler_graph = false;
1576 draw_control->show_wireframe = false;
1577 m_game_ui->showTranslatedStatusText("Debug info shown");
1578 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
1579 if (client->checkPrivilege("basic_debug")) {
1580 m_game_ui->m_flags.show_basic_debug = true;
1582 m_game_ui->m_flags.show_profiler_graph = true;
1583 m_game_ui->showTranslatedStatusText("Profiler graph shown");
1584 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
1585 if (client->checkPrivilege("basic_debug")) {
1586 m_game_ui->m_flags.show_basic_debug = true;
1588 m_game_ui->m_flags.show_profiler_graph = false;
1589 draw_control->show_wireframe = true;
1590 m_game_ui->showTranslatedStatusText("Wireframe shown");
1592 m_game_ui->m_flags.show_minimal_debug = false;
1593 m_game_ui->m_flags.show_basic_debug = false;
1594 m_game_ui->m_flags.show_profiler_graph = false;
1595 draw_control->show_wireframe = false;
1596 if (client->checkPrivilege("debug")) {
1597 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
1599 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
1605 void Game::toggleUpdateCamera()
1607 if (g_settings->getBool("freecam"))
1609 m_flags.disable_camera_update = !m_flags.disable_camera_update;
1610 if (m_flags.disable_camera_update)
1611 m_game_ui->showTranslatedStatusText("Camera update disabled");
1613 m_game_ui->showTranslatedStatusText("Camera update enabled");
1617 void Game::increaseViewRange()
1619 s16 range = g_settings->getS16("viewing_range");
1620 s16 range_new = range + 10;
1622 if (range_new > 4000) {
1624 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
1625 m_game_ui->showStatusText(msg);
1627 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
1628 m_game_ui->showStatusText(msg);
1630 g_settings->set("viewing_range", itos(range_new));
1634 void Game::decreaseViewRange()
1636 s16 range = g_settings->getS16("viewing_range");
1637 s16 range_new = range - 10;
1639 if (range_new < 20) {
1641 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
1642 m_game_ui->showStatusText(msg);
1644 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
1645 m_game_ui->showStatusText(msg);
1647 g_settings->set("viewing_range", itos(range_new));
1651 void Game::toggleFullViewRange()
1653 draw_control->range_all = !draw_control->range_all;
1654 if (draw_control->range_all)
1655 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
1657 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
1661 void Game::checkZoomEnabled()
1663 LocalPlayer *player = client->getEnv().getLocalPlayer();
1664 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
1665 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
1668 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
1670 if ((device->isWindowActive() && device->isWindowFocused()
1671 && !isMenuActive()) || input->isRandom()) {
1674 if (!input->isRandom()) {
1675 // Mac OSX gets upset if this is set every frame
1676 if (device->getCursorControl()->isVisible())
1677 device->getCursorControl()->setVisible(false);
1681 if (m_first_loop_after_window_activation) {
1682 m_first_loop_after_window_activation = false;
1684 input->setMousePos(driver->getScreenSize().Width / 2,
1685 driver->getScreenSize().Height / 2);
1687 updateCameraOrientation(cam, dtime);
1693 // Mac OSX gets upset if this is set every frame
1694 if (!device->getCursorControl()->isVisible())
1695 device->getCursorControl()->setVisible(true);
1698 m_first_loop_after_window_activation = true;
1703 // Get the factor to multiply with sensitivity to get the same mouse/joystick
1704 // responsiveness independently of FOV.
1705 f32 Game::getSensitivityScaleFactor() const
1707 f32 fov_y = client->getCamera()->getFovY();
1709 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
1710 // 16:9 aspect ratio to minimize disruption of existing sensitivity
1712 return tan(fov_y / 2.0f) * 1.3763818698f;
1715 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
1717 #ifdef HAVE_TOUCHSCREENGUI
1718 if (g_touchscreengui) {
1719 cam->camera_yaw += g_touchscreengui->getYawChange();
1720 cam->camera_pitch = g_touchscreengui->getPitch();
1723 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
1724 v2s32 dist = input->getMousePos() - center;
1726 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
1730 f32 sens_scale = getSensitivityScaleFactor();
1731 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
1732 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
1734 if (dist.X != 0 || dist.Y != 0)
1735 input->setMousePos(center.X, center.Y);
1736 #ifdef HAVE_TOUCHSCREENGUI
1740 if (m_cache_enable_joysticks) {
1741 f32 sens_scale = getSensitivityScaleFactor();
1742 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
1743 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
1744 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
1747 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
1751 void Game::updatePlayerControl(const CameraOrientation &cam)
1753 LocalPlayer *player = client->getEnv().getLocalPlayer();
1755 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
1757 PlayerControl control(
1758 isKeyDown(KeyType::JUMP) || player->getAutojump(),
1759 isKeyDown(KeyType::AUX1),
1760 isKeyDown(KeyType::SNEAK),
1761 isKeyDown(KeyType::ZOOM),
1762 isKeyDown(KeyType::DIG),
1763 isKeyDown(KeyType::PLACE),
1766 input->getMovementSpeed(),
1767 input->getMovementDirection()
1770 // autoforward if set: move towards pointed position at maximum speed
1771 if (player->getPlayerSettings().continuous_forward &&
1772 client->activeObjectsReceived() && !player->isDead()) {
1773 control.movement_speed = 1.0f;
1774 control.movement_direction = 0.0f;
1778 /* For Android, simulate holding down AUX1 (fast move) if the user has
1779 * the fast_move setting toggled on. If there is an aux1 key defined for
1780 * Android then its meaning is inverted (i.e. holding aux1 means walk and
1783 if (m_cache_hold_aux1) {
1784 control.aux1 = control.aux1 ^ true;
1788 u32 keypress_bits = (
1789 ( (u32)(control.jump & 0x1) << 4) |
1790 ( (u32)(control.aux1 & 0x1) << 5) |
1791 ( (u32)(control.sneak & 0x1) << 6) |
1792 ( (u32)(control.dig & 0x1) << 7) |
1793 ( (u32)(control.place & 0x1) << 8) |
1794 ( (u32)(control.zoom & 0x1) << 9)
1797 // Set direction keys to ensure mod compatibility
1798 if (control.movement_speed > 0.001f) {
1799 float absolute_direction;
1801 // Check in original orientation (absolute value indicates forward / backward)
1802 absolute_direction = abs(control.movement_direction);
1803 if (absolute_direction < (3.0f / 8.0f * M_PI))
1804 keypress_bits |= (u32)(0x1 << 0); // Forward
1805 if (absolute_direction > (5.0f / 8.0f * M_PI))
1806 keypress_bits |= (u32)(0x1 << 1); // Backward
1808 // Rotate entire coordinate system by 90 degrees (absolute value indicates left / right)
1809 absolute_direction = control.movement_direction + M_PI_2;
1810 if (absolute_direction >= M_PI)
1811 absolute_direction -= 2 * M_PI;
1812 absolute_direction = abs(absolute_direction);
1813 if (absolute_direction < (3.0f / 8.0f * M_PI))
1814 keypress_bits |= (u32)(0x1 << 2); // Left
1815 if (absolute_direction > (5.0f / 8.0f * M_PI))
1816 keypress_bits |= (u32)(0x1 << 3); // Right
1819 client->setPlayerControl(control);
1820 player->keyPressed = keypress_bits;
1826 inline void Game::step(f32 *dtime)
1828 bool can_be_and_is_paused =
1829 (simple_singleplayer_mode && g_menumgr.pausesGame());
1831 if (can_be_and_is_paused) { // This is for a singleplayer server
1832 *dtime = 0; // No time passes
1834 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
1838 server->step(*dtime);
1840 client->step(*dtime);
1844 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
1847 for (auto &&child: node->getChildren())
1848 pauseNodeAnimation(paused, child);
1849 if (node->getType() != scene::ESNT_ANIMATED_MESH)
1851 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
1852 float speed = animated_node->getAnimationSpeed();
1855 paused.push_back({grab(animated_node), speed});
1856 animated_node->setAnimationSpeed(0.0f);
1859 void Game::pauseAnimation()
1861 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
1864 void Game::resumeAnimation()
1866 for (auto &&pair: paused_animated_nodes)
1867 pair.first->setAnimationSpeed(pair.second);
1868 paused_animated_nodes.clear();
1871 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
1872 {&Game::handleClientEvent_None},
1873 {&Game::handleClientEvent_PlayerDamage},
1874 {&Game::handleClientEvent_PlayerForceMove},
1875 {&Game::handleClientEvent_Deathscreen},
1876 {&Game::handleClientEvent_ShowFormSpec},
1877 {&Game::handleClientEvent_ShowLocalFormSpec},
1878 {&Game::handleClientEvent_HandleParticleEvent},
1879 {&Game::handleClientEvent_HandleParticleEvent},
1880 {&Game::handleClientEvent_HandleParticleEvent},
1881 {&Game::handleClientEvent_HudAdd},
1882 {&Game::handleClientEvent_HudRemove},
1883 {&Game::handleClientEvent_HudChange},
1884 {&Game::handleClientEvent_SetSky},
1885 {&Game::handleClientEvent_SetSun},
1886 {&Game::handleClientEvent_SetMoon},
1887 {&Game::handleClientEvent_SetStars},
1888 {&Game::handleClientEvent_OverrideDayNigthRatio},
1889 {&Game::handleClientEvent_CloudParams},
1892 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
1894 FATAL_ERROR("ClientEvent type None received");
1897 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
1899 if (client->modsLoaded())
1900 client->getScript()->on_damage_taken(event->player_damage.amount);
1902 // Damage flash and hurt tilt are not used at death
1903 if (client->getHP() > 0) {
1904 LocalPlayer *player = client->getEnv().getLocalPlayer();
1906 f32 hp_max = player->getCAO() ?
1907 player->getCAO()->getProperties()->hp_max : PLAYER_MAX_HP_DEFAULT;
1908 f32 damage_ratio = event->player_damage.amount / hp_max;
1910 runData.damage_flash += 95.0f + 64.f * damage_ratio;
1911 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
1913 player->hurt_tilt_timer = 1.5f;
1914 player->hurt_tilt_strength =
1915 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
1918 // Play damage sound
1919 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
1922 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
1924 cam->camera_yaw = event->player_force_move.yaw;
1925 cam->camera_pitch = event->player_force_move.pitch;
1928 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
1930 // If client scripting is enabled, deathscreen is handled by CSM code in
1931 // builtin/client/init.lua
1932 if (client->modsLoaded())
1933 client->getScript()->on_death();
1935 showDeathFormspec();
1937 /* Handle visualization */
1938 LocalPlayer *player = client->getEnv().getLocalPlayer();
1939 runData.damage_flash = 0;
1940 player->hurt_tilt_timer = 0;
1941 player->hurt_tilt_strength = 0;
1944 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
1946 if (event->show_formspec.formspec->empty()) {
1947 auto formspec = m_game_ui->getFormspecGUI();
1948 if (formspec && (event->show_formspec.formname->empty()
1949 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
1950 formspec->quitMenu();
1953 FormspecFormSource *fs_src =
1954 new FormspecFormSource(*(event->show_formspec.formspec));
1955 TextDestPlayerInventory *txt_dst =
1956 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
1958 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
1959 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
1960 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
1963 delete event->show_formspec.formspec;
1964 delete event->show_formspec.formname;
1967 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
1969 if (event->show_formspec.formspec->empty()) {
1970 auto formspec = m_game_ui->getFormspecGUI();
1971 if (formspec && (event->show_formspec.formname->empty()
1972 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
1973 formspec->quitMenu();
1976 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
1977 LocalFormspecHandler *txt_dst =
1978 new LocalFormspecHandler(*event->show_formspec.formname, client);
1979 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(), &input->joystick,
1980 fs_src, txt_dst, client->getFormspecPrepend(), sound);
1983 delete event->show_formspec.formspec;
1984 delete event->show_formspec.formname;
1987 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
1988 CameraOrientation *cam)
1990 LocalPlayer *player = client->getEnv().getLocalPlayer();
1991 client->getParticleManager()->handleParticleEvent(event, client, player);
1994 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
1996 LocalPlayer *player = client->getEnv().getLocalPlayer();
1998 u32 server_id = event->hudadd->server_id;
1999 // ignore if we already have a HUD with that ID
2000 auto i = m_hud_server_to_client.find(server_id);
2001 if (i != m_hud_server_to_client.end()) {
2002 delete event->hudadd;
2006 HudElement *e = new HudElement;
2007 e->type = static_cast<HudElementType>(event->hudadd->type);
2008 e->pos = event->hudadd->pos;
2009 e->name = event->hudadd->name;
2010 e->scale = event->hudadd->scale;
2011 e->text = event->hudadd->text;
2012 e->number = event->hudadd->number;
2013 e->item = event->hudadd->item;
2014 e->dir = event->hudadd->dir;
2015 e->align = event->hudadd->align;
2016 e->offset = event->hudadd->offset;
2017 e->world_pos = event->hudadd->world_pos;
2018 e->size = event->hudadd->size;
2019 e->z_index = event->hudadd->z_index;
2020 e->text2 = event->hudadd->text2;
2021 e->style = event->hudadd->style;
2022 m_hud_server_to_client[server_id] = player->addHud(e);
2024 delete event->hudadd;
2027 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2029 LocalPlayer *player = client->getEnv().getLocalPlayer();
2031 auto i = m_hud_server_to_client.find(event->hudrm.id);
2032 if (i != m_hud_server_to_client.end()) {
2033 HudElement *e = player->removeHud(i->second);
2035 m_hud_server_to_client.erase(i);
2040 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2042 LocalPlayer *player = client->getEnv().getLocalPlayer();
2044 HudElement *e = nullptr;
2046 auto i = m_hud_server_to_client.find(event->hudchange->id);
2047 if (i != m_hud_server_to_client.end()) {
2048 e = player->getHud(i->second);
2052 delete event->hudchange;
2056 #define CASE_SET(statval, prop, dataprop) \
2058 e->prop = event->hudchange->dataprop; \
2061 switch (event->hudchange->stat) {
2062 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2064 CASE_SET(HUD_STAT_NAME, name, sdata);
2066 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2068 CASE_SET(HUD_STAT_TEXT, text, sdata);
2070 CASE_SET(HUD_STAT_NUMBER, number, data);
2072 CASE_SET(HUD_STAT_ITEM, item, data);
2074 CASE_SET(HUD_STAT_DIR, dir, data);
2076 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2078 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2080 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2082 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2084 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2086 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2088 CASE_SET(HUD_STAT_STYLE, style, data);
2093 delete event->hudchange;
2096 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2098 sky->setVisible(false);
2099 // Whether clouds are visible in front of a custom skybox.
2100 sky->setCloudsEnabled(event->set_sky->clouds);
2106 // Clear the old textures out in case we switch rendering type.
2107 sky->clearSkyboxTextures();
2108 // Handle according to type
2109 if (event->set_sky->type == "regular") {
2110 // Shows the mesh skybox
2111 sky->setVisible(true);
2112 // Update mesh based skybox colours if applicable.
2113 sky->setSkyColors(event->set_sky->sky_color);
2114 sky->setHorizonTint(
2115 event->set_sky->fog_sun_tint,
2116 event->set_sky->fog_moon_tint,
2117 event->set_sky->fog_tint_type
2119 } else if (event->set_sky->type == "skybox" &&
2120 event->set_sky->textures.size() == 6) {
2121 // Disable the dyanmic mesh skybox:
2122 sky->setVisible(false);
2124 sky->setFallbackBgColor(event->set_sky->bgcolor);
2125 // Set sunrise and sunset fog tinting:
2126 sky->setHorizonTint(
2127 event->set_sky->fog_sun_tint,
2128 event->set_sky->fog_moon_tint,
2129 event->set_sky->fog_tint_type
2131 // Add textures to skybox.
2132 for (int i = 0; i < 6; i++)
2133 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2135 // Handle everything else as plain color.
2136 if (event->set_sky->type != "plain")
2137 infostream << "Unknown sky type: "
2138 << (event->set_sky->type) << std::endl;
2139 sky->setVisible(false);
2140 sky->setFallbackBgColor(event->set_sky->bgcolor);
2141 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2142 sky->setHorizonTint(
2143 event->set_sky->bgcolor,
2144 event->set_sky->bgcolor,
2149 delete event->set_sky;
2152 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2154 sky->setSunVisible(event->sun_params->visible);
2155 sky->setSunTexture(event->sun_params->texture,
2156 event->sun_params->tonemap, texture_src);
2157 sky->setSunScale(event->sun_params->scale);
2158 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2159 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2160 delete event->sun_params;
2163 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2165 sky->setMoonVisible(event->moon_params->visible);
2166 sky->setMoonTexture(event->moon_params->texture,
2167 event->moon_params->tonemap, texture_src);
2168 sky->setMoonScale(event->moon_params->scale);
2169 delete event->moon_params;
2172 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2174 sky->setStarsVisible(event->star_params->visible);
2175 sky->setStarCount(event->star_params->count, false);
2176 sky->setStarColor(event->star_params->starcolor);
2177 sky->setStarScale(event->star_params->scale);
2178 delete event->star_params;
2181 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2182 CameraOrientation *cam)
2184 client->getEnv().setDayNightRatioOverride(
2185 event->override_day_night_ratio.do_override,
2186 event->override_day_night_ratio.ratio_f * 1000.0f);
2189 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2194 clouds->setDensity(event->cloud_params.density);
2195 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2196 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2197 clouds->setHeight(event->cloud_params.height);
2198 clouds->setThickness(event->cloud_params.thickness);
2199 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2202 void Game::processClientEvents(CameraOrientation *cam)
2204 while (client->hasClientEvents()) {
2205 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2206 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2207 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2208 (this->*evHandler.handler)(event.get(), cam);
2212 void Game::updateChat(f32 dtime, const v2u32 &screensize)
2214 // Get new messages from error log buffer
2215 while (!m_chat_log_buf.empty())
2216 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2218 // Get new messages from client
2219 std::wstring message;
2220 while (client->getChatMessage(message)) {
2221 chat_backend->addUnparsedMessage(message);
2224 // Remove old messages
2225 chat_backend->step(dtime);
2227 // Display all messages in a static text element
2228 m_game_ui->setChatText(chat_backend->getRecentChat(),
2229 chat_backend->getRecentBuffer().getLineCount());
2232 void Game::updateCamera(u32 busy_time, f32 dtime)
2234 LocalPlayer *player = client->getEnv().getLocalPlayer();
2237 For interaction purposes, get info about the held item
2239 - Is it a usable item?
2240 - Can it point to liquids?
2242 ItemStack playeritem;
2244 ItemStack selected, hand;
2245 playeritem = player->getWieldedItem(&selected, &hand);
2248 ToolCapabilities playeritem_toolcap =
2249 playeritem.getToolCapabilities(itemdef_manager);
2251 v3s16 old_camera_offset = camera->getOffset();
2253 if (wasKeyDown(KeyType::CAMERA_MODE) && ! g_settings->getBool("freecam")) {
2254 camera->toggleCameraMode();
2255 updatePlayerCAOVisibility();
2258 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2259 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2261 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2262 camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
2263 camera->step(dtime);
2265 v3f camera_position = camera->getPosition();
2266 v3f camera_direction = camera->getDirection();
2267 f32 camera_fov = camera->getFovMax();
2268 v3s16 camera_offset = camera->getOffset();
2270 m_camera_offset_changed = (camera_offset != old_camera_offset);
2272 if (!m_flags.disable_camera_update) {
2273 client->getEnv().getClientMap().updateCamera(camera_position,
2274 camera_direction, camera_fov, camera_offset);
2276 if (m_camera_offset_changed) {
2277 client->updateCameraOffset(camera_offset);
2278 client->getEnv().updateCameraOffset(camera_offset);
2281 clouds->updateCameraOffset(camera_offset);
2286 void Game::updatePlayerCAOVisibility()
2288 // Make the player visible depending on camera mode.
2289 LocalPlayer *player = client->getEnv().getLocalPlayer();
2290 GenericCAO *playercao = player->getCAO();
2293 playercao->updateMeshCulling();
2294 bool is_visible = camera->getCameraMode() > CAMERA_MODE_FIRST || g_settings->getBool("freecam");
2295 playercao->setChildrenVisible(is_visible);
2298 void Game::updateSound(f32 dtime)
2300 // Update sound listener
2301 v3s16 camera_offset = camera->getOffset();
2302 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
2303 v3f(0, 0, 0), // velocity
2304 camera->getDirection(),
2305 camera->getCameraNode()->getUpVector());
2307 bool mute_sound = g_settings->getBool("mute_sound");
2309 sound->setListenerGain(0.0f);
2311 // Check if volume is in the proper range, else fix it.
2312 float old_volume = g_settings->getFloat("sound_volume");
2313 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
2314 sound->setListenerGain(new_volume);
2316 if (old_volume != new_volume) {
2317 g_settings->setFloat("sound_volume", new_volume);
2321 LocalPlayer *player = client->getEnv().getLocalPlayer();
2323 // Tell the sound maker whether to make footstep sounds
2324 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
2326 // Update sound maker
2327 if (player->makes_footstep_sound)
2328 soundmaker->step(dtime);
2330 ClientMap &map = client->getEnv().getClientMap();
2331 MapNode n = map.getNode(player->getFootstepNodePos());
2332 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
2336 void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
2338 LocalPlayer *player = client->getEnv().getLocalPlayer();
2340 const v3f camera_direction = camera->getDirection();
2341 const v3s16 camera_offset = camera->getOffset();
2344 Calculate what block is the crosshair pointing to
2347 ItemStack selected_item, hand_item;
2348 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
2350 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
2351 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
2354 if (g_settings->getBool("reach"))
2355 d += g_settings->getU16("tool_range");
2357 core::line3d<f32> shootline;
2359 switch (camera->getCameraMode()) {
2360 case CAMERA_MODE_FIRST:
2361 // Shoot from camera position, with bobbing
2362 shootline.start = camera->getPosition();
2364 case CAMERA_MODE_THIRD:
2365 // Shoot from player head, no bobbing
2366 shootline.start = camera->getHeadPosition();
2368 case CAMERA_MODE_THIRD_FRONT:
2369 shootline.start = camera->getHeadPosition();
2370 // prevent player pointing anything in front-view
2374 shootline.end = shootline.start + camera_direction * BS * d;
2376 #ifdef HAVE_TOUCHSCREENGUI
2378 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
2379 shootline = g_touchscreengui->getShootline();
2380 // Scale shootline to the acual distance the player can reach
2381 shootline.end = shootline.start
2382 + shootline.getVector().normalize() * BS * d;
2383 shootline.start += intToFloat(camera_offset, BS);
2384 shootline.end += intToFloat(camera_offset, BS);
2389 PointedThing pointed = updatePointedThing(shootline,
2390 selected_def.liquids_pointable,
2391 !runData.btn_down_for_dig,
2394 if (pointed != runData.pointed_old) {
2395 infostream << "Pointing at " << pointed.dump() << std::endl;
2396 hud->updateSelectionMesh(camera_offset);
2399 // Allow digging again if button is not pressed
2400 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
2401 runData.digging_blocked = false;
2405 - releasing dig button
2406 - pointing away from node
2408 if (runData.digging) {
2409 if (wasKeyReleased(KeyType::DIG)) {
2410 infostream << "Dig button released (stopped digging)" << std::endl;
2411 runData.digging = false;
2412 } else if (pointed != runData.pointed_old) {
2413 if (pointed.type == POINTEDTHING_NODE
2414 && runData.pointed_old.type == POINTEDTHING_NODE
2415 && pointed.node_undersurface
2416 == runData.pointed_old.node_undersurface) {
2417 // Still pointing to the same node, but a different face.
2420 infostream << "Pointing away from node (stopped digging)" << std::endl;
2421 runData.digging = false;
2422 hud->updateSelectionMesh(camera_offset);
2426 if (!runData.digging) {
2427 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
2428 client->setCrack(-1, v3s16(0, 0, 0));
2429 runData.dig_time = 0.0;
2431 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
2432 // Remove e.g. torches faster when clicking instead of holding dig button
2433 runData.nodig_delay_timer = 0;
2434 runData.dig_instantly = false;
2437 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
2438 runData.btn_down_for_dig = false;
2440 runData.punching = false;
2442 soundmaker->m_player_leftpunch_sound.name = "";
2444 // Prepare for repeating, unless we're not supposed to
2445 if ((isKeyDown(KeyType::PLACE) || g_settings->getBool("autoplace")) && !g_settings->getBool("safe_dig_and_place"))
2446 runData.repeat_place_timer += dtime;
2448 runData.repeat_place_timer = 0;
2450 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
2451 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
2452 !client->getScript()->on_item_use(selected_item, pointed)))
2453 client->interact(INTERACT_USE, pointed);
2454 } else if (pointed.type == POINTEDTHING_NODE) {
2455 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
2456 } else if (pointed.type == POINTEDTHING_OBJECT) {
2457 v3f player_position = player->getPosition();
2458 handlePointingAtObject(pointed, tool_item, player_position, show_debug);
2459 } else if (isKeyDown(KeyType::DIG)) {
2460 // When button is held down in air, show continuous animation
2461 runData.punching = true;
2462 // Run callback even though item is not usable
2463 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
2464 client->getScript()->on_item_use(selected_item, pointed);
2465 } else if (wasKeyPressed(KeyType::PLACE)) {
2466 handlePointingAtNothing(selected_item);
2469 runData.pointed_old = pointed;
2471 if (runData.punching || wasKeyPressed(KeyType::DIG))
2472 camera->setDigging(0); // dig animation
2474 input->clearWasKeyPressed();
2475 input->clearWasKeyReleased();
2476 // Ensure DIG & PLACE are marked as handled
2477 wasKeyDown(KeyType::DIG);
2478 wasKeyDown(KeyType::PLACE);
2480 input->joystick.clearWasKeyPressed(KeyType::DIG);
2481 input->joystick.clearWasKeyPressed(KeyType::PLACE);
2483 input->joystick.clearWasKeyReleased(KeyType::DIG);
2484 input->joystick.clearWasKeyReleased(KeyType::PLACE);
2488 PointedThing Game::updatePointedThing(
2489 const core::line3d<f32> &shootline,
2490 bool liquids_pointable,
2491 bool look_for_object,
2492 const v3s16 &camera_offset)
2494 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
2495 selectionboxes->clear();
2496 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
2497 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
2498 "show_entity_selectionbox");
2500 ClientEnvironment &env = client->getEnv();
2501 ClientMap &map = env.getClientMap();
2502 const NodeDefManager *nodedef = map.getNodeDefManager();
2504 runData.selected_object = NULL;
2505 hud->pointing_at_object = false;
2506 RaycastState s(shootline, look_for_object, liquids_pointable, ! g_settings->getBool("dont_point_nodes"));
2507 PointedThing result;
2508 env.continueRaycast(&s, &result);
2509 if (result.type == POINTEDTHING_OBJECT) {
2510 hud->pointing_at_object = true;
2512 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
2513 aabb3f selection_box;
2514 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
2515 runData.selected_object->getSelectionBox(&selection_box)) {
2516 v3f pos = runData.selected_object->getPosition();
2517 selectionboxes->push_back(aabb3f(selection_box));
2518 hud->setSelectionPos(pos, camera_offset);
2520 } else if (result.type == POINTEDTHING_NODE) {
2521 // Update selection boxes
2522 MapNode n = map.getNode(result.node_undersurface);
2523 std::vector<aabb3f> boxes;
2524 n.getSelectionBoxes(nodedef, &boxes,
2525 n.getNeighbors(result.node_undersurface, &map));
2528 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
2529 i != boxes.end(); ++i) {
2531 box.MinEdge -= v3f(d, d, d);
2532 box.MaxEdge += v3f(d, d, d);
2533 selectionboxes->push_back(box);
2535 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
2537 hud->setSelectedFaceNormal(v3f(
2538 result.intersection_normal.X,
2539 result.intersection_normal.Y,
2540 result.intersection_normal.Z));
2543 // Update selection mesh light level and vertex colors
2544 if (!selectionboxes->empty()) {
2545 v3f pf = hud->getSelectionPos();
2546 v3s16 p = floatToInt(pf, BS);
2548 // Get selection mesh light level
2549 MapNode n = map.getNode(p);
2550 u16 node_light = getInteriorLight(n, -1, nodedef);
2551 u16 light_level = node_light;
2553 for (const v3s16 &dir : g_6dirs) {
2554 n = map.getNode(p + dir);
2555 node_light = getInteriorLight(n, -1, nodedef);
2556 if (node_light > light_level)
2557 light_level = node_light;
2560 u32 daynight_ratio = client->getEnv().getDayNightRatio();
2562 final_color_blend(&c, light_level, daynight_ratio);
2564 // Modify final color a bit with time
2565 u32 timer = porting::getTimeMs() % 5000;
2566 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
2567 float sin_r = 0.08f * std::sin(timerf);
2568 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
2569 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
2570 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
2571 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
2572 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
2574 // Set mesh final color
2575 hud->setSelectionMeshColor(c);
2580 void Game::handlePointingAtNothing(const ItemStack &playerItem)
2582 infostream << "Attempted to place item while pointing at nothing" << std::endl;
2583 PointedThing fauxPointed;
2584 fauxPointed.type = POINTEDTHING_NOTHING;
2585 client->interact(INTERACT_ACTIVATE, fauxPointed);
2589 void Game::handlePointingAtNode(const PointedThing &pointed,
2590 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
2592 v3s16 nodepos = pointed.node_undersurface;
2593 v3s16 neighbourpos = pointed.node_abovesurface;
2596 Check information text of node
2599 ClientMap &map = client->getEnv().getClientMap();
2601 if (((runData.nodig_delay_timer <= 0.0 || g_settings->getBool("fastdig")) && (isKeyDown(KeyType::DIG) || g_settings->getBool("autodig"))
2602 && !runData.digging_blocked
2603 && client->checkPrivilege("interact"))
2605 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
2608 // This should be done after digging handling
2609 NodeMetadata *meta = map.getNodeMetadata(nodepos);
2612 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
2613 meta->getString("infotext"))));
2615 MapNode n = map.getNode(nodepos);
2617 if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
2618 m_game_ui->setInfoText(L"Unknown node: " +
2619 utf8_to_wide(nodedef_manager->get(n).name));
2623 if ((wasKeyPressed(KeyType::PLACE) ||
2624 (runData.repeat_place_timer >= (g_settings->getBool("fastplace") ? 0.001 : m_repeat_place_time))) &&
2625 client->checkPrivilege("interact")) {
2626 runData.repeat_place_timer = 0;
2627 infostream << "Place button pressed while looking at ground" << std::endl;
2629 // Placing animation (always shown for feedback)
2630 camera->setDigging(1);
2632 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
2634 // If the wielded item has node placement prediction,
2636 // And also set the sound and send the interact
2637 // But first check for meta formspec and rightclickable
2638 auto &def = selected_item.getDefinition(itemdef_manager);
2639 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
2642 if (placed && client->modsLoaded())
2643 client->getScript()->on_placenode(pointed, def);
2647 bool Game::nodePlacement(const ItemDefinition &selected_def,
2648 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
2649 const PointedThing &pointed, const NodeMetadata *meta, bool force)
2651 const auto &prediction = selected_def.node_placement_prediction;
2653 const NodeDefManager *nodedef = client->ndef();
2654 ClientMap &map = client->getEnv().getClientMap();
2656 bool is_valid_position;
2658 node = map.getNode(nodepos, &is_valid_position);
2659 if (!is_valid_position) {
2660 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2665 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
2666 && !isKeyDown(KeyType::SNEAK) && !force) {
2667 // on_rightclick callbacks are called anyway
2668 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
2669 client->interact(INTERACT_PLACE, pointed);
2671 infostream << "Launching custom inventory view" << std::endl;
2673 InventoryLocation inventoryloc;
2674 inventoryloc.setNodeMeta(nodepos);
2676 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
2677 &client->getEnv().getClientMap(), nodepos);
2678 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
2680 auto *&formspec = m_game_ui->updateFormspec("");
2681 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2682 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2684 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
2688 // on_rightclick callback
2689 if (prediction.empty() || (nodedef->get(node).rightclickable &&
2690 !isKeyDown(KeyType::SNEAK) && !force)) {
2692 client->interact(INTERACT_PLACE, pointed);
2696 verbosestream << "Node placement prediction for "
2697 << selected_def.name << " is " << prediction << std::endl;
2698 v3s16 p = neighbourpos;
2700 // Place inside node itself if buildable_to
2701 MapNode n_under = map.getNode(nodepos, &is_valid_position);
2702 if (is_valid_position) {
2703 if (nodedef->get(n_under).buildable_to) {
2706 node = map.getNode(p, &is_valid_position);
2707 if (is_valid_position && !nodedef->get(node).buildable_to) {
2708 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2710 client->interact(INTERACT_PLACE, pointed);
2716 // Find id of predicted node
2718 bool found = nodedef->getId(prediction, id);
2721 errorstream << "Node placement prediction failed for "
2722 << selected_def.name << " (places " << prediction
2723 << ") - Name not known" << std::endl;
2724 // Handle this as if prediction was empty
2726 client->interact(INTERACT_PLACE, pointed);
2730 const ContentFeatures &predicted_f = nodedef->get(id);
2732 // Predict param2 for facedir and wallmounted nodes
2733 // Compare core.item_place_node() for what the server does
2736 const u8 place_param2 = selected_def.place_param2;
2739 param2 = place_param2;
2740 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
2741 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
2742 v3s16 dir = nodepos - neighbourpos;
2744 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
2745 param2 = dir.Y < 0 ? 1 : 0;
2746 } else if (abs(dir.X) > abs(dir.Z)) {
2747 param2 = dir.X < 0 ? 3 : 2;
2749 param2 = dir.Z < 0 ? 5 : 4;
2751 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
2752 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
2753 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
2755 if (abs(dir.X) > abs(dir.Z)) {
2756 param2 = dir.X < 0 ? 3 : 1;
2758 param2 = dir.Z < 0 ? 2 : 0;
2762 // Check attachment if node is in group attached_node
2763 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
2764 const static v3s16 wallmounted_dirs[8] = {
2774 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
2775 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
2776 pp = p + wallmounted_dirs[param2];
2778 pp = p + v3s16(0, -1, 0);
2780 if (!nodedef->get(map.getNode(pp)).walkable) {
2781 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2783 client->interact(INTERACT_PLACE, pointed);
2789 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
2790 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
2791 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
2792 const auto &indexstr = selected_item.metadata.
2793 getString("palette_index", 0);
2794 if (!indexstr.empty()) {
2795 s32 index = mystoi(indexstr);
2796 if (predicted_f.param_type_2 == CPT2_COLOR) {
2798 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
2799 // param2 = pure palette index + other
2800 param2 = (index & 0xf8) | (param2 & 0x07);
2801 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
2802 // param2 = pure palette index + other
2803 param2 = (index & 0xe0) | (param2 & 0x1f);
2808 // Add node to client map
2809 MapNode n(id, 0, param2);
2812 LocalPlayer *player = client->getEnv().getLocalPlayer();
2814 // Dont place node when player would be inside new node
2815 // NOTE: This is to be eventually implemented by a mod as client-side Lua
2816 if (!nodedef->get(n).walkable ||
2817 g_settings->getBool("enable_build_where_you_stand") ||
2818 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
2819 (nodedef->get(n).walkable &&
2820 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
2821 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
2822 // This triggers the required mesh update too
2823 client->addNode(p, n);
2825 client->interact(INTERACT_PLACE, pointed);
2826 // A node is predicted, also play a sound
2827 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
2830 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2833 } catch (const InvalidPositionException &e) {
2834 errorstream << "Node placement prediction failed for "
2835 << selected_def.name << " (places "
2836 << prediction << ") - Position not loaded" << std::endl;
2837 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2842 void Game::handlePointingAtObject(const PointedThing &pointed,
2843 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
2845 std::wstring infotext = unescape_translate(
2846 utf8_to_wide(runData.selected_object->infoText()));
2849 if (!infotext.empty()) {
2852 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
2855 m_game_ui->setInfoText(infotext);
2857 if (isKeyDown(KeyType::DIG) || g_settings->getBool("autohit")) {
2858 bool do_punch = false;
2859 bool do_punch_damage = false;
2861 if (runData.object_hit_delay_timer <= 0.0 || g_settings->getBool("spamclick")) {
2863 do_punch_damage = true;
2864 runData.object_hit_delay_timer = object_hit_delay;
2867 if (wasKeyPressed(KeyType::DIG))
2871 infostream << "Punched object" << std::endl;
2872 runData.punching = true;
2875 if (do_punch_damage) {
2876 // Report direct punch
2877 v3f objpos = runData.selected_object->getPosition();
2878 v3f dir = (objpos - player_position).normalize();
2880 bool disable_send = runData.selected_object->directReportPunch(
2881 dir, &tool_item, runData.time_from_last_punch);
2882 runData.time_from_last_punch = 0;
2884 if (!disable_send) {
2885 client->interact(INTERACT_START_DIGGING, pointed);
2888 } else if (wasKeyDown(KeyType::PLACE)) {
2889 infostream << "Pressed place button while pointing at object" << std::endl;
2890 client->interact(INTERACT_PLACE, pointed); // place
2895 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
2896 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
2898 // See also: serverpackethandle.cpp, action == 2
2899 LocalPlayer *player = client->getEnv().getLocalPlayer();
2900 ClientMap &map = client->getEnv().getClientMap();
2901 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
2903 // NOTE: Similar piece of code exists on the server side for
2905 // Get digging parameters
2906 DigParams params = getDigParams(nodedef_manager->get(n).groups,
2907 &selected_item.getToolCapabilities(itemdef_manager));
2909 // If can't dig, try hand
2910 if (!params.diggable) {
2911 params = getDigParams(nodedef_manager->get(n).groups,
2912 &hand_item.getToolCapabilities(itemdef_manager));
2915 if (!params.diggable) {
2916 // I guess nobody will wait for this long
2917 runData.dig_time_complete = 10000000.0;
2919 runData.dig_time_complete = params.time;
2921 if (m_cache_enable_particles) {
2922 const ContentFeatures &features = client->getNodeDefManager()->get(n);
2923 client->getParticleManager()->addNodeParticle(client,
2924 player, nodepos, n, features);
2928 if(g_settings->getBool("instant_break")) {
2929 runData.dig_time_complete = 0;
2930 runData.dig_instantly = true;
2932 if (!runData.digging) {
2933 infostream << "Started digging" << std::endl;
2934 runData.dig_instantly = runData.dig_time_complete == 0;
2935 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
2937 client->interact(INTERACT_START_DIGGING, pointed);
2938 runData.digging = true;
2939 runData.btn_down_for_dig = true;
2942 if (!runData.dig_instantly) {
2943 runData.dig_index = (float)crack_animation_length
2945 / runData.dig_time_complete;
2947 // This is for e.g. torches
2948 runData.dig_index = crack_animation_length;
2951 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
2953 if (sound_dig.exists() && params.diggable) {
2954 if (sound_dig.name == "__group") {
2955 if (!params.main_group.empty()) {
2956 soundmaker->m_player_leftpunch_sound.gain = 0.5;
2957 soundmaker->m_player_leftpunch_sound.name =
2958 std::string("default_dig_") +
2962 soundmaker->m_player_leftpunch_sound = sound_dig;
2966 // Don't show cracks if not diggable
2967 if (runData.dig_time_complete >= 100000.0) {
2968 } else if (runData.dig_index < crack_animation_length) {
2969 //TimeTaker timer("client.setTempMod");
2970 //infostream<<"dig_index="<<dig_index<<std::endl;
2971 client->setCrack(runData.dig_index, nodepos);
2973 infostream << "Digging completed" << std::endl;
2974 client->setCrack(-1, v3s16(0, 0, 0));
2976 runData.dig_time = 0;
2977 runData.digging = false;
2978 // we successfully dug, now block it from repeating if we want to be safe
2979 if (g_settings->getBool("safe_dig_and_place"))
2980 runData.digging_blocked = true;
2982 runData.nodig_delay_timer =
2983 runData.dig_time_complete / (float)crack_animation_length;
2985 // We don't want a corresponding delay to very time consuming nodes
2986 // and nodes without digging time (e.g. torches) get a fixed delay.
2987 if (runData.nodig_delay_timer > 0.3)
2988 runData.nodig_delay_timer = 0.3;
2989 else if (runData.dig_instantly)
2990 runData.nodig_delay_timer = 0.15;
2992 bool is_valid_position;
2993 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
2994 if (is_valid_position) {
2995 if (client->modsLoaded() &&
2996 client->getScript()->on_dignode(nodepos, wasnode)) {
3000 const ContentFeatures &f = client->ndef()->get(wasnode);
3001 if (f.node_dig_prediction == "air") {
3002 client->removeNode(nodepos);
3003 } else if (!f.node_dig_prediction.empty()) {
3005 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3007 client->addNode(nodepos, id, true);
3009 // implicit else: no prediction
3012 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3014 if (m_cache_enable_particles) {
3015 const ContentFeatures &features =
3016 client->getNodeDefManager()->get(wasnode);
3017 client->getParticleManager()->addDiggingParticles(client,
3018 player, nodepos, wasnode, features);
3022 // Send event to trigger sound
3023 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3026 if (runData.dig_time_complete < 100000.0) {
3027 runData.dig_time += dtime;
3029 runData.dig_time = 0;
3030 client->setCrack(-1, nodepos);
3033 camera->setDigging(0); // Dig animation
3036 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3037 const CameraOrientation &cam)
3039 TimeTaker tt_update("Game::updateFrame()");
3040 LocalPlayer *player = client->getEnv().getLocalPlayer();
3046 if (draw_control->range_all) {
3047 runData.fog_range = 100000 * BS;
3049 runData.fog_range = draw_control->wanted_range * BS;
3053 Calculate general brightness
3055 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3056 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3057 float direct_brightness;
3060 if ((m_cache_enable_noclip && m_cache_enable_free_move) || g_settings->getBool("freecam")) {
3061 direct_brightness = time_brightness;
3062 sunlight_seen = true;
3064 float old_brightness = sky->getBrightness();
3065 direct_brightness = client->getEnv().getClientMap()
3066 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3067 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3071 float time_of_day_smooth = runData.time_of_day_smooth;
3072 float time_of_day = client->getEnv().getTimeOfDayF();
3074 static const float maxsm = 0.05f;
3075 static const float todsm = 0.05f;
3077 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3078 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3079 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3080 time_of_day_smooth = time_of_day;
3082 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3083 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3084 + (time_of_day + 1.0) * todsm;
3086 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3087 + time_of_day * todsm;
3089 runData.time_of_day_smooth = time_of_day_smooth;
3091 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3092 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3093 player->getPitch());
3099 if (sky->getCloudsVisible()) {
3100 clouds->setVisible(true);
3101 clouds->step(dtime);
3102 // camera->getPosition is not enough for 3rd person views
3103 v3f camera_node_position = camera->getCameraNode()->getPosition();
3104 v3s16 camera_offset = camera->getOffset();
3105 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3106 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3107 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3108 clouds->update(camera_node_position,
3109 sky->getCloudColor());
3110 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3111 // if inside clouds, and fog enabled, use that as sky
3113 video::SColor clouds_dark = clouds->getColor()
3114 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3115 sky->overrideColors(clouds_dark, clouds->getColor());
3116 sky->setInClouds(true);
3117 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3118 // do not draw clouds after all
3119 clouds->setVisible(false);
3122 clouds->setVisible(false);
3129 client->getParticleManager()->step(dtime);
3135 if (m_cache_enable_fog) {
3138 video::EFT_FOG_LINEAR,
3139 runData.fog_range * m_cache_fog_start,
3140 runData.fog_range * 1.0,
3148 video::EFT_FOG_LINEAR,
3158 Get chat messages from client
3161 v2u32 screensize = driver->getScreenSize();
3163 updateChat(dtime, screensize);
3169 if (player->getWieldIndex() != runData.new_playeritem)
3170 client->setPlayerItem(runData.new_playeritem);
3172 if (client->updateWieldedItem()) {
3173 // Update wielded tool
3174 ItemStack selected_item, hand_item;
3175 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3176 camera->wield(tool_item);
3180 Update block draw list every 200ms or when camera direction has
3183 runData.update_draw_list_timer += dtime;
3185 float update_draw_list_delta = 0.2f;
3187 v3f camera_direction = camera->getDirection();
3188 if (runData.update_draw_list_timer >= update_draw_list_delta
3189 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3190 || m_camera_offset_changed) {
3192 runData.update_draw_list_timer = 0;
3193 client->getEnv().getClientMap().updateDrawList();
3194 runData.update_draw_list_last_cam_dir = camera_direction;
3197 if (RenderingEngine::get_shadow_renderer()) {
3201 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3204 make sure menu is on top
3205 1. Delete formspec menu reference if menu was removed
3206 2. Else, make sure formspec menu is on top
3208 auto formspec = m_game_ui->getFormspecGUI();
3209 do { // breakable. only runs for one iteration
3213 if (formspec->getReferenceCount() == 1) {
3214 m_game_ui->deleteFormspec();
3218 auto &loc = formspec->getFormspecLocation();
3219 if (loc.type == InventoryLocation::NODEMETA) {
3220 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3221 if (!meta || meta->getString("formspec").empty()) {
3222 formspec->quitMenu();
3228 guiroot->bringToFront(formspec);
3234 const video::SColor &skycolor = sky->getSkyColor();
3236 TimeTaker tt_draw("Draw scene");
3237 driver->beginScene(true, true, skycolor);
3239 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3240 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3241 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3242 bool draw_crosshair = (
3243 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3244 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3245 #ifdef HAVE_TOUCHSCREENGUI
3247 draw_crosshair = !g_settings->getBool("touchtarget");
3248 } catch (SettingNotFoundException) {
3251 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3252 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3257 if (m_game_ui->m_flags.show_profiler_graph)
3258 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3264 if (! gui_chat_console->isOpen()) {
3265 if (m_game_ui->m_flags.show_cheat_menu)
3266 m_cheat_menu->draw(driver, m_game_ui->m_flags.show_minimal_debug);
3267 if (g_settings->getBool("cheat_hud"))
3268 m_cheat_menu->drawHUD(driver, dtime);
3273 if (runData.damage_flash > 0.0f) {
3274 video::SColor color(runData.damage_flash, 180, 0, 0);
3275 if (! g_settings->getBool("no_hurt_cam"))
3276 driver->draw2DRectangle(color, core::rect<s32>(0, 0, screensize.X, screensize.Y), NULL);
3278 runData.damage_flash -= 384.0f * dtime;
3284 if (player->hurt_tilt_timer > 0.0f) {
3285 player->hurt_tilt_timer -= dtime * 6.0f;
3287 if (player->hurt_tilt_timer < 0.0f || g_settings->getBool("no_hurt_cam"))
3288 player->hurt_tilt_strength = 0.0f;
3292 Update minimap pos and rotation
3294 if (mapper && m_game_ui->m_flags.show_hud) {
3295 mapper->setPos(floatToInt(player->getPosition(), BS));
3296 mapper->setAngle(player->getYaw());
3302 if (++m_reset_HW_buffer_counter > 500) {
3304 Periodically remove all mesh HW buffers.
3306 Work around for a quirk in Irrlicht where a HW buffer is only
3307 released after 20000 iterations (triggered from endScene()).
3309 Without this, all loaded but unused meshes will retain their HW
3310 buffers for at least 5 minutes, at which point looking up the HW buffers
3311 becomes a bottleneck and the framerate drops (as much as 30%).
3313 Tests showed that numbers between 50 and 1000 are good, so picked 500.
3314 There are no other public Irrlicht APIs that allow interacting with the
3315 HW buffers without tracking the status of every individual mesh.
3317 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
3319 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
3320 driver->removeAllHardwareBuffers();
3321 m_reset_HW_buffer_counter = 0;
3325 stats->drawtime = tt_draw.stop(true);
3326 g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
3327 g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
3330 /* Log times and stuff for visualization */
3331 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
3333 Profiler::GraphValues values;
3334 g_profiler->graphGet(values);
3338 /****************************************************************************
3340 *****************************************************************************/
3341 void Game::updateShadows()
3343 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
3347 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
3349 float timeoftheday = fmod(getWickedTimeOfDay(in_timeofday) + 0.75f, 0.5f) + 0.25f;
3350 const float offset_constant = 10000.0f;
3352 v3f light(0.0f, 0.0f, -1.0f);
3353 light.rotateXZBy(90);
3354 light.rotateXYBy(timeoftheday * 360 - 90);
3355 light.rotateYZBy(sky->getSkyBodyOrbitTilt());
3357 v3f sun_pos = light * offset_constant;
3359 if (shadow->getDirectionalLightCount() == 0)
3360 shadow->addDirectionalLight();
3361 shadow->getDirectionalLight().setDirection(sun_pos);
3362 shadow->setTimeOfDay(in_timeofday);
3364 shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
3367 /****************************************************************************
3369 ****************************************************************************/
3371 /* On some computers framerate doesn't seem to be automatically limited
3373 inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
3375 // not using getRealTime is necessary for wine
3376 device->getTimer()->tick(); // Maker sure device time is up-to-date
3377 u32 time = device->getTimer()->getTime();
3378 u32 last_time = fps_timings->last_time;
3380 if (time > last_time) // Make sure time hasn't overflowed
3381 fps_timings->busy_time = time - last_time;
3383 fps_timings->busy_time = 0;
3385 u32 frametime_min = 1000 / (
3386 device->isWindowFocused() && !g_menumgr.pausesGame()
3387 ? g_settings->getFloat("fps_max")
3388 : g_settings->getFloat("fps_max_unfocused"));
3390 if (fps_timings->busy_time < frametime_min) {
3391 fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
3392 device->sleep(fps_timings->sleep_time);
3394 fps_timings->sleep_time = 0;
3397 /* Get the new value of the device timer. Note that device->sleep() may
3398 * not sleep for the entire requested time as sleep may be interrupted and
3399 * therefore it is arguably more accurate to get the new time from the
3400 * device rather than calculating it by adding sleep_time to time.
3403 device->getTimer()->tick(); // Update device timer
3404 time = device->getTimer()->getTime();
3406 if (time > last_time) // Make sure last_time hasn't overflowed
3407 *dtime = (time - last_time) / 1000.0;
3411 fps_timings->last_time = time;
3414 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
3416 const wchar_t *wmsg = wgettext(msg);
3417 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
3422 void Game::settingChangedCallback(const std::string &setting_name, void *data)
3424 ((Game *)data)->readSettings();
3427 void Game::updateAllMapBlocksCallback(const std::string &setting_name, void *data)
3429 ((Game *) data)->client->updateAllMapBlocks();
3432 void Game::freecamChangedCallback(const std::string &setting_name, void *data)
3434 Game *game = (Game *) data;
3435 LocalPlayer *player = game->client->getEnv().getLocalPlayer();
3436 if (g_settings->getBool("freecam")) {
3437 game->camera->setCameraMode(CAMERA_MODE_FIRST);
3438 player->freecamEnable();
3440 player->freecamDisable();
3442 game->updatePlayerCAOVisibility();
3445 void Game::readSettings()
3447 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
3448 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
3449 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
3450 m_cache_enable_particles = g_settings->getBool("enable_particles");
3451 m_cache_enable_fog = g_settings->getBool("enable_fog");
3452 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
3453 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
3454 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
3456 m_cache_enable_noclip = g_settings->getBool("noclip");
3457 m_cache_enable_free_move = g_settings->getBool("free_move");
3459 m_cache_fog_start = g_settings->getFloat("fog_start");
3461 m_cache_cam_smoothing = 0;
3462 if (g_settings->getBool("cinematic"))
3463 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
3465 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
3467 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
3468 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
3469 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
3471 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
3474 bool Game::isKeyDown(GameKeyType k)
3476 return input->isKeyDown(k);
3479 bool Game::wasKeyDown(GameKeyType k)
3481 return input->wasKeyDown(k);
3484 bool Game::wasKeyPressed(GameKeyType k)
3486 return input->wasKeyPressed(k);
3489 bool Game::wasKeyReleased(GameKeyType k)
3491 return input->wasKeyReleased(k);
3494 /****************************************************************************/
3495 /****************************************************************************
3497 ****************************************************************************/
3498 /****************************************************************************/
3500 void Game::showDeathFormspec()
3502 static std::string formspec_str =
3503 std::string("formspec_version[1]") +
3505 "bgcolor[#320000b4;true]"
3506 "label[4.85,1.35;" + gettext("You died") + "]"
3507 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
3511 /* Note: FormspecFormSource and LocalFormspecHandler *
3512 * are deleted by guiFormSpecMenu */
3513 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
3514 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
3516 auto *&formspec = m_game_ui->getFormspecGUI();
3517 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3518 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3519 formspec->setFocus("btn_respawn");
3522 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
3523 void Game::showPauseMenu()
3526 static const std::string control_text = strgettext("Default Controls:\n"
3527 "No menu visible:\n"
3528 "- single tap: button activate\n"
3529 "- double tap: place/use\n"
3530 "- slide finger: look around\n"
3531 "Menu/Inventory visible:\n"
3532 "- double tap (outside):\n"
3534 "- touch stack, touch slot:\n"
3536 "- touch&drag, tap 2nd finger\n"
3537 " --> place single item to slot\n"
3540 static const std::string control_text_template = strgettext("Controls:\n"
3541 "- %s: move forwards\n"
3542 "- %s: move backwards\n"
3544 "- %s: move right\n"
3545 "- %s: jump/climb up\n"
3548 "- %s: sneak/climb down\n"
3551 "- %s: enderchest\n"
3552 "- Mouse: turn/look\n"
3553 "- Mouse wheel: select item\n"
3560 char control_text_buf[600];
3562 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
3563 GET_KEY_NAME(keymap_forward),
3564 GET_KEY_NAME(keymap_backward),
3565 GET_KEY_NAME(keymap_left),
3566 GET_KEY_NAME(keymap_right),
3567 GET_KEY_NAME(keymap_jump),
3568 GET_KEY_NAME(keymap_dig),
3569 GET_KEY_NAME(keymap_place),
3570 GET_KEY_NAME(keymap_sneak),
3571 GET_KEY_NAME(keymap_drop),
3572 GET_KEY_NAME(keymap_inventory),
3573 GET_KEY_NAME(keymap_enderchest),
3574 GET_KEY_NAME(keymap_chat),
3575 GET_KEY_NAME(keymap_toggle_killaura),
3576 GET_KEY_NAME(keymap_toggle_freecam),
3577 GET_KEY_NAME(keymap_toggle_scaffold)
3580 std::string control_text = std::string(control_text_buf);
3581 str_formspec_escape(control_text);
3584 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
3585 std::ostringstream os;
3587 os << "formspec_version[1]" << SIZE_TAG
3588 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
3589 << strgettext("Continue") << "]";
3591 if (!simple_singleplayer_mode) {
3592 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
3593 << strgettext("Change Password") << "]";
3595 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
3600 if (g_settings->getBool("enable_sound")) {
3601 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
3602 << strgettext("Sound Volume") << "]";
3605 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
3606 << strgettext("Change Keys") << "]";
3608 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
3609 << strgettext("Exit to Menu") << "]";
3610 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
3611 << strgettext("Exit to OS") << "]"
3612 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
3613 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
3615 << strgettext("Game info:") << "\n";
3616 const std::string &address = client->getAddressName();
3617 static const std::string mode = strgettext("- Mode: ");
3618 if (!simple_singleplayer_mode) {
3619 Address serverAddress = client->getServerAddress();
3620 if (!address.empty()) {
3621 os << mode << strgettext("Remote server") << "\n"
3622 << strgettext("- Address: ") << address;
3624 os << mode << strgettext("Hosting server");
3626 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
3628 os << mode << strgettext("Singleplayer") << "\n";
3630 if (simple_singleplayer_mode || address.empty()) {
3631 static const std::string on = strgettext("On");
3632 static const std::string off = strgettext("Off");
3633 const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
3634 const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
3635 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
3636 os << strgettext("- Damage: ") << damage << "\n"
3637 << strgettext("- Creative Mode: ") << creative << "\n";
3638 if (!simple_singleplayer_mode) {
3639 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
3640 //~ PvP = Player versus Player
3641 os << strgettext("- PvP: ") << pvp << "\n"
3642 << strgettext("- Public: ") << announced << "\n";
3643 std::string server_name = g_settings->get("server_name");
3644 str_formspec_escape(server_name);
3645 if (announced == on && !server_name.empty())
3646 os << strgettext("- Server Name: ") << server_name;
3653 /* Note: FormspecFormSource and LocalFormspecHandler *
3654 * are deleted by guiFormSpecMenu */
3655 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
3656 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
3658 auto *&formspec = m_game_ui->getFormspecGUI();
3659 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3660 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3661 formspec->setFocus("btn_continue");
3662 formspec->doPause = true;
3664 if (simple_singleplayer_mode)
3668 /****************************************************************************/
3669 /****************************************************************************
3670 extern function for launching the game
3671 ****************************************************************************/
3672 /****************************************************************************/
3676 void the_game(bool *kill,
3677 InputHandler *input,
3678 RenderingEngine *rendering_engine,
3679 const GameStartData &start_data,
3680 std::string &error_message,
3681 ChatBackend &chat_backend,
3682 bool *reconnect_requested) // Used for local game
3688 /* Make a copy of the server address because if a local singleplayer server
3689 * is created then this is updated and we don't want to change the value
3690 * passed to us by the calling function
3695 if (game.startup(kill, input, rendering_engine, start_data,
3696 error_message, reconnect_requested, &chat_backend)) {
3700 } catch (SerializationError &e) {
3701 error_message = std::string("A serialization error occurred:\n")
3702 + e.what() + "\n\nThe server is probably "
3703 " running a different version of " PROJECT_NAME_C ".";
3704 errorstream << error_message << std::endl;
3705 } catch (ServerError &e) {
3706 error_message = e.what();
3707 errorstream << "ServerError: " << error_message << std::endl;
3708 } catch (ModError &e) {
3709 error_message = std::string("ModError: ") + e.what() +
3710 strgettext("\nCheck debug.txt for details.");
3711 errorstream << error_message << std::endl;