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);
125 #ifdef HAVE_TOUCHSCREENGUI
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);
247 FpsControl draw_times;
248 f32 dtime; // in seconds
250 /* Clear the profiler */
251 Profiler::GraphValues dummyvalues;
252 g_profiler->graphGet(dummyvalues);
256 set_light_table(g_settings->getFloat("display_gamma"));
258 #ifdef HAVE_TOUCHSCREENGUI
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 draw_times.limit(device, &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);
318 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
319 updateFrame(&graph, &stats, dtime, cam_view);
320 updateProfilerGraphs(&graph);
322 // Update if minimap has been disabled by the server
323 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
325 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
332 void Game::shutdown()
334 m_rendering_engine->finalize();
335 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
336 if (g_settings->get("3d_mode") == "pageflip") {
337 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
340 auto formspec = m_game_ui->getFormspecGUI();
342 formspec->quitMenu();
344 #ifdef HAVE_TOUCHSCREENGUI
345 g_touchscreengui->hide();
348 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
353 if (gui_chat_console)
354 gui_chat_console->drop();
363 while (g_menumgr.menuCount() > 0) {
364 g_menumgr.m_stack.front()->setVisible(false);
365 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
368 m_game_ui->deleteFormspec();
370 chat_backend->addMessage(L"", L"# Disconnected.");
371 chat_backend->addMessage(L"", L"");
372 m_chat_log_buf.clear();
376 while (!client->isShutdown()) {
377 assert(texture_src != NULL);
378 assert(shader_src != NULL);
379 texture_src->processQueue();
380 shader_src->processQueue();
387 /****************************************************************************/
388 /****************************************************************************
390 ****************************************************************************/
391 /****************************************************************************/
394 const std::string &map_dir,
395 const std::string &address,
397 const SubgameSpec &gamespec)
399 texture_src = createTextureSource();
401 showOverlayMessage(N_("Loading..."), 0, 0);
403 shader_src = createShaderSource();
405 itemdef_manager = createItemDefManager();
406 nodedef_manager = createNodeDefManager();
408 eventmgr = new EventManager();
409 quicktune = new QuicktuneShortcutter();
411 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
412 && eventmgr && quicktune))
418 // Create a server if not connecting to an existing one
419 if (address.empty()) {
420 if (!createSingleplayerServer(map_dir, gamespec, port))
427 bool Game::initSound()
430 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
431 infostream << "Attempting to use OpenAL audio" << std::endl;
432 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
434 infostream << "Failed to initialize OpenAL audio" << std::endl;
436 infostream << "Sound disabled." << std::endl;
440 infostream << "Using dummy audio." << std::endl;
441 sound = &dummySoundManager;
442 sound_is_dummy = true;
445 soundmaker = new SoundMaker(sound, nodedef_manager);
449 soundmaker->registerReceiver(eventmgr);
454 bool Game::createSingleplayerServer(const std::string &map_dir,
455 const SubgameSpec &gamespec, u16 port)
457 showOverlayMessage(N_("Creating server..."), 0, 5);
459 std::string bind_str = g_settings->get("bind_address");
460 Address bind_addr(0, 0, 0, 0, port);
462 if (g_settings->getBool("ipv6_server")) {
463 bind_addr.setAddress((IPv6AddressBytes *) NULL);
467 bind_addr.Resolve(bind_str.c_str());
468 } catch (ResolveError &e) {
469 infostream << "Resolving bind address \"" << bind_str
470 << "\" failed: " << e.what()
471 << " -- Listening on all addresses." << std::endl;
474 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
475 *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
476 bind_addr.serializeString().c_str());
477 errorstream << *error_message << std::endl;
481 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
482 false, nullptr, error_message);
488 bool Game::createClient(const GameStartData &start_data)
490 showOverlayMessage(N_("Creating client..."), 0, 10);
492 draw_control = new MapDrawControl();
496 bool could_connect, connect_aborted;
497 #ifdef HAVE_TOUCHSCREENGUI
498 if (g_touchscreengui) {
499 g_touchscreengui->init(texture_src);
500 g_touchscreengui->hide();
503 if (!connectToServer(start_data, &could_connect, &connect_aborted))
506 if (!could_connect) {
507 if (error_message->empty() && !connect_aborted) {
508 // Should not happen if error messages are set properly
509 *error_message = gettext("Connection failed for unknown reason");
510 errorstream << *error_message << std::endl;
515 if (!getServerContent(&connect_aborted)) {
516 if (error_message->empty() && !connect_aborted) {
517 // Should not happen if error messages are set properly
518 *error_message = gettext("Connection failed for unknown reason");
519 errorstream << *error_message << std::endl;
524 auto *scsf = new GameGlobalShaderConstantSetterFactory(
525 &m_flags.force_fog_off, &runData.fog_range, client);
526 shader_src->addShaderConstantSetterFactory(scsf);
528 // Update cached textures, meshes and materials
529 client->afterContentReceived();
533 camera = new Camera(*draw_control, client, m_rendering_engine);
534 if (client->modsLoaded())
535 client->getScript()->on_camera_ready(camera);
536 client->setCamera(camera);
540 if (m_cache_enable_clouds)
541 clouds = new Clouds(smgr, -1, time(0));
545 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
547 skybox = NULL; // This is used/set later on in the main run loop
549 /* Pre-calculated values
551 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
553 v2u32 size = t->getOriginalSize();
554 crack_animation_length = size.Y / size.X;
556 crack_animation_length = 5;
562 /* Set window caption
564 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
566 str += utf8_to_wide(g_version_hash);
568 const wchar_t *text = nullptr;
569 if (simple_singleplayer_mode)
570 text = wgettext("Singleplayer");
572 text = wgettext("Multiplayer");
579 str += L"Minetest Hackclient";
582 device->setWindowCaption(str.c_str());
584 LocalPlayer *player = client->getEnv().getLocalPlayer();
585 player->hurt_tilt_timer = 0;
586 player->hurt_tilt_strength = 0;
588 hud = new Hud(client, player, &player->inventory);
590 mapper = client->getMinimap();
592 if (mapper && client->modsLoaded())
593 client->getScript()->on_minimap_ready(mapper);
602 // Remove stale "recent" chat messages from previous connections
603 chat_backend->clearRecentChat();
605 // Make sure the size of the recent messages buffer is right
606 chat_backend->applySettings();
608 // Chat backend and console
609 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
610 -1, chat_backend, client, &g_menumgr);
612 if (!gui_chat_console) {
613 *error_message = "Could not allocate memory for chat console";
614 errorstream << *error_message << std::endl;
618 m_cheat_menu = new CheatMenu(client);
621 *error_message = "Could not allocate memory for cheat menu";
622 errorstream << *error_message << std::endl;
626 #ifdef HAVE_TOUCHSCREENGUI
628 if (g_touchscreengui)
629 g_touchscreengui->show();
636 bool Game::connectToServer(const GameStartData &start_data,
637 bool *connect_ok, bool *connection_aborted)
639 *connect_ok = false; // Let's not be overly optimistic
640 *connection_aborted = false;
641 bool local_server_mode = false;
643 showOverlayMessage(N_("Resolving address..."), 0, 15);
645 Address connect_address(0, 0, 0, 0, start_data.socket_port);
648 connect_address.Resolve(start_data.address.c_str());
650 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
651 if (connect_address.isIPv6()) {
652 IPv6AddressBytes addr_bytes;
653 addr_bytes.bytes[15] = 1;
654 connect_address.setAddress(&addr_bytes);
656 connect_address.setAddress(127, 0, 0, 1);
658 local_server_mode = true;
660 } catch (ResolveError &e) {
661 *error_message = fmtgettext("Couldn't resolve address: %s", e.what());
663 errorstream << *error_message << std::endl;
667 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
668 *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
669 errorstream << *error_message << std::endl;
674 client = new Client(start_data.name.c_str(),
675 start_data.password, start_data.address,
676 *draw_control, texture_src, shader_src,
677 itemdef_manager, nodedef_manager, sound, eventmgr,
678 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get());
679 client->migrateModStorage();
680 } catch (const BaseException &e) {
681 *error_message = fmtgettext("Error creating client: %s", e.what());
682 errorstream << *error_message << std::endl;
686 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
688 infostream << "Connecting to server at ";
689 connect_address.print(infostream);
690 infostream << std::endl;
692 client->connect(connect_address,
693 simple_singleplayer_mode || local_server_mode);
696 Wait for server to accept connection
702 FpsControl fps_control;
704 f32 wait_time = 0; // in seconds
708 while (m_rendering_engine->run()) {
710 fps_control.limit(device, &dtime);
712 // Update client and server
719 if (client->getState() == LC_Init) {
725 if (*connection_aborted)
728 if (client->accessDenied()) {
729 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
730 *reconnect_requested = client->reconnectRequested();
731 errorstream << *error_message << std::endl;
735 if (input->cancelPressed()) {
736 *connection_aborted = true;
737 infostream << "Connect aborted [Escape]" << std::endl;
741 if (client->m_is_registration_confirmation_state) {
742 if (registration_confirmation_shown) {
743 // Keep drawing the GUI
744 m_rendering_engine->draw_menu_scene(guienv, dtime, true);
746 registration_confirmation_shown = true;
747 (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
748 &g_menumgr, client, start_data.name, start_data.password,
749 connection_aborted, texture_src))->drop();
753 // Only time out if we aren't waiting for the server we started
754 if (!start_data.address.empty() && wait_time > 10) {
755 *error_message = gettext("Connection timed out.");
756 errorstream << *error_message << std::endl;
761 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
764 } catch (con::PeerNotFoundException &e) {
765 // TODO: Should something be done here? At least an info/error
773 bool Game::getServerContent(bool *aborted)
777 FpsControl fps_control;
778 f32 dtime; // in seconds
782 while (m_rendering_engine->run()) {
784 fps_control.limit(device, &dtime);
786 // Update client and server
793 if (client->mediaReceived() && client->itemdefReceived() &&
794 client->nodedefReceived()) {
799 if (!checkConnection())
802 if (client->getState() < LC_Init) {
803 *error_message = gettext("Client disconnected");
804 errorstream << *error_message << std::endl;
808 if (input->cancelPressed()) {
810 infostream << "Connect aborted [Escape]" << std::endl;
817 if (!client->itemdefReceived()) {
818 const wchar_t *text = wgettext("Item definitions...");
820 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
823 } else if (!client->nodedefReceived()) {
824 const wchar_t *text = wgettext("Node definitions...");
826 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
830 std::ostringstream message;
832 message.precision(0);
833 float receive = client->mediaReceiveProgress() * 100;
834 message << gettext("Media...");
836 message << " " << receive << "%";
837 message.precision(2);
839 if ((USE_CURL == 0) ||
840 (!g_settings->getBool("enable_remote_media_server"))) {
841 float cur = client->getCurRate();
842 std::string cur_unit = gettext("KiB/s");
846 cur_unit = gettext("MiB/s");
849 message << " (" << cur << ' ' << cur_unit << ")";
852 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
853 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
854 texture_src, dtime, progress);
862 /****************************************************************************/
863 /****************************************************************************
865 ****************************************************************************/
866 /****************************************************************************/
868 inline void Game::updateInteractTimers(f32 dtime)
870 if (runData.nodig_delay_timer >= 0)
871 runData.nodig_delay_timer -= dtime;
873 if (runData.object_hit_delay_timer >= 0)
874 runData.object_hit_delay_timer -= dtime;
876 runData.time_from_last_punch += dtime;
880 /* returns false if game should exit, otherwise true
882 inline bool Game::checkConnection()
884 if (client->accessDenied()) {
885 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
886 *reconnect_requested = client->reconnectRequested();
887 errorstream << *error_message << std::endl;
895 /* returns false if game should exit, otherwise true
897 inline bool Game::handleCallbacks()
899 if (g_gamecallback->disconnect_requested) {
900 g_gamecallback->disconnect_requested = false;
904 if (g_gamecallback->changepassword_requested) {
905 (new GUIPasswordChange(guienv, guiroot, -1,
906 &g_menumgr, client, texture_src))->drop();
907 g_gamecallback->changepassword_requested = false;
910 if (g_gamecallback->changevolume_requested) {
911 (new GUIVolumeChange(guienv, guiroot, -1,
912 &g_menumgr, texture_src))->drop();
913 g_gamecallback->changevolume_requested = false;
916 if (g_gamecallback->keyconfig_requested) {
917 (new GUIKeyChangeMenu(guienv, guiroot, -1,
918 &g_menumgr, texture_src))->drop();
919 g_gamecallback->keyconfig_requested = false;
922 if (g_gamecallback->keyconfig_changed) {
923 input->keycache.populate(); // update the cache with new settings
924 g_gamecallback->keyconfig_changed = false;
931 void Game::processQueues()
933 texture_src->processQueue();
934 itemdef_manager->processQueue(client);
935 shader_src->processQueue();
938 void Game::updateDebugState()
940 LocalPlayer *player = client->getEnv().getLocalPlayer();
941 bool has_debug = client->checkPrivilege("debug");
942 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
944 if (m_game_ui->m_flags.show_basic_debug) {
945 if (!has_basic_debug)
946 m_game_ui->m_flags.show_basic_debug = false;
947 } else if (m_game_ui->m_flags.show_minimal_debug) {
949 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 [us]",
981 draw_times.busy_time - stats.drawtime);
983 g_profiler->graphAdd("Sleep [us]", 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.getBusyMs() * 0.02;
1018 jitter = draw_times.getBusyMs() - 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->show();
1056 g_touchscreengui->step(dtime);
1060 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1061 gui_chat_console->closeConsoleAtOnce();
1064 // Input handler step() (used by the random input generator)
1068 auto formspec = m_game_ui->getFormspecGUI();
1070 formspec->getAndroidUIInput();
1072 handleAndroidChatInput();
1075 // Increase timer for double tap of "keymap_jump"
1076 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1077 runData.jump_timer += dtime;
1080 processItemSelection(&runData.new_playeritem);
1084 void Game::processKeyInput()
1086 if (wasKeyDown(KeyType::SELECT_UP)) {
1087 m_cheat_menu->selectUp();
1088 } else if (wasKeyDown(KeyType::SELECT_DOWN)) {
1089 m_cheat_menu->selectDown();
1090 } else if (wasKeyDown(KeyType::SELECT_LEFT)) {
1091 m_cheat_menu->selectLeft();
1092 } else if (wasKeyDown(KeyType::SELECT_RIGHT)) {
1093 m_cheat_menu->selectRight();
1094 } else if (wasKeyDown(KeyType::SELECT_CONFIRM)) {
1095 m_cheat_menu->selectConfirm();
1098 if (wasKeyDown(KeyType::DROP)) {
1099 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1100 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1101 toggleAutoforward();
1102 } else if (wasKeyDown(KeyType::BACKWARD)) {
1103 if (g_settings->getBool("continuous_forward"))
1104 toggleAutoforward();
1105 } else if (wasKeyDown(KeyType::INVENTORY)) {
1107 } else if (wasKeyDown(KeyType::ENDERCHEST)) {
1109 } else if (input->cancelPressed()) {
1111 m_android_chat_open = false;
1113 if (!gui_chat_console->isOpenInhibited()) {
1116 } else if (wasKeyDown(KeyType::CHAT)) {
1117 openConsole(0.2, L"");
1118 } else if (wasKeyDown(KeyType::CMD)) {
1119 openConsole(0.2, L"/");
1120 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1121 if (client->modsLoaded())
1122 openConsole(0.2, L".");
1124 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1125 } else if (wasKeyDown(KeyType::CONSOLE)) {
1126 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1127 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1129 } else if (wasKeyDown(KeyType::JUMP)) {
1130 toggleFreeMoveAlt();
1131 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1133 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1135 } else if (wasKeyDown(KeyType::NOCLIP)) {
1137 } else if (wasKeyDown(KeyType::KILLAURA)) {
1139 } else if (wasKeyDown(KeyType::FREECAM)) {
1141 } else if (wasKeyDown(KeyType::SCAFFOLD)) {
1144 } else if (wasKeyDown(KeyType::MUTE)) {
1145 if (g_settings->getBool("enable_sound")) {
1146 bool new_mute_sound = !g_settings->getBool("mute_sound");
1147 g_settings->setBool("mute_sound", new_mute_sound);
1149 m_game_ui->showTranslatedStatusText("Sound muted");
1151 m_game_ui->showTranslatedStatusText("Sound unmuted");
1153 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1155 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1156 if (g_settings->getBool("enable_sound")) {
1157 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1158 g_settings->setFloat("sound_volume", new_volume);
1159 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1160 m_game_ui->showStatusText(msg);
1162 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1164 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1165 if (g_settings->getBool("enable_sound")) {
1166 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1167 g_settings->setFloat("sound_volume", new_volume);
1168 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1169 m_game_ui->showStatusText(msg);
1171 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1174 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1175 || wasKeyDown(KeyType::DEC_VOLUME)) {
1176 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1178 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1180 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1181 client->makeScreenshot();
1182 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
1183 toggleBlockBounds();
1184 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1185 m_game_ui->toggleHud();
1186 } else if (wasKeyDown(KeyType::MINIMAP)) {
1187 toggleMinimap(isKeyDown(KeyType::SNEAK));
1188 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1189 m_game_ui->toggleChat();
1190 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1192 } else if (wasKeyDown(KeyType::TOGGLE_CHEAT_MENU)) {
1193 m_game_ui->toggleCheatMenu();
1194 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1195 toggleUpdateCamera();
1196 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1198 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1199 m_game_ui->toggleProfiler();
1200 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1201 increaseViewRange();
1202 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1203 decreaseViewRange();
1204 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1205 toggleFullViewRange();
1206 } else if (wasKeyDown(KeyType::ZOOM)) {
1208 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1210 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1212 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1214 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1218 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1219 runData.reset_jump_timer = false;
1220 runData.jump_timer = 0.0f;
1223 if (quicktune->hasMessage()) {
1224 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
1228 void Game::processItemSelection(u16 *new_playeritem)
1230 LocalPlayer *player = client->getEnv().getLocalPlayer();
1232 /* Item selection using mouse wheel
1234 *new_playeritem = player->getWieldIndex();
1236 s32 wheel = input->getMouseWheel();
1237 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
1238 player->hud_hotbar_itemcount - 1);
1242 if (wasKeyDown(KeyType::HOTBAR_NEXT))
1245 if (wasKeyDown(KeyType::HOTBAR_PREV))
1249 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
1251 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
1254 /* Item selection using hotbar slot keys
1256 for (u16 i = 0; i <= max_item; i++) {
1257 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
1258 *new_playeritem = i;
1265 void Game::dropSelectedItem(bool single_item)
1267 IDropAction *a = new IDropAction();
1268 a->count = single_item ? 1 : 0;
1269 a->from_inv.setCurrentPlayer();
1270 a->from_list = "main";
1271 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
1272 client->inventoryAction(a);
1276 void Game::openInventory()
1279 * Don't permit to open inventory is CAO or player doesn't exists.
1280 * This prevent showing an empty inventory at player load
1283 LocalPlayer *player = client->getEnv().getLocalPlayer();
1284 if (!player || !player->getCAO())
1287 infostream << "Game: Launching inventory" << std::endl;
1289 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
1291 InventoryLocation inventoryloc;
1292 inventoryloc.setCurrentPlayer();
1294 if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
1299 if (fs_src->getForm().empty()) {
1304 TextDest *txt_dst = new TextDestPlayerInventory(client);
1305 auto *&formspec = m_game_ui->updateFormspec("");
1306 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
1307 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
1309 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
1312 void Game::openEnderchest()
1314 LocalPlayer *player = client->getEnv().getLocalPlayer();
1315 if (!player || !player->getCAO())
1318 infostream << "Game: Launching special inventory" << std::endl;
1320 if (client->modsLoaded())
1321 client->getScript()->open_enderchest();
1325 void Game::openConsole(float scale, const wchar_t *line)
1327 assert(scale > 0.0f && scale <= 1.0f);
1330 porting::showInputDialog(gettext("ok"), "", "", 2);
1331 m_android_chat_open = true;
1333 if (gui_chat_console->isOpenInhibited())
1335 gui_chat_console->openConsole(scale);
1337 gui_chat_console->setCloseOnEnter(true);
1338 gui_chat_console->replaceAndAddToHistory(line);
1344 void Game::handleAndroidChatInput()
1346 if (m_android_chat_open && porting::getInputDialogState() == 0) {
1347 std::string text = porting::getInputDialogValue();
1348 client->typeChatMessage(utf8_to_wide(text));
1349 m_android_chat_open = false;
1355 void Game::toggleFreeMove()
1357 bool free_move = !g_settings->getBool("free_move");
1358 g_settings->set("free_move", bool_to_cstr(free_move));
1361 if (client->checkPrivilege("fly")) {
1362 m_game_ui->showTranslatedStatusText("Fly mode enabled");
1364 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
1367 m_game_ui->showTranslatedStatusText("Fly mode disabled");
1371 void Game::toggleFreeMoveAlt()
1373 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
1376 runData.reset_jump_timer = true;
1380 void Game::togglePitchMove()
1382 bool pitch_move = !g_settings->getBool("pitch_move");
1383 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
1386 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
1388 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
1393 void Game::toggleFast()
1395 bool fast_move = !g_settings->getBool("fast_move");
1396 bool has_fast_privs = client->checkPrivilege("fast");
1397 g_settings->set("fast_move", bool_to_cstr(fast_move));
1400 if (has_fast_privs) {
1401 m_game_ui->showTranslatedStatusText("Fast mode enabled");
1403 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
1406 m_game_ui->showTranslatedStatusText("Fast mode disabled");
1409 #ifdef HAVE_TOUCHSCREENGUI
1410 m_cache_hold_aux1 = fast_move && has_fast_privs;
1415 void Game::toggleNoClip()
1417 bool noclip = !g_settings->getBool("noclip");
1418 g_settings->set("noclip", bool_to_cstr(noclip));
1421 if (client->checkPrivilege("noclip")) {
1422 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
1424 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
1427 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
1431 void Game::toggleKillaura()
1433 bool killaura = ! g_settings->getBool("killaura");
1434 g_settings->set("killaura", bool_to_cstr(killaura));
1437 m_game_ui->showTranslatedStatusText("Killaura enabled");
1439 m_game_ui->showTranslatedStatusText("Killaura disabled");
1443 void Game::toggleFreecam()
1445 bool freecam = ! g_settings->getBool("freecam");
1446 g_settings->set("freecam", bool_to_cstr(freecam));
1449 m_game_ui->showTranslatedStatusText("Freecam enabled");
1451 m_game_ui->showTranslatedStatusText("Freecam disabled");
1455 void Game::toggleScaffold()
1457 bool scaffold = ! g_settings->getBool("scaffold");
1458 g_settings->set("scaffold", bool_to_cstr(scaffold));
1461 m_game_ui->showTranslatedStatusText("Scaffold enabled");
1463 m_game_ui->showTranslatedStatusText("Scaffold disabled");
1467 void Game::toggleCinematic()
1469 bool cinematic = !g_settings->getBool("cinematic");
1470 g_settings->set("cinematic", bool_to_cstr(cinematic));
1473 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
1475 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
1478 void Game::toggleBlockBounds()
1480 LocalPlayer *player = client->getEnv().getLocalPlayer();
1481 if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
1482 m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
1485 enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
1487 case Hud::BLOCK_BOUNDS_OFF:
1488 m_game_ui->showTranslatedStatusText("Block bounds hidden");
1490 case Hud::BLOCK_BOUNDS_CURRENT:
1491 m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
1493 case Hud::BLOCK_BOUNDS_NEAR:
1494 m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
1496 case Hud::BLOCK_BOUNDS_MAX:
1497 m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
1504 // Autoforward by toggling continuous forward.
1505 void Game::toggleAutoforward()
1507 bool autorun_enabled = !g_settings->getBool("continuous_forward");
1508 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
1510 if (autorun_enabled)
1511 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
1513 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
1516 void Game::toggleMinimap(bool shift_pressed)
1518 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
1522 mapper->toggleMinimapShape();
1526 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
1528 // Not so satisying code to keep compatibility with old fixed mode system
1530 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
1532 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
1533 m_game_ui->m_flags.show_minimap = false;
1536 // If radar is disabled, try to find a non radar mode or fall back to 0
1537 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
1538 while (mapper->getModeIndex() &&
1539 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
1542 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
1546 // End of 'not so satifying code'
1547 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
1548 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
1549 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
1551 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
1554 void Game::toggleFog()
1556 bool fog_enabled = g_settings->getBool("enable_fog");
1557 g_settings->setBool("enable_fog", !fog_enabled);
1559 m_game_ui->showTranslatedStatusText("Fog disabled");
1561 m_game_ui->showTranslatedStatusText("Fog enabled");
1565 void Game::toggleDebug()
1567 LocalPlayer *player = client->getEnv().getLocalPlayer();
1568 bool has_debug = client->checkPrivilege("debug");
1569 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
1570 // Initial: No debug info
1571 // 1x toggle: Debug text
1572 // 2x toggle: Debug text with profiler graph
1573 // 3x toggle: Debug text and wireframe (needs "debug" priv)
1574 // Next toggle: Back to initial
1576 // The debug text can be in 2 modes: minimal and basic.
1577 // * Minimal: Only technical client info that not gameplay-relevant
1578 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
1579 // Basic mode is used when player has the debug HUD flag set,
1580 // otherwise the Minimal mode is used.
1581 if (!m_game_ui->m_flags.show_minimal_debug) {
1582 m_game_ui->m_flags.show_minimal_debug = true;
1583 if (has_basic_debug)
1584 m_game_ui->m_flags.show_basic_debug = true;
1585 m_game_ui->m_flags.show_profiler_graph = false;
1586 draw_control->show_wireframe = false;
1587 m_game_ui->showTranslatedStatusText("Debug info shown");
1588 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
1589 if (has_basic_debug)
1590 m_game_ui->m_flags.show_basic_debug = true;
1591 m_game_ui->m_flags.show_profiler_graph = true;
1592 m_game_ui->showTranslatedStatusText("Profiler graph shown");
1593 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
1594 if (has_basic_debug)
1595 m_game_ui->m_flags.show_basic_debug = true;
1596 m_game_ui->m_flags.show_profiler_graph = false;
1597 draw_control->show_wireframe = true;
1598 m_game_ui->showTranslatedStatusText("Wireframe shown");
1600 m_game_ui->m_flags.show_minimal_debug = false;
1601 m_game_ui->m_flags.show_basic_debug = false;
1602 m_game_ui->m_flags.show_profiler_graph = false;
1603 draw_control->show_wireframe = false;
1605 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
1607 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
1613 void Game::toggleUpdateCamera()
1615 if (g_settings->getBool("freecam"))
1617 m_flags.disable_camera_update = !m_flags.disable_camera_update;
1618 if (m_flags.disable_camera_update)
1619 m_game_ui->showTranslatedStatusText("Camera update disabled");
1621 m_game_ui->showTranslatedStatusText("Camera update enabled");
1625 void Game::increaseViewRange()
1627 s16 range = g_settings->getS16("viewing_range");
1628 s16 range_new = range + 10;
1630 if (range_new > 4000) {
1632 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
1633 m_game_ui->showStatusText(msg);
1635 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
1636 m_game_ui->showStatusText(msg);
1638 g_settings->set("viewing_range", itos(range_new));
1642 void Game::decreaseViewRange()
1644 s16 range = g_settings->getS16("viewing_range");
1645 s16 range_new = range - 10;
1647 if (range_new < 20) {
1649 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
1650 m_game_ui->showStatusText(msg);
1652 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
1653 m_game_ui->showStatusText(msg);
1655 g_settings->set("viewing_range", itos(range_new));
1659 void Game::toggleFullViewRange()
1661 draw_control->range_all = !draw_control->range_all;
1662 if (draw_control->range_all)
1663 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
1665 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
1669 void Game::checkZoomEnabled()
1671 LocalPlayer *player = client->getEnv().getLocalPlayer();
1672 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
1673 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
1676 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
1678 if ((device->isWindowActive() && device->isWindowFocused()
1679 && !isMenuActive()) || input->isRandom()) {
1682 if (!input->isRandom()) {
1683 // Mac OSX gets upset if this is set every frame
1684 if (device->getCursorControl()->isVisible())
1685 device->getCursorControl()->setVisible(false);
1689 if (m_first_loop_after_window_activation) {
1690 m_first_loop_after_window_activation = false;
1692 input->setMousePos(driver->getScreenSize().Width / 2,
1693 driver->getScreenSize().Height / 2);
1695 updateCameraOrientation(cam, dtime);
1701 // Mac OSX gets upset if this is set every frame
1702 if (!device->getCursorControl()->isVisible())
1703 device->getCursorControl()->setVisible(true);
1706 m_first_loop_after_window_activation = true;
1711 // Get the factor to multiply with sensitivity to get the same mouse/joystick
1712 // responsiveness independently of FOV.
1713 f32 Game::getSensitivityScaleFactor() const
1715 f32 fov_y = client->getCamera()->getFovY();
1717 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
1718 // 16:9 aspect ratio to minimize disruption of existing sensitivity
1720 return tan(fov_y / 2.0f) * 1.3763818698f;
1723 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
1725 #ifdef HAVE_TOUCHSCREENGUI
1726 if (g_touchscreengui) {
1727 cam->camera_yaw += g_touchscreengui->getYawChange();
1728 cam->camera_pitch = g_touchscreengui->getPitch();
1731 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
1732 v2s32 dist = input->getMousePos() - center;
1734 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
1738 f32 sens_scale = getSensitivityScaleFactor();
1739 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
1740 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
1742 if (dist.X != 0 || dist.Y != 0)
1743 input->setMousePos(center.X, center.Y);
1744 #ifdef HAVE_TOUCHSCREENGUI
1748 if (m_cache_enable_joysticks) {
1749 f32 sens_scale = getSensitivityScaleFactor();
1750 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
1751 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
1752 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
1755 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
1759 void Game::updatePlayerControl(const CameraOrientation &cam)
1761 LocalPlayer *player = client->getEnv().getLocalPlayer();
1763 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
1765 PlayerControl control(
1766 isKeyDown(KeyType::FORWARD),
1767 isKeyDown(KeyType::BACKWARD),
1768 isKeyDown(KeyType::LEFT),
1769 isKeyDown(KeyType::RIGHT),
1770 isKeyDown(KeyType::JUMP) || player->getAutojump(),
1771 isKeyDown(KeyType::AUX1),
1772 isKeyDown(KeyType::SNEAK),
1773 isKeyDown(KeyType::ZOOM),
1774 isKeyDown(KeyType::DIG),
1775 isKeyDown(KeyType::PLACE),
1778 input->getMovementSpeed(),
1779 input->getMovementDirection()
1782 // autoforward if set: move towards pointed position at maximum speed
1783 if (player->getPlayerSettings().continuous_forward &&
1784 client->activeObjectsReceived() && !player->isDead()) {
1785 control.movement_speed = 1.0f;
1786 control.movement_direction = 0.0f;
1789 #ifdef HAVE_TOUCHSCREENGUI
1790 /* For touch, simulate holding down AUX1 (fast move) if the user has
1791 * the fast_move setting toggled on. If there is an aux1 key defined for
1792 * touch then its meaning is inverted (i.e. holding aux1 means walk and
1795 if (m_cache_hold_aux1) {
1796 control.aux1 = control.aux1 ^ true;
1800 client->setPlayerControl(control);
1806 inline void Game::step(f32 *dtime)
1808 bool can_be_and_is_paused =
1809 (simple_singleplayer_mode && g_menumgr.pausesGame());
1811 if (can_be_and_is_paused) { // This is for a singleplayer server
1812 *dtime = 0; // No time passes
1814 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
1818 server->step(*dtime);
1820 client->step(*dtime);
1824 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
1827 for (auto &&child: node->getChildren())
1828 pauseNodeAnimation(paused, child);
1829 if (node->getType() != scene::ESNT_ANIMATED_MESH)
1831 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
1832 float speed = animated_node->getAnimationSpeed();
1835 paused.push_back({grab(animated_node), speed});
1836 animated_node->setAnimationSpeed(0.0f);
1839 void Game::pauseAnimation()
1841 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
1844 void Game::resumeAnimation()
1846 for (auto &&pair: paused_animated_nodes)
1847 pair.first->setAnimationSpeed(pair.second);
1848 paused_animated_nodes.clear();
1851 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
1852 {&Game::handleClientEvent_None},
1853 {&Game::handleClientEvent_PlayerDamage},
1854 {&Game::handleClientEvent_PlayerForceMove},
1855 {&Game::handleClientEvent_Deathscreen},
1856 {&Game::handleClientEvent_ShowFormSpec},
1857 {&Game::handleClientEvent_ShowLocalFormSpec},
1858 {&Game::handleClientEvent_HandleParticleEvent},
1859 {&Game::handleClientEvent_HandleParticleEvent},
1860 {&Game::handleClientEvent_HandleParticleEvent},
1861 {&Game::handleClientEvent_HudAdd},
1862 {&Game::handleClientEvent_HudRemove},
1863 {&Game::handleClientEvent_HudChange},
1864 {&Game::handleClientEvent_SetSky},
1865 {&Game::handleClientEvent_SetSun},
1866 {&Game::handleClientEvent_SetMoon},
1867 {&Game::handleClientEvent_SetStars},
1868 {&Game::handleClientEvent_OverrideDayNigthRatio},
1869 {&Game::handleClientEvent_CloudParams},
1872 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
1874 FATAL_ERROR("ClientEvent type None received");
1877 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
1879 if (client->modsLoaded())
1880 client->getScript()->on_damage_taken(event->player_damage.amount);
1882 // Damage flash and hurt tilt are not used at death
1883 if (client->getHP() > 0) {
1884 LocalPlayer *player = client->getEnv().getLocalPlayer();
1886 f32 hp_max = player->getCAO() ?
1887 player->getCAO()->getProperties()->hp_max : PLAYER_MAX_HP_DEFAULT;
1888 f32 damage_ratio = event->player_damage.amount / hp_max;
1890 runData.damage_flash += 95.0f + 64.f * damage_ratio;
1891 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
1893 player->hurt_tilt_timer = 1.5f;
1894 player->hurt_tilt_strength =
1895 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
1898 // Play damage sound
1899 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
1902 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
1904 cam->camera_yaw = event->player_force_move.yaw;
1905 cam->camera_pitch = event->player_force_move.pitch;
1908 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
1910 // If client scripting is enabled, deathscreen is handled by CSM code in
1911 // builtin/client/init.lua
1912 if (client->modsLoaded())
1913 client->getScript()->on_death();
1915 showDeathFormspec();
1917 /* Handle visualization */
1918 LocalPlayer *player = client->getEnv().getLocalPlayer();
1919 runData.damage_flash = 0;
1920 player->hurt_tilt_timer = 0;
1921 player->hurt_tilt_strength = 0;
1924 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
1926 if (event->show_formspec.formspec->empty()) {
1927 auto formspec = m_game_ui->getFormspecGUI();
1928 if (formspec && (event->show_formspec.formname->empty()
1929 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
1930 formspec->quitMenu();
1933 FormspecFormSource *fs_src =
1934 new FormspecFormSource(*(event->show_formspec.formspec));
1935 TextDestPlayerInventory *txt_dst =
1936 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
1938 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
1939 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
1940 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
1943 delete event->show_formspec.formspec;
1944 delete event->show_formspec.formname;
1947 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
1949 if (event->show_formspec.formspec->empty()) {
1950 auto formspec = m_game_ui->getFormspecGUI();
1951 if (formspec && (event->show_formspec.formname->empty()
1952 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
1953 formspec->quitMenu();
1956 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
1957 LocalFormspecHandler *txt_dst =
1958 new LocalFormspecHandler(*event->show_formspec.formname, client);
1959 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(), &input->joystick,
1960 fs_src, txt_dst, client->getFormspecPrepend(), sound);
1963 delete event->show_formspec.formspec;
1964 delete event->show_formspec.formname;
1967 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
1968 CameraOrientation *cam)
1970 LocalPlayer *player = client->getEnv().getLocalPlayer();
1971 client->getParticleManager()->handleParticleEvent(event, client, player);
1974 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
1976 LocalPlayer *player = client->getEnv().getLocalPlayer();
1978 u32 server_id = event->hudadd->server_id;
1979 // ignore if we already have a HUD with that ID
1980 auto i = m_hud_server_to_client.find(server_id);
1981 if (i != m_hud_server_to_client.end()) {
1982 delete event->hudadd;
1986 HudElement *e = new HudElement;
1987 e->type = static_cast<HudElementType>(event->hudadd->type);
1988 e->pos = event->hudadd->pos;
1989 e->name = event->hudadd->name;
1990 e->scale = event->hudadd->scale;
1991 e->text = event->hudadd->text;
1992 e->number = event->hudadd->number;
1993 e->item = event->hudadd->item;
1994 e->dir = event->hudadd->dir;
1995 e->align = event->hudadd->align;
1996 e->offset = event->hudadd->offset;
1997 e->world_pos = event->hudadd->world_pos;
1998 e->size = event->hudadd->size;
1999 e->z_index = event->hudadd->z_index;
2000 e->text2 = event->hudadd->text2;
2001 e->style = event->hudadd->style;
2002 m_hud_server_to_client[server_id] = player->addHud(e);
2004 delete event->hudadd;
2007 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2009 LocalPlayer *player = client->getEnv().getLocalPlayer();
2011 auto i = m_hud_server_to_client.find(event->hudrm.id);
2012 if (i != m_hud_server_to_client.end()) {
2013 HudElement *e = player->removeHud(i->second);
2015 m_hud_server_to_client.erase(i);
2020 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2022 LocalPlayer *player = client->getEnv().getLocalPlayer();
2024 HudElement *e = nullptr;
2026 auto i = m_hud_server_to_client.find(event->hudchange->id);
2027 if (i != m_hud_server_to_client.end()) {
2028 e = player->getHud(i->second);
2032 delete event->hudchange;
2036 #define CASE_SET(statval, prop, dataprop) \
2038 e->prop = event->hudchange->dataprop; \
2041 switch (event->hudchange->stat) {
2042 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2044 CASE_SET(HUD_STAT_NAME, name, sdata);
2046 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2048 CASE_SET(HUD_STAT_TEXT, text, sdata);
2050 CASE_SET(HUD_STAT_NUMBER, number, data);
2052 CASE_SET(HUD_STAT_ITEM, item, data);
2054 CASE_SET(HUD_STAT_DIR, dir, data);
2056 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2058 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2060 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2062 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2064 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2066 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2068 CASE_SET(HUD_STAT_STYLE, style, data);
2073 delete event->hudchange;
2076 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2078 sky->setVisible(false);
2079 // Whether clouds are visible in front of a custom skybox.
2080 sky->setCloudsEnabled(event->set_sky->clouds);
2086 // Clear the old textures out in case we switch rendering type.
2087 sky->clearSkyboxTextures();
2088 // Handle according to type
2089 if (event->set_sky->type == "regular") {
2090 // Shows the mesh skybox
2091 sky->setVisible(true);
2092 // Update mesh based skybox colours if applicable.
2093 sky->setSkyColors(event->set_sky->sky_color);
2094 sky->setHorizonTint(
2095 event->set_sky->fog_sun_tint,
2096 event->set_sky->fog_moon_tint,
2097 event->set_sky->fog_tint_type
2099 } else if (event->set_sky->type == "skybox" &&
2100 event->set_sky->textures.size() == 6) {
2101 // Disable the dyanmic mesh skybox:
2102 sky->setVisible(false);
2104 sky->setFallbackBgColor(event->set_sky->bgcolor);
2105 // Set sunrise and sunset fog tinting:
2106 sky->setHorizonTint(
2107 event->set_sky->fog_sun_tint,
2108 event->set_sky->fog_moon_tint,
2109 event->set_sky->fog_tint_type
2111 // Add textures to skybox.
2112 for (int i = 0; i < 6; i++)
2113 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2115 // Handle everything else as plain color.
2116 if (event->set_sky->type != "plain")
2117 infostream << "Unknown sky type: "
2118 << (event->set_sky->type) << std::endl;
2119 sky->setVisible(false);
2120 sky->setFallbackBgColor(event->set_sky->bgcolor);
2121 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2122 sky->setHorizonTint(
2123 event->set_sky->bgcolor,
2124 event->set_sky->bgcolor,
2129 delete event->set_sky;
2132 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2134 sky->setSunVisible(event->sun_params->visible);
2135 sky->setSunTexture(event->sun_params->texture,
2136 event->sun_params->tonemap, texture_src);
2137 sky->setSunScale(event->sun_params->scale);
2138 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2139 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2140 delete event->sun_params;
2143 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2145 sky->setMoonVisible(event->moon_params->visible);
2146 sky->setMoonTexture(event->moon_params->texture,
2147 event->moon_params->tonemap, texture_src);
2148 sky->setMoonScale(event->moon_params->scale);
2149 delete event->moon_params;
2152 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2154 sky->setStarsVisible(event->star_params->visible);
2155 sky->setStarCount(event->star_params->count);
2156 sky->setStarColor(event->star_params->starcolor);
2157 sky->setStarScale(event->star_params->scale);
2158 delete event->star_params;
2161 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2162 CameraOrientation *cam)
2164 client->getEnv().setDayNightRatioOverride(
2165 event->override_day_night_ratio.do_override,
2166 event->override_day_night_ratio.ratio_f * 1000.0f);
2169 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2174 clouds->setDensity(event->cloud_params.density);
2175 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2176 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2177 clouds->setHeight(event->cloud_params.height);
2178 clouds->setThickness(event->cloud_params.thickness);
2179 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2182 void Game::processClientEvents(CameraOrientation *cam)
2184 while (client->hasClientEvents()) {
2185 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2186 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2187 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2188 (this->*evHandler.handler)(event.get(), cam);
2192 void Game::updateChat(f32 dtime)
2194 // Get new messages from error log buffer
2195 while (!m_chat_log_buf.empty())
2196 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2198 // Get new messages from client
2199 std::wstring message;
2200 while (client->getChatMessage(message)) {
2201 chat_backend->addUnparsedMessage(message);
2204 // Remove old messages
2205 chat_backend->step(dtime);
2207 // Display all messages in a static text element
2208 auto &buf = chat_backend->getRecentBuffer();
2209 if (buf.getLinesModified()) {
2210 buf.resetLinesModified();
2211 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
2214 // Make sure that the size is still correct
2215 m_game_ui->updateChatSize();
2218 void Game::updateCamera(f32 dtime)
2220 LocalPlayer *player = client->getEnv().getLocalPlayer();
2223 For interaction purposes, get info about the held item
2225 - Is it a usable item?
2226 - Can it point to liquids?
2228 ItemStack playeritem;
2230 ItemStack selected, hand;
2231 playeritem = player->getWieldedItem(&selected, &hand);
2234 ToolCapabilities playeritem_toolcap =
2235 playeritem.getToolCapabilities(itemdef_manager);
2237 v3s16 old_camera_offset = camera->getOffset();
2239 if (wasKeyDown(KeyType::CAMERA_MODE) && ! g_settings->getBool("freecam")) {
2240 camera->toggleCameraMode();
2241 updatePlayerCAOVisibility();
2244 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2245 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2247 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2248 camera->update(player, dtime, tool_reload_ratio);
2249 camera->step(dtime);
2251 v3f camera_position = camera->getPosition();
2252 v3f camera_direction = camera->getDirection();
2253 f32 camera_fov = camera->getFovMax();
2254 v3s16 camera_offset = camera->getOffset();
2256 m_camera_offset_changed = (camera_offset != old_camera_offset);
2258 if (!m_flags.disable_camera_update) {
2259 client->getEnv().getClientMap().updateCamera(camera_position,
2260 camera_direction, camera_fov, camera_offset);
2262 if (m_camera_offset_changed) {
2263 client->updateCameraOffset(camera_offset);
2264 client->getEnv().updateCameraOffset(camera_offset);
2267 clouds->updateCameraOffset(camera_offset);
2272 void Game::updatePlayerCAOVisibility()
2274 // Make the player visible depending on camera mode.
2275 LocalPlayer *player = client->getEnv().getLocalPlayer();
2276 GenericCAO *playercao = player->getCAO();
2279 playercao->updateMeshCulling();
2280 bool is_visible = camera->getCameraMode() > CAMERA_MODE_FIRST || g_settings->getBool("freecam");
2281 playercao->setChildrenVisible(is_visible);
2284 void Game::updateSound(f32 dtime)
2286 // Update sound listener
2287 v3s16 camera_offset = camera->getOffset();
2288 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
2289 v3f(0, 0, 0), // velocity
2290 camera->getDirection(),
2291 camera->getCameraNode()->getUpVector());
2293 bool mute_sound = g_settings->getBool("mute_sound");
2295 sound->setListenerGain(0.0f);
2297 // Check if volume is in the proper range, else fix it.
2298 float old_volume = g_settings->getFloat("sound_volume");
2299 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
2300 sound->setListenerGain(new_volume);
2302 if (old_volume != new_volume) {
2303 g_settings->setFloat("sound_volume", new_volume);
2307 LocalPlayer *player = client->getEnv().getLocalPlayer();
2309 // Tell the sound maker whether to make footstep sounds
2310 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
2312 // Update sound maker
2313 if (player->makes_footstep_sound)
2314 soundmaker->step(dtime);
2316 ClientMap &map = client->getEnv().getClientMap();
2317 MapNode n = map.getNode(player->getFootstepNodePos());
2318 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
2322 void Game::processPlayerInteraction(f32 dtime, bool show_hud)
2324 LocalPlayer *player = client->getEnv().getLocalPlayer();
2326 const v3f camera_direction = camera->getDirection();
2327 const v3s16 camera_offset = camera->getOffset();
2330 Calculate what block is the crosshair pointing to
2333 ItemStack selected_item, hand_item;
2334 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
2336 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
2337 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
2340 if (g_settings->getBool("reach"))
2341 d += g_settings->getU16("tool_range");
2343 core::line3d<f32> shootline;
2345 switch (camera->getCameraMode()) {
2346 case CAMERA_MODE_FIRST:
2347 // Shoot from camera position, with bobbing
2348 shootline.start = camera->getPosition();
2350 case CAMERA_MODE_THIRD:
2351 // Shoot from player head, no bobbing
2352 shootline.start = camera->getHeadPosition();
2354 case CAMERA_MODE_THIRD_FRONT:
2355 shootline.start = camera->getHeadPosition();
2356 // prevent player pointing anything in front-view
2360 shootline.end = shootline.start + camera_direction * BS * d;
2362 #ifdef HAVE_TOUCHSCREENGUI
2364 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
2365 shootline = g_touchscreengui->getShootline();
2366 // Scale shootline to the acual distance the player can reach
2367 shootline.end = shootline.start
2368 + shootline.getVector().normalize() * BS * d;
2369 shootline.start += intToFloat(camera_offset, BS);
2370 shootline.end += intToFloat(camera_offset, BS);
2375 PointedThing pointed = updatePointedThing(shootline,
2376 selected_def.liquids_pointable,
2377 !runData.btn_down_for_dig,
2380 if (pointed != runData.pointed_old)
2381 infostream << "Pointing at " << pointed.dump() << std::endl;
2383 // Note that updating the selection mesh every frame is not particularly efficient,
2384 // but the halo rendering code is already inefficient so there's no point in optimizing it here
2385 hud->updateSelectionMesh(camera_offset);
2387 // Allow digging again if button is not pressed
2388 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
2389 runData.digging_blocked = false;
2393 - releasing dig button
2394 - pointing away from node
2396 if (runData.digging) {
2397 if (wasKeyReleased(KeyType::DIG)) {
2398 infostream << "Dig button released (stopped digging)" << std::endl;
2399 runData.digging = false;
2400 } else if (pointed != runData.pointed_old) {
2401 if (pointed.type == POINTEDTHING_NODE
2402 && runData.pointed_old.type == POINTEDTHING_NODE
2403 && pointed.node_undersurface
2404 == runData.pointed_old.node_undersurface) {
2405 // Still pointing to the same node, but a different face.
2408 infostream << "Pointing away from node (stopped digging)" << std::endl;
2409 runData.digging = false;
2410 hud->updateSelectionMesh(camera_offset);
2414 if (!runData.digging) {
2415 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
2416 client->setCrack(-1, v3s16(0, 0, 0));
2417 runData.dig_time = 0.0;
2419 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
2420 // Remove e.g. torches faster when clicking instead of holding dig button
2421 runData.nodig_delay_timer = 0;
2422 runData.dig_instantly = false;
2425 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
2426 runData.btn_down_for_dig = false;
2428 runData.punching = false;
2430 soundmaker->m_player_leftpunch_sound.name = "";
2432 // Prepare for repeating, unless we're not supposed to
2433 if ((isKeyDown(KeyType::PLACE) || g_settings->getBool("autoplace")) && !g_settings->getBool("safe_dig_and_place"))
2434 runData.repeat_place_timer += dtime;
2436 runData.repeat_place_timer = 0;
2438 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
2439 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
2440 !client->getScript()->on_item_use(selected_item, pointed)))
2441 client->interact(INTERACT_USE, pointed);
2442 } else if (pointed.type == POINTEDTHING_NODE) {
2443 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
2444 } else if (pointed.type == POINTEDTHING_OBJECT) {
2445 v3f player_position = player->getPosition();
2446 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
2447 handlePointingAtObject(pointed, tool_item, player_position,
2448 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
2449 } else if (isKeyDown(KeyType::DIG)) {
2450 // When button is held down in air, show continuous animation
2451 runData.punching = true;
2452 // Run callback even though item is not usable
2453 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
2454 client->getScript()->on_item_use(selected_item, pointed);
2455 } else if (wasKeyPressed(KeyType::PLACE)) {
2456 handlePointingAtNothing(selected_item);
2459 runData.pointed_old = pointed;
2461 if (runData.punching || wasKeyPressed(KeyType::DIG))
2462 camera->setDigging(0); // dig animation
2464 input->clearWasKeyPressed();
2465 input->clearWasKeyReleased();
2466 // Ensure DIG & PLACE are marked as handled
2467 wasKeyDown(KeyType::DIG);
2468 wasKeyDown(KeyType::PLACE);
2470 input->joystick.clearWasKeyPressed(KeyType::DIG);
2471 input->joystick.clearWasKeyPressed(KeyType::PLACE);
2473 input->joystick.clearWasKeyReleased(KeyType::DIG);
2474 input->joystick.clearWasKeyReleased(KeyType::PLACE);
2478 PointedThing Game::updatePointedThing(
2479 const core::line3d<f32> &shootline,
2480 bool liquids_pointable,
2481 bool look_for_object,
2482 const v3s16 &camera_offset)
2484 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
2485 selectionboxes->clear();
2486 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
2487 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
2488 "show_entity_selectionbox");
2490 ClientEnvironment &env = client->getEnv();
2491 ClientMap &map = env.getClientMap();
2492 const NodeDefManager *nodedef = map.getNodeDefManager();
2494 runData.selected_object = NULL;
2495 hud->pointing_at_object = false;
2496 RaycastState s(shootline, look_for_object, liquids_pointable, ! g_settings->getBool("dont_point_nodes"));
2497 PointedThing result;
2498 env.continueRaycast(&s, &result);
2499 if (result.type == POINTEDTHING_OBJECT) {
2500 hud->pointing_at_object = true;
2502 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
2503 aabb3f selection_box;
2504 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
2505 runData.selected_object->getSelectionBox(&selection_box)) {
2506 v3f pos = runData.selected_object->getPosition();
2507 selectionboxes->push_back(aabb3f(selection_box));
2508 hud->setSelectionPos(pos, camera_offset);
2510 } else if (result.type == POINTEDTHING_NODE) {
2511 // Update selection boxes
2512 MapNode n = map.getNode(result.node_undersurface);
2513 std::vector<aabb3f> boxes;
2514 n.getSelectionBoxes(nodedef, &boxes,
2515 n.getNeighbors(result.node_undersurface, &map));
2518 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
2519 i != boxes.end(); ++i) {
2521 box.MinEdge -= v3f(d, d, d);
2522 box.MaxEdge += v3f(d, d, d);
2523 selectionboxes->push_back(box);
2525 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
2527 hud->setSelectedFaceNormal(v3f(
2528 result.intersection_normal.X,
2529 result.intersection_normal.Y,
2530 result.intersection_normal.Z));
2533 // Update selection mesh light level and vertex colors
2534 if (!selectionboxes->empty()) {
2535 v3f pf = hud->getSelectionPos();
2536 v3s16 p = floatToInt(pf, BS);
2538 // Get selection mesh light level
2539 MapNode n = map.getNode(p);
2540 u16 node_light = getInteriorLight(n, -1, nodedef);
2541 u16 light_level = node_light;
2543 for (const v3s16 &dir : g_6dirs) {
2544 n = map.getNode(p + dir);
2545 node_light = getInteriorLight(n, -1, nodedef);
2546 if (node_light > light_level)
2547 light_level = node_light;
2550 u32 daynight_ratio = client->getEnv().getDayNightRatio();
2552 final_color_blend(&c, light_level, daynight_ratio);
2554 // Modify final color a bit with time
2555 u32 timer = porting::getTimeMs() % 5000;
2556 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
2557 float sin_r = 0.08f * std::sin(timerf);
2558 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
2559 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
2560 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
2561 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
2562 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
2564 // Set mesh final color
2565 hud->setSelectionMeshColor(c);
2570 void Game::handlePointingAtNothing(const ItemStack &playerItem)
2572 infostream << "Attempted to place item while pointing at nothing" << std::endl;
2573 PointedThing fauxPointed;
2574 fauxPointed.type = POINTEDTHING_NOTHING;
2575 client->interact(INTERACT_ACTIVATE, fauxPointed);
2579 void Game::handlePointingAtNode(const PointedThing &pointed,
2580 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
2582 v3s16 nodepos = pointed.node_undersurface;
2583 v3s16 neighbourpos = pointed.node_abovesurface;
2586 Check information text of node
2589 ClientMap &map = client->getEnv().getClientMap();
2591 if (((runData.nodig_delay_timer <= 0.0 || g_settings->getBool("fastdig")) && (isKeyDown(KeyType::DIG) || g_settings->getBool("autodig"))
2592 && !runData.digging_blocked
2593 && client->checkPrivilege("interact"))
2595 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
2598 // This should be done after digging handling
2599 NodeMetadata *meta = map.getNodeMetadata(nodepos);
2602 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
2603 meta->getString("infotext"))));
2605 MapNode n = map.getNode(nodepos);
2607 if (nodedef_manager->get(n).name == "unknown") {
2608 m_game_ui->setInfoText(L"Unknown node");
2612 if ((wasKeyPressed(KeyType::PLACE) ||
2613 (runData.repeat_place_timer >= (g_settings->getBool("fastplace") ? 0.001 : m_repeat_place_time))) &&
2614 client->checkPrivilege("interact")) {
2615 runData.repeat_place_timer = 0;
2616 infostream << "Place button pressed while looking at ground" << std::endl;
2618 // Placing animation (always shown for feedback)
2619 camera->setDigging(1);
2621 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
2623 // If the wielded item has node placement prediction,
2625 // And also set the sound and send the interact
2626 // But first check for meta formspec and rightclickable
2627 auto &def = selected_item.getDefinition(itemdef_manager);
2628 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
2631 if (placed && client->modsLoaded())
2632 client->getScript()->on_placenode(pointed, def);
2636 bool Game::nodePlacement(const ItemDefinition &selected_def,
2637 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
2638 const PointedThing &pointed, const NodeMetadata *meta, bool force)
2640 const auto &prediction = selected_def.node_placement_prediction;
2642 const NodeDefManager *nodedef = client->ndef();
2643 ClientMap &map = client->getEnv().getClientMap();
2645 bool is_valid_position;
2647 node = map.getNode(nodepos, &is_valid_position);
2648 if (!is_valid_position) {
2649 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2654 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
2655 && !isKeyDown(KeyType::SNEAK) && !force) {
2656 // on_rightclick callbacks are called anyway
2657 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
2658 client->interact(INTERACT_PLACE, pointed);
2660 infostream << "Launching custom inventory view" << std::endl;
2662 InventoryLocation inventoryloc;
2663 inventoryloc.setNodeMeta(nodepos);
2665 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
2666 &client->getEnv().getClientMap(), nodepos);
2667 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
2669 auto *&formspec = m_game_ui->updateFormspec("");
2670 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2671 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2673 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
2677 // on_rightclick callback
2678 if (prediction.empty() || (nodedef->get(node).rightclickable &&
2679 !isKeyDown(KeyType::SNEAK) && !force)) {
2681 client->interact(INTERACT_PLACE, pointed);
2685 verbosestream << "Node placement prediction for "
2686 << selected_def.name << " is " << prediction << std::endl;
2687 v3s16 p = neighbourpos;
2689 // Place inside node itself if buildable_to
2690 MapNode n_under = map.getNode(nodepos, &is_valid_position);
2691 if (is_valid_position) {
2692 if (nodedef->get(n_under).buildable_to) {
2695 node = map.getNode(p, &is_valid_position);
2696 if (is_valid_position && !nodedef->get(node).buildable_to) {
2697 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2699 client->interact(INTERACT_PLACE, pointed);
2705 // Find id of predicted node
2707 bool found = nodedef->getId(prediction, id);
2710 errorstream << "Node placement prediction failed for "
2711 << selected_def.name << " (places " << prediction
2712 << ") - Name not known" << std::endl;
2713 // Handle this as if prediction was empty
2715 client->interact(INTERACT_PLACE, pointed);
2719 const ContentFeatures &predicted_f = nodedef->get(id);
2721 // Predict param2 for facedir and wallmounted nodes
2722 // Compare core.item_place_node() for what the server does
2725 const u8 place_param2 = selected_def.place_param2;
2728 param2 = place_param2;
2729 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
2730 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
2731 v3s16 dir = nodepos - neighbourpos;
2733 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
2734 param2 = dir.Y < 0 ? 1 : 0;
2735 } else if (abs(dir.X) > abs(dir.Z)) {
2736 param2 = dir.X < 0 ? 3 : 2;
2738 param2 = dir.Z < 0 ? 5 : 4;
2740 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
2741 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
2742 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
2744 if (abs(dir.X) > abs(dir.Z)) {
2745 param2 = dir.X < 0 ? 3 : 1;
2747 param2 = dir.Z < 0 ? 2 : 0;
2751 // Check attachment if node is in group attached_node
2752 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
2753 const static v3s16 wallmounted_dirs[8] = {
2763 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
2764 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
2765 pp = p + wallmounted_dirs[param2];
2767 pp = p + v3s16(0, -1, 0);
2769 if (!nodedef->get(map.getNode(pp)).walkable) {
2770 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2772 client->interact(INTERACT_PLACE, pointed);
2778 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
2779 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
2780 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
2781 const auto &indexstr = selected_item.metadata.
2782 getString("palette_index", 0);
2783 if (!indexstr.empty()) {
2784 s32 index = mystoi(indexstr);
2785 if (predicted_f.param_type_2 == CPT2_COLOR) {
2787 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
2788 // param2 = pure palette index + other
2789 param2 = (index & 0xf8) | (param2 & 0x07);
2790 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
2791 // param2 = pure palette index + other
2792 param2 = (index & 0xe0) | (param2 & 0x1f);
2797 // Add node to client map
2798 MapNode n(id, 0, param2);
2801 LocalPlayer *player = client->getEnv().getLocalPlayer();
2803 // Dont place node when player would be inside new node
2804 // NOTE: This is to be eventually implemented by a mod as client-side Lua
2805 if (!nodedef->get(n).walkable ||
2806 g_settings->getBool("enable_build_where_you_stand") ||
2807 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
2808 (nodedef->get(n).walkable &&
2809 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
2810 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
2811 // This triggers the required mesh update too
2812 client->addNode(p, n);
2814 client->interact(INTERACT_PLACE, pointed);
2815 // A node is predicted, also play a sound
2816 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
2819 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2822 } catch (const InvalidPositionException &e) {
2823 errorstream << "Node placement prediction failed for "
2824 << selected_def.name << " (places "
2825 << prediction << ") - Position not loaded" << std::endl;
2826 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2831 void Game::handlePointingAtObject(const PointedThing &pointed,
2832 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
2834 std::wstring infotext = unescape_translate(
2835 utf8_to_wide(runData.selected_object->infoText()));
2838 if (!infotext.empty()) {
2841 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
2844 m_game_ui->setInfoText(infotext);
2846 if (isKeyDown(KeyType::DIG) || g_settings->getBool("autohit")) {
2847 bool do_punch = false;
2848 bool do_punch_damage = false;
2850 if (runData.object_hit_delay_timer <= 0.0 || g_settings->getBool("spamclick")) {
2852 do_punch_damage = true;
2853 runData.object_hit_delay_timer = object_hit_delay;
2856 if (wasKeyPressed(KeyType::DIG))
2860 infostream << "Punched object" << std::endl;
2861 runData.punching = true;
2864 if (do_punch_damage) {
2865 // Report direct punch
2866 v3f objpos = runData.selected_object->getPosition();
2867 v3f dir = (objpos - player_position).normalize();
2869 bool disable_send = runData.selected_object->directReportPunch(
2870 dir, &tool_item, runData.time_from_last_punch);
2871 runData.time_from_last_punch = 0;
2873 if (!disable_send) {
2874 client->interact(INTERACT_START_DIGGING, pointed);
2877 } else if (wasKeyDown(KeyType::PLACE)) {
2878 infostream << "Pressed place button while pointing at object" << std::endl;
2879 client->interact(INTERACT_PLACE, pointed); // place
2884 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
2885 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
2887 // See also: serverpackethandle.cpp, action == 2
2888 LocalPlayer *player = client->getEnv().getLocalPlayer();
2889 ClientMap &map = client->getEnv().getClientMap();
2890 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
2892 // NOTE: Similar piece of code exists on the server side for
2894 // Get digging parameters
2895 DigParams params = getDigParams(nodedef_manager->get(n).groups,
2896 &selected_item.getToolCapabilities(itemdef_manager),
2897 selected_item.wear);
2899 // If can't dig, try hand
2900 if (!params.diggable) {
2901 params = getDigParams(nodedef_manager->get(n).groups,
2902 &hand_item.getToolCapabilities(itemdef_manager));
2905 if (!params.diggable) {
2906 // I guess nobody will wait for this long
2907 runData.dig_time_complete = 10000000.0;
2909 runData.dig_time_complete = params.time;
2911 if (m_cache_enable_particles) {
2912 const ContentFeatures &features = client->getNodeDefManager()->get(n);
2913 client->getParticleManager()->addNodeParticle(client,
2914 player, nodepos, n, features);
2918 if(g_settings->getBool("instant_break")) {
2919 runData.dig_time_complete = 0;
2920 runData.dig_instantly = true;
2922 if (!runData.digging) {
2923 infostream << "Started digging" << std::endl;
2924 runData.dig_instantly = runData.dig_time_complete == 0;
2925 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
2927 client->interact(INTERACT_START_DIGGING, pointed);
2928 runData.digging = true;
2929 runData.btn_down_for_dig = true;
2932 if (!runData.dig_instantly) {
2933 runData.dig_index = (float)crack_animation_length
2935 / runData.dig_time_complete;
2937 // This is for e.g. torches
2938 runData.dig_index = crack_animation_length;
2941 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
2943 if (sound_dig.exists() && params.diggable) {
2944 if (sound_dig.name == "__group") {
2945 if (!params.main_group.empty()) {
2946 soundmaker->m_player_leftpunch_sound.gain = 0.5;
2947 soundmaker->m_player_leftpunch_sound.name =
2948 std::string("default_dig_") +
2952 soundmaker->m_player_leftpunch_sound = sound_dig;
2956 // Don't show cracks if not diggable
2957 if (runData.dig_time_complete >= 100000.0) {
2958 } else if (runData.dig_index < crack_animation_length) {
2959 //TimeTaker timer("client.setTempMod");
2960 //infostream<<"dig_index="<<dig_index<<std::endl;
2961 client->setCrack(runData.dig_index, nodepos);
2963 infostream << "Digging completed" << std::endl;
2964 client->setCrack(-1, v3s16(0, 0, 0));
2966 runData.dig_time = 0;
2967 runData.digging = false;
2968 // we successfully dug, now block it from repeating if we want to be safe
2969 if (g_settings->getBool("safe_dig_and_place"))
2970 runData.digging_blocked = true;
2972 runData.nodig_delay_timer =
2973 runData.dig_time_complete / (float)crack_animation_length;
2975 // We don't want a corresponding delay to very time consuming nodes
2976 // and nodes without digging time (e.g. torches) get a fixed delay.
2977 if (runData.nodig_delay_timer > 0.3)
2978 runData.nodig_delay_timer = 0.3;
2979 else if (runData.dig_instantly)
2980 runData.nodig_delay_timer = 0.15;
2982 bool is_valid_position;
2983 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
2984 if (is_valid_position) {
2985 if (client->modsLoaded() &&
2986 client->getScript()->on_dignode(nodepos, wasnode)) {
2990 const ContentFeatures &f = client->ndef()->get(wasnode);
2991 if (f.node_dig_prediction == "air") {
2992 client->removeNode(nodepos);
2993 } else if (!f.node_dig_prediction.empty()) {
2995 bool found = client->ndef()->getId(f.node_dig_prediction, id);
2997 client->addNode(nodepos, id, true);
2999 // implicit else: no prediction
3002 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3004 if (m_cache_enable_particles) {
3005 const ContentFeatures &features =
3006 client->getNodeDefManager()->get(wasnode);
3007 client->getParticleManager()->addDiggingParticles(client,
3008 player, nodepos, wasnode, features);
3012 // Send event to trigger sound
3013 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3016 if (runData.dig_time_complete < 100000.0) {
3017 runData.dig_time += dtime;
3019 runData.dig_time = 0;
3020 client->setCrack(-1, nodepos);
3023 camera->setDigging(0); // Dig animation
3026 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3027 const CameraOrientation &cam)
3029 TimeTaker tt_update("Game::updateFrame()");
3030 LocalPlayer *player = client->getEnv().getLocalPlayer();
3036 if (draw_control->range_all) {
3037 runData.fog_range = 100000 * BS;
3039 runData.fog_range = draw_control->wanted_range * BS;
3043 Calculate general brightness
3045 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3046 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3047 float direct_brightness;
3050 if ((m_cache_enable_noclip && m_cache_enable_free_move) || g_settings->getBool("freecam")) {
3051 direct_brightness = time_brightness;
3052 sunlight_seen = true;
3054 float old_brightness = sky->getBrightness();
3055 direct_brightness = client->getEnv().getClientMap()
3056 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3057 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3061 float time_of_day_smooth = runData.time_of_day_smooth;
3062 float time_of_day = client->getEnv().getTimeOfDayF();
3064 static const float maxsm = 0.05f;
3065 static const float todsm = 0.05f;
3067 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3068 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3069 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3070 time_of_day_smooth = time_of_day;
3072 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3073 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3074 + (time_of_day + 1.0) * todsm;
3076 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3077 + time_of_day * todsm;
3079 runData.time_of_day_smooth = time_of_day_smooth;
3081 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3082 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3083 player->getPitch());
3089 if (sky->getCloudsVisible()) {
3090 clouds->setVisible(true);
3091 clouds->step(dtime);
3092 // camera->getPosition is not enough for 3rd person views
3093 v3f camera_node_position = camera->getCameraNode()->getPosition();
3094 v3s16 camera_offset = camera->getOffset();
3095 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3096 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3097 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3098 clouds->update(camera_node_position,
3099 sky->getCloudColor());
3100 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3101 // if inside clouds, and fog enabled, use that as sky
3103 video::SColor clouds_dark = clouds->getColor()
3104 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3105 sky->overrideColors(clouds_dark, clouds->getColor());
3106 sky->setInClouds(true);
3107 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3108 // do not draw clouds after all
3109 clouds->setVisible(false);
3112 clouds->setVisible(false);
3119 client->getParticleManager()->step(dtime);
3125 if (m_cache_enable_fog) {
3128 video::EFT_FOG_LINEAR,
3129 runData.fog_range * m_cache_fog_start,
3130 runData.fog_range * 1.0,
3138 video::EFT_FOG_LINEAR,
3150 if (player->hurt_tilt_timer > 0.0f) {
3151 player->hurt_tilt_timer -= dtime * 6.0f;
3153 if (player->hurt_tilt_timer < 0.0f || g_settings->getBool("no_hurt_cam"))
3154 player->hurt_tilt_strength = 0.0f;
3158 Update minimap pos and rotation
3160 if (mapper && m_game_ui->m_flags.show_hud) {
3161 mapper->setPos(floatToInt(player->getPosition(), BS));
3162 mapper->setAngle(player->getYaw());
3166 Get chat messages from client
3175 if (player->getWieldIndex() != runData.new_playeritem)
3176 client->setPlayerItem(runData.new_playeritem);
3178 if (client->updateWieldedItem()) {
3179 // Update wielded tool
3180 ItemStack selected_item, hand_item;
3181 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3182 camera->wield(tool_item);
3186 Update block draw list every 200ms or when camera direction has
3189 runData.update_draw_list_timer += dtime;
3191 float update_draw_list_delta = 0.2f;
3193 v3f camera_direction = camera->getDirection();
3194 if (runData.update_draw_list_timer >= update_draw_list_delta
3195 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3196 || m_camera_offset_changed
3197 || client->getEnv().getClientMap().needsUpdateDrawList()) {
3198 runData.update_draw_list_timer = 0;
3199 client->getEnv().getClientMap().updateDrawList();
3200 runData.update_draw_list_last_cam_dir = camera_direction;
3203 if (RenderingEngine::get_shadow_renderer()) {
3207 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3210 make sure menu is on top
3211 1. Delete formspec menu reference if menu was removed
3212 2. Else, make sure formspec menu is on top
3214 auto formspec = m_game_ui->getFormspecGUI();
3215 do { // breakable. only runs for one iteration
3219 if (formspec->getReferenceCount() == 1) {
3220 m_game_ui->deleteFormspec();
3224 auto &loc = formspec->getFormspecLocation();
3225 if (loc.type == InventoryLocation::NODEMETA) {
3226 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3227 if (!meta || meta->getString("formspec").empty()) {
3228 formspec->quitMenu();
3234 guiroot->bringToFront(formspec);
3238 ==================== Drawing begins ====================
3240 const video::SColor skycolor = sky->getSkyColor();
3242 TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
3243 driver->beginScene(true, true, skycolor);
3245 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3246 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3247 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3248 bool draw_crosshair = (
3249 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3250 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3251 #ifdef HAVE_TOUCHSCREENGUI
3253 draw_crosshair = !g_settings->getBool("touchtarget");
3254 } catch (SettingNotFoundException) {
3257 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3258 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3263 v2u32 screensize = driver->getScreenSize();
3265 if (m_game_ui->m_flags.show_profiler_graph)
3266 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3272 if (! gui_chat_console->isOpen()) {
3273 if (m_game_ui->m_flags.show_cheat_menu)
3274 m_cheat_menu->draw(driver, m_game_ui->m_flags.show_minimal_debug);
3275 if (g_settings->getBool("cheat_hud"))
3276 m_cheat_menu->drawHUD(driver, dtime);
3281 if (runData.damage_flash > 0.0f) {
3282 video::SColor color(runData.damage_flash, 180, 0, 0);
3283 if (! g_settings->getBool("no_hurt_cam"))
3284 driver->draw2DRectangle(color, core::rect<s32>(0, 0, screensize.X, screensize.Y), NULL);
3286 runData.damage_flash -= 384.0f * dtime;
3292 #if IRRLICHT_VERSION_MT_REVISION < 5
3293 if (++m_reset_HW_buffer_counter > 500) {
3295 Periodically remove all mesh HW buffers.
3297 Work around for a quirk in Irrlicht where a HW buffer is only
3298 released after 20000 iterations (triggered from endScene()).
3300 Without this, all loaded but unused meshes will retain their HW
3301 buffers for at least 5 minutes, at which point looking up the HW buffers
3302 becomes a bottleneck and the framerate drops (as much as 30%).
3304 Tests showed that numbers between 50 and 1000 are good, so picked 500.
3305 There are no other public Irrlicht APIs that allow interacting with the
3306 HW buffers without tracking the status of every individual mesh.
3308 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
3310 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
3311 driver->removeAllHardwareBuffers();
3312 m_reset_HW_buffer_counter = 0;
3318 stats->drawtime = tt_draw.stop(true);
3319 g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
3320 g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
3323 /* Log times and stuff for visualization */
3324 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
3326 Profiler::GraphValues values;
3327 g_profiler->graphGet(values);
3331 /****************************************************************************
3333 *****************************************************************************/
3334 void Game::updateShadows()
3336 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
3340 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
3342 float timeoftheday = getWickedTimeOfDay(in_timeofday);
3343 bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
3344 bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
3345 shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
3347 timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
3348 const float offset_constant = 10000.0f;
3350 v3f light(0.0f, 0.0f, -1.0f);
3351 light.rotateXZBy(90);
3352 light.rotateXYBy(timeoftheday * 360 - 90);
3353 light.rotateYZBy(sky->getSkyBodyOrbitTilt());
3355 v3f sun_pos = light * offset_constant;
3357 if (shadow->getDirectionalLightCount() == 0)
3358 shadow->addDirectionalLight();
3359 shadow->getDirectionalLight().setDirection(sun_pos);
3360 shadow->setTimeOfDay(in_timeofday);
3362 shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
3365 /****************************************************************************
3367 ****************************************************************************/
3369 void FpsControl::reset()
3371 last_time = porting::getTimeUs();
3375 * On some computers framerate doesn't seem to be automatically limited
3377 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
3379 const u64 frametime_min = 1000000.0f / (
3380 device->isWindowFocused() && !g_menumgr.pausesGame()
3381 ? g_settings->getFloat("fps_max")
3382 : g_settings->getFloat("fps_max_unfocused"));
3384 u64 time = porting::getTimeUs();
3386 if (time > last_time) // Make sure time hasn't overflowed
3387 busy_time = time - last_time;
3391 if (busy_time < frametime_min) {
3392 sleep_time = frametime_min - busy_time;
3393 if (sleep_time > 1000)
3394 sleep_ms(sleep_time / 1000);
3399 // Read the timer again to accurately determine how long we actually slept,
3400 // rather than calculating it by adding sleep_time to time.
3401 time = porting::getTimeUs();
3403 if (time > last_time) // Make sure last_time hasn't overflowed
3404 *dtime = (time - last_time) / 1000000.0f;
3411 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
3413 const wchar_t *wmsg = wgettext(msg);
3414 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
3419 void Game::settingChangedCallback(const std::string &setting_name, void *data)
3421 ((Game *)data)->readSettings();
3424 void Game::updateAllMapBlocksCallback(const std::string &setting_name, void *data)
3426 ((Game *) data)->client->updateAllMapBlocks();
3429 void Game::freecamChangedCallback(const std::string &setting_name, void *data)
3431 Game *game = (Game *) data;
3432 LocalPlayer *player = game->client->getEnv().getLocalPlayer();
3433 if (g_settings->getBool("freecam")) {
3434 game->camera->setCameraMode(CAMERA_MODE_FIRST);
3435 player->freecamEnable();
3437 player->freecamDisable();
3439 game->updatePlayerCAOVisibility();
3442 void Game::readSettings()
3444 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
3445 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
3446 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
3447 m_cache_enable_particles = g_settings->getBool("enable_particles");
3448 m_cache_enable_fog = g_settings->getBool("enable_fog");
3449 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
3450 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
3451 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
3453 m_cache_enable_noclip = g_settings->getBool("noclip");
3454 m_cache_enable_free_move = g_settings->getBool("free_move");
3456 m_cache_fog_start = g_settings->getFloat("fog_start");
3458 m_cache_cam_smoothing = 0;
3459 if (g_settings->getBool("cinematic"))
3460 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
3462 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
3464 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
3465 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
3466 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
3468 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
3471 bool Game::isKeyDown(GameKeyType k)
3473 return input->isKeyDown(k);
3476 bool Game::wasKeyDown(GameKeyType k)
3478 return input->wasKeyDown(k);
3481 bool Game::wasKeyPressed(GameKeyType k)
3483 return input->wasKeyPressed(k);
3486 bool Game::wasKeyReleased(GameKeyType k)
3488 return input->wasKeyReleased(k);
3491 /****************************************************************************/
3492 /****************************************************************************
3494 ****************************************************************************/
3495 /****************************************************************************/
3497 void Game::showDeathFormspec()
3499 static std::string formspec_str =
3500 std::string("formspec_version[1]") +
3502 "bgcolor[#320000b4;true]"
3503 "label[4.85,1.35;" + gettext("You died") + "]"
3504 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
3508 /* Note: FormspecFormSource and LocalFormspecHandler *
3509 * are deleted by guiFormSpecMenu */
3510 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
3511 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
3513 auto *&formspec = m_game_ui->getFormspecGUI();
3514 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3515 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3516 formspec->setFocus("btn_respawn");
3519 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
3520 void Game::showPauseMenu()
3522 #ifdef HAVE_TOUCHSCREENGUI
3523 static const std::string control_text = strgettext("Default Controls:\n"
3524 "No menu visible:\n"
3525 "- single tap: button activate\n"
3526 "- double tap: place/use\n"
3527 "- slide finger: look around\n"
3528 "Menu/Inventory visible:\n"
3529 "- double tap (outside):\n"
3531 "- touch stack, touch slot:\n"
3533 "- touch&drag, tap 2nd finger\n"
3534 " --> place single item to slot\n"
3537 static const std::string control_text_template = strgettext("Controls:\n"
3538 "- %s: move forwards\n"
3539 "- %s: move backwards\n"
3541 "- %s: move right\n"
3542 "- %s: jump/climb up\n"
3545 "- %s: sneak/climb down\n"
3548 "- %s: enderchest\n"
3549 "- Mouse: turn/look\n"
3550 "- Mouse wheel: select item\n"
3557 char control_text_buf[600];
3559 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
3560 GET_KEY_NAME(keymap_forward),
3561 GET_KEY_NAME(keymap_backward),
3562 GET_KEY_NAME(keymap_left),
3563 GET_KEY_NAME(keymap_right),
3564 GET_KEY_NAME(keymap_jump),
3565 GET_KEY_NAME(keymap_dig),
3566 GET_KEY_NAME(keymap_place),
3567 GET_KEY_NAME(keymap_sneak),
3568 GET_KEY_NAME(keymap_drop),
3569 GET_KEY_NAME(keymap_inventory),
3570 GET_KEY_NAME(keymap_enderchest),
3571 GET_KEY_NAME(keymap_chat),
3572 GET_KEY_NAME(keymap_toggle_killaura),
3573 GET_KEY_NAME(keymap_toggle_freecam),
3574 GET_KEY_NAME(keymap_toggle_scaffold)
3577 std::string control_text = std::string(control_text_buf);
3578 str_formspec_escape(control_text);
3581 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
3582 std::ostringstream os;
3584 os << "formspec_version[1]" << SIZE_TAG
3585 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
3586 << strgettext("Continue") << "]";
3588 if (!simple_singleplayer_mode) {
3589 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
3590 << strgettext("Change Password") << "]";
3592 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
3597 if (g_settings->getBool("enable_sound")) {
3598 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
3599 << strgettext("Sound Volume") << "]";
3602 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
3603 << strgettext("Change Keys") << "]";
3605 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
3606 << strgettext("Exit to Menu") << "]";
3607 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
3608 << strgettext("Exit to OS") << "]"
3609 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
3610 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
3612 << strgettext("Game info:") << "\n";
3613 const std::string &address = client->getAddressName();
3614 static const std::string mode = strgettext("- Mode: ");
3615 if (!simple_singleplayer_mode) {
3616 Address serverAddress = client->getServerAddress();
3617 if (!address.empty()) {
3618 os << mode << strgettext("Remote server") << "\n"
3619 << strgettext("- Address: ") << address;
3621 os << mode << strgettext("Hosting server");
3623 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
3625 os << mode << strgettext("Singleplayer") << "\n";
3627 if (simple_singleplayer_mode || address.empty()) {
3628 static const std::string on = strgettext("On");
3629 static const std::string off = strgettext("Off");
3630 // Note: Status of enable_damage and creative_mode settings is intentionally
3631 // NOT shown here because the game might roll its own damage system and/or do
3632 // a per-player Creative Mode, in which case writing it here would mislead.
3633 bool damage = g_settings->getBool("enable_damage");
3634 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
3635 if (!simple_singleplayer_mode) {
3637 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
3638 //~ PvP = Player versus Player
3639 os << strgettext("- PvP: ") << pvp << "\n";
3641 os << strgettext("- Public: ") << announced << "\n";
3642 std::string server_name = g_settings->get("server_name");
3643 str_formspec_escape(server_name);
3644 if (announced == on && !server_name.empty())
3645 os << strgettext("- Server Name: ") << server_name;
3652 /* Note: FormspecFormSource and LocalFormspecHandler *
3653 * are deleted by guiFormSpecMenu */
3654 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
3655 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
3657 auto *&formspec = m_game_ui->getFormspecGUI();
3658 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3659 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3660 formspec->setFocus("btn_continue");
3661 formspec->doPause = true;
3663 if (simple_singleplayer_mode)
3667 /****************************************************************************/
3668 /****************************************************************************
3669 extern function for launching the game
3670 ****************************************************************************/
3671 /****************************************************************************/
3675 void the_game(bool *kill,
3676 InputHandler *input,
3677 RenderingEngine *rendering_engine,
3678 const GameStartData &start_data,
3679 std::string &error_message,
3680 ChatBackend &chat_backend,
3681 bool *reconnect_requested) // Used for local game
3687 /* Make a copy of the server address because if a local singleplayer server
3688 * is created then this is updated and we don't want to change the value
3689 * passed to us by the calling function
3694 if (game.startup(kill, input, rendering_engine, start_data,
3695 error_message, reconnect_requested, &chat_backend)) {
3699 } catch (SerializationError &e) {
3700 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
3701 error_message = strgettext("A serialization error occurred:") +"\n"
3702 + e.what() + "\n\n" + ver_err;
3703 errorstream << error_message << std::endl;
3704 } catch (ServerError &e) {
3705 error_message = e.what();
3706 errorstream << "ServerError: " << error_message << std::endl;
3707 } catch (ModError &e) {
3708 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
3709 error_message = std::string("ModError: ") + e.what() +
3710 strgettext("\nCheck debug.txt for details.");
3711 errorstream << error_message << std::endl;