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/guiFormSpecMenu.h"
47 #include "gui/guiKeyChangeMenu.h"
48 #include "gui/guiPasswordChange.h"
49 #include "gui/guiVolumeChange.h"
50 #include "gui/mainmenumanager.h"
51 #include "gui/profilergraph.h"
54 #include "nodedef.h" // Needed for determining pointing to nodes
55 #include "nodemetadata.h"
56 #include "particles.h"
64 #include "translation.h"
65 #include "util/basic_macros.h"
66 #include "util/directiontables.h"
67 #include "util/pointedthing.h"
68 #include "util/quicktune_shortcutter.h"
69 #include "irrlicht_changes/static_text.h"
72 #include "script/scripting_client.h"
76 #include "client/sound_openal.h"
78 #include "client/sound.h"
82 m_chat_log_buf(g_logger),
83 m_game_ui(new GameUI())
85 g_settings->registerChangedCallback("doubletap_jump",
86 &settingChangedCallback, this);
87 g_settings->registerChangedCallback("enable_clouds",
88 &settingChangedCallback, this);
89 g_settings->registerChangedCallback("doubletap_joysticks",
90 &settingChangedCallback, this);
91 g_settings->registerChangedCallback("enable_particles",
92 &settingChangedCallback, this);
93 g_settings->registerChangedCallback("enable_fog",
94 &settingChangedCallback, this);
95 g_settings->registerChangedCallback("mouse_sensitivity",
96 &settingChangedCallback, this);
97 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
98 &settingChangedCallback, this);
99 g_settings->registerChangedCallback("repeat_place_time",
100 &settingChangedCallback, this);
101 g_settings->registerChangedCallback("noclip",
102 &settingChangedCallback, this);
103 g_settings->registerChangedCallback("free_move",
104 &settingChangedCallback, this);
105 g_settings->registerChangedCallback("cinematic",
106 &settingChangedCallback, this);
107 g_settings->registerChangedCallback("cinematic_camera_smoothing",
108 &settingChangedCallback, this);
109 g_settings->registerChangedCallback("camera_smoothing",
110 &settingChangedCallback, this);
111 g_settings->registerChangedCallback("freecam",
112 &freecamChangedCallback, this);
113 g_settings->registerChangedCallback("xray",
114 &updateAllMapBlocksCallback, this);
115 g_settings->registerChangedCallback("xray_nodes",
116 &updateAllMapBlocksCallback, this);
117 g_settings->registerChangedCallback("fullbright",
118 &updateAllMapBlocksCallback, this);
119 g_settings->registerChangedCallback("node_esp_nodes",
120 &updateAllMapBlocksCallback, this);
124 #ifdef HAVE_TOUCHSCREENGUI
125 m_cache_hold_aux1 = false; // This is initialised properly later
132 /****************************************************************************
134 ****************************************************************************/
143 delete server; // deleted first to stop all server threads
151 delete nodedef_manager;
152 delete itemdef_manager;
155 clearTextureNameCache();
157 g_settings->deregisterChangedCallback("doubletap_jump",
158 &settingChangedCallback, this);
159 g_settings->deregisterChangedCallback("enable_clouds",
160 &settingChangedCallback, this);
161 g_settings->deregisterChangedCallback("enable_particles",
162 &settingChangedCallback, this);
163 g_settings->deregisterChangedCallback("enable_fog",
164 &settingChangedCallback, this);
165 g_settings->deregisterChangedCallback("mouse_sensitivity",
166 &settingChangedCallback, this);
167 g_settings->deregisterChangedCallback("repeat_place_time",
168 &settingChangedCallback, this);
169 g_settings->deregisterChangedCallback("noclip",
170 &settingChangedCallback, this);
171 g_settings->deregisterChangedCallback("free_move",
172 &settingChangedCallback, this);
173 g_settings->deregisterChangedCallback("cinematic",
174 &settingChangedCallback, this);
175 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
176 &settingChangedCallback, this);
177 g_settings->deregisterChangedCallback("camera_smoothing",
178 &settingChangedCallback, this);
179 g_settings->deregisterChangedCallback("freecam",
180 &freecamChangedCallback, this);
181 g_settings->deregisterChangedCallback("xray",
182 &updateAllMapBlocksCallback, this);
183 g_settings->deregisterChangedCallback("xray_nodes",
184 &updateAllMapBlocksCallback, this);
185 g_settings->deregisterChangedCallback("fullbright",
186 &updateAllMapBlocksCallback, this);
187 g_settings->deregisterChangedCallback("node_esp_nodes",
188 &updateAllMapBlocksCallback, this);
191 bool Game::startup(bool *kill,
193 RenderingEngine *rendering_engine,
194 const GameStartData &start_data,
195 std::string &error_message,
197 ChatBackend *chat_backend)
201 m_rendering_engine = rendering_engine;
202 device = m_rendering_engine->get_raw_device();
204 this->error_message = &error_message;
205 reconnect_requested = reconnect;
207 this->chat_backend = chat_backend;
208 simple_singleplayer_mode = start_data.isSinglePlayer();
210 input->keycache.populate();
212 driver = device->getVideoDriver();
213 smgr = m_rendering_engine->get_scene_manager();
215 smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
218 runData = GameRunData();
219 runData.time_from_last_punch = 10.0;
221 m_game_ui->initFlags();
223 m_invert_mouse = g_settings->getBool("invert_mouse");
224 m_first_loop_after_window_activation = true;
226 g_client_translations->clear();
228 // address can change if simple_singleplayer_mode
229 if (!init(start_data.world_spec.path, start_data.address,
230 start_data.socket_port, start_data.game_spec))
233 if (!createClient(start_data))
236 m_rendering_engine->initialize(client, hud);
246 FpsControl draw_times;
247 f32 dtime; // in seconds
249 /* Clear the profiler */
250 Profiler::GraphValues dummyvalues;
251 g_profiler->graphGet(dummyvalues);
255 set_light_table(g_settings->getFloat("display_gamma"));
257 #ifdef HAVE_TOUCHSCREENGUI
258 m_cache_hold_aux1 = g_settings->getBool("fast_move")
259 && client->checkPrivilege("fast");
262 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
263 g_settings->getU16("screen_h"));
265 while (m_rendering_engine->run()
266 && !(*kill || g_gamecallback->shutdown_requested
267 || (server && server->isShutdownRequested()))) {
269 const irr::core::dimension2d<u32> ¤t_screen_size =
270 m_rendering_engine->get_video_driver()->getScreenSize();
271 // Verify if window size has changed and save it if it's the case
272 // Ensure evaluating settings->getBool after verifying screensize
273 // First condition is cheaper
274 if (previous_screen_size != current_screen_size &&
275 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
276 g_settings->getBool("autosave_screensize")) {
277 g_settings->setU16("screen_w", current_screen_size.Width);
278 g_settings->setU16("screen_h", current_screen_size.Height);
279 previous_screen_size = current_screen_size;
283 // m_rendering_engine->run() from this iteration
284 // + Sleep time until the wanted FPS are reached
285 draw_times.limit(device, &dtime);
287 // Prepare render data for next iteration
289 updateStats(&stats, draw_times, dtime);
290 updateInteractTimers(dtime);
292 if (!checkConnection())
294 if (!handleCallbacks())
299 m_game_ui->clearInfoText();
303 updateProfilers(stats, draw_times, dtime);
304 processUserInput(dtime);
305 // Update camera before player movement to avoid camera lag of one frame
306 updateCameraDirection(&cam_view_target, dtime);
307 cam_view.camera_yaw += (cam_view_target.camera_yaw -
308 cam_view.camera_yaw) * m_cache_cam_smoothing;
309 cam_view.camera_pitch += (cam_view_target.camera_pitch -
310 cam_view.camera_pitch) * m_cache_cam_smoothing;
311 updatePlayerControl(cam_view);
313 processClientEvents(&cam_view_target);
317 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
318 updateFrame(&graph, &stats, dtime, cam_view);
319 updateProfilerGraphs(&graph);
321 // Update if minimap has been disabled by the server
322 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
324 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
331 void Game::shutdown()
333 m_rendering_engine->finalize();
334 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
335 if (g_settings->get("3d_mode") == "pageflip") {
336 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
339 auto formspec = m_game_ui->getFormspecGUI();
341 formspec->quitMenu();
343 #ifdef HAVE_TOUCHSCREENGUI
344 g_touchscreengui->hide();
347 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
352 if (gui_chat_console)
353 gui_chat_console->drop();
362 while (g_menumgr.menuCount() > 0) {
363 g_menumgr.m_stack.front()->setVisible(false);
364 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
367 m_game_ui->deleteFormspec();
369 chat_backend->addMessage(L"", L"# Disconnected.");
370 chat_backend->addMessage(L"", L"");
371 m_chat_log_buf.clear();
375 while (!client->isShutdown()) {
376 assert(texture_src != NULL);
377 assert(shader_src != NULL);
378 texture_src->processQueue();
379 shader_src->processQueue();
386 /****************************************************************************/
387 /****************************************************************************
389 ****************************************************************************/
390 /****************************************************************************/
393 const std::string &map_dir,
394 const std::string &address,
396 const SubgameSpec &gamespec)
398 texture_src = createTextureSource();
400 showOverlayMessage(N_("Loading..."), 0, 0);
402 shader_src = createShaderSource();
404 itemdef_manager = createItemDefManager();
405 nodedef_manager = createNodeDefManager();
407 eventmgr = new EventManager();
408 quicktune = new QuicktuneShortcutter();
410 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
411 && eventmgr && quicktune))
417 // Create a server if not connecting to an existing one
418 if (address.empty()) {
419 if (!createSingleplayerServer(map_dir, gamespec, port))
426 bool Game::initSound()
429 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
430 infostream << "Attempting to use OpenAL audio" << std::endl;
431 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
433 infostream << "Failed to initialize OpenAL audio" << std::endl;
435 infostream << "Sound disabled." << std::endl;
439 infostream << "Using dummy audio." << std::endl;
440 sound = &dummySoundManager;
441 sound_is_dummy = true;
444 soundmaker = new SoundMaker(sound, nodedef_manager);
448 soundmaker->registerReceiver(eventmgr);
453 bool Game::createSingleplayerServer(const std::string &map_dir,
454 const SubgameSpec &gamespec, u16 port)
456 showOverlayMessage(N_("Creating server..."), 0, 5);
458 std::string bind_str = g_settings->get("bind_address");
459 Address bind_addr(0, 0, 0, 0, port);
461 if (g_settings->getBool("ipv6_server")) {
462 bind_addr.setAddress((IPv6AddressBytes *) NULL);
466 bind_addr.Resolve(bind_str.c_str());
467 } catch (ResolveError &e) {
468 infostream << "Resolving bind address \"" << bind_str
469 << "\" failed: " << e.what()
470 << " -- Listening on all addresses." << std::endl;
473 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
474 *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
475 bind_addr.serializeString().c_str());
476 errorstream << *error_message << std::endl;
480 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
481 false, nullptr, error_message);
487 bool Game::createClient(const GameStartData &start_data)
489 showOverlayMessage(N_("Creating client..."), 0, 10);
491 draw_control = new MapDrawControl();
495 bool could_connect, connect_aborted;
496 #ifdef HAVE_TOUCHSCREENGUI
497 if (g_touchscreengui) {
498 g_touchscreengui->init(texture_src);
499 g_touchscreengui->hide();
502 if (!connectToServer(start_data, &could_connect, &connect_aborted))
505 if (!could_connect) {
506 if (error_message->empty() && !connect_aborted) {
507 // Should not happen if error messages are set properly
508 *error_message = gettext("Connection failed for unknown reason");
509 errorstream << *error_message << std::endl;
514 if (!getServerContent(&connect_aborted)) {
515 if (error_message->empty() && !connect_aborted) {
516 // Should not happen if error messages are set properly
517 *error_message = gettext("Connection failed for unknown reason");
518 errorstream << *error_message << std::endl;
523 auto *scsf = new GameGlobalShaderConstantSetterFactory(
524 &m_flags.force_fog_off, &runData.fog_range, client);
525 shader_src->addShaderConstantSetterFactory(scsf);
527 // Update cached textures, meshes and materials
528 client->afterContentReceived();
532 camera = new Camera(*draw_control, client, m_rendering_engine);
533 if (client->modsLoaded())
534 client->getScript()->on_camera_ready(camera);
535 client->setCamera(camera);
539 if (m_cache_enable_clouds)
540 clouds = new Clouds(smgr, -1, time(0));
544 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
546 skybox = NULL; // This is used/set later on in the main run loop
548 /* Pre-calculated values
550 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
552 v2u32 size = t->getOriginalSize();
553 crack_animation_length = size.Y / size.X;
555 crack_animation_length = 5;
561 /* Set window caption
563 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
565 str += utf8_to_wide(g_version_hash);
567 const wchar_t *text = nullptr;
568 if (simple_singleplayer_mode)
569 text = wgettext("Singleplayer");
571 text = wgettext("Multiplayer");
578 str += L"Minetest Hackclient";
581 device->setWindowCaption(str.c_str());
583 LocalPlayer *player = client->getEnv().getLocalPlayer();
584 player->hurt_tilt_timer = 0;
585 player->hurt_tilt_strength = 0;
587 hud = new Hud(client, player, &player->inventory);
589 mapper = client->getMinimap();
591 if (mapper && client->modsLoaded())
592 client->getScript()->on_minimap_ready(mapper);
601 // Remove stale "recent" chat messages from previous connections
602 chat_backend->clearRecentChat();
604 // Make sure the size of the recent messages buffer is right
605 chat_backend->applySettings();
607 // Chat backend and console
608 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
609 -1, chat_backend, client, &g_menumgr);
611 if (!gui_chat_console) {
612 *error_message = "Could not allocate memory for chat console";
613 errorstream << *error_message << std::endl;
617 m_cheat_menu = new CheatMenu(client);
620 *error_message = "Could not allocate memory for cheat menu";
621 errorstream << *error_message << std::endl;
625 #ifdef HAVE_TOUCHSCREENGUI
627 if (g_touchscreengui)
628 g_touchscreengui->show();
635 bool Game::connectToServer(const GameStartData &start_data,
636 bool *connect_ok, bool *connection_aborted)
638 *connect_ok = false; // Let's not be overly optimistic
639 *connection_aborted = false;
640 bool local_server_mode = false;
642 showOverlayMessage(N_("Resolving address..."), 0, 15);
644 Address connect_address(0, 0, 0, 0, start_data.socket_port);
647 connect_address.Resolve(start_data.address.c_str());
649 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
650 if (connect_address.isIPv6()) {
651 IPv6AddressBytes addr_bytes;
652 addr_bytes.bytes[15] = 1;
653 connect_address.setAddress(&addr_bytes);
655 connect_address.setAddress(127, 0, 0, 1);
657 local_server_mode = true;
659 } catch (ResolveError &e) {
660 *error_message = fmtgettext("Couldn't resolve address: %s", e.what());
662 errorstream << *error_message << std::endl;
666 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
667 *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
668 errorstream << *error_message << std::endl;
673 client = new Client(start_data.name.c_str(),
674 start_data.password, start_data.address,
675 *draw_control, texture_src, shader_src,
676 itemdef_manager, nodedef_manager, sound, eventmgr,
677 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get(),
678 start_data.allow_login_or_register);
679 client->migrateModStorage();
680 } catch (const BaseException &e) {
681 *error_message = fmtgettext("Error creating client: %s", e.what());
682 errorstream << *error_message << std::endl;
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;
742 // Only time out if we aren't waiting for the server we started
743 if (!start_data.address.empty() && wait_time > 10) {
744 *error_message = gettext("Connection timed out.");
745 errorstream << *error_message << std::endl;
750 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
752 } catch (con::PeerNotFoundException &e) {
753 // TODO: Should something be done here? At least an info/error
761 bool Game::getServerContent(bool *aborted)
765 FpsControl fps_control;
766 f32 dtime; // in seconds
770 while (m_rendering_engine->run()) {
772 fps_control.limit(device, &dtime);
774 // Update client and server
781 if (client->mediaReceived() && client->itemdefReceived() &&
782 client->nodedefReceived()) {
787 if (!checkConnection())
790 if (client->getState() < LC_Init) {
791 *error_message = gettext("Client disconnected");
792 errorstream << *error_message << std::endl;
796 if (input->cancelPressed()) {
798 infostream << "Connect aborted [Escape]" << std::endl;
805 if (!client->itemdefReceived()) {
806 const wchar_t *text = wgettext("Item definitions...");
808 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
811 } else if (!client->nodedefReceived()) {
812 const wchar_t *text = wgettext("Node definitions...");
814 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
818 std::ostringstream message;
820 message.precision(0);
821 float receive = client->mediaReceiveProgress() * 100;
822 message << gettext("Media...");
824 message << " " << receive << "%";
825 message.precision(2);
827 if ((USE_CURL == 0) ||
828 (!g_settings->getBool("enable_remote_media_server"))) {
829 float cur = client->getCurRate();
830 std::string cur_unit = gettext("KiB/s");
834 cur_unit = gettext("MiB/s");
837 message << " (" << cur << ' ' << cur_unit << ")";
840 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
841 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
842 texture_src, dtime, progress);
850 /****************************************************************************/
851 /****************************************************************************
853 ****************************************************************************/
854 /****************************************************************************/
856 inline void Game::updateInteractTimers(f32 dtime)
858 if (runData.nodig_delay_timer >= 0)
859 runData.nodig_delay_timer -= dtime;
861 if (runData.object_hit_delay_timer >= 0)
862 runData.object_hit_delay_timer -= dtime;
864 runData.time_from_last_punch += dtime;
868 /* returns false if game should exit, otherwise true
870 inline bool Game::checkConnection()
872 if (client->accessDenied()) {
873 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
874 *reconnect_requested = client->reconnectRequested();
875 errorstream << *error_message << std::endl;
883 /* returns false if game should exit, otherwise true
885 inline bool Game::handleCallbacks()
887 if (g_gamecallback->disconnect_requested) {
888 g_gamecallback->disconnect_requested = false;
892 if (g_gamecallback->changepassword_requested) {
893 (new GUIPasswordChange(guienv, guiroot, -1,
894 &g_menumgr, client, texture_src))->drop();
895 g_gamecallback->changepassword_requested = false;
898 if (g_gamecallback->changevolume_requested) {
899 (new GUIVolumeChange(guienv, guiroot, -1,
900 &g_menumgr, texture_src))->drop();
901 g_gamecallback->changevolume_requested = false;
904 if (g_gamecallback->keyconfig_requested) {
905 (new GUIKeyChangeMenu(guienv, guiroot, -1,
906 &g_menumgr, texture_src))->drop();
907 g_gamecallback->keyconfig_requested = false;
910 if (g_gamecallback->keyconfig_changed) {
911 input->keycache.populate(); // update the cache with new settings
912 g_gamecallback->keyconfig_changed = false;
919 void Game::processQueues()
921 texture_src->processQueue();
922 itemdef_manager->processQueue(client);
923 shader_src->processQueue();
926 void Game::updateDebugState()
928 LocalPlayer *player = client->getEnv().getLocalPlayer();
930 // debug UI and wireframe
931 bool has_debug = client->checkPrivilege("debug");
932 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
934 if (m_game_ui->m_flags.show_basic_debug) {
935 if (!has_basic_debug)
936 m_game_ui->m_flags.show_basic_debug = false;
937 } else if (m_game_ui->m_flags.show_minimal_debug) {
939 m_game_ui->m_flags.show_basic_debug = true;
941 if (!has_basic_debug)
942 hud->disableBlockBounds();
944 draw_control->show_wireframe = false;
947 draw_control->allow_noclip = m_cache_enable_noclip && client->checkPrivilege("noclip");
950 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
953 float profiler_print_interval =
954 g_settings->getFloat("profiler_print_interval");
955 bool print_to_log = true;
957 if (profiler_print_interval == 0) {
958 print_to_log = false;
959 profiler_print_interval = 3;
962 if (profiler_interval.step(dtime, profiler_print_interval)) {
964 infostream << "Profiler:" << std::endl;
965 g_profiler->print(infostream);
968 m_game_ui->updateProfiler();
972 // Update update graphs
973 g_profiler->graphAdd("Time non-rendering [us]",
974 draw_times.busy_time - stats.drawtime);
976 g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
977 g_profiler->graphAdd("FPS", 1.0f / dtime);
980 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
987 /* Time average and jitter calculation
989 jp = &stats->dtime_jitter;
990 jp->avg = jp->avg * 0.96 + dtime * 0.04;
992 jitter = dtime - jp->avg;
994 if (jitter > jp->max)
997 jp->counter += dtime;
999 if (jp->counter > 0.0) {
1001 jp->max_sample = jp->max;
1002 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1006 /* Busytime average and jitter calculation
1008 jp = &stats->busy_time_jitter;
1009 jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
1011 jitter = draw_times.getBusyMs() - jp->avg;
1013 if (jitter > jp->max)
1015 if (jitter < jp->min)
1018 jp->counter += dtime;
1020 if (jp->counter > 0.0) {
1022 jp->max_sample = jp->max;
1023 jp->min_sample = jp->min;
1031 /****************************************************************************
1033 ****************************************************************************/
1035 void Game::processUserInput(f32 dtime)
1037 // Reset input if window not active or some menu is active
1038 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1040 #ifdef HAVE_TOUCHSCREENGUI
1041 g_touchscreengui->hide();
1044 #ifdef HAVE_TOUCHSCREENGUI
1045 else if (g_touchscreengui) {
1046 /* on touchscreengui step may generate own input events which ain't
1047 * what we want in case we just did clear them */
1048 g_touchscreengui->show();
1049 g_touchscreengui->step(dtime);
1053 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1054 gui_chat_console->closeConsoleAtOnce();
1057 // Input handler step() (used by the random input generator)
1061 auto formspec = m_game_ui->getFormspecGUI();
1063 formspec->getAndroidUIInput();
1065 handleAndroidChatInput();
1068 // Increase timer for double tap of "keymap_jump"
1069 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1070 runData.jump_timer += dtime;
1073 processItemSelection(&runData.new_playeritem);
1077 void Game::processKeyInput()
1079 if (wasKeyDown(KeyType::SELECT_UP)) {
1080 m_cheat_menu->selectUp();
1081 } else if (wasKeyDown(KeyType::SELECT_DOWN)) {
1082 m_cheat_menu->selectDown();
1083 } else if (wasKeyDown(KeyType::SELECT_LEFT)) {
1084 m_cheat_menu->selectLeft();
1085 } else if (wasKeyDown(KeyType::SELECT_RIGHT)) {
1086 m_cheat_menu->selectRight();
1087 } else if (wasKeyDown(KeyType::SELECT_CONFIRM)) {
1088 m_cheat_menu->selectConfirm();
1091 if (wasKeyDown(KeyType::DROP)) {
1092 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1093 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1094 toggleAutoforward();
1095 } else if (wasKeyDown(KeyType::BACKWARD)) {
1096 if (g_settings->getBool("continuous_forward"))
1097 toggleAutoforward();
1098 } else if (wasKeyDown(KeyType::INVENTORY)) {
1100 } else if (wasKeyDown(KeyType::ENDERCHEST)) {
1102 } else if (input->cancelPressed()) {
1104 m_android_chat_open = false;
1106 if (!gui_chat_console->isOpenInhibited()) {
1109 } else if (wasKeyDown(KeyType::CHAT)) {
1110 openConsole(0.2, L"");
1111 } else if (wasKeyDown(KeyType::CMD)) {
1112 openConsole(0.2, L"/");
1113 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1114 if (client->modsLoaded())
1115 openConsole(0.2, L".");
1117 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1118 } else if (wasKeyDown(KeyType::CONSOLE)) {
1119 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1120 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1122 } else if (wasKeyDown(KeyType::JUMP)) {
1123 toggleFreeMoveAlt();
1124 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1126 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1128 } else if (wasKeyDown(KeyType::NOCLIP)) {
1130 } else if (wasKeyDown(KeyType::KILLAURA)) {
1132 } else if (wasKeyDown(KeyType::FREECAM)) {
1134 } else if (wasKeyDown(KeyType::SCAFFOLD)) {
1137 } else if (wasKeyDown(KeyType::MUTE)) {
1138 if (g_settings->getBool("enable_sound")) {
1139 bool new_mute_sound = !g_settings->getBool("mute_sound");
1140 g_settings->setBool("mute_sound", new_mute_sound);
1142 m_game_ui->showTranslatedStatusText("Sound muted");
1144 m_game_ui->showTranslatedStatusText("Sound unmuted");
1146 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1148 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1149 if (g_settings->getBool("enable_sound")) {
1150 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1151 g_settings->setFloat("sound_volume", new_volume);
1152 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1153 m_game_ui->showStatusText(msg);
1155 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1157 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1158 if (g_settings->getBool("enable_sound")) {
1159 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1160 g_settings->setFloat("sound_volume", new_volume);
1161 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1162 m_game_ui->showStatusText(msg);
1164 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1167 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1168 || wasKeyDown(KeyType::DEC_VOLUME)) {
1169 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1171 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1173 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1174 client->makeScreenshot();
1175 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
1176 toggleBlockBounds();
1177 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1178 m_game_ui->toggleHud();
1179 } else if (wasKeyDown(KeyType::MINIMAP)) {
1180 toggleMinimap(isKeyDown(KeyType::SNEAK));
1181 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1182 m_game_ui->toggleChat();
1183 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1185 } else if (wasKeyDown(KeyType::TOGGLE_CHEAT_MENU)) {
1186 m_game_ui->toggleCheatMenu();
1187 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1188 toggleUpdateCamera();
1189 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1191 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1192 m_game_ui->toggleProfiler();
1193 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1194 increaseViewRange();
1195 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1196 decreaseViewRange();
1197 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1198 toggleFullViewRange();
1199 } else if (wasKeyDown(KeyType::ZOOM)) {
1201 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1203 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1205 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1207 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1211 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1212 runData.reset_jump_timer = false;
1213 runData.jump_timer = 0.0f;
1216 if (quicktune->hasMessage()) {
1217 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
1221 void Game::processItemSelection(u16 *new_playeritem)
1223 LocalPlayer *player = client->getEnv().getLocalPlayer();
1225 /* Item selection using mouse wheel
1227 *new_playeritem = player->getWieldIndex();
1229 s32 wheel = input->getMouseWheel();
1230 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
1231 player->hud_hotbar_itemcount - 1);
1235 if (wasKeyDown(KeyType::HOTBAR_NEXT))
1238 if (wasKeyDown(KeyType::HOTBAR_PREV))
1242 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
1244 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
1247 /* Item selection using hotbar slot keys
1249 for (u16 i = 0; i <= max_item; i++) {
1250 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
1251 *new_playeritem = i;
1258 void Game::dropSelectedItem(bool single_item)
1260 IDropAction *a = new IDropAction();
1261 a->count = single_item ? 1 : 0;
1262 a->from_inv.setCurrentPlayer();
1263 a->from_list = "main";
1264 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
1265 client->inventoryAction(a);
1269 void Game::openInventory()
1272 * Don't permit to open inventory is CAO or player doesn't exists.
1273 * This prevent showing an empty inventory at player load
1276 LocalPlayer *player = client->getEnv().getLocalPlayer();
1277 if (!player || !player->getCAO())
1280 infostream << "Game: Launching inventory" << std::endl;
1282 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
1284 InventoryLocation inventoryloc;
1285 inventoryloc.setCurrentPlayer();
1287 if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
1292 if (fs_src->getForm().empty()) {
1297 TextDest *txt_dst = new TextDestPlayerInventory(client);
1298 auto *&formspec = m_game_ui->updateFormspec("");
1299 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
1300 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
1302 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
1305 void Game::openEnderchest()
1307 LocalPlayer *player = client->getEnv().getLocalPlayer();
1308 if (!player || !player->getCAO())
1311 infostream << "Game: Launching special inventory" << std::endl;
1313 if (client->modsLoaded())
1314 client->getScript()->open_enderchest();
1318 void Game::openConsole(float scale, const wchar_t *line)
1320 assert(scale > 0.0f && scale <= 1.0f);
1323 porting::showInputDialog(gettext("ok"), "", "", 2);
1324 m_android_chat_open = true;
1326 if (gui_chat_console->isOpenInhibited())
1328 gui_chat_console->openConsole(scale);
1330 gui_chat_console->setCloseOnEnter(true);
1331 gui_chat_console->replaceAndAddToHistory(line);
1337 void Game::handleAndroidChatInput()
1339 if (m_android_chat_open && porting::getInputDialogState() == 0) {
1340 std::string text = porting::getInputDialogValue();
1341 client->typeChatMessage(utf8_to_wide(text));
1342 m_android_chat_open = false;
1348 void Game::toggleFreeMove()
1350 bool free_move = !g_settings->getBool("free_move");
1351 g_settings->set("free_move", bool_to_cstr(free_move));
1354 if (client->checkPrivilege("fly")) {
1355 m_game_ui->showTranslatedStatusText("Fly mode enabled");
1357 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
1360 m_game_ui->showTranslatedStatusText("Fly mode disabled");
1364 void Game::toggleFreeMoveAlt()
1366 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
1369 runData.reset_jump_timer = true;
1373 void Game::togglePitchMove()
1375 bool pitch_move = !g_settings->getBool("pitch_move");
1376 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
1379 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
1381 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
1386 void Game::toggleFast()
1388 bool fast_move = !g_settings->getBool("fast_move");
1389 bool has_fast_privs = client->checkPrivilege("fast");
1390 g_settings->set("fast_move", bool_to_cstr(fast_move));
1393 if (has_fast_privs) {
1394 m_game_ui->showTranslatedStatusText("Fast mode enabled");
1396 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
1399 m_game_ui->showTranslatedStatusText("Fast mode disabled");
1402 #ifdef HAVE_TOUCHSCREENGUI
1403 m_cache_hold_aux1 = fast_move && has_fast_privs;
1408 void Game::toggleNoClip()
1410 bool noclip = !g_settings->getBool("noclip");
1411 g_settings->set("noclip", bool_to_cstr(noclip));
1414 if (client->checkPrivilege("noclip")) {
1415 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
1417 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
1420 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
1424 void Game::toggleKillaura()
1426 bool killaura = ! g_settings->getBool("killaura");
1427 g_settings->set("killaura", bool_to_cstr(killaura));
1430 m_game_ui->showTranslatedStatusText("Killaura enabled");
1432 m_game_ui->showTranslatedStatusText("Killaura disabled");
1436 void Game::toggleFreecam()
1438 bool freecam = ! g_settings->getBool("freecam");
1439 g_settings->set("freecam", bool_to_cstr(freecam));
1442 m_game_ui->showTranslatedStatusText("Freecam enabled");
1444 m_game_ui->showTranslatedStatusText("Freecam disabled");
1448 void Game::toggleScaffold()
1450 bool scaffold = ! g_settings->getBool("scaffold");
1451 g_settings->set("scaffold", bool_to_cstr(scaffold));
1454 m_game_ui->showTranslatedStatusText("Scaffold enabled");
1456 m_game_ui->showTranslatedStatusText("Scaffold disabled");
1460 void Game::toggleCinematic()
1462 bool cinematic = !g_settings->getBool("cinematic");
1463 g_settings->set("cinematic", bool_to_cstr(cinematic));
1466 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
1468 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
1471 void Game::toggleBlockBounds()
1473 LocalPlayer *player = client->getEnv().getLocalPlayer();
1474 if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
1475 m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
1478 enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
1480 case Hud::BLOCK_BOUNDS_OFF:
1481 m_game_ui->showTranslatedStatusText("Block bounds hidden");
1483 case Hud::BLOCK_BOUNDS_CURRENT:
1484 m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
1486 case Hud::BLOCK_BOUNDS_NEAR:
1487 m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
1489 case Hud::BLOCK_BOUNDS_MAX:
1490 m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
1497 // Autoforward by toggling continuous forward.
1498 void Game::toggleAutoforward()
1500 bool autorun_enabled = !g_settings->getBool("continuous_forward");
1501 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
1503 if (autorun_enabled)
1504 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
1506 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
1509 void Game::toggleMinimap(bool shift_pressed)
1511 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
1515 mapper->toggleMinimapShape();
1519 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
1521 // Not so satisying code to keep compatibility with old fixed mode system
1523 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
1525 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
1526 m_game_ui->m_flags.show_minimap = false;
1529 // If radar is disabled, try to find a non radar mode or fall back to 0
1530 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
1531 while (mapper->getModeIndex() &&
1532 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
1535 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
1539 // End of 'not so satifying code'
1540 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
1541 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
1542 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
1544 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
1547 void Game::toggleFog()
1549 bool fog_enabled = g_settings->getBool("enable_fog");
1550 g_settings->setBool("enable_fog", !fog_enabled);
1552 m_game_ui->showTranslatedStatusText("Fog disabled");
1554 m_game_ui->showTranslatedStatusText("Fog enabled");
1558 void Game::toggleDebug()
1560 LocalPlayer *player = client->getEnv().getLocalPlayer();
1561 bool has_debug = client->checkPrivilege("debug");
1562 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
1563 // Initial: No debug info
1564 // 1x toggle: Debug text
1565 // 2x toggle: Debug text with profiler graph
1566 // 3x toggle: Debug text and wireframe (needs "debug" priv)
1567 // Next toggle: Back to initial
1569 // The debug text can be in 2 modes: minimal and basic.
1570 // * Minimal: Only technical client info that not gameplay-relevant
1571 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
1572 // Basic mode is used when player has the debug HUD flag set,
1573 // otherwise the Minimal mode is used.
1574 if (!m_game_ui->m_flags.show_minimal_debug) {
1575 m_game_ui->m_flags.show_minimal_debug = true;
1576 if (has_basic_debug)
1577 m_game_ui->m_flags.show_basic_debug = true;
1578 m_game_ui->m_flags.show_profiler_graph = false;
1579 draw_control->show_wireframe = false;
1580 m_game_ui->showTranslatedStatusText("Debug info shown");
1581 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
1582 if (has_basic_debug)
1583 m_game_ui->m_flags.show_basic_debug = true;
1584 m_game_ui->m_flags.show_profiler_graph = true;
1585 m_game_ui->showTranslatedStatusText("Profiler graph shown");
1586 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
1587 if (has_basic_debug)
1588 m_game_ui->m_flags.show_basic_debug = true;
1589 m_game_ui->m_flags.show_profiler_graph = false;
1590 draw_control->show_wireframe = true;
1591 m_game_ui->showTranslatedStatusText("Wireframe shown");
1593 m_game_ui->m_flags.show_minimal_debug = false;
1594 m_game_ui->m_flags.show_basic_debug = false;
1595 m_game_ui->m_flags.show_profiler_graph = false;
1596 draw_control->show_wireframe = false;
1598 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
1600 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
1606 void Game::toggleUpdateCamera()
1608 if (g_settings->getBool("freecam"))
1610 m_flags.disable_camera_update = !m_flags.disable_camera_update;
1611 if (m_flags.disable_camera_update)
1612 m_game_ui->showTranslatedStatusText("Camera update disabled");
1614 m_game_ui->showTranslatedStatusText("Camera update enabled");
1618 void Game::increaseViewRange()
1620 s16 range = g_settings->getS16("viewing_range");
1621 s16 range_new = range + 10;
1623 if (range_new > 4000) {
1625 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
1626 m_game_ui->showStatusText(msg);
1628 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
1629 m_game_ui->showStatusText(msg);
1631 g_settings->set("viewing_range", itos(range_new));
1635 void Game::decreaseViewRange()
1637 s16 range = g_settings->getS16("viewing_range");
1638 s16 range_new = range - 10;
1640 if (range_new < 20) {
1642 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
1643 m_game_ui->showStatusText(msg);
1645 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
1646 m_game_ui->showStatusText(msg);
1648 g_settings->set("viewing_range", itos(range_new));
1652 void Game::toggleFullViewRange()
1654 draw_control->range_all = !draw_control->range_all;
1655 if (draw_control->range_all)
1656 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
1658 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
1662 void Game::checkZoomEnabled()
1664 LocalPlayer *player = client->getEnv().getLocalPlayer();
1665 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
1666 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
1669 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
1671 if ((device->isWindowActive() && device->isWindowFocused()
1672 && !isMenuActive()) || input->isRandom()) {
1675 if (!input->isRandom()) {
1676 // Mac OSX gets upset if this is set every frame
1677 if (device->getCursorControl()->isVisible())
1678 device->getCursorControl()->setVisible(false);
1682 if (m_first_loop_after_window_activation) {
1683 m_first_loop_after_window_activation = false;
1685 input->setMousePos(driver->getScreenSize().Width / 2,
1686 driver->getScreenSize().Height / 2);
1688 updateCameraOrientation(cam, dtime);
1694 // Mac OSX gets upset if this is set every frame
1695 if (!device->getCursorControl()->isVisible())
1696 device->getCursorControl()->setVisible(true);
1699 m_first_loop_after_window_activation = true;
1704 // Get the factor to multiply with sensitivity to get the same mouse/joystick
1705 // responsiveness independently of FOV.
1706 f32 Game::getSensitivityScaleFactor() const
1708 f32 fov_y = client->getCamera()->getFovY();
1710 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
1711 // 16:9 aspect ratio to minimize disruption of existing sensitivity
1713 return tan(fov_y / 2.0f) * 1.3763818698f;
1716 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
1718 #ifdef HAVE_TOUCHSCREENGUI
1719 if (g_touchscreengui) {
1720 cam->camera_yaw += g_touchscreengui->getYawChange();
1721 cam->camera_pitch = g_touchscreengui->getPitch();
1724 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
1725 v2s32 dist = input->getMousePos() - center;
1727 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
1731 f32 sens_scale = getSensitivityScaleFactor();
1732 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
1733 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
1735 if (dist.X != 0 || dist.Y != 0)
1736 input->setMousePos(center.X, center.Y);
1737 #ifdef HAVE_TOUCHSCREENGUI
1741 if (m_cache_enable_joysticks) {
1742 f32 sens_scale = getSensitivityScaleFactor();
1743 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
1744 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
1745 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
1748 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
1752 void Game::updatePlayerControl(const CameraOrientation &cam)
1754 LocalPlayer *player = client->getEnv().getLocalPlayer();
1756 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
1758 PlayerControl control(
1759 isKeyDown(KeyType::FORWARD),
1760 isKeyDown(KeyType::BACKWARD),
1761 isKeyDown(KeyType::LEFT),
1762 isKeyDown(KeyType::RIGHT),
1763 isKeyDown(KeyType::JUMP) || player->getAutojump(),
1764 isKeyDown(KeyType::AUX1),
1765 isKeyDown(KeyType::SNEAK),
1766 isKeyDown(KeyType::ZOOM),
1767 isKeyDown(KeyType::DIG),
1768 isKeyDown(KeyType::PLACE),
1771 input->getMovementSpeed(),
1772 input->getMovementDirection()
1775 // autoforward if set: move towards pointed position at maximum speed
1776 if (player->getPlayerSettings().continuous_forward &&
1777 client->activeObjectsReceived() && !player->isDead()) {
1778 control.movement_speed = 1.0f;
1779 control.movement_direction = 0.0f;
1782 #ifdef HAVE_TOUCHSCREENGUI
1783 /* For touch, simulate holding down AUX1 (fast move) if the user has
1784 * the fast_move setting toggled on. If there is an aux1 key defined for
1785 * touch then its meaning is inverted (i.e. holding aux1 means walk and
1788 if (m_cache_hold_aux1) {
1789 control.aux1 = control.aux1 ^ true;
1793 client->setPlayerControl(control);
1799 inline void Game::step(f32 *dtime)
1801 bool can_be_and_is_paused =
1802 (simple_singleplayer_mode && g_menumgr.pausesGame());
1804 if (can_be_and_is_paused) { // This is for a singleplayer server
1805 *dtime = 0; // No time passes
1807 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
1811 server->step(*dtime);
1813 client->step(*dtime);
1817 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
1820 for (auto &&child: node->getChildren())
1821 pauseNodeAnimation(paused, child);
1822 if (node->getType() != scene::ESNT_ANIMATED_MESH)
1824 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
1825 float speed = animated_node->getAnimationSpeed();
1828 paused.push_back({grab(animated_node), speed});
1829 animated_node->setAnimationSpeed(0.0f);
1832 void Game::pauseAnimation()
1834 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
1837 void Game::resumeAnimation()
1839 for (auto &&pair: paused_animated_nodes)
1840 pair.first->setAnimationSpeed(pair.second);
1841 paused_animated_nodes.clear();
1844 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
1845 {&Game::handleClientEvent_None},
1846 {&Game::handleClientEvent_PlayerDamage},
1847 {&Game::handleClientEvent_PlayerForceMove},
1848 {&Game::handleClientEvent_Deathscreen},
1849 {&Game::handleClientEvent_ShowFormSpec},
1850 {&Game::handleClientEvent_ShowLocalFormSpec},
1851 {&Game::handleClientEvent_HandleParticleEvent},
1852 {&Game::handleClientEvent_HandleParticleEvent},
1853 {&Game::handleClientEvent_HandleParticleEvent},
1854 {&Game::handleClientEvent_HudAdd},
1855 {&Game::handleClientEvent_HudRemove},
1856 {&Game::handleClientEvent_HudChange},
1857 {&Game::handleClientEvent_SetSky},
1858 {&Game::handleClientEvent_SetSun},
1859 {&Game::handleClientEvent_SetMoon},
1860 {&Game::handleClientEvent_SetStars},
1861 {&Game::handleClientEvent_OverrideDayNigthRatio},
1862 {&Game::handleClientEvent_CloudParams},
1865 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
1867 FATAL_ERROR("ClientEvent type None received");
1870 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
1872 if (client->modsLoaded())
1873 client->getScript()->on_damage_taken(event->player_damage.amount);
1875 // Damage flash and hurt tilt are not used at death
1876 if (client->getHP() > 0) {
1877 LocalPlayer *player = client->getEnv().getLocalPlayer();
1879 f32 hp_max = player->getCAO() ?
1880 player->getCAO()->getProperties()->hp_max : PLAYER_MAX_HP_DEFAULT;
1881 f32 damage_ratio = event->player_damage.amount / hp_max;
1883 runData.damage_flash += 95.0f + 64.f * damage_ratio;
1884 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
1886 player->hurt_tilt_timer = 1.5f;
1887 player->hurt_tilt_strength =
1888 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
1891 // Play damage sound
1892 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
1895 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
1897 cam->camera_yaw = event->player_force_move.yaw;
1898 cam->camera_pitch = event->player_force_move.pitch;
1901 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
1903 // If client scripting is enabled, deathscreen is handled by CSM code in
1904 // builtin/client/init.lua
1905 if (client->modsLoaded())
1906 client->getScript()->on_death();
1908 showDeathFormspec();
1910 /* Handle visualization */
1911 LocalPlayer *player = client->getEnv().getLocalPlayer();
1912 runData.damage_flash = 0;
1913 player->hurt_tilt_timer = 0;
1914 player->hurt_tilt_strength = 0;
1917 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
1919 if (event->show_formspec.formspec->empty()) {
1920 auto formspec = m_game_ui->getFormspecGUI();
1921 if (formspec && (event->show_formspec.formname->empty()
1922 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
1923 formspec->quitMenu();
1926 FormspecFormSource *fs_src =
1927 new FormspecFormSource(*(event->show_formspec.formspec));
1928 TextDestPlayerInventory *txt_dst =
1929 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
1931 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
1932 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
1933 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
1936 delete event->show_formspec.formspec;
1937 delete event->show_formspec.formname;
1940 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
1942 if (event->show_formspec.formspec->empty()) {
1943 auto formspec = m_game_ui->getFormspecGUI();
1944 if (formspec && (event->show_formspec.formname->empty()
1945 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
1946 formspec->quitMenu();
1949 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
1950 LocalFormspecHandler *txt_dst =
1951 new LocalFormspecHandler(*event->show_formspec.formname, client);
1952 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(), &input->joystick,
1953 fs_src, txt_dst, client->getFormspecPrepend(), sound);
1956 delete event->show_formspec.formspec;
1957 delete event->show_formspec.formname;
1960 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
1961 CameraOrientation *cam)
1963 LocalPlayer *player = client->getEnv().getLocalPlayer();
1964 client->getParticleManager()->handleParticleEvent(event, client, player);
1967 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
1969 LocalPlayer *player = client->getEnv().getLocalPlayer();
1971 u32 server_id = event->hudadd->server_id;
1972 // ignore if we already have a HUD with that ID
1973 auto i = m_hud_server_to_client.find(server_id);
1974 if (i != m_hud_server_to_client.end()) {
1975 delete event->hudadd;
1979 HudElement *e = new HudElement;
1980 e->type = static_cast<HudElementType>(event->hudadd->type);
1981 e->pos = event->hudadd->pos;
1982 e->name = event->hudadd->name;
1983 e->scale = event->hudadd->scale;
1984 e->text = event->hudadd->text;
1985 e->number = event->hudadd->number;
1986 e->item = event->hudadd->item;
1987 e->dir = event->hudadd->dir;
1988 e->align = event->hudadd->align;
1989 e->offset = event->hudadd->offset;
1990 e->world_pos = event->hudadd->world_pos;
1991 e->size = event->hudadd->size;
1992 e->z_index = event->hudadd->z_index;
1993 e->text2 = event->hudadd->text2;
1994 e->style = event->hudadd->style;
1995 m_hud_server_to_client[server_id] = player->addHud(e);
1997 delete event->hudadd;
2000 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2002 LocalPlayer *player = client->getEnv().getLocalPlayer();
2004 auto i = m_hud_server_to_client.find(event->hudrm.id);
2005 if (i != m_hud_server_to_client.end()) {
2006 HudElement *e = player->removeHud(i->second);
2008 m_hud_server_to_client.erase(i);
2013 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2015 LocalPlayer *player = client->getEnv().getLocalPlayer();
2017 HudElement *e = nullptr;
2019 auto i = m_hud_server_to_client.find(event->hudchange->id);
2020 if (i != m_hud_server_to_client.end()) {
2021 e = player->getHud(i->second);
2025 delete event->hudchange;
2029 #define CASE_SET(statval, prop, dataprop) \
2031 e->prop = event->hudchange->dataprop; \
2034 switch (event->hudchange->stat) {
2035 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2037 CASE_SET(HUD_STAT_NAME, name, sdata);
2039 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2041 CASE_SET(HUD_STAT_TEXT, text, sdata);
2043 CASE_SET(HUD_STAT_NUMBER, number, data);
2045 CASE_SET(HUD_STAT_ITEM, item, data);
2047 CASE_SET(HUD_STAT_DIR, dir, data);
2049 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2051 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2053 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2055 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2057 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2059 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2061 CASE_SET(HUD_STAT_STYLE, style, data);
2066 delete event->hudchange;
2069 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2071 sky->setVisible(false);
2072 // Whether clouds are visible in front of a custom skybox.
2073 sky->setCloudsEnabled(event->set_sky->clouds);
2079 // Clear the old textures out in case we switch rendering type.
2080 sky->clearSkyboxTextures();
2081 // Handle according to type
2082 if (event->set_sky->type == "regular") {
2083 // Shows the mesh skybox
2084 sky->setVisible(true);
2085 // Update mesh based skybox colours if applicable.
2086 sky->setSkyColors(event->set_sky->sky_color);
2087 sky->setHorizonTint(
2088 event->set_sky->fog_sun_tint,
2089 event->set_sky->fog_moon_tint,
2090 event->set_sky->fog_tint_type
2092 } else if (event->set_sky->type == "skybox" &&
2093 event->set_sky->textures.size() == 6) {
2094 // Disable the dyanmic mesh skybox:
2095 sky->setVisible(false);
2097 sky->setFallbackBgColor(event->set_sky->bgcolor);
2098 // Set sunrise and sunset fog tinting:
2099 sky->setHorizonTint(
2100 event->set_sky->fog_sun_tint,
2101 event->set_sky->fog_moon_tint,
2102 event->set_sky->fog_tint_type
2104 // Add textures to skybox.
2105 for (int i = 0; i < 6; i++)
2106 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2108 // Handle everything else as plain color.
2109 if (event->set_sky->type != "plain")
2110 infostream << "Unknown sky type: "
2111 << (event->set_sky->type) << std::endl;
2112 sky->setVisible(false);
2113 sky->setFallbackBgColor(event->set_sky->bgcolor);
2114 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2115 sky->setHorizonTint(
2116 event->set_sky->bgcolor,
2117 event->set_sky->bgcolor,
2122 delete event->set_sky;
2125 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2127 sky->setSunVisible(event->sun_params->visible);
2128 sky->setSunTexture(event->sun_params->texture,
2129 event->sun_params->tonemap, texture_src);
2130 sky->setSunScale(event->sun_params->scale);
2131 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2132 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2133 delete event->sun_params;
2136 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2138 sky->setMoonVisible(event->moon_params->visible);
2139 sky->setMoonTexture(event->moon_params->texture,
2140 event->moon_params->tonemap, texture_src);
2141 sky->setMoonScale(event->moon_params->scale);
2142 delete event->moon_params;
2145 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2147 sky->setStarsVisible(event->star_params->visible);
2148 sky->setStarCount(event->star_params->count);
2149 sky->setStarColor(event->star_params->starcolor);
2150 sky->setStarScale(event->star_params->scale);
2151 delete event->star_params;
2154 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2155 CameraOrientation *cam)
2157 client->getEnv().setDayNightRatioOverride(
2158 event->override_day_night_ratio.do_override,
2159 event->override_day_night_ratio.ratio_f * 1000.0f);
2162 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2167 clouds->setDensity(event->cloud_params.density);
2168 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2169 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2170 clouds->setHeight(event->cloud_params.height);
2171 clouds->setThickness(event->cloud_params.thickness);
2172 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2175 void Game::processClientEvents(CameraOrientation *cam)
2177 while (client->hasClientEvents()) {
2178 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2179 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2180 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2181 (this->*evHandler.handler)(event.get(), cam);
2185 void Game::updateChat(f32 dtime)
2187 // Get new messages from error log buffer
2188 while (!m_chat_log_buf.empty())
2189 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2191 // Get new messages from client
2192 std::wstring message;
2193 while (client->getChatMessage(message)) {
2194 chat_backend->addUnparsedMessage(message);
2197 // Remove old messages
2198 chat_backend->step(dtime);
2200 // Display all messages in a static text element
2201 auto &buf = chat_backend->getRecentBuffer();
2202 if (buf.getLinesModified()) {
2203 buf.resetLinesModified();
2204 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
2207 // Make sure that the size is still correct
2208 m_game_ui->updateChatSize();
2211 void Game::updateCamera(f32 dtime)
2213 LocalPlayer *player = client->getEnv().getLocalPlayer();
2216 For interaction purposes, get info about the held item
2218 - Is it a usable item?
2219 - Can it point to liquids?
2221 ItemStack playeritem;
2223 ItemStack selected, hand;
2224 playeritem = player->getWieldedItem(&selected, &hand);
2227 ToolCapabilities playeritem_toolcap =
2228 playeritem.getToolCapabilities(itemdef_manager);
2230 v3s16 old_camera_offset = camera->getOffset();
2232 if (wasKeyDown(KeyType::CAMERA_MODE) && ! g_settings->getBool("freecam")) {
2233 camera->toggleCameraMode();
2234 updatePlayerCAOVisibility();
2237 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2238 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2240 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2241 camera->update(player, dtime, tool_reload_ratio);
2242 camera->step(dtime);
2244 v3f camera_position = camera->getPosition();
2245 v3f camera_direction = camera->getDirection();
2246 f32 camera_fov = camera->getFovMax();
2247 v3s16 camera_offset = camera->getOffset();
2249 m_camera_offset_changed = (camera_offset != old_camera_offset);
2251 if (!m_flags.disable_camera_update) {
2252 client->getEnv().getClientMap().updateCamera(camera_position,
2253 camera_direction, camera_fov, camera_offset);
2255 if (m_camera_offset_changed) {
2256 client->updateCameraOffset(camera_offset);
2257 client->getEnv().updateCameraOffset(camera_offset);
2260 clouds->updateCameraOffset(camera_offset);
2265 void Game::updatePlayerCAOVisibility()
2267 // Make the player visible depending on camera mode.
2268 LocalPlayer *player = client->getEnv().getLocalPlayer();
2269 GenericCAO *playercao = player->getCAO();
2272 playercao->updateMeshCulling();
2273 bool is_visible = camera->getCameraMode() > CAMERA_MODE_FIRST || g_settings->getBool("freecam");
2274 playercao->setChildrenVisible(is_visible);
2277 void Game::updateSound(f32 dtime)
2279 // Update sound listener
2280 v3s16 camera_offset = camera->getOffset();
2281 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
2282 v3f(0, 0, 0), // velocity
2283 camera->getDirection(),
2284 camera->getCameraNode()->getUpVector());
2286 bool mute_sound = g_settings->getBool("mute_sound");
2288 sound->setListenerGain(0.0f);
2290 // Check if volume is in the proper range, else fix it.
2291 float old_volume = g_settings->getFloat("sound_volume");
2292 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
2293 sound->setListenerGain(new_volume);
2295 if (old_volume != new_volume) {
2296 g_settings->setFloat("sound_volume", new_volume);
2300 LocalPlayer *player = client->getEnv().getLocalPlayer();
2302 // Tell the sound maker whether to make footstep sounds
2303 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
2305 // Update sound maker
2306 if (player->makes_footstep_sound)
2307 soundmaker->step(dtime);
2309 ClientMap &map = client->getEnv().getClientMap();
2310 MapNode n = map.getNode(player->getFootstepNodePos());
2311 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
2315 void Game::processPlayerInteraction(f32 dtime, bool show_hud)
2317 LocalPlayer *player = client->getEnv().getLocalPlayer();
2319 const v3f camera_direction = camera->getDirection();
2320 const v3s16 camera_offset = camera->getOffset();
2323 Calculate what block is the crosshair pointing to
2326 ItemStack selected_item, hand_item;
2327 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
2329 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
2330 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
2333 if (g_settings->getBool("reach"))
2334 d += g_settings->getU16("tool_range");
2336 core::line3d<f32> shootline;
2338 switch (camera->getCameraMode()) {
2339 case CAMERA_MODE_FIRST:
2340 // Shoot from camera position, with bobbing
2341 shootline.start = camera->getPosition();
2343 case CAMERA_MODE_THIRD:
2344 // Shoot from player head, no bobbing
2345 shootline.start = camera->getHeadPosition();
2347 case CAMERA_MODE_THIRD_FRONT:
2348 shootline.start = camera->getHeadPosition();
2349 // prevent player pointing anything in front-view
2353 shootline.end = shootline.start + camera_direction * BS * d;
2355 #ifdef HAVE_TOUCHSCREENGUI
2357 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
2358 shootline = g_touchscreengui->getShootline();
2359 // Scale shootline to the acual distance the player can reach
2360 shootline.end = shootline.start
2361 + shootline.getVector().normalize() * BS * d;
2362 shootline.start += intToFloat(camera_offset, BS);
2363 shootline.end += intToFloat(camera_offset, BS);
2368 PointedThing pointed = updatePointedThing(shootline,
2369 selected_def.liquids_pointable,
2370 !runData.btn_down_for_dig,
2373 if (pointed != runData.pointed_old)
2374 infostream << "Pointing at " << pointed.dump() << std::endl;
2376 // Note that updating the selection mesh every frame is not particularly efficient,
2377 // but the halo rendering code is already inefficient so there's no point in optimizing it here
2378 hud->updateSelectionMesh(camera_offset);
2380 // Allow digging again if button is not pressed
2381 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
2382 runData.digging_blocked = false;
2386 - releasing dig button
2387 - pointing away from node
2389 if (runData.digging) {
2390 if (wasKeyReleased(KeyType::DIG)) {
2391 infostream << "Dig button released (stopped digging)" << std::endl;
2392 runData.digging = false;
2393 } else if (pointed != runData.pointed_old) {
2394 if (pointed.type == POINTEDTHING_NODE
2395 && runData.pointed_old.type == POINTEDTHING_NODE
2396 && pointed.node_undersurface
2397 == runData.pointed_old.node_undersurface) {
2398 // Still pointing to the same node, but a different face.
2401 infostream << "Pointing away from node (stopped digging)" << std::endl;
2402 runData.digging = false;
2403 hud->updateSelectionMesh(camera_offset);
2407 if (!runData.digging) {
2408 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
2409 client->setCrack(-1, v3s16(0, 0, 0));
2410 runData.dig_time = 0.0;
2412 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
2413 // Remove e.g. torches faster when clicking instead of holding dig button
2414 runData.nodig_delay_timer = 0;
2415 runData.dig_instantly = false;
2418 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
2419 runData.btn_down_for_dig = false;
2421 runData.punching = false;
2423 soundmaker->m_player_leftpunch_sound.name = "";
2425 // Prepare for repeating, unless we're not supposed to
2426 if ((isKeyDown(KeyType::PLACE) || g_settings->getBool("autoplace")) && !g_settings->getBool("safe_dig_and_place"))
2427 runData.repeat_place_timer += dtime;
2429 runData.repeat_place_timer = 0;
2431 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
2432 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
2433 !client->getScript()->on_item_use(selected_item, pointed)))
2434 client->interact(INTERACT_USE, pointed);
2435 } else if (pointed.type == POINTEDTHING_NODE) {
2436 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
2437 } else if (pointed.type == POINTEDTHING_OBJECT) {
2438 v3f player_position = player->getPosition();
2439 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
2440 handlePointingAtObject(pointed, tool_item, player_position,
2441 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
2442 } else if (isKeyDown(KeyType::DIG)) {
2443 // When button is held down in air, show continuous animation
2444 runData.punching = true;
2445 // Run callback even though item is not usable
2446 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
2447 client->getScript()->on_item_use(selected_item, pointed);
2448 } else if (wasKeyPressed(KeyType::PLACE)) {
2449 handlePointingAtNothing(selected_item);
2452 runData.pointed_old = pointed;
2454 if (runData.punching || wasKeyPressed(KeyType::DIG))
2455 camera->setDigging(0); // dig animation
2457 input->clearWasKeyPressed();
2458 input->clearWasKeyReleased();
2459 // Ensure DIG & PLACE are marked as handled
2460 wasKeyDown(KeyType::DIG);
2461 wasKeyDown(KeyType::PLACE);
2463 input->joystick.clearWasKeyPressed(KeyType::DIG);
2464 input->joystick.clearWasKeyPressed(KeyType::PLACE);
2466 input->joystick.clearWasKeyReleased(KeyType::DIG);
2467 input->joystick.clearWasKeyReleased(KeyType::PLACE);
2471 PointedThing Game::updatePointedThing(
2472 const core::line3d<f32> &shootline,
2473 bool liquids_pointable,
2474 bool look_for_object,
2475 const v3s16 &camera_offset)
2477 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
2478 selectionboxes->clear();
2479 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
2480 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
2481 "show_entity_selectionbox");
2483 ClientEnvironment &env = client->getEnv();
2484 ClientMap &map = env.getClientMap();
2485 const NodeDefManager *nodedef = map.getNodeDefManager();
2487 runData.selected_object = NULL;
2488 hud->pointing_at_object = false;
2489 RaycastState s(shootline, look_for_object, liquids_pointable, ! g_settings->getBool("dont_point_nodes"));
2490 PointedThing result;
2491 env.continueRaycast(&s, &result);
2492 if (result.type == POINTEDTHING_OBJECT) {
2493 hud->pointing_at_object = true;
2495 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
2496 aabb3f selection_box;
2497 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
2498 runData.selected_object->getSelectionBox(&selection_box)) {
2499 v3f pos = runData.selected_object->getPosition();
2500 selectionboxes->push_back(aabb3f(selection_box));
2501 hud->setSelectionPos(pos, camera_offset);
2503 } else if (result.type == POINTEDTHING_NODE) {
2504 // Update selection boxes
2505 MapNode n = map.getNode(result.node_undersurface);
2506 std::vector<aabb3f> boxes;
2507 n.getSelectionBoxes(nodedef, &boxes,
2508 n.getNeighbors(result.node_undersurface, &map));
2511 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
2512 i != boxes.end(); ++i) {
2514 box.MinEdge -= v3f(d, d, d);
2515 box.MaxEdge += v3f(d, d, d);
2516 selectionboxes->push_back(box);
2518 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
2520 hud->setSelectedFaceNormal(v3f(
2521 result.intersection_normal.X,
2522 result.intersection_normal.Y,
2523 result.intersection_normal.Z));
2526 // Update selection mesh light level and vertex colors
2527 if (!selectionboxes->empty()) {
2528 v3f pf = hud->getSelectionPos();
2529 v3s16 p = floatToInt(pf, BS);
2531 // Get selection mesh light level
2532 MapNode n = map.getNode(p);
2533 u16 node_light = getInteriorLight(n, -1, nodedef);
2534 u16 light_level = node_light;
2536 for (const v3s16 &dir : g_6dirs) {
2537 n = map.getNode(p + dir);
2538 node_light = getInteriorLight(n, -1, nodedef);
2539 if (node_light > light_level)
2540 light_level = node_light;
2543 u32 daynight_ratio = client->getEnv().getDayNightRatio();
2545 final_color_blend(&c, light_level, daynight_ratio);
2547 // Modify final color a bit with time
2548 u32 timer = porting::getTimeMs() % 5000;
2549 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
2550 float sin_r = 0.08f * std::sin(timerf);
2551 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
2552 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
2553 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
2554 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
2555 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
2557 // Set mesh final color
2558 hud->setSelectionMeshColor(c);
2563 void Game::handlePointingAtNothing(const ItemStack &playerItem)
2565 infostream << "Attempted to place item while pointing at nothing" << std::endl;
2566 PointedThing fauxPointed;
2567 fauxPointed.type = POINTEDTHING_NOTHING;
2568 client->interact(INTERACT_ACTIVATE, fauxPointed);
2572 void Game::handlePointingAtNode(const PointedThing &pointed,
2573 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
2575 v3s16 nodepos = pointed.node_undersurface;
2576 v3s16 neighbourpos = pointed.node_abovesurface;
2579 Check information text of node
2582 ClientMap &map = client->getEnv().getClientMap();
2584 if (((runData.nodig_delay_timer <= 0.0 || g_settings->getBool("fastdig")) && (isKeyDown(KeyType::DIG) || g_settings->getBool("autodig"))
2585 && !runData.digging_blocked
2586 && client->checkPrivilege("interact"))
2588 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
2591 // This should be done after digging handling
2592 NodeMetadata *meta = map.getNodeMetadata(nodepos);
2595 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
2596 meta->getString("infotext"))));
2598 MapNode n = map.getNode(nodepos);
2600 if (nodedef_manager->get(n).name == "unknown") {
2601 m_game_ui->setInfoText(L"Unknown node");
2605 if ((wasKeyPressed(KeyType::PLACE) ||
2606 (runData.repeat_place_timer >= (g_settings->getBool("fastplace") ? 0.001 : m_repeat_place_time))) &&
2607 client->checkPrivilege("interact")) {
2608 runData.repeat_place_timer = 0;
2609 infostream << "Place button pressed while looking at ground" << std::endl;
2611 // Placing animation (always shown for feedback)
2612 camera->setDigging(1);
2614 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
2616 // If the wielded item has node placement prediction,
2618 // And also set the sound and send the interact
2619 // But first check for meta formspec and rightclickable
2620 auto &def = selected_item.getDefinition(itemdef_manager);
2621 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
2624 if (placed && client->modsLoaded())
2625 client->getScript()->on_placenode(pointed, def);
2629 bool Game::nodePlacement(const ItemDefinition &selected_def,
2630 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
2631 const PointedThing &pointed, const NodeMetadata *meta, bool force)
2633 const auto &prediction = selected_def.node_placement_prediction;
2635 const NodeDefManager *nodedef = client->ndef();
2636 ClientMap &map = client->getEnv().getClientMap();
2638 bool is_valid_position;
2640 node = map.getNode(nodepos, &is_valid_position);
2641 if (!is_valid_position) {
2642 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2647 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
2648 && !isKeyDown(KeyType::SNEAK) && !force) {
2649 // on_rightclick callbacks are called anyway
2650 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
2651 client->interact(INTERACT_PLACE, pointed);
2653 infostream << "Launching custom inventory view" << std::endl;
2655 InventoryLocation inventoryloc;
2656 inventoryloc.setNodeMeta(nodepos);
2658 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
2659 &client->getEnv().getClientMap(), nodepos);
2660 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
2662 auto *&formspec = m_game_ui->updateFormspec("");
2663 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2664 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2666 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
2670 // on_rightclick callback
2671 if (prediction.empty() || (nodedef->get(node).rightclickable &&
2672 !isKeyDown(KeyType::SNEAK) && !force)) {
2674 client->interact(INTERACT_PLACE, pointed);
2678 verbosestream << "Node placement prediction for "
2679 << selected_def.name << " is " << prediction << std::endl;
2680 v3s16 p = neighbourpos;
2682 // Place inside node itself if buildable_to
2683 MapNode n_under = map.getNode(nodepos, &is_valid_position);
2684 if (is_valid_position) {
2685 if (nodedef->get(n_under).buildable_to) {
2688 node = map.getNode(p, &is_valid_position);
2689 if (is_valid_position && !nodedef->get(node).buildable_to) {
2690 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2692 client->interact(INTERACT_PLACE, pointed);
2698 // Find id of predicted node
2700 bool found = nodedef->getId(prediction, id);
2703 errorstream << "Node placement prediction failed for "
2704 << selected_def.name << " (places " << prediction
2705 << ") - Name not known" << std::endl;
2706 // Handle this as if prediction was empty
2708 client->interact(INTERACT_PLACE, pointed);
2712 const ContentFeatures &predicted_f = nodedef->get(id);
2714 // Predict param2 for facedir and wallmounted nodes
2715 // Compare core.item_place_node() for what the server does
2718 const u8 place_param2 = selected_def.place_param2;
2721 param2 = place_param2;
2722 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
2723 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
2724 v3s16 dir = nodepos - neighbourpos;
2726 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
2727 param2 = dir.Y < 0 ? 1 : 0;
2728 } else if (abs(dir.X) > abs(dir.Z)) {
2729 param2 = dir.X < 0 ? 3 : 2;
2731 param2 = dir.Z < 0 ? 5 : 4;
2733 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
2734 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
2735 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
2737 if (abs(dir.X) > abs(dir.Z)) {
2738 param2 = dir.X < 0 ? 3 : 1;
2740 param2 = dir.Z < 0 ? 2 : 0;
2744 // Check attachment if node is in group attached_node
2745 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
2746 const static v3s16 wallmounted_dirs[8] = {
2756 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
2757 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
2758 pp = p + wallmounted_dirs[param2];
2760 pp = p + v3s16(0, -1, 0);
2762 if (!nodedef->get(map.getNode(pp)).walkable) {
2763 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2765 client->interact(INTERACT_PLACE, pointed);
2771 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
2772 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
2773 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
2774 const auto &indexstr = selected_item.metadata.
2775 getString("palette_index", 0);
2776 if (!indexstr.empty()) {
2777 s32 index = mystoi(indexstr);
2778 if (predicted_f.param_type_2 == CPT2_COLOR) {
2780 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
2781 // param2 = pure palette index + other
2782 param2 = (index & 0xf8) | (param2 & 0x07);
2783 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
2784 // param2 = pure palette index + other
2785 param2 = (index & 0xe0) | (param2 & 0x1f);
2790 // Add node to client map
2791 MapNode n(id, 0, param2);
2794 LocalPlayer *player = client->getEnv().getLocalPlayer();
2796 // Dont place node when player would be inside new node
2797 // NOTE: This is to be eventually implemented by a mod as client-side Lua
2798 if (!nodedef->get(n).walkable ||
2799 g_settings->getBool("enable_build_where_you_stand") ||
2800 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
2801 (nodedef->get(n).walkable &&
2802 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
2803 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
2804 // This triggers the required mesh update too
2805 client->addNode(p, n);
2807 client->interact(INTERACT_PLACE, pointed);
2808 // A node is predicted, also play a sound
2809 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
2812 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2815 } catch (const InvalidPositionException &e) {
2816 errorstream << "Node placement prediction failed for "
2817 << selected_def.name << " (places "
2818 << prediction << ") - Position not loaded" << std::endl;
2819 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2824 void Game::handlePointingAtObject(const PointedThing &pointed,
2825 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
2827 std::wstring infotext = unescape_translate(
2828 utf8_to_wide(runData.selected_object->infoText()));
2831 if (!infotext.empty()) {
2834 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
2837 m_game_ui->setInfoText(infotext);
2839 if (isKeyDown(KeyType::DIG) || g_settings->getBool("autohit")) {
2840 bool do_punch = false;
2841 bool do_punch_damage = false;
2843 if (runData.object_hit_delay_timer <= 0.0 || g_settings->getBool("spamclick")) {
2845 do_punch_damage = true;
2846 runData.object_hit_delay_timer = object_hit_delay;
2849 if (wasKeyPressed(KeyType::DIG))
2853 infostream << "Punched object" << std::endl;
2854 runData.punching = true;
2857 if (do_punch_damage) {
2858 // Report direct punch
2859 v3f objpos = runData.selected_object->getPosition();
2860 v3f dir = (objpos - player_position).normalize();
2862 bool disable_send = runData.selected_object->directReportPunch(
2863 dir, &tool_item, runData.time_from_last_punch);
2864 runData.time_from_last_punch = 0;
2866 if (!disable_send) {
2867 client->interact(INTERACT_START_DIGGING, pointed);
2870 } else if (wasKeyDown(KeyType::PLACE)) {
2871 infostream << "Pressed place button while pointing at object" << std::endl;
2872 client->interact(INTERACT_PLACE, pointed); // place
2877 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
2878 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
2880 // See also: serverpackethandle.cpp, action == 2
2881 LocalPlayer *player = client->getEnv().getLocalPlayer();
2882 ClientMap &map = client->getEnv().getClientMap();
2883 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
2885 // NOTE: Similar piece of code exists on the server side for
2887 // Get digging parameters
2888 DigParams params = getDigParams(nodedef_manager->get(n).groups,
2889 &selected_item.getToolCapabilities(itemdef_manager),
2890 selected_item.wear);
2892 // If can't dig, try hand
2893 if (!params.diggable) {
2894 params = getDigParams(nodedef_manager->get(n).groups,
2895 &hand_item.getToolCapabilities(itemdef_manager));
2898 if (!params.diggable) {
2899 // I guess nobody will wait for this long
2900 runData.dig_time_complete = 10000000.0;
2902 runData.dig_time_complete = params.time;
2904 if (m_cache_enable_particles) {
2905 const ContentFeatures &features = client->getNodeDefManager()->get(n);
2906 client->getParticleManager()->addNodeParticle(client,
2907 player, nodepos, n, features);
2911 if(g_settings->getBool("instant_break")) {
2912 runData.dig_time_complete = 0;
2913 runData.dig_instantly = true;
2915 if (!runData.digging) {
2916 infostream << "Started digging" << std::endl;
2917 runData.dig_instantly = runData.dig_time_complete == 0;
2918 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
2920 client->interact(INTERACT_START_DIGGING, pointed);
2921 runData.digging = true;
2922 runData.btn_down_for_dig = true;
2925 if (!runData.dig_instantly) {
2926 runData.dig_index = (float)crack_animation_length
2928 / runData.dig_time_complete;
2930 // This is for e.g. torches
2931 runData.dig_index = crack_animation_length;
2934 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
2936 if (sound_dig.exists() && params.diggable) {
2937 if (sound_dig.name == "__group") {
2938 if (!params.main_group.empty()) {
2939 soundmaker->m_player_leftpunch_sound.gain = 0.5;
2940 soundmaker->m_player_leftpunch_sound.name =
2941 std::string("default_dig_") +
2945 soundmaker->m_player_leftpunch_sound = sound_dig;
2949 // Don't show cracks if not diggable
2950 if (runData.dig_time_complete >= 100000.0) {
2951 } else if (runData.dig_index < crack_animation_length) {
2952 //TimeTaker timer("client.setTempMod");
2953 //infostream<<"dig_index="<<dig_index<<std::endl;
2954 client->setCrack(runData.dig_index, nodepos);
2956 infostream << "Digging completed" << std::endl;
2957 client->setCrack(-1, v3s16(0, 0, 0));
2959 runData.dig_time = 0;
2960 runData.digging = false;
2961 // we successfully dug, now block it from repeating if we want to be safe
2962 if (g_settings->getBool("safe_dig_and_place"))
2963 runData.digging_blocked = true;
2965 runData.nodig_delay_timer =
2966 runData.dig_time_complete / (float)crack_animation_length;
2968 // We don't want a corresponding delay to very time consuming nodes
2969 // and nodes without digging time (e.g. torches) get a fixed delay.
2970 if (runData.nodig_delay_timer > 0.3)
2971 runData.nodig_delay_timer = 0.3;
2972 else if (runData.dig_instantly)
2973 runData.nodig_delay_timer = 0.15;
2975 bool is_valid_position;
2976 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
2977 if (is_valid_position) {
2978 if (client->modsLoaded() &&
2979 client->getScript()->on_dignode(nodepos, wasnode)) {
2983 const ContentFeatures &f = client->ndef()->get(wasnode);
2984 if (f.node_dig_prediction == "air") {
2985 client->removeNode(nodepos);
2986 } else if (!f.node_dig_prediction.empty()) {
2988 bool found = client->ndef()->getId(f.node_dig_prediction, id);
2990 client->addNode(nodepos, id, true);
2992 // implicit else: no prediction
2995 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
2997 if (m_cache_enable_particles) {
2998 const ContentFeatures &features =
2999 client->getNodeDefManager()->get(wasnode);
3000 client->getParticleManager()->addDiggingParticles(client,
3001 player, nodepos, wasnode, features);
3005 // Send event to trigger sound
3006 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3009 if (runData.dig_time_complete < 100000.0) {
3010 runData.dig_time += dtime;
3012 runData.dig_time = 0;
3013 client->setCrack(-1, nodepos);
3016 camera->setDigging(0); // Dig animation
3019 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3020 const CameraOrientation &cam)
3022 TimeTaker tt_update("Game::updateFrame()");
3023 LocalPlayer *player = client->getEnv().getLocalPlayer();
3029 if (draw_control->range_all) {
3030 runData.fog_range = 100000 * BS;
3032 runData.fog_range = draw_control->wanted_range * BS;
3036 Calculate general brightness
3038 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3039 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3040 float direct_brightness;
3043 // When in noclip mode force same sky brightness as above ground so you
3045 if ((draw_control->allow_noclip && m_cache_enable_free_move &&
3046 client->checkPrivilege("fly")) || g_settings->getBool("freecam")) {
3047 direct_brightness = time_brightness;
3048 sunlight_seen = true;
3050 float old_brightness = sky->getBrightness();
3051 direct_brightness = client->getEnv().getClientMap()
3052 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3053 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3057 float time_of_day_smooth = runData.time_of_day_smooth;
3058 float time_of_day = client->getEnv().getTimeOfDayF();
3060 static const float maxsm = 0.05f;
3061 static const float todsm = 0.05f;
3063 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3064 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3065 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3066 time_of_day_smooth = time_of_day;
3068 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3069 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3070 + (time_of_day + 1.0) * todsm;
3072 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3073 + time_of_day * todsm;
3075 runData.time_of_day_smooth = time_of_day_smooth;
3077 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3078 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3079 player->getPitch());
3085 if (sky->getCloudsVisible()) {
3086 clouds->setVisible(true);
3087 clouds->step(dtime);
3088 // camera->getPosition is not enough for 3rd person views
3089 v3f camera_node_position = camera->getCameraNode()->getPosition();
3090 v3s16 camera_offset = camera->getOffset();
3091 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3092 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3093 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3094 clouds->update(camera_node_position,
3095 sky->getCloudColor());
3096 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3097 // if inside clouds, and fog enabled, use that as sky
3099 video::SColor clouds_dark = clouds->getColor()
3100 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3101 sky->overrideColors(clouds_dark, clouds->getColor());
3102 sky->setInClouds(true);
3103 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3104 // do not draw clouds after all
3105 clouds->setVisible(false);
3108 clouds->setVisible(false);
3115 client->getParticleManager()->step(dtime);
3121 if (m_cache_enable_fog) {
3124 video::EFT_FOG_LINEAR,
3125 runData.fog_range * m_cache_fog_start,
3126 runData.fog_range * 1.0,
3134 video::EFT_FOG_LINEAR,
3146 if (player->hurt_tilt_timer > 0.0f) {
3147 player->hurt_tilt_timer -= dtime * 6.0f;
3149 if (player->hurt_tilt_timer < 0.0f || g_settings->getBool("no_hurt_cam"))
3150 player->hurt_tilt_strength = 0.0f;
3154 Update minimap pos and rotation
3156 if (mapper && m_game_ui->m_flags.show_hud) {
3157 mapper->setPos(floatToInt(player->getPosition(), BS));
3158 mapper->setAngle(player->getYaw());
3162 Get chat messages from client
3171 if (player->getWieldIndex() != runData.new_playeritem)
3172 client->setPlayerItem(runData.new_playeritem);
3174 if (client->updateWieldedItem()) {
3175 // Update wielded tool
3176 ItemStack selected_item, hand_item;
3177 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3178 camera->wield(tool_item);
3182 Update block draw list every 200ms or when camera direction has
3185 runData.update_draw_list_timer += dtime;
3187 float update_draw_list_delta = 0.2f;
3189 v3f camera_direction = camera->getDirection();
3190 if (runData.update_draw_list_timer >= update_draw_list_delta
3191 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3192 || m_camera_offset_changed
3193 || client->getEnv().getClientMap().needsUpdateDrawList()) {
3194 runData.update_draw_list_timer = 0;
3195 client->getEnv().getClientMap().updateDrawList();
3196 runData.update_draw_list_last_cam_dir = camera_direction;
3199 if (RenderingEngine::get_shadow_renderer()) {
3203 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3206 make sure menu is on top
3207 1. Delete formspec menu reference if menu was removed
3208 2. Else, make sure formspec menu is on top
3210 auto formspec = m_game_ui->getFormspecGUI();
3211 do { // breakable. only runs for one iteration
3215 if (formspec->getReferenceCount() == 1) {
3216 m_game_ui->deleteFormspec();
3220 auto &loc = formspec->getFormspecLocation();
3221 if (loc.type == InventoryLocation::NODEMETA) {
3222 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3223 if (!meta || meta->getString("formspec").empty()) {
3224 formspec->quitMenu();
3230 guiroot->bringToFront(formspec);
3234 ==================== Drawing begins ====================
3236 const video::SColor skycolor = sky->getSkyColor();
3238 TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
3239 driver->beginScene(true, true, skycolor);
3241 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3242 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3243 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3244 bool draw_crosshair = (
3245 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3246 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3247 #ifdef HAVE_TOUCHSCREENGUI
3249 draw_crosshair = !g_settings->getBool("touchtarget");
3250 } catch (SettingNotFoundException) {
3253 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3254 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3259 v2u32 screensize = driver->getScreenSize();
3261 if (m_game_ui->m_flags.show_profiler_graph)
3262 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3268 if (! gui_chat_console->isOpen()) {
3269 if (m_game_ui->m_flags.show_cheat_menu)
3270 m_cheat_menu->draw(driver, m_game_ui->m_flags.show_minimal_debug);
3271 if (g_settings->getBool("cheat_hud"))
3272 m_cheat_menu->drawHUD(driver, dtime);
3277 if (runData.damage_flash > 0.0f) {
3278 video::SColor color(runData.damage_flash, 180, 0, 0);
3279 if (! g_settings->getBool("no_hurt_cam"))
3280 driver->draw2DRectangle(color, core::rect<s32>(0, 0, screensize.X, screensize.Y), NULL);
3282 runData.damage_flash -= 384.0f * dtime;
3288 #if IRRLICHT_VERSION_MT_REVISION < 5
3289 if (++m_reset_HW_buffer_counter > 500) {
3291 Periodically remove all mesh HW buffers.
3293 Work around for a quirk in Irrlicht where a HW buffer is only
3294 released after 20000 iterations (triggered from endScene()).
3296 Without this, all loaded but unused meshes will retain their HW
3297 buffers for at least 5 minutes, at which point looking up the HW buffers
3298 becomes a bottleneck and the framerate drops (as much as 30%).
3300 Tests showed that numbers between 50 and 1000 are good, so picked 500.
3301 There are no other public Irrlicht APIs that allow interacting with the
3302 HW buffers without tracking the status of every individual mesh.
3304 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
3306 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
3307 driver->removeAllHardwareBuffers();
3308 m_reset_HW_buffer_counter = 0;
3314 stats->drawtime = tt_draw.stop(true);
3315 g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
3316 g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
3319 /* Log times and stuff for visualization */
3320 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
3322 Profiler::GraphValues values;
3323 g_profiler->graphGet(values);
3327 /****************************************************************************
3329 *****************************************************************************/
3330 void Game::updateShadows()
3332 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
3336 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
3338 float timeoftheday = getWickedTimeOfDay(in_timeofday);
3339 bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
3340 bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
3341 shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
3343 timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
3344 const float offset_constant = 10000.0f;
3346 v3f light(0.0f, 0.0f, -1.0f);
3347 light.rotateXZBy(90);
3348 light.rotateXYBy(timeoftheday * 360 - 90);
3349 light.rotateYZBy(sky->getSkyBodyOrbitTilt());
3351 v3f sun_pos = light * offset_constant;
3353 if (shadow->getDirectionalLightCount() == 0)
3354 shadow->addDirectionalLight();
3355 shadow->getDirectionalLight().setDirection(sun_pos);
3356 shadow->setTimeOfDay(in_timeofday);
3358 shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
3361 /****************************************************************************
3363 ****************************************************************************/
3365 void FpsControl::reset()
3367 last_time = porting::getTimeUs();
3371 * On some computers framerate doesn't seem to be automatically limited
3373 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
3375 const u64 frametime_min = 1000000.0f / (
3376 device->isWindowFocused() && !g_menumgr.pausesGame()
3377 ? g_settings->getFloat("fps_max")
3378 : g_settings->getFloat("fps_max_unfocused"));
3380 u64 time = porting::getTimeUs();
3382 if (time > last_time) // Make sure time hasn't overflowed
3383 busy_time = time - last_time;
3387 if (busy_time < frametime_min) {
3388 sleep_time = frametime_min - busy_time;
3389 if (sleep_time > 1000)
3390 sleep_ms(sleep_time / 1000);
3395 // Read the timer again to accurately determine how long we actually slept,
3396 // rather than calculating it by adding sleep_time to time.
3397 time = porting::getTimeUs();
3399 if (time > last_time) // Make sure last_time hasn't overflowed
3400 *dtime = (time - last_time) / 1000000.0f;
3407 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
3409 const wchar_t *wmsg = wgettext(msg);
3410 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
3415 void Game::settingChangedCallback(const std::string &setting_name, void *data)
3417 ((Game *)data)->readSettings();
3420 void Game::updateAllMapBlocksCallback(const std::string &setting_name, void *data)
3422 ((Game *) data)->client->updateAllMapBlocks();
3425 void Game::freecamChangedCallback(const std::string &setting_name, void *data)
3427 Game *game = (Game *) data;
3428 LocalPlayer *player = game->client->getEnv().getLocalPlayer();
3429 if (g_settings->getBool("freecam")) {
3430 game->camera->setCameraMode(CAMERA_MODE_FIRST);
3431 player->freecamEnable();
3433 player->freecamDisable();
3435 game->updatePlayerCAOVisibility();
3438 void Game::readSettings()
3440 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
3441 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
3442 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
3443 m_cache_enable_particles = g_settings->getBool("enable_particles");
3444 m_cache_enable_fog = g_settings->getBool("enable_fog");
3445 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
3446 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
3447 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
3449 m_cache_enable_noclip = g_settings->getBool("noclip");
3450 m_cache_enable_free_move = g_settings->getBool("free_move");
3452 m_cache_fog_start = g_settings->getFloat("fog_start");
3454 m_cache_cam_smoothing = 0;
3455 if (g_settings->getBool("cinematic"))
3456 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
3458 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
3460 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
3461 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
3462 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
3464 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
3467 bool Game::isKeyDown(GameKeyType k)
3469 return input->isKeyDown(k);
3472 bool Game::wasKeyDown(GameKeyType k)
3474 return input->wasKeyDown(k);
3477 bool Game::wasKeyPressed(GameKeyType k)
3479 return input->wasKeyPressed(k);
3482 bool Game::wasKeyReleased(GameKeyType k)
3484 return input->wasKeyReleased(k);
3487 /****************************************************************************/
3488 /****************************************************************************
3490 ****************************************************************************/
3491 /****************************************************************************/
3493 void Game::showDeathFormspec()
3495 static std::string formspec_str =
3496 std::string("formspec_version[1]") +
3498 "bgcolor[#320000b4;true]"
3499 "label[4.85,1.35;" + gettext("You died") + "]"
3500 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
3504 /* Note: FormspecFormSource and LocalFormspecHandler *
3505 * are deleted by guiFormSpecMenu */
3506 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
3507 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
3509 auto *&formspec = m_game_ui->getFormspecGUI();
3510 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3511 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3512 formspec->setFocus("btn_respawn");
3515 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
3516 void Game::showPauseMenu()
3518 #ifdef HAVE_TOUCHSCREENGUI
3519 static const std::string control_text = strgettext("Default Controls:\n"
3520 "No menu visible:\n"
3521 "- single tap: button activate\n"
3522 "- double tap: place/use\n"
3523 "- slide finger: look around\n"
3524 "Menu/Inventory visible:\n"
3525 "- double tap (outside):\n"
3527 "- touch stack, touch slot:\n"
3529 "- touch&drag, tap 2nd finger\n"
3530 " --> place single item to slot\n"
3533 static const std::string control_text_template = strgettext("Controls:\n"
3534 "- %s: move forwards\n"
3535 "- %s: move backwards\n"
3537 "- %s: move right\n"
3538 "- %s: jump/climb up\n"
3541 "- %s: sneak/climb down\n"
3544 "- %s: enderchest\n"
3545 "- Mouse: turn/look\n"
3546 "- Mouse wheel: select item\n"
3553 char control_text_buf[600];
3555 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
3556 GET_KEY_NAME(keymap_forward),
3557 GET_KEY_NAME(keymap_backward),
3558 GET_KEY_NAME(keymap_left),
3559 GET_KEY_NAME(keymap_right),
3560 GET_KEY_NAME(keymap_jump),
3561 GET_KEY_NAME(keymap_dig),
3562 GET_KEY_NAME(keymap_place),
3563 GET_KEY_NAME(keymap_sneak),
3564 GET_KEY_NAME(keymap_drop),
3565 GET_KEY_NAME(keymap_inventory),
3566 GET_KEY_NAME(keymap_enderchest),
3567 GET_KEY_NAME(keymap_chat),
3568 GET_KEY_NAME(keymap_toggle_killaura),
3569 GET_KEY_NAME(keymap_toggle_freecam),
3570 GET_KEY_NAME(keymap_toggle_scaffold)
3573 std::string control_text = std::string(control_text_buf);
3574 str_formspec_escape(control_text);
3577 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
3578 std::ostringstream os;
3580 os << "formspec_version[1]" << SIZE_TAG
3581 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
3582 << strgettext("Continue") << "]";
3584 if (!simple_singleplayer_mode) {
3585 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
3586 << strgettext("Change Password") << "]";
3588 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
3593 if (g_settings->getBool("enable_sound")) {
3594 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
3595 << strgettext("Sound Volume") << "]";
3598 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
3599 << strgettext("Change Keys") << "]";
3601 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
3602 << strgettext("Exit to Menu") << "]";
3603 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
3604 << strgettext("Exit to OS") << "]"
3605 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
3606 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
3608 << strgettext("Game info:") << "\n";
3609 const std::string &address = client->getAddressName();
3610 static const std::string mode = strgettext("- Mode: ");
3611 if (!simple_singleplayer_mode) {
3612 Address serverAddress = client->getServerAddress();
3613 if (!address.empty()) {
3614 os << mode << strgettext("Remote server") << "\n"
3615 << strgettext("- Address: ") << address;
3617 os << mode << strgettext("Hosting server");
3619 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
3621 os << mode << strgettext("Singleplayer") << "\n";
3623 if (simple_singleplayer_mode || address.empty()) {
3624 static const std::string on = strgettext("On");
3625 static const std::string off = strgettext("Off");
3626 // Note: Status of enable_damage and creative_mode settings is intentionally
3627 // NOT shown here because the game might roll its own damage system and/or do
3628 // a per-player Creative Mode, in which case writing it here would mislead.
3629 bool damage = g_settings->getBool("enable_damage");
3630 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
3631 if (!simple_singleplayer_mode) {
3633 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
3634 //~ PvP = Player versus Player
3635 os << strgettext("- PvP: ") << pvp << "\n";
3637 os << strgettext("- Public: ") << announced << "\n";
3638 std::string server_name = g_settings->get("server_name");
3639 str_formspec_escape(server_name);
3640 if (announced == on && !server_name.empty())
3641 os << strgettext("- Server Name: ") << server_name;
3648 /* Note: FormspecFormSource and LocalFormspecHandler *
3649 * are deleted by guiFormSpecMenu */
3650 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
3651 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
3653 auto *&formspec = m_game_ui->getFormspecGUI();
3654 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3655 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3656 formspec->setFocus("btn_continue");
3657 formspec->doPause = true;
3659 if (simple_singleplayer_mode)
3663 /****************************************************************************/
3664 /****************************************************************************
3665 extern function for launching the game
3666 ****************************************************************************/
3667 /****************************************************************************/
3671 void the_game(bool *kill,
3672 InputHandler *input,
3673 RenderingEngine *rendering_engine,
3674 const GameStartData &start_data,
3675 std::string &error_message,
3676 ChatBackend &chat_backend,
3677 bool *reconnect_requested) // Used for local game
3683 /* Make a copy of the server address because if a local singleplayer server
3684 * is created then this is updated and we don't want to change the value
3685 * passed to us by the calling function
3690 if (game.startup(kill, input, rendering_engine, start_data,
3691 error_message, reconnect_requested, &chat_backend)) {
3695 } catch (SerializationError &e) {
3696 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
3697 error_message = strgettext("A serialization error occurred:") +"\n"
3698 + e.what() + "\n\n" + ver_err;
3699 errorstream << error_message << std::endl;
3700 } catch (ServerError &e) {
3701 error_message = e.what();
3702 errorstream << "ServerError: " << error_message << std::endl;
3703 } catch (ModError &e) {
3704 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
3705 error_message = std::string("ModError: ") + e.what() +
3706 strgettext("\nCheck debug.txt for details.");
3707 errorstream << error_message << std::endl;