3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "client/renderingengine.h"
27 #include "client/clientevent.h"
28 #include "client/gameui.h"
29 #include "client/inputhandler.h"
30 #include "client/tile.h" // For TextureSource
31 #include "client/keys.h"
32 #include "client/joystick_controller.h"
33 #include "clientmap.h"
36 #include "content_cao.h"
37 #include "content/subgames.h"
38 #include "client/event_manager.h"
39 #include "fontengine.h"
43 #include "gameparams.h"
45 #include "gui/guiChatConsole.h"
46 #include "gui/guiConfirmRegistration.h"
47 #include "gui/guiFormSpecMenu.h"
48 #include "gui/guiKeyChangeMenu.h"
49 #include "gui/guiPasswordChange.h"
50 #include "gui/guiVolumeChange.h"
51 #include "gui/mainmenumanager.h"
52 #include "gui/profilergraph.h"
55 #include "nodedef.h" // Needed for determining pointing to nodes
56 #include "nodemetadata.h"
57 #include "particles.h"
65 #include "translation.h"
66 #include "util/basic_macros.h"
67 #include "util/directiontables.h"
68 #include "util/pointedthing.h"
69 #include "util/quicktune_shortcutter.h"
70 #include "irrlicht_changes/static_text.h"
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);
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 extendedResourceCleanup();
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 const GameStartData &start_data,
194 std::string &error_message,
196 ChatBackend *chat_backend)
200 this->device = RenderingEngine::get_raw_device();
202 this->error_message = &error_message;
203 this->reconnect_requested = reconnect;
205 this->chat_backend = chat_backend;
206 this->simple_singleplayer_mode = start_data.isSinglePlayer();
208 input->keycache.populate();
210 driver = device->getVideoDriver();
211 smgr = RenderingEngine::get_scene_manager();
213 RenderingEngine::get_scene_manager()->getParameters()->
214 setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
217 runData = GameRunData();
218 runData.time_from_last_punch = 10.0;
220 m_game_ui->initFlags();
222 m_invert_mouse = g_settings->getBool("invert_mouse");
223 m_first_loop_after_window_activation = true;
225 g_client_translations->clear();
227 // address can change if simple_singleplayer_mode
228 if (!init(start_data.world_spec.path, start_data.address,
229 start_data.socket_port, start_data.game_spec))
232 if (!createClient(start_data))
235 RenderingEngine::initialize(client, hud);
244 RunStats stats = { 0 };
245 FpsControl draw_times = { 0 };
246 f32 dtime; // in seconds
248 /* Clear the profiler */
249 Profiler::GraphValues dummyvalues;
250 g_profiler->graphGet(dummyvalues);
252 draw_times.last_time = RenderingEngine::get_timer_time();
254 set_light_table(g_settings->getFloat("display_gamma"));
257 m_cache_hold_aux1 = g_settings->getBool("fast_move")
258 && client->checkPrivilege("fast");
261 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
262 g_settings->getU16("screen_h"));
264 while (RenderingEngine::run()
265 && !(*kill || g_gamecallback->shutdown_requested
266 || (server && server->isShutdownRequested()))) {
268 const irr::core::dimension2d<u32> ¤t_screen_size =
269 RenderingEngine::get_video_driver()->getScreenSize();
270 // Verify if window size has changed and save it if it's the case
271 // Ensure evaluating settings->getBool after verifying screensize
272 // First condition is cheaper
273 if (previous_screen_size != current_screen_size &&
274 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
275 g_settings->getBool("autosave_screensize")) {
276 g_settings->setU16("screen_w", current_screen_size.Width);
277 g_settings->setU16("screen_h", current_screen_size.Height);
278 previous_screen_size = current_screen_size;
282 // RenderingEngine::run() from this iteration
283 // + Sleep time until the wanted FPS are reached
284 limitFps(&draw_times, &dtime);
286 // Prepare render data for next iteration
288 updateStats(&stats, draw_times, dtime);
289 updateInteractTimers(dtime);
291 if (!checkConnection())
293 if (!handleCallbacks())
298 m_game_ui->clearInfoText();
301 updateProfilers(stats, draw_times, dtime);
302 processUserInput(dtime);
303 // Update camera before player movement to avoid camera lag of one frame
304 updateCameraDirection(&cam_view_target, dtime);
305 cam_view.camera_yaw += (cam_view_target.camera_yaw -
306 cam_view.camera_yaw) * m_cache_cam_smoothing;
307 cam_view.camera_pitch += (cam_view_target.camera_pitch -
308 cam_view.camera_pitch) * m_cache_cam_smoothing;
309 updatePlayerControl(cam_view);
311 processClientEvents(&cam_view_target);
312 updateCamera(draw_times.busy_time, dtime);
314 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
315 m_game_ui->m_flags.show_debug);
316 updateFrame(&graph, &stats, dtime, cam_view);
317 updateProfilerGraphs(&graph);
319 // Update if minimap has been disabled by the server
320 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
322 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
329 void Game::shutdown()
331 RenderingEngine::finalize();
332 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
333 if (g_settings->get("3d_mode") == "pageflip") {
334 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
337 auto formspec = m_game_ui->getFormspecGUI();
339 formspec->quitMenu();
341 #ifdef HAVE_TOUCHSCREENGUI
342 g_touchscreengui->hide();
345 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
350 if (gui_chat_console)
351 gui_chat_console->drop();
360 while (g_menumgr.menuCount() > 0) {
361 g_menumgr.m_stack.front()->setVisible(false);
362 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
365 m_game_ui->deleteFormspec();
367 chat_backend->addMessage(L"", L"# Disconnected.");
368 chat_backend->addMessage(L"", L"");
369 m_chat_log_buf.clear();
373 while (!client->isShutdown()) {
374 assert(texture_src != NULL);
375 assert(shader_src != NULL);
376 texture_src->processQueue();
377 shader_src->processQueue();
384 /****************************************************************************/
385 /****************************************************************************
387 ****************************************************************************/
388 /****************************************************************************/
391 const std::string &map_dir,
392 const std::string &address,
394 const SubgameSpec &gamespec)
396 texture_src = createTextureSource();
398 showOverlayMessage(N_("Loading..."), 0, 0);
400 shader_src = createShaderSource();
402 itemdef_manager = createItemDefManager();
403 nodedef_manager = createNodeDefManager();
405 eventmgr = new EventManager();
406 quicktune = new QuicktuneShortcutter();
408 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
409 && eventmgr && quicktune))
415 // Create a server if not connecting to an existing one
416 if (address.empty()) {
417 if (!createSingleplayerServer(map_dir, gamespec, port))
424 bool Game::initSound()
427 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
428 infostream << "Attempting to use OpenAL audio" << std::endl;
429 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
431 infostream << "Failed to initialize OpenAL audio" << std::endl;
433 infostream << "Sound disabled." << std::endl;
437 infostream << "Using dummy audio." << std::endl;
438 sound = &dummySoundManager;
439 sound_is_dummy = true;
442 soundmaker = new SoundMaker(sound, nodedef_manager);
446 soundmaker->registerReceiver(eventmgr);
451 bool Game::createSingleplayerServer(const std::string &map_dir,
452 const SubgameSpec &gamespec, u16 port)
454 showOverlayMessage(N_("Creating server..."), 0, 5);
456 std::string bind_str = g_settings->get("bind_address");
457 Address bind_addr(0, 0, 0, 0, port);
459 if (g_settings->getBool("ipv6_server")) {
460 bind_addr.setAddress((IPv6AddressBytes *) NULL);
464 bind_addr.Resolve(bind_str.c_str());
465 } catch (ResolveError &e) {
466 infostream << "Resolving bind address \"" << bind_str
467 << "\" failed: " << e.what()
468 << " -- Listening on all addresses." << std::endl;
471 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
472 *error_message = "Unable to listen on " +
473 bind_addr.serializeString() +
474 " because IPv6 is disabled";
475 errorstream << *error_message << std::endl;
479 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
480 false, nullptr, error_message);
486 bool Game::createClient(const GameStartData &start_data)
488 showOverlayMessage(N_("Creating client..."), 0, 10);
490 draw_control = new MapDrawControl;
494 bool could_connect, connect_aborted;
495 #ifdef HAVE_TOUCHSCREENGUI
496 if (g_touchscreengui) {
497 g_touchscreengui->init(texture_src);
498 g_touchscreengui->hide();
501 if (!connectToServer(start_data, &could_connect, &connect_aborted))
504 if (!could_connect) {
505 if (error_message->empty() && !connect_aborted) {
506 // Should not happen if error messages are set properly
507 *error_message = "Connection failed for unknown reason";
508 errorstream << *error_message << std::endl;
513 if (!getServerContent(&connect_aborted)) {
514 if (error_message->empty() && !connect_aborted) {
515 // Should not happen if error messages are set properly
516 *error_message = "Connection failed for unknown reason";
517 errorstream << *error_message << std::endl;
522 GameGlobalShaderConstantSetterFactory *scsf = new GameGlobalShaderConstantSetterFactory(
523 &m_flags.force_fog_off, &runData.fog_range, client);
524 shader_src->addShaderConstantSetterFactory(scsf);
526 // Update cached textures, meshes and materials
527 client->afterContentReceived();
531 camera = new Camera(*draw_control, client);
532 if (!camera || !camera->successfullyCreated(*error_message))
534 client->setCamera(camera);
538 if (m_cache_enable_clouds) {
539 clouds = new Clouds(smgr, -1, time(0));
541 *error_message = "Memory allocation error (clouds)";
542 errorstream << *error_message << std::endl;
549 sky = new Sky(-1, texture_src, shader_src);
551 skybox = NULL; // This is used/set later on in the main run loop
554 *error_message = "Memory allocation error sky";
555 errorstream << *error_message << std::endl;
559 /* Pre-calculated values
561 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
563 v2u32 size = t->getOriginalSize();
564 crack_animation_length = size.Y / size.X;
566 crack_animation_length = 5;
572 /* Set window caption
574 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
576 str += utf8_to_wide(g_version_hash);
578 str += L"Minetest Hackclient";
580 device->setWindowCaption(str.c_str());
582 LocalPlayer *player = client->getEnv().getLocalPlayer();
583 player->hurt_tilt_timer = 0;
584 player->hurt_tilt_strength = 0;
586 hud = new Hud(guienv, client, player, &player->inventory);
589 *error_message = "Memory error: could not create HUD";
590 errorstream << *error_message << std::endl;
594 mapper = client->getMinimap();
596 if (mapper && client->modsLoaded())
597 client->getScript()->on_minimap_ready(mapper);
606 // Remove stale "recent" chat messages from previous connections
607 chat_backend->clearRecentChat();
609 // Make sure the size of the recent messages buffer is right
610 chat_backend->applySettings();
612 // Chat backend and console
613 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
614 -1, chat_backend, client, &g_menumgr);
616 if (!gui_chat_console) {
617 *error_message = "Could not allocate memory for chat console";
618 errorstream << *error_message << std::endl;
622 m_cheat_menu = new CheatMenu(client);
625 *error_message = "Could not allocate memory for cheat menu";
626 errorstream << *error_message << std::endl;
630 #ifdef HAVE_TOUCHSCREENGUI
632 if (g_touchscreengui)
633 g_touchscreengui->show();
640 bool Game::connectToServer(const GameStartData &start_data,
641 bool *connect_ok, bool *connection_aborted)
643 *connect_ok = false; // Let's not be overly optimistic
644 *connection_aborted = false;
645 bool local_server_mode = false;
647 showOverlayMessage(N_("Resolving address..."), 0, 15);
649 Address connect_address(0, 0, 0, 0, start_data.socket_port);
652 connect_address.Resolve(start_data.address.c_str());
654 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
655 //connect_address.Resolve("localhost");
656 if (connect_address.isIPv6()) {
657 IPv6AddressBytes addr_bytes;
658 addr_bytes.bytes[15] = 1;
659 connect_address.setAddress(&addr_bytes);
661 connect_address.setAddress(127, 0, 0, 1);
663 local_server_mode = true;
665 } catch (ResolveError &e) {
666 *error_message = std::string("Couldn't resolve address: ") + e.what();
667 errorstream << *error_message << std::endl;
671 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
672 *error_message = "Unable to connect to " +
673 connect_address.serializeString() +
674 " because IPv6 is disabled";
675 errorstream << *error_message << std::endl;
679 client = new Client(start_data.name.c_str(),
680 start_data.password, start_data.address,
681 *draw_control, texture_src, shader_src,
682 itemdef_manager, nodedef_manager, sound, eventmgr,
683 connect_address.isIPv6(), m_game_ui.get());
688 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
690 infostream << "Connecting to server at ";
691 connect_address.print(&infostream);
692 infostream << std::endl;
694 client->connect(connect_address,
695 simple_singleplayer_mode || local_server_mode);
698 Wait for server to accept connection
704 FpsControl fps_control = { 0 };
706 f32 wait_time = 0; // in seconds
708 fps_control.last_time = RenderingEngine::get_timer_time();
710 while (RenderingEngine::run()) {
712 limitFps(&fps_control, &dtime);
714 // Update client and server
721 if (client->getState() == LC_Init) {
727 if (*connection_aborted)
730 if (client->accessDenied()) {
731 *error_message = "Access denied. Reason: "
732 + client->accessDeniedReason();
733 *reconnect_requested = client->reconnectRequested();
734 errorstream << *error_message << std::endl;
738 if (input->cancelPressed()) {
739 *connection_aborted = true;
740 infostream << "Connect aborted [Escape]" << std::endl;
744 if (client->m_is_registration_confirmation_state) {
745 if (registration_confirmation_shown) {
746 // Keep drawing the GUI
747 RenderingEngine::draw_menu_scene(guienv, dtime, true);
749 registration_confirmation_shown = true;
750 (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
751 &g_menumgr, client, start_data.name, start_data.password,
752 connection_aborted, texture_src))->drop();
756 // Only time out if we aren't waiting for the server we started
757 if (!start_data.isSinglePlayer() && wait_time > 10) {
758 *error_message = "Connection timed out.";
759 errorstream << *error_message << std::endl;
764 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
767 } catch (con::PeerNotFoundException &e) {
768 // TODO: Should something be done here? At least an info/error
776 bool Game::getServerContent(bool *aborted)
780 FpsControl fps_control = { 0 };
781 f32 dtime; // in seconds
783 fps_control.last_time = RenderingEngine::get_timer_time();
785 while (RenderingEngine::run()) {
787 limitFps(&fps_control, &dtime);
789 // Update client and server
796 if (client->mediaReceived() && client->itemdefReceived() &&
797 client->nodedefReceived()) {
802 if (!checkConnection())
805 if (client->getState() < LC_Init) {
806 *error_message = "Client disconnected";
807 errorstream << *error_message << std::endl;
811 if (input->cancelPressed()) {
813 infostream << "Connect aborted [Escape]" << std::endl;
820 if (!client->itemdefReceived()) {
821 const wchar_t *text = wgettext("Item definitions...");
823 RenderingEngine::draw_load_screen(text, guienv, texture_src,
826 } else if (!client->nodedefReceived()) {
827 const wchar_t *text = wgettext("Node definitions...");
829 RenderingEngine::draw_load_screen(text, guienv, texture_src,
833 std::stringstream message;
835 message.precision(0);
836 float receive = client->mediaReceiveProgress() * 100;
837 message << gettext("Media...");
839 message << " " << receive << "%";
840 message.precision(2);
842 if ((USE_CURL == 0) ||
843 (!g_settings->getBool("enable_remote_media_server"))) {
844 float cur = client->getCurRate();
845 std::string cur_unit = gettext("KiB/s");
849 cur_unit = gettext("MiB/s");
852 message << " (" << cur << ' ' << cur_unit << ")";
855 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
856 RenderingEngine::draw_load_screen(utf8_to_wide(message.str()), guienv,
857 texture_src, dtime, progress);
865 /****************************************************************************/
866 /****************************************************************************
868 ****************************************************************************/
869 /****************************************************************************/
871 inline void Game::updateInteractTimers(f32 dtime)
873 if (runData.nodig_delay_timer >= 0)
874 runData.nodig_delay_timer -= dtime;
876 if (runData.object_hit_delay_timer >= 0)
877 runData.object_hit_delay_timer -= dtime;
879 runData.time_from_last_punch += dtime;
883 /* returns false if game should exit, otherwise true
885 inline bool Game::checkConnection()
887 if (client->accessDenied()) {
888 *error_message = "Access denied. Reason: "
889 + client->accessDeniedReason();
890 *reconnect_requested = client->reconnectRequested();
891 errorstream << *error_message << std::endl;
899 /* returns false if game should exit, otherwise true
901 inline bool Game::handleCallbacks()
903 if (g_gamecallback->disconnect_requested) {
904 g_gamecallback->disconnect_requested = false;
908 if (g_gamecallback->changepassword_requested) {
909 (new GUIPasswordChange(guienv, guiroot, -1,
910 &g_menumgr, client, texture_src))->drop();
911 g_gamecallback->changepassword_requested = false;
914 if (g_gamecallback->changevolume_requested) {
915 (new GUIVolumeChange(guienv, guiroot, -1,
916 &g_menumgr, texture_src))->drop();
917 g_gamecallback->changevolume_requested = false;
920 if (g_gamecallback->keyconfig_requested) {
921 (new GUIKeyChangeMenu(guienv, guiroot, -1,
922 &g_menumgr, texture_src))->drop();
923 g_gamecallback->keyconfig_requested = false;
926 if (g_gamecallback->keyconfig_changed) {
927 input->keycache.populate(); // update the cache with new settings
928 g_gamecallback->keyconfig_changed = false;
935 void Game::processQueues()
937 texture_src->processQueue();
938 itemdef_manager->processQueue(client);
939 shader_src->processQueue();
943 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
946 float profiler_print_interval =
947 g_settings->getFloat("profiler_print_interval");
948 bool print_to_log = true;
950 if (profiler_print_interval == 0) {
951 print_to_log = false;
952 profiler_print_interval = 3;
955 if (profiler_interval.step(dtime, profiler_print_interval)) {
957 infostream << "Profiler:" << std::endl;
958 g_profiler->print(infostream);
961 m_game_ui->updateProfiler();
965 // Update update graphs
966 g_profiler->graphAdd("Time non-rendering [ms]",
967 draw_times.busy_time - stats.drawtime);
969 g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time);
970 g_profiler->graphAdd("FPS", 1.0f / dtime);
973 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
980 /* Time average and jitter calculation
982 jp = &stats->dtime_jitter;
983 jp->avg = jp->avg * 0.96 + dtime * 0.04;
985 jitter = dtime - jp->avg;
987 if (jitter > jp->max)
990 jp->counter += dtime;
992 if (jp->counter > 0.0) {
994 jp->max_sample = jp->max;
995 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
999 /* Busytime average and jitter calculation
1001 jp = &stats->busy_time_jitter;
1002 jp->avg = jp->avg + draw_times.busy_time * 0.02;
1004 jitter = draw_times.busy_time - jp->avg;
1006 if (jitter > jp->max)
1008 if (jitter < jp->min)
1011 jp->counter += dtime;
1013 if (jp->counter > 0.0) {
1015 jp->max_sample = jp->max;
1016 jp->min_sample = jp->min;
1024 /****************************************************************************
1026 ****************************************************************************/
1028 void Game::processUserInput(f32 dtime)
1030 // Reset input if window not active or some menu is active
1031 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1033 #ifdef HAVE_TOUCHSCREENGUI
1034 g_touchscreengui->hide();
1037 #ifdef HAVE_TOUCHSCREENGUI
1038 else if (g_touchscreengui) {
1039 /* on touchscreengui step may generate own input events which ain't
1040 * what we want in case we just did clear them */
1041 g_touchscreengui->step(dtime);
1045 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1046 gui_chat_console->closeConsoleAtOnce();
1049 // Input handler step() (used by the random input generator)
1053 auto formspec = m_game_ui->getFormspecGUI();
1055 formspec->getAndroidUIInput();
1057 handleAndroidChatInput();
1060 // Increase timer for double tap of "keymap_jump"
1061 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1062 runData.jump_timer += dtime;
1065 processItemSelection(&runData.new_playeritem);
1069 void Game::processKeyInput()
1071 if (wasKeyDown(KeyType::SELECT_UP)) {
1072 m_cheat_menu->selectUp();
1073 } else if (wasKeyDown(KeyType::SELECT_DOWN)) {
1074 m_cheat_menu->selectDown();
1075 } else if (wasKeyDown(KeyType::SELECT_LEFT)) {
1076 m_cheat_menu->selectLeft();
1077 } else if (wasKeyDown(KeyType::SELECT_RIGHT)) {
1078 m_cheat_menu->selectRight();
1079 } else if (wasKeyDown(KeyType::SELECT_CONFIRM)) {
1080 m_cheat_menu->selectConfirm();
1083 if (wasKeyDown(KeyType::DROP)) {
1084 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1085 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1086 toggleAutoforward();
1087 } else if (wasKeyDown(KeyType::BACKWARD)) {
1088 if (g_settings->getBool("continuous_forward"))
1089 toggleAutoforward();
1090 } else if (wasKeyDown(KeyType::INVENTORY)) {
1092 } else if (wasKeyDown(KeyType::ENDERCHEST)) {
1094 } else if (input->cancelPressed()) {
1096 m_android_chat_open = false;
1098 if (!gui_chat_console->isOpenInhibited()) {
1101 } else if (wasKeyDown(KeyType::CHAT)) {
1102 openConsole(0.2, L"");
1103 } else if (wasKeyDown(KeyType::CMD)) {
1104 openConsole(0.2, L"/");
1105 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1106 if (client->modsLoaded())
1107 openConsole(0.2, L".");
1109 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1110 } else if (wasKeyDown(KeyType::CONSOLE)) {
1111 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1112 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1114 } else if (wasKeyDown(KeyType::JUMP)) {
1115 toggleFreeMoveAlt();
1116 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1118 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1120 } else if (wasKeyDown(KeyType::NOCLIP)) {
1122 } else if (wasKeyDown(KeyType::KILLAURA)) {
1124 } else if (wasKeyDown(KeyType::FREECAM)) {
1126 } else if (wasKeyDown(KeyType::SCAFFOLD)) {
1128 } else if (wasKeyDown(KeyType::NEXT_ITEM)) {
1131 } else if (wasKeyDown(KeyType::MUTE)) {
1132 if (g_settings->getBool("enable_sound")) {
1133 bool new_mute_sound = !g_settings->getBool("mute_sound");
1134 g_settings->setBool("mute_sound", new_mute_sound);
1136 m_game_ui->showTranslatedStatusText("Sound muted");
1138 m_game_ui->showTranslatedStatusText("Sound unmuted");
1140 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1142 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1143 if (g_settings->getBool("enable_sound")) {
1144 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1146 g_settings->setFloat("sound_volume", new_volume);
1147 const wchar_t *str = wgettext("Volume changed to %d%%");
1148 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1150 m_game_ui->showStatusText(buf);
1152 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1154 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1155 if (g_settings->getBool("enable_sound")) {
1156 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1158 g_settings->setFloat("sound_volume", new_volume);
1159 const wchar_t *str = wgettext("Volume changed to %d%%");
1160 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1162 m_game_ui->showStatusText(buf);
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_HUD)) {
1176 m_game_ui->toggleHud();
1177 } else if (wasKeyDown(KeyType::MINIMAP)) {
1178 toggleMinimap(isKeyDown(KeyType::SNEAK));
1179 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1180 m_game_ui->toggleChat();
1181 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1183 } else if (wasKeyDown(KeyType::TOGGLE_CHEAT_MENU)) {
1184 m_game_ui->toggleCheatMenu();
1185 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1186 toggleUpdateCamera();
1187 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1189 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1190 m_game_ui->toggleProfiler();
1191 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1192 increaseViewRange();
1193 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1194 decreaseViewRange();
1195 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1196 toggleFullViewRange();
1197 } else if (wasKeyDown(KeyType::ZOOM)) {
1199 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1201 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1203 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1205 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1209 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1210 runData.reset_jump_timer = false;
1211 runData.jump_timer = 0.0f;
1214 if (quicktune->hasMessage()) {
1215 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
1219 void Game::processItemSelection(u16 *new_playeritem)
1221 LocalPlayer *player = client->getEnv().getLocalPlayer();
1223 /* Item selection using mouse wheel
1225 *new_playeritem = player->getWieldIndex();
1227 s32 wheel = input->getMouseWheel();
1228 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
1229 player->hud_hotbar_itemcount - 1);
1233 if (wasKeyDown(KeyType::HOTBAR_NEXT))
1236 if (wasKeyDown(KeyType::HOTBAR_PREV))
1240 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
1242 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
1245 /* Item selection using hotbar slot keys
1247 for (u16 i = 0; i <= max_item; i++) {
1248 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
1249 *new_playeritem = i;
1256 void Game::dropSelectedItem(bool single_item)
1258 IDropAction *a = new IDropAction();
1259 a->count = single_item ? 1 : 0;
1260 a->from_inv.setCurrentPlayer();
1261 a->from_list = "main";
1262 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
1263 client->inventoryAction(a);
1267 void Game::openInventory()
1270 * Don't permit to open inventory is CAO or player doesn't exists.
1271 * This prevent showing an empty inventory at player load
1274 LocalPlayer *player = client->getEnv().getLocalPlayer();
1275 if (!player || !player->getCAO())
1278 infostream << "Game: Launching inventory" << std::endl;
1280 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
1282 InventoryLocation inventoryloc;
1283 inventoryloc.setCurrentPlayer();
1285 if (!client->modsLoaded()
1286 || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
1287 TextDest *txt_dst = new TextDestPlayerInventory(client);
1288 auto *&formspec = m_game_ui->updateFormspec("");
1289 GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
1290 txt_dst, client->getFormspecPrepend(), sound);
1292 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
1296 void Game::openEnderchest()
1298 LocalPlayer *player = client->getEnv().getLocalPlayer();
1299 if (!player || !player->getCAO())
1302 infostream << "Game: Launching special inventory" << std::endl;
1304 if (client->modsLoaded())
1305 client->getScript()->open_enderchest();
1309 void Game::openConsole(float scale, const wchar_t *line)
1311 assert(scale > 0.0f && scale <= 1.0f);
1314 porting::showInputDialog(gettext("ok"), "", "", 2);
1315 m_android_chat_open = true;
1317 if (gui_chat_console->isOpenInhibited())
1319 gui_chat_console->openConsole(scale);
1321 gui_chat_console->setCloseOnEnter(true);
1322 gui_chat_console->replaceAndAddToHistory(line);
1328 void Game::handleAndroidChatInput()
1330 if (m_android_chat_open && porting::getInputDialogState() == 0) {
1331 std::string text = porting::getInputDialogValue();
1332 client->typeChatMessage(utf8_to_wide(text));
1333 m_android_chat_open = false;
1339 void Game::toggleFreeMove()
1341 bool free_move = !g_settings->getBool("free_move");
1342 g_settings->set("free_move", bool_to_cstr(free_move));
1345 if (client->checkPrivilege("fly")) {
1346 m_game_ui->showTranslatedStatusText("Fly mode enabled");
1348 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
1351 m_game_ui->showTranslatedStatusText("Fly mode disabled");
1355 void Game::toggleFreeMoveAlt()
1357 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
1360 runData.reset_jump_timer = true;
1364 void Game::togglePitchMove()
1366 bool pitch_move = !g_settings->getBool("pitch_move");
1367 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
1370 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
1372 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
1377 void Game::toggleFast()
1379 bool fast_move = !g_settings->getBool("fast_move");
1380 bool has_fast_privs = client->checkPrivilege("fast");
1381 g_settings->set("fast_move", bool_to_cstr(fast_move));
1384 if (has_fast_privs) {
1385 m_game_ui->showTranslatedStatusText("Fast mode enabled");
1387 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
1390 m_game_ui->showTranslatedStatusText("Fast mode disabled");
1394 m_cache_hold_aux1 = fast_move && has_fast_privs;
1399 void Game::toggleNoClip()
1401 bool noclip = !g_settings->getBool("noclip");
1402 g_settings->set("noclip", bool_to_cstr(noclip));
1405 if (client->checkPrivilege("noclip")) {
1406 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
1408 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
1411 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
1415 void Game::toggleKillaura()
1417 bool killaura = ! g_settings->getBool("killaura");
1418 g_settings->set("killaura", bool_to_cstr(killaura));
1421 m_game_ui->showTranslatedStatusText("Killaura enabled");
1423 m_game_ui->showTranslatedStatusText("Killaura disabled");
1427 void Game::toggleFreecam()
1429 bool freecam = ! g_settings->getBool("freecam");
1430 g_settings->set("freecam", bool_to_cstr(freecam));
1433 m_game_ui->showTranslatedStatusText("Freecam enabled");
1435 m_game_ui->showTranslatedStatusText("Freecam disabled");
1439 void Game::toggleScaffold()
1441 bool scaffold = ! g_settings->getBool("scaffold");
1442 g_settings->set("scaffold", bool_to_cstr(scaffold));
1445 m_game_ui->showTranslatedStatusText("Scaffold enabled");
1447 m_game_ui->showTranslatedStatusText("Scaffold disabled");
1451 void Game::toggleNextItem()
1453 bool next_item = ! g_settings->getBool("next_item");
1454 g_settings->set("next_item", bool_to_cstr(next_item));
1457 m_game_ui->showTranslatedStatusText("NextItem enabled");
1459 m_game_ui->showTranslatedStatusText("NextItem disabled");
1463 void Game::toggleCinematic()
1465 bool cinematic = !g_settings->getBool("cinematic");
1466 g_settings->set("cinematic", bool_to_cstr(cinematic));
1469 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
1471 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
1474 // Autoforward by toggling continuous forward.
1475 void Game::toggleAutoforward()
1477 bool autorun_enabled = !g_settings->getBool("continuous_forward");
1478 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
1480 if (autorun_enabled)
1481 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
1483 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
1486 void Game::toggleMinimap(bool shift_pressed)
1488 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
1492 mapper->toggleMinimapShape();
1496 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
1498 // Not so satisying code to keep compatibility with old fixed mode system
1500 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
1502 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
1503 m_game_ui->m_flags.show_minimap = false;
1506 // If radar is disabled, try to find a non radar mode or fall back to 0
1507 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
1508 while (mapper->getModeIndex() &&
1509 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
1512 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
1516 // End of 'not so satifying code'
1517 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
1518 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
1519 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
1521 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
1524 void Game::toggleFog()
1526 bool fog_enabled = g_settings->getBool("enable_fog");
1527 g_settings->setBool("enable_fog", !fog_enabled);
1529 m_game_ui->showTranslatedStatusText("Fog disabled");
1531 m_game_ui->showTranslatedStatusText("Fog enabled");
1535 void Game::toggleDebug()
1537 // Initial / 4x toggle: Chat only
1538 // 1x toggle: Debug text with chat
1539 // 2x toggle: Debug text with profiler graph
1540 // 3x toggle: Debug text and wireframe
1541 if (!m_game_ui->m_flags.show_debug) {
1542 m_game_ui->m_flags.show_debug = true;
1543 m_game_ui->m_flags.show_profiler_graph = false;
1544 draw_control->show_wireframe = false;
1545 m_game_ui->showTranslatedStatusText("Debug info shown");
1546 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
1547 m_game_ui->m_flags.show_profiler_graph = true;
1548 m_game_ui->showTranslatedStatusText("Profiler graph shown");
1549 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
1550 m_game_ui->m_flags.show_profiler_graph = false;
1551 draw_control->show_wireframe = true;
1552 m_game_ui->showTranslatedStatusText("Wireframe shown");
1554 m_game_ui->m_flags.show_debug = false;
1555 m_game_ui->m_flags.show_profiler_graph = false;
1556 draw_control->show_wireframe = false;
1557 if (client->checkPrivilege("debug")) {
1558 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
1560 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
1566 void Game::toggleUpdateCamera()
1568 if (g_settings->getBool("freecam"))
1570 m_flags.disable_camera_update = !m_flags.disable_camera_update;
1571 if (m_flags.disable_camera_update)
1572 m_game_ui->showTranslatedStatusText("Camera update disabled");
1574 m_game_ui->showTranslatedStatusText("Camera update enabled");
1578 void Game::increaseViewRange()
1580 s16 range = g_settings->getS16("viewing_range");
1581 s16 range_new = range + 10;
1585 if (range_new > 4000) {
1587 str = wgettext("Viewing range is at maximum: %d");
1588 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
1590 m_game_ui->showStatusText(buf);
1593 str = wgettext("Viewing range changed to %d");
1594 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
1596 m_game_ui->showStatusText(buf);
1598 g_settings->set("viewing_range", itos(range_new));
1602 void Game::decreaseViewRange()
1604 s16 range = g_settings->getS16("viewing_range");
1605 s16 range_new = range - 10;
1609 if (range_new < 20) {
1611 str = wgettext("Viewing range is at minimum: %d");
1612 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
1614 m_game_ui->showStatusText(buf);
1616 str = wgettext("Viewing range changed to %d");
1617 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
1619 m_game_ui->showStatusText(buf);
1621 g_settings->set("viewing_range", itos(range_new));
1625 void Game::toggleFullViewRange()
1627 draw_control->range_all = !draw_control->range_all;
1628 if (draw_control->range_all)
1629 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
1631 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
1635 void Game::checkZoomEnabled()
1637 LocalPlayer *player = client->getEnv().getLocalPlayer();
1638 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
1639 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
1643 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
1645 if ((device->isWindowActive() && device->isWindowFocused()
1646 && !isMenuActive()) || input->isRandom()) {
1649 if (!input->isRandom()) {
1650 // Mac OSX gets upset if this is set every frame
1651 if (device->getCursorControl()->isVisible())
1652 device->getCursorControl()->setVisible(false);
1656 if (m_first_loop_after_window_activation) {
1657 m_first_loop_after_window_activation = false;
1659 input->setMousePos(driver->getScreenSize().Width / 2,
1660 driver->getScreenSize().Height / 2);
1662 updateCameraOrientation(cam, dtime);
1668 // Mac OSX gets upset if this is set every frame
1669 if (!device->getCursorControl()->isVisible())
1670 device->getCursorControl()->setVisible(true);
1673 m_first_loop_after_window_activation = true;
1678 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
1680 #ifdef HAVE_TOUCHSCREENGUI
1681 if (g_touchscreengui) {
1682 cam->camera_yaw += g_touchscreengui->getYawChange();
1683 cam->camera_pitch = g_touchscreengui->getPitch();
1686 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
1687 v2s32 dist = input->getMousePos() - center;
1689 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
1693 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity;
1694 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity;
1696 if (dist.X != 0 || dist.Y != 0)
1697 input->setMousePos(center.X, center.Y);
1698 #ifdef HAVE_TOUCHSCREENGUI
1702 if (m_cache_enable_joysticks) {
1703 f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime;
1704 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
1705 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
1708 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
1712 void Game::updatePlayerControl(const CameraOrientation &cam)
1714 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
1716 // DO NOT use the isKeyDown method for the forward, backward, left, right
1717 // buttons, as the code that uses the controls needs to be able to
1718 // distinguish between the two in order to know when to use joysticks.
1720 PlayerControl control(
1721 input->isKeyDown(KeyType::FORWARD),
1722 input->isKeyDown(KeyType::BACKWARD),
1723 input->isKeyDown(KeyType::LEFT),
1724 input->isKeyDown(KeyType::RIGHT),
1725 isKeyDown(KeyType::JUMP),
1726 isKeyDown(KeyType::SPECIAL1),
1727 isKeyDown(KeyType::SNEAK),
1728 isKeyDown(KeyType::ZOOM),
1729 isKeyDown(KeyType::DIG),
1730 isKeyDown(KeyType::PLACE),
1733 input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE),
1734 input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE)
1737 u32 keypress_bits = (
1738 ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) |
1739 ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) |
1740 ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) |
1741 ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) |
1742 ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) |
1743 ( (u32)(isKeyDown(KeyType::SPECIAL1) & 0x1) << 5) |
1744 ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) |
1745 ( (u32)(isKeyDown(KeyType::DIG) & 0x1) << 7) |
1746 ( (u32)(isKeyDown(KeyType::PLACE) & 0x1) << 8) |
1747 ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9)
1751 /* For Android, simulate holding down AUX1 (fast move) if the user has
1752 * the fast_move setting toggled on. If there is an aux1 key defined for
1753 * Android then its meaning is inverted (i.e. holding aux1 means walk and
1756 if (m_cache_hold_aux1) {
1757 control.aux1 = control.aux1 ^ true;
1758 keypress_bits ^= ((u32)(1U << 5));
1762 LocalPlayer *player = client->getEnv().getLocalPlayer();
1764 // autojump if set: simulate "jump" key
1765 if (player->getAutojump()) {
1766 control.jump = true;
1767 keypress_bits |= 1U << 4;
1770 // autoforward if set: simulate "up" key
1771 if (player->getPlayerSettings().continuous_forward &&
1772 client->activeObjectsReceived() && !player->isDead()) {
1774 keypress_bits |= 1U << 0;
1777 client->setPlayerControl(control);
1778 player->keyPressed = keypress_bits;
1784 inline void Game::step(f32 *dtime)
1786 bool can_be_and_is_paused =
1787 (simple_singleplayer_mode && g_menumgr.pausesGame());
1789 if (can_be_and_is_paused) { // This is for a singleplayer server
1790 *dtime = 0; // No time passes
1793 server->step(*dtime);
1795 client->step(*dtime);
1799 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
1800 {&Game::handleClientEvent_None},
1801 {&Game::handleClientEvent_PlayerDamage},
1802 {&Game::handleClientEvent_PlayerForceMove},
1803 {&Game::handleClientEvent_Deathscreen},
1804 {&Game::handleClientEvent_ShowFormSpec},
1805 {&Game::handleClientEvent_ShowLocalFormSpec},
1806 {&Game::handleClientEvent_HandleParticleEvent},
1807 {&Game::handleClientEvent_HandleParticleEvent},
1808 {&Game::handleClientEvent_HandleParticleEvent},
1809 {&Game::handleClientEvent_HudAdd},
1810 {&Game::handleClientEvent_HudRemove},
1811 {&Game::handleClientEvent_HudChange},
1812 {&Game::handleClientEvent_SetSky},
1813 {&Game::handleClientEvent_SetSun},
1814 {&Game::handleClientEvent_SetMoon},
1815 {&Game::handleClientEvent_SetStars},
1816 {&Game::handleClientEvent_OverrideDayNigthRatio},
1817 {&Game::handleClientEvent_CloudParams},
1820 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
1822 FATAL_ERROR("ClientEvent type None received");
1825 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
1827 if (client->modsLoaded())
1828 client->getScript()->on_damage_taken(event->player_damage.amount);
1830 // Damage flash and hurt tilt are not used at death
1831 if (client->getHP() > 0) {
1832 runData.damage_flash += 95.0f + 3.2f * event->player_damage.amount;
1833 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
1835 LocalPlayer *player = client->getEnv().getLocalPlayer();
1837 player->hurt_tilt_timer = 1.5f;
1838 player->hurt_tilt_strength =
1839 rangelim(event->player_damage.amount / 4.0f, 1.0f, 4.0f);
1842 // Play damage sound
1843 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
1846 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
1848 cam->camera_yaw = event->player_force_move.yaw;
1849 cam->camera_pitch = event->player_force_move.pitch;
1852 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
1854 // If client scripting is enabled, deathscreen is handled by CSM code in
1855 // builtin/client/init.lua
1856 if (client->modsLoaded())
1857 client->getScript()->on_death();
1859 showDeathFormspec();
1861 /* Handle visualization */
1862 LocalPlayer *player = client->getEnv().getLocalPlayer();
1863 runData.damage_flash = 0;
1864 player->hurt_tilt_timer = 0;
1865 player->hurt_tilt_strength = 0;
1868 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
1870 if (event->show_formspec.formspec->empty()) {
1871 auto formspec = m_game_ui->getFormspecGUI();
1872 if (formspec && (event->show_formspec.formname->empty()
1873 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
1874 formspec->quitMenu();
1877 FormspecFormSource *fs_src =
1878 new FormspecFormSource(*(event->show_formspec.formspec));
1879 TextDestPlayerInventory *txt_dst =
1880 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
1882 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
1883 GUIFormSpecMenu::create(formspec, client, &input->joystick,
1884 fs_src, txt_dst, client->getFormspecPrepend(), sound);
1887 delete event->show_formspec.formspec;
1888 delete event->show_formspec.formname;
1891 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
1893 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
1894 LocalFormspecHandler *txt_dst =
1895 new LocalFormspecHandler(*event->show_formspec.formname, client);
1896 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, &input->joystick,
1897 fs_src, txt_dst, client->getFormspecPrepend(), sound);
1899 delete event->show_formspec.formspec;
1900 delete event->show_formspec.formname;
1903 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
1904 CameraOrientation *cam)
1906 LocalPlayer *player = client->getEnv().getLocalPlayer();
1907 client->getParticleManager()->handleParticleEvent(event, client, player);
1910 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
1912 LocalPlayer *player = client->getEnv().getLocalPlayer();
1913 auto &hud_server_to_client = client->getHUDTranslationMap();
1915 u32 server_id = event->hudadd.server_id;
1916 // ignore if we already have a HUD with that ID
1917 auto i = hud_server_to_client.find(server_id);
1918 if (i != hud_server_to_client.end()) {
1919 delete event->hudadd.pos;
1920 delete event->hudadd.name;
1921 delete event->hudadd.scale;
1922 delete event->hudadd.text;
1923 delete event->hudadd.align;
1924 delete event->hudadd.offset;
1925 delete event->hudadd.world_pos;
1926 delete event->hudadd.size;
1927 delete event->hudadd.text2;
1931 HudElement *e = new HudElement;
1932 e->type = (HudElementType)event->hudadd.type;
1933 e->pos = *event->hudadd.pos;
1934 e->name = *event->hudadd.name;
1935 e->scale = *event->hudadd.scale;
1936 e->text = *event->hudadd.text;
1937 e->number = event->hudadd.number;
1938 e->item = event->hudadd.item;
1939 e->dir = event->hudadd.dir;
1940 e->align = *event->hudadd.align;
1941 e->offset = *event->hudadd.offset;
1942 e->world_pos = *event->hudadd.world_pos;
1943 e->size = *event->hudadd.size;
1944 e->z_index = event->hudadd.z_index;
1945 e->text2 = *event->hudadd.text2;
1946 hud_server_to_client[server_id] = player->addHud(e);
1948 delete event->hudadd.pos;
1949 delete event->hudadd.name;
1950 delete event->hudadd.scale;
1951 delete event->hudadd.text;
1952 delete event->hudadd.align;
1953 delete event->hudadd.offset;
1954 delete event->hudadd.world_pos;
1955 delete event->hudadd.size;
1956 delete event->hudadd.text2;
1959 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
1961 LocalPlayer *player = client->getEnv().getLocalPlayer();
1962 HudElement *e = player->removeHud(event->hudrm.id);
1966 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
1968 LocalPlayer *player = client->getEnv().getLocalPlayer();
1970 u32 id = event->hudchange.id;
1971 HudElement *e = player->getHud(id);
1974 delete event->hudchange.v3fdata;
1975 delete event->hudchange.v2fdata;
1976 delete event->hudchange.sdata;
1977 delete event->hudchange.v2s32data;
1981 switch (event->hudchange.stat) {
1983 e->pos = *event->hudchange.v2fdata;
1987 e->name = *event->hudchange.sdata;
1990 case HUD_STAT_SCALE:
1991 e->scale = *event->hudchange.v2fdata;
1995 e->text = *event->hudchange.sdata;
1998 case HUD_STAT_NUMBER:
1999 e->number = event->hudchange.data;
2003 e->item = event->hudchange.data;
2007 e->dir = event->hudchange.data;
2010 case HUD_STAT_ALIGN:
2011 e->align = *event->hudchange.v2fdata;
2014 case HUD_STAT_OFFSET:
2015 e->offset = *event->hudchange.v2fdata;
2018 case HUD_STAT_WORLD_POS:
2019 e->world_pos = *event->hudchange.v3fdata;
2023 e->size = *event->hudchange.v2s32data;
2026 case HUD_STAT_Z_INDEX:
2027 e->z_index = event->hudchange.data;
2030 case HUD_STAT_TEXT2:
2031 e->text2 = *event->hudchange.sdata;
2035 delete event->hudchange.v3fdata;
2036 delete event->hudchange.v2fdata;
2037 delete event->hudchange.sdata;
2038 delete event->hudchange.v2s32data;
2041 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2043 sky->setVisible(false);
2044 // Whether clouds are visible in front of a custom skybox.
2045 sky->setCloudsEnabled(event->set_sky->clouds);
2051 // Clear the old textures out in case we switch rendering type.
2052 sky->clearSkyboxTextures();
2053 // Handle according to type
2054 if (event->set_sky->type == "regular") {
2055 // Shows the mesh skybox
2056 sky->setVisible(true);
2057 // Update mesh based skybox colours if applicable.
2058 sky->setSkyColors(event->set_sky->sky_color);
2059 sky->setHorizonTint(
2060 event->set_sky->fog_sun_tint,
2061 event->set_sky->fog_moon_tint,
2062 event->set_sky->fog_tint_type
2064 } else if (event->set_sky->type == "skybox" &&
2065 event->set_sky->textures.size() == 6) {
2066 // Disable the dyanmic mesh skybox:
2067 sky->setVisible(false);
2069 sky->setFallbackBgColor(event->set_sky->bgcolor);
2070 // Set sunrise and sunset fog tinting:
2071 sky->setHorizonTint(
2072 event->set_sky->fog_sun_tint,
2073 event->set_sky->fog_moon_tint,
2074 event->set_sky->fog_tint_type
2076 // Add textures to skybox.
2077 for (int i = 0; i < 6; i++)
2078 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2080 // Handle everything else as plain color.
2081 if (event->set_sky->type != "plain")
2082 infostream << "Unknown sky type: "
2083 << (event->set_sky->type) << std::endl;
2084 sky->setVisible(false);
2085 sky->setFallbackBgColor(event->set_sky->bgcolor);
2086 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2087 sky->setHorizonTint(
2088 event->set_sky->bgcolor,
2089 event->set_sky->bgcolor,
2093 delete event->set_sky;
2096 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2098 sky->setSunVisible(event->sun_params->visible);
2099 sky->setSunTexture(event->sun_params->texture,
2100 event->sun_params->tonemap, texture_src);
2101 sky->setSunScale(event->sun_params->scale);
2102 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2103 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2104 delete event->sun_params;
2107 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2109 sky->setMoonVisible(event->moon_params->visible);
2110 sky->setMoonTexture(event->moon_params->texture,
2111 event->moon_params->tonemap, texture_src);
2112 sky->setMoonScale(event->moon_params->scale);
2113 delete event->moon_params;
2116 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2118 sky->setStarsVisible(event->star_params->visible);
2119 sky->setStarCount(event->star_params->count, false);
2120 sky->setStarColor(event->star_params->starcolor);
2121 sky->setStarScale(event->star_params->scale);
2122 delete event->star_params;
2125 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2126 CameraOrientation *cam)
2128 client->getEnv().setDayNightRatioOverride(
2129 event->override_day_night_ratio.do_override,
2130 event->override_day_night_ratio.ratio_f * 1000.0f);
2133 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2138 clouds->setDensity(event->cloud_params.density);
2139 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2140 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2141 clouds->setHeight(event->cloud_params.height);
2142 clouds->setThickness(event->cloud_params.thickness);
2143 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2146 void Game::processClientEvents(CameraOrientation *cam)
2148 while (client->hasClientEvents()) {
2149 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2150 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2151 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2152 (this->*evHandler.handler)(event.get(), cam);
2156 void Game::updateChat(f32 dtime, const v2u32 &screensize)
2158 // Get new messages from error log buffer
2159 while (!m_chat_log_buf.empty())
2160 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2162 // Get new messages from client
2163 std::wstring message;
2164 while (client->getChatMessage(message)) {
2165 chat_backend->addUnparsedMessage(message);
2168 // Remove old messages
2169 chat_backend->step(dtime);
2171 // Display all messages in a static text element
2172 m_game_ui->setChatText(chat_backend->getRecentChat(),
2173 chat_backend->getRecentBuffer().getLineCount());
2176 void Game::updateCamera(u32 busy_time, f32 dtime)
2178 LocalPlayer *player = client->getEnv().getLocalPlayer();
2181 For interaction purposes, get info about the held item
2183 - Is it a usable item?
2184 - Can it point to liquids?
2186 ItemStack playeritem;
2188 ItemStack selected, hand;
2189 playeritem = player->getWieldedItem(&selected, &hand);
2192 ToolCapabilities playeritem_toolcap =
2193 playeritem.getToolCapabilities(itemdef_manager);
2195 v3s16 old_camera_offset = camera->getOffset();
2197 if (wasKeyDown(KeyType::CAMERA_MODE) && ! g_settings->getBool("freecam")) {
2198 camera->toggleCameraMode();
2199 updatePlayerCAOVisibility();
2202 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2203 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2205 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2206 camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
2207 camera->step(dtime);
2209 v3f camera_position = camera->getPosition();
2210 v3f camera_direction = camera->getDirection();
2211 f32 camera_fov = camera->getFovMax();
2212 v3s16 camera_offset = camera->getOffset();
2214 m_camera_offset_changed = (camera_offset != old_camera_offset);
2216 if (!m_flags.disable_camera_update) {
2217 client->getEnv().getClientMap().updateCamera(camera_position,
2218 camera_direction, camera_fov, camera_offset);
2220 if (m_camera_offset_changed) {
2221 client->updateCameraOffset(camera_offset);
2222 client->getEnv().updateCameraOffset(camera_offset);
2225 clouds->updateCameraOffset(camera_offset);
2230 void Game::updatePlayerCAOVisibility()
2232 // Make the player visible depending on camera mode.
2233 LocalPlayer *player = client->getEnv().getLocalPlayer();
2234 GenericCAO *playercao = player->getCAO();
2237 playercao->updateMeshCulling();
2238 bool is_visible = camera->getCameraMode() > CAMERA_MODE_FIRST || g_settings->getBool("freecam");
2239 playercao->setChildrenVisible(is_visible);
2242 void Game::updateSound(f32 dtime)
2244 // Update sound listener
2245 v3s16 camera_offset = camera->getOffset();
2246 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
2247 v3f(0, 0, 0), // velocity
2248 camera->getDirection(),
2249 camera->getCameraNode()->getUpVector());
2251 bool mute_sound = g_settings->getBool("mute_sound");
2253 sound->setListenerGain(0.0f);
2255 // Check if volume is in the proper range, else fix it.
2256 float old_volume = g_settings->getFloat("sound_volume");
2257 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
2258 sound->setListenerGain(new_volume);
2260 if (old_volume != new_volume) {
2261 g_settings->setFloat("sound_volume", new_volume);
2265 LocalPlayer *player = client->getEnv().getLocalPlayer();
2267 // Tell the sound maker whether to make footstep sounds
2268 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
2270 // Update sound maker
2271 if (player->makes_footstep_sound)
2272 soundmaker->step(dtime);
2274 ClientMap &map = client->getEnv().getClientMap();
2275 MapNode n = map.getNode(player->getFootstepNodePos());
2276 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
2280 void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
2282 LocalPlayer *player = client->getEnv().getLocalPlayer();
2284 const v3f camera_direction = camera->getDirection();
2285 const v3s16 camera_offset = camera->getOffset();
2288 Calculate what block is the crosshair pointing to
2291 ItemStack selected_item, hand_item;
2292 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
2294 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
2295 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
2297 if (g_settings->getBool("increase_tool_range"))
2299 if (g_settings->getBool("increase_tool_range_plus"))
2302 core::line3d<f32> shootline;
2304 switch (camera->getCameraMode()) {
2305 case CAMERA_MODE_FIRST:
2306 // Shoot from camera position, with bobbing
2307 shootline.start = camera->getPosition();
2309 case CAMERA_MODE_THIRD:
2310 // Shoot from player head, no bobbing
2311 shootline.start = camera->getHeadPosition();
2313 case CAMERA_MODE_THIRD_FRONT:
2314 shootline.start = camera->getHeadPosition();
2315 // prevent player pointing anything in front-view
2319 shootline.end = shootline.start + camera_direction * BS * d;
2321 #ifdef HAVE_TOUCHSCREENGUI
2323 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
2324 shootline = g_touchscreengui->getShootline();
2325 // Scale shootline to the acual distance the player can reach
2326 shootline.end = shootline.start
2327 + shootline.getVector().normalize() * BS * d;
2328 shootline.start += intToFloat(camera_offset, BS);
2329 shootline.end += intToFloat(camera_offset, BS);
2334 PointedThing pointed = updatePointedThing(shootline,
2335 selected_def.liquids_pointable,
2336 !runData.btn_down_for_dig,
2339 if (pointed != runData.pointed_old) {
2340 infostream << "Pointing at " << pointed.dump() << std::endl;
2341 hud->updateSelectionMesh(camera_offset);
2344 // Allow digging again if button is not pressed
2345 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
2346 runData.digging_blocked = false;
2350 - releasing dig button
2351 - pointing away from node
2353 if (runData.digging) {
2354 if (wasKeyReleased(KeyType::DIG)) {
2355 infostream << "Dig button released (stopped digging)" << std::endl;
2356 runData.digging = false;
2357 } else if (pointed != runData.pointed_old) {
2358 if (pointed.type == POINTEDTHING_NODE
2359 && runData.pointed_old.type == POINTEDTHING_NODE
2360 && pointed.node_undersurface
2361 == runData.pointed_old.node_undersurface) {
2362 // Still pointing to the same node, but a different face.
2365 infostream << "Pointing away from node (stopped digging)" << std::endl;
2366 runData.digging = false;
2367 hud->updateSelectionMesh(camera_offset);
2371 if (!runData.digging) {
2372 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
2373 client->setCrack(-1, v3s16(0, 0, 0));
2374 runData.dig_time = 0.0;
2376 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
2377 // Remove e.g. torches faster when clicking instead of holding dig button
2378 runData.nodig_delay_timer = 0;
2379 runData.dig_instantly = false;
2382 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
2383 runData.btn_down_for_dig = false;
2385 runData.punching = false;
2387 soundmaker->m_player_leftpunch_sound.name = "";
2389 // Prepare for repeating, unless we're not supposed to
2390 if ((isKeyDown(KeyType::PLACE) || g_settings->getBool("autoplace")) && !g_settings->getBool("safe_dig_and_place"))
2391 runData.repeat_place_timer += dtime;
2393 runData.repeat_place_timer = 0;
2395 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
2396 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
2397 !client->getScript()->on_item_use(selected_item, pointed)))
2398 client->interact(INTERACT_USE, pointed);
2399 } else if (pointed.type == POINTEDTHING_NODE) {
2400 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
2401 } else if (pointed.type == POINTEDTHING_OBJECT) {
2402 v3f player_position = player->getPosition();
2403 handlePointingAtObject(pointed, tool_item, player_position, show_debug);
2404 } else if (isKeyDown(KeyType::DIG)) {
2405 // When button is held down in air, show continuous animation
2406 runData.punching = true;
2407 // Run callback even though item is not usable
2408 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
2409 client->getScript()->on_item_use(selected_item, pointed);
2410 } else if (wasKeyPressed(KeyType::PLACE)) {
2411 handlePointingAtNothing(selected_item);
2414 runData.pointed_old = pointed;
2416 if (runData.punching || wasKeyPressed(KeyType::DIG))
2417 camera->setDigging(0); // dig animation
2419 input->clearWasKeyPressed();
2420 input->clearWasKeyReleased();
2422 input->joystick.clearWasKeyDown(KeyType::DIG);
2423 input->joystick.clearWasKeyDown(KeyType::PLACE);
2425 input->joystick.clearWasKeyReleased(KeyType::DIG);
2426 input->joystick.clearWasKeyReleased(KeyType::PLACE);
2430 PointedThing Game::updatePointedThing(
2431 const core::line3d<f32> &shootline,
2432 bool liquids_pointable,
2433 bool look_for_object,
2434 const v3s16 &camera_offset)
2436 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
2437 selectionboxes->clear();
2438 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
2439 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
2440 "show_entity_selectionbox");
2442 ClientEnvironment &env = client->getEnv();
2443 ClientMap &map = env.getClientMap();
2444 const NodeDefManager *nodedef = map.getNodeDefManager();
2446 runData.selected_object = NULL;
2447 hud->pointing_at_object = false;
2448 RaycastState s(shootline, look_for_object, liquids_pointable, ! g_settings->getBool("dont_point_nodes"));
2449 PointedThing result;
2450 env.continueRaycast(&s, &result);
2451 if (result.type == POINTEDTHING_OBJECT) {
2452 hud->pointing_at_object = true;
2454 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
2455 aabb3f selection_box;
2456 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
2457 runData.selected_object->getSelectionBox(&selection_box)) {
2458 v3f pos = runData.selected_object->getPosition();
2459 selectionboxes->push_back(aabb3f(selection_box));
2460 hud->setSelectionPos(pos, camera_offset);
2462 } else if (result.type == POINTEDTHING_NODE) {
2463 // Update selection boxes
2464 MapNode n = map.getNode(result.node_undersurface);
2465 std::vector<aabb3f> boxes;
2466 n.getSelectionBoxes(nodedef, &boxes,
2467 n.getNeighbors(result.node_undersurface, &map));
2470 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
2471 i != boxes.end(); ++i) {
2473 box.MinEdge -= v3f(d, d, d);
2474 box.MaxEdge += v3f(d, d, d);
2475 selectionboxes->push_back(box);
2477 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
2479 hud->setSelectedFaceNormal(v3f(
2480 result.intersection_normal.X,
2481 result.intersection_normal.Y,
2482 result.intersection_normal.Z));
2485 // Update selection mesh light level and vertex colors
2486 if (!selectionboxes->empty()) {
2487 v3f pf = hud->getSelectionPos();
2488 v3s16 p = floatToInt(pf, BS);
2490 // Get selection mesh light level
2491 MapNode n = map.getNode(p);
2492 u16 node_light = getInteriorLight(n, -1, nodedef);
2493 u16 light_level = node_light;
2495 for (const v3s16 &dir : g_6dirs) {
2496 n = map.getNode(p + dir);
2497 node_light = getInteriorLight(n, -1, nodedef);
2498 if (node_light > light_level)
2499 light_level = node_light;
2502 u32 daynight_ratio = client->getEnv().getDayNightRatio();
2504 final_color_blend(&c, light_level, daynight_ratio);
2506 // Modify final color a bit with time
2507 u32 timer = porting::getTimeMs() % 5000;
2508 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
2509 float sin_r = 0.08f * std::sin(timerf);
2510 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
2511 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
2512 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
2513 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
2514 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
2516 // Set mesh final color
2517 hud->setSelectionMeshColor(c);
2522 void Game::handlePointingAtNothing(const ItemStack &playerItem)
2524 infostream << "Attempted to place item while pointing at nothing" << std::endl;
2525 PointedThing fauxPointed;
2526 fauxPointed.type = POINTEDTHING_NOTHING;
2527 client->interact(INTERACT_ACTIVATE, fauxPointed);
2531 void Game::handlePointingAtNode(const PointedThing &pointed,
2532 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
2534 v3s16 nodepos = pointed.node_undersurface;
2535 v3s16 neighbourpos = pointed.node_abovesurface;
2538 Check information text of node
2541 ClientMap &map = client->getEnv().getClientMap();
2543 if (((runData.nodig_delay_timer <= 0.0 || g_settings->getBool("fastdig")) && (isKeyDown(KeyType::DIG) || g_settings->getBool("autodig"))
2544 && !runData.digging_blocked
2545 && client->checkPrivilege("interact"))
2547 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
2550 // This should be done after digging handling
2551 NodeMetadata *meta = map.getNodeMetadata(nodepos);
2554 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
2555 meta->getString("infotext"))));
2557 MapNode n = map.getNode(nodepos);
2559 if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
2560 m_game_ui->setInfoText(L"Unknown node: " +
2561 utf8_to_wide(nodedef_manager->get(n).name));
2565 if ((wasKeyPressed(KeyType::PLACE) ||
2566 (runData.repeat_place_timer >= (g_settings->getBool("fastplace") ? 0.001 : m_repeat_place_time))) &&
2567 client->checkPrivilege("interact")) {
2568 runData.repeat_place_timer = 0;
2569 infostream << "Place button pressed while looking at ground" << std::endl;
2571 // Placing animation (always shown for feedback)
2572 camera->setDigging(1);
2574 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
2576 // If the wielded item has node placement prediction,
2578 // And also set the sound and send the interact
2579 // But first check for meta formspec and rightclickable
2580 auto &def = selected_item.getDefinition(itemdef_manager);
2581 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
2584 if (placed && client->modsLoaded())
2585 client->getScript()->on_placenode(pointed, def);
2589 bool Game::nodePlacement(const ItemDefinition &selected_def,
2590 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
2591 const PointedThing &pointed, const NodeMetadata *meta)
2593 std::string prediction = selected_def.node_placement_prediction;
2594 const NodeDefManager *nodedef = client->ndef();
2595 ClientMap &map = client->getEnv().getClientMap();
2597 bool is_valid_position;
2599 node = map.getNode(nodepos, &is_valid_position);
2600 if (!is_valid_position) {
2601 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2606 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
2607 && !isKeyDown(KeyType::SNEAK)) {
2608 // on_rightclick callbacks are called anyway
2609 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
2610 client->interact(INTERACT_PLACE, pointed);
2612 infostream << "Launching custom inventory view" << std::endl;
2614 InventoryLocation inventoryloc;
2615 inventoryloc.setNodeMeta(nodepos);
2617 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
2618 &client->getEnv().getClientMap(), nodepos);
2619 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
2621 auto *&formspec = m_game_ui->updateFormspec("");
2622 GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
2623 txt_dst, client->getFormspecPrepend(), sound);
2625 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
2629 // on_rightclick callback
2630 if (prediction.empty() || (nodedef->get(node).rightclickable &&
2631 !isKeyDown(KeyType::SNEAK))) {
2633 client->interact(INTERACT_PLACE, pointed);
2637 verbosestream << "Node placement prediction for "
2638 << selected_def.name << " is " << prediction << std::endl;
2639 v3s16 p = neighbourpos;
2641 // Place inside node itself if buildable_to
2642 MapNode n_under = map.getNode(nodepos, &is_valid_position);
2643 if (is_valid_position) {
2644 if (nodedef->get(n_under).buildable_to) {
2647 node = map.getNode(p, &is_valid_position);
2648 if (is_valid_position && !nodedef->get(node).buildable_to) {
2649 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2651 client->interact(INTERACT_PLACE, pointed);
2657 // Find id of predicted node
2659 bool found = nodedef->getId(prediction, id);
2662 errorstream << "Node placement prediction failed for "
2663 << selected_def.name << " (places "
2665 << ") - Name not known" << std::endl;
2666 // Handle this as if prediction was empty
2668 client->interact(INTERACT_PLACE, pointed);
2672 const ContentFeatures &predicted_f = nodedef->get(id);
2674 // Predict param2 for facedir and wallmounted nodes
2677 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
2678 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
2679 v3s16 dir = nodepos - neighbourpos;
2681 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
2682 param2 = dir.Y < 0 ? 1 : 0;
2683 } else if (abs(dir.X) > abs(dir.Z)) {
2684 param2 = dir.X < 0 ? 3 : 2;
2686 param2 = dir.Z < 0 ? 5 : 4;
2690 if (predicted_f.param_type_2 == CPT2_FACEDIR ||
2691 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
2692 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
2694 if (abs(dir.X) > abs(dir.Z)) {
2695 param2 = dir.X < 0 ? 3 : 1;
2697 param2 = dir.Z < 0 ? 2 : 0;
2701 assert(param2 <= 5);
2703 //Check attachment if node is in group attached_node
2704 if (((ItemGroupList) predicted_f.groups)["attached_node"] != 0) {
2705 static v3s16 wallmounted_dirs[8] = {
2715 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
2716 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
2717 pp = p + wallmounted_dirs[param2];
2719 pp = p + v3s16(0, -1, 0);
2721 if (!nodedef->get(map.getNode(pp)).walkable) {
2722 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2724 client->interact(INTERACT_PLACE, pointed);
2730 if ((predicted_f.param_type_2 == CPT2_COLOR
2731 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
2732 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
2733 const std::string &indexstr = selected_item.metadata.getString(
2734 "palette_index", 0);
2735 if (!indexstr.empty()) {
2736 s32 index = mystoi(indexstr);
2737 if (predicted_f.param_type_2 == CPT2_COLOR) {
2739 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
2740 // param2 = pure palette index + other
2741 param2 = (index & 0xf8) | (param2 & 0x07);
2742 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
2743 // param2 = pure palette index + other
2744 param2 = (index & 0xe0) | (param2 & 0x1f);
2749 // Add node to client map
2750 MapNode n(id, 0, param2);
2753 LocalPlayer *player = client->getEnv().getLocalPlayer();
2755 // Dont place node when player would be inside new node
2756 // NOTE: This is to be eventually implemented by a mod as client-side Lua
2757 if (!nodedef->get(n).walkable ||
2758 g_settings->getBool("enable_build_where_you_stand") ||
2759 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
2760 (nodedef->get(n).walkable &&
2761 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
2762 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
2763 // This triggers the required mesh update too
2764 client->addNode(p, n);
2766 client->interact(INTERACT_PLACE, pointed);
2767 // A node is predicted, also play a sound
2768 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
2771 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2774 } catch (InvalidPositionException &e) {
2775 errorstream << "Node placement prediction failed for "
2776 << selected_def.name << " (places "
2778 << ") - Position not loaded" << std::endl;
2779 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2784 void Game::handlePointingAtObject(const PointedThing &pointed,
2785 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
2787 std::wstring infotext = unescape_translate(
2788 utf8_to_wide(runData.selected_object->infoText()));
2791 if (!infotext.empty()) {
2794 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
2797 m_game_ui->setInfoText(infotext);
2799 if (isKeyDown(KeyType::DIG) || g_settings->getBool("autohit")) {
2800 bool do_punch = false;
2801 bool do_punch_damage = false;
2803 if (runData.object_hit_delay_timer <= 0.0 || g_settings->getBool("spamclick")) {
2805 do_punch_damage = true;
2806 runData.object_hit_delay_timer = object_hit_delay;
2809 if (wasKeyPressed(KeyType::DIG))
2813 infostream << "Punched object" << std::endl;
2814 runData.punching = true;
2817 if (do_punch_damage) {
2818 // Report direct punch
2819 v3f objpos = runData.selected_object->getPosition();
2820 v3f dir = (objpos - player_position).normalize();
2822 bool disable_send = runData.selected_object->directReportPunch(
2823 dir, &tool_item, runData.time_from_last_punch);
2824 runData.time_from_last_punch = 0;
2826 if (!disable_send) {
2827 client->interact(INTERACT_START_DIGGING, pointed);
2830 } else if (wasKeyDown(KeyType::PLACE)) {
2831 infostream << "Pressed place button while pointing at object" << std::endl;
2832 client->interact(INTERACT_PLACE, pointed); // place
2837 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
2838 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
2840 // See also: serverpackethandle.cpp, action == 2
2841 LocalPlayer *player = client->getEnv().getLocalPlayer();
2842 ClientMap &map = client->getEnv().getClientMap();
2843 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
2845 // NOTE: Similar piece of code exists on the server side for
2847 // Get digging parameters
2848 DigParams params = getDigParams(nodedef_manager->get(n).groups,
2849 &selected_item.getToolCapabilities(itemdef_manager));
2851 // If can't dig, try hand
2852 if (!params.diggable) {
2853 params = getDigParams(nodedef_manager->get(n).groups,
2854 &hand_item.getToolCapabilities(itemdef_manager));
2857 if (!params.diggable) {
2858 // I guess nobody will wait for this long
2859 runData.dig_time_complete = 10000000.0;
2861 runData.dig_time_complete = params.time;
2863 if (m_cache_enable_particles) {
2864 const ContentFeatures &features = client->getNodeDefManager()->get(n);
2865 client->getParticleManager()->addNodeParticle(client,
2866 player, nodepos, n, features);
2870 if(g_settings->getBool("instant_break")) {
2871 runData.dig_time_complete = 0;
2872 runData.dig_instantly = true;
2874 if (!runData.digging) {
2875 infostream << "Started digging" << std::endl;
2876 runData.dig_instantly = runData.dig_time_complete == 0;
2877 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
2879 client->interact(INTERACT_START_DIGGING, pointed);
2880 runData.digging = true;
2881 runData.btn_down_for_dig = true;
2884 if (!runData.dig_instantly) {
2885 runData.dig_index = (float)crack_animation_length
2887 / runData.dig_time_complete;
2889 // This is for e.g. torches
2890 runData.dig_index = crack_animation_length;
2893 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
2895 if (sound_dig.exists() && params.diggable) {
2896 if (sound_dig.name == "__group") {
2897 if (!params.main_group.empty()) {
2898 soundmaker->m_player_leftpunch_sound.gain = 0.5;
2899 soundmaker->m_player_leftpunch_sound.name =
2900 std::string("default_dig_") +
2904 soundmaker->m_player_leftpunch_sound = sound_dig;
2908 // Don't show cracks if not diggable
2909 if (runData.dig_time_complete >= 100000.0) {
2910 } else if (runData.dig_index < crack_animation_length) {
2911 //TimeTaker timer("client.setTempMod");
2912 //infostream<<"dig_index="<<dig_index<<std::endl;
2913 client->setCrack(runData.dig_index, nodepos);
2915 infostream << "Digging completed" << std::endl;
2916 client->setCrack(-1, v3s16(0, 0, 0));
2918 runData.dig_time = 0;
2919 runData.digging = false;
2920 // we successfully dug, now block it from repeating if we want to be safe
2921 if (g_settings->getBool("safe_dig_and_place"))
2922 runData.digging_blocked = true;
2924 runData.nodig_delay_timer =
2925 runData.dig_time_complete / (float)crack_animation_length;
2927 // We don't want a corresponding delay to very time consuming nodes
2928 // and nodes without digging time (e.g. torches) get a fixed delay.
2929 if (runData.nodig_delay_timer > 0.3)
2930 runData.nodig_delay_timer = 0.3;
2931 else if (runData.dig_instantly)
2932 runData.nodig_delay_timer = 0.15;
2934 bool is_valid_position;
2935 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
2936 if (is_valid_position) {
2937 if (client->modsLoaded() &&
2938 client->getScript()->on_dignode(nodepos, wasnode)) {
2942 const ContentFeatures &f = client->ndef()->get(wasnode);
2943 if (f.node_dig_prediction == "air") {
2944 client->removeNode(nodepos);
2945 } else if (!f.node_dig_prediction.empty()) {
2947 bool found = client->ndef()->getId(f.node_dig_prediction, id);
2949 client->addNode(nodepos, id, true);
2951 // implicit else: no prediction
2954 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
2956 if (m_cache_enable_particles) {
2957 const ContentFeatures &features =
2958 client->getNodeDefManager()->get(wasnode);
2959 client->getParticleManager()->addDiggingParticles(client,
2960 player, nodepos, wasnode, features);
2964 // Send event to trigger sound
2965 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
2968 if (runData.dig_time_complete < 100000.0) {
2969 runData.dig_time += dtime;
2971 runData.dig_time = 0;
2972 client->setCrack(-1, nodepos);
2975 camera->setDigging(0); // Dig animation
2978 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
2979 const CameraOrientation &cam)
2981 TimeTaker tt_update("Game::updateFrame()");
2982 LocalPlayer *player = client->getEnv().getLocalPlayer();
2988 if (draw_control->range_all) {
2989 runData.fog_range = 100000 * BS;
2991 runData.fog_range = draw_control->wanted_range * BS;
2995 Calculate general brightness
2997 u32 daynight_ratio = client->getEnv().getDayNightRatio();
2998 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
2999 float direct_brightness;
3002 if ((m_cache_enable_noclip && m_cache_enable_free_move) || g_settings->getBool("freecam")) {
3003 direct_brightness = time_brightness;
3004 sunlight_seen = true;
3006 float old_brightness = sky->getBrightness();
3007 direct_brightness = client->getEnv().getClientMap()
3008 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3009 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3013 float time_of_day_smooth = runData.time_of_day_smooth;
3014 float time_of_day = client->getEnv().getTimeOfDayF();
3016 static const float maxsm = 0.05f;
3017 static const float todsm = 0.05f;
3019 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3020 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3021 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3022 time_of_day_smooth = time_of_day;
3024 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3025 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3026 + (time_of_day + 1.0) * todsm;
3028 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3029 + time_of_day * todsm;
3031 runData.time_of_day_smooth = time_of_day_smooth;
3033 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3034 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3035 player->getPitch());
3041 if (sky->getCloudsVisible()) {
3042 clouds->setVisible(true);
3043 clouds->step(dtime);
3044 // camera->getPosition is not enough for 3rd person views
3045 v3f camera_node_position = camera->getCameraNode()->getPosition();
3046 v3s16 camera_offset = camera->getOffset();
3047 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3048 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3049 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3050 clouds->update(camera_node_position,
3051 sky->getCloudColor());
3052 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3053 // if inside clouds, and fog enabled, use that as sky
3055 video::SColor clouds_dark = clouds->getColor()
3056 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3057 sky->overrideColors(clouds_dark, clouds->getColor());
3058 sky->setInClouds(true);
3059 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3060 // do not draw clouds after all
3061 clouds->setVisible(false);
3064 clouds->setVisible(false);
3071 client->getParticleManager()->step(dtime);
3077 if (m_cache_enable_fog) {
3080 video::EFT_FOG_LINEAR,
3081 runData.fog_range * m_cache_fog_start,
3082 runData.fog_range * 1.0,
3090 video::EFT_FOG_LINEAR,
3100 Get chat messages from client
3103 v2u32 screensize = driver->getScreenSize();
3105 updateChat(dtime, screensize);
3111 if (player->getWieldIndex() != runData.new_playeritem)
3112 client->setPlayerItem(runData.new_playeritem);
3114 if (client->updateWieldedItem()) {
3115 // Update wielded tool
3116 ItemStack selected_item, hand_item;
3117 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3118 camera->wield(tool_item);
3122 Update block draw list every 200ms or when camera direction has
3125 runData.update_draw_list_timer += dtime;
3127 v3f camera_direction = camera->getDirection();
3128 if (runData.update_draw_list_timer >= 0.2
3129 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3130 || m_camera_offset_changed) {
3131 runData.update_draw_list_timer = 0;
3132 client->getEnv().getClientMap().updateDrawList();
3133 runData.update_draw_list_last_cam_dir = camera_direction;
3136 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3139 make sure menu is on top
3140 1. Delete formspec menu reference if menu was removed
3141 2. Else, make sure formspec menu is on top
3143 auto formspec = m_game_ui->getFormspecGUI();
3144 do { // breakable. only runs for one iteration
3148 if (formspec->getReferenceCount() == 1) {
3149 m_game_ui->deleteFormspec();
3153 auto &loc = formspec->getFormspecLocation();
3154 if (loc.type == InventoryLocation::NODEMETA) {
3155 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3156 if (!meta || meta->getString("formspec").empty()) {
3157 formspec->quitMenu();
3163 guiroot->bringToFront(formspec);
3169 const video::SColor &skycolor = sky->getSkyColor();
3171 TimeTaker tt_draw("Draw scene");
3172 driver->beginScene(true, true, skycolor);
3174 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3175 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3176 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3177 bool draw_crosshair = (
3178 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3179 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3180 #ifdef HAVE_TOUCHSCREENGUI
3182 draw_crosshair = !g_settings->getBool("touchtarget");
3183 } catch (SettingNotFoundException) {
3186 RenderingEngine::draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3187 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3192 if (m_game_ui->m_flags.show_profiler_graph)
3193 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3199 if (! gui_chat_console->isOpen()) {
3200 if (m_game_ui->m_flags.show_cheat_menu)
3201 m_cheat_menu->draw(driver, m_game_ui->m_flags.show_debug);
3202 if (g_settings->getBool("cheat_hud"))
3203 m_cheat_menu->drawHUD(driver, dtime);
3208 if (runData.damage_flash > 0.0f) {
3209 video::SColor color(runData.damage_flash, 180, 0, 0);
3210 if (! g_settings->getBool("no_hurt_cam"))
3211 driver->draw2DRectangle(color, core::rect<s32>(0, 0, screensize.X, screensize.Y), NULL);
3213 runData.damage_flash -= 384.0f * dtime;
3219 if (player->hurt_tilt_timer > 0.0f) {
3220 player->hurt_tilt_timer -= dtime * 6.0f;
3222 if (player->hurt_tilt_timer < 0.0f || g_settings->getBool("no_hurt_cam"))
3223 player->hurt_tilt_strength = 0.0f;
3227 Update minimap pos and rotation
3229 if (mapper && m_game_ui->m_flags.show_hud) {
3230 mapper->setPos(floatToInt(player->getPosition(), BS));
3231 mapper->setAngle(player->getYaw());
3237 if (++m_reset_HW_buffer_counter > 500) {
3239 Periodically remove all mesh HW buffers.
3241 Work around for a quirk in Irrlicht where a HW buffer is only
3242 released after 20000 iterations (triggered from endScene()).
3244 Without this, all loaded but unused meshes will retain their HW
3245 buffers for at least 5 minutes, at which point looking up the HW buffers
3246 becomes a bottleneck and the framerate drops (as much as 30%).
3248 Tests showed that numbers between 50 and 1000 are good, so picked 500.
3249 There are no other public Irrlicht APIs that allow interacting with the
3250 HW buffers without tracking the status of every individual mesh.
3252 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
3254 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
3255 driver->removeAllHardwareBuffers();
3256 m_reset_HW_buffer_counter = 0;
3260 stats->drawtime = tt_draw.stop(true);
3261 g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
3262 g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
3265 /* Log times and stuff for visualization */
3266 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
3268 Profiler::GraphValues values;
3269 g_profiler->graphGet(values);
3275 /****************************************************************************
3277 ****************************************************************************/
3279 /* On some computers framerate doesn't seem to be automatically limited
3281 inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
3283 // not using getRealTime is necessary for wine
3284 device->getTimer()->tick(); // Maker sure device time is up-to-date
3285 u32 time = device->getTimer()->getTime();
3286 u32 last_time = fps_timings->last_time;
3288 if (time > last_time) // Make sure time hasn't overflowed
3289 fps_timings->busy_time = time - last_time;
3291 fps_timings->busy_time = 0;
3293 u32 frametime_min = 1000 / (
3294 device->isWindowFocused() && !g_menumgr.pausesGame()
3295 ? g_settings->getFloat("fps_max")
3296 : g_settings->getFloat("fps_max_unfocused"));
3298 if (fps_timings->busy_time < frametime_min) {
3299 fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
3300 device->sleep(fps_timings->sleep_time);
3302 fps_timings->sleep_time = 0;
3305 /* Get the new value of the device timer. Note that device->sleep() may
3306 * not sleep for the entire requested time as sleep may be interrupted and
3307 * therefore it is arguably more accurate to get the new time from the
3308 * device rather than calculating it by adding sleep_time to time.
3311 device->getTimer()->tick(); // Update device timer
3312 time = device->getTimer()->getTime();
3314 if (time > last_time) // Make sure last_time hasn't overflowed
3315 *dtime = (time - last_time) / 1000.0;
3319 fps_timings->last_time = time;
3322 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
3324 const wchar_t *wmsg = wgettext(msg);
3325 RenderingEngine::draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
3330 void Game::settingChangedCallback(const std::string &setting_name, void *data)
3332 ((Game *)data)->readSettings();
3335 void Game::updateAllMapBlocksCallback(const std::string &setting_name, void *data)
3337 ((Game *) data)->client->updateAllMapBlocks();
3340 void Game::freecamChangedCallback(const std::string &setting_name, void *data)
3342 Game *game = (Game *) data;
3343 LocalPlayer *player = game->client->getEnv().getLocalPlayer();
3344 if (g_settings->getBool("freecam")) {
3345 game->camera->setCameraMode(CAMERA_MODE_FIRST);
3346 player->freecamEnable();
3348 player->freecamDisable();
3350 game->updatePlayerCAOVisibility();
3353 void Game::readSettings()
3355 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
3356 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
3357 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
3358 m_cache_enable_particles = g_settings->getBool("enable_particles");
3359 m_cache_enable_fog = g_settings->getBool("enable_fog");
3360 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
3361 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
3362 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
3364 m_cache_enable_noclip = g_settings->getBool("noclip");
3365 m_cache_enable_free_move = g_settings->getBool("free_move");
3367 m_cache_fog_start = g_settings->getFloat("fog_start");
3369 m_cache_cam_smoothing = 0;
3370 if (g_settings->getBool("cinematic"))
3371 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
3373 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
3375 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
3376 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
3377 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
3379 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
3382 /****************************************************************************/
3383 /****************************************************************************
3385 ****************************************************************************/
3386 /****************************************************************************/
3388 void Game::extendedResourceCleanup()
3390 // Extended resource accounting
3391 infostream << "Irrlicht resources after cleanup:" << std::endl;
3392 infostream << "\tRemaining meshes : "
3393 << RenderingEngine::get_mesh_cache()->getMeshCount() << std::endl;
3394 infostream << "\tRemaining textures : "
3395 << driver->getTextureCount() << std::endl;
3397 for (unsigned int i = 0; i < driver->getTextureCount(); i++) {
3398 irr::video::ITexture *texture = driver->getTextureByIndex(i);
3399 infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str()
3403 clearTextureNameCache();
3404 infostream << "\tRemaining materials: "
3405 << driver-> getMaterialRendererCount()
3406 << " (note: irrlicht doesn't support removing renderers)" << std::endl;
3409 void Game::showDeathFormspec()
3411 static std::string formspec_str =
3412 std::string("formspec_version[1]") +
3414 "bgcolor[#320000b4;true]"
3415 "label[4.85,1.35;" + gettext("You died") + "]"
3416 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
3420 /* Note: FormspecFormSource and LocalFormspecHandler *
3421 * are deleted by guiFormSpecMenu */
3422 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
3423 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
3425 auto *&formspec = m_game_ui->getFormspecGUI();
3426 GUIFormSpecMenu::create(formspec, client, &input->joystick,
3427 fs_src, txt_dst, client->getFormspecPrepend(), sound);
3428 formspec->setFocus("btn_respawn");
3431 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
3432 void Game::showPauseMenu()
3435 static const std::string control_text = strgettext("Default Controls:\n"
3436 "No menu visible:\n"
3437 "- single tap: button activate\n"
3438 "- double tap: place/use\n"
3439 "- slide finger: look around\n"
3440 "Menu/Inventory visible:\n"
3441 "- double tap (outside):\n"
3443 "- touch stack, touch slot:\n"
3445 "- touch&drag, tap 2nd finger\n"
3446 " --> place single item to slot\n"
3449 static const std::string control_text_template = strgettext("Controls:\n"
3450 "- %s: move forwards\n"
3451 "- %s: move backwards\n"
3453 "- %s: move right\n"
3454 "- %s: jump/climb up\n"
3457 "- %s: sneak/climb down\n"
3460 "- %s: enderchest\n"
3461 "- Mouse: turn/look\n"
3462 "- Mouse wheel: select item\n"
3470 char control_text_buf[600];
3472 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
3473 GET_KEY_NAME(keymap_forward),
3474 GET_KEY_NAME(keymap_backward),
3475 GET_KEY_NAME(keymap_left),
3476 GET_KEY_NAME(keymap_right),
3477 GET_KEY_NAME(keymap_jump),
3478 GET_KEY_NAME(keymap_dig),
3479 GET_KEY_NAME(keymap_place),
3480 GET_KEY_NAME(keymap_sneak),
3481 GET_KEY_NAME(keymap_drop),
3482 GET_KEY_NAME(keymap_inventory),
3483 GET_KEY_NAME(keymap_enderchest),
3484 GET_KEY_NAME(keymap_chat),
3485 GET_KEY_NAME(keymap_toggle_killaura),
3486 GET_KEY_NAME(keymap_toggle_freecam),
3487 GET_KEY_NAME(keymap_toggle_scaffold),
3488 GET_KEY_NAME(keymap_toggle_next_item)
3491 std::string control_text = std::string(control_text_buf);
3492 str_formspec_escape(control_text);
3495 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
3496 std::ostringstream os;
3498 os << "formspec_version[1]" << SIZE_TAG
3499 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
3500 << strgettext("Continue") << "]";
3502 if (!simple_singleplayer_mode) {
3503 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
3504 << strgettext("Change Password") << "]";
3506 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
3511 if (g_settings->getBool("enable_sound")) {
3512 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
3513 << strgettext("Sound Volume") << "]";
3516 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
3517 << strgettext("Change Keys") << "]";
3519 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
3520 << strgettext("Exit to Menu") << "]";
3521 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
3522 << strgettext("Exit to OS") << "]"
3523 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
3524 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
3526 << strgettext("Game info:") << "\n";
3527 const std::string &address = client->getAddressName();
3528 static const std::string mode = strgettext("- Mode: ");
3529 if (!simple_singleplayer_mode) {
3530 Address serverAddress = client->getServerAddress();
3531 if (!address.empty()) {
3532 os << mode << strgettext("Remote server") << "\n"
3533 << strgettext("- Address: ") << address;
3535 os << mode << strgettext("Hosting server");
3537 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
3539 os << mode << strgettext("Singleplayer") << "\n";
3541 if (simple_singleplayer_mode || address.empty()) {
3542 static const std::string on = strgettext("On");
3543 static const std::string off = strgettext("Off");
3544 const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
3545 const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
3546 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
3547 os << strgettext("- Damage: ") << damage << "\n"
3548 << strgettext("- Creative Mode: ") << creative << "\n";
3549 if (!simple_singleplayer_mode) {
3550 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
3551 //~ PvP = Player versus Player
3552 os << strgettext("- PvP: ") << pvp << "\n"
3553 << strgettext("- Public: ") << announced << "\n";
3554 std::string server_name = g_settings->get("server_name");
3555 str_formspec_escape(server_name);
3556 if (announced == on && !server_name.empty())
3557 os << strgettext("- Server Name: ") << server_name;
3564 /* Note: FormspecFormSource and LocalFormspecHandler *
3565 * are deleted by guiFormSpecMenu */
3566 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
3567 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
3569 auto *&formspec = m_game_ui->getFormspecGUI();
3570 GUIFormSpecMenu::create(formspec, client, &input->joystick,
3571 fs_src, txt_dst, client->getFormspecPrepend(), sound);
3572 formspec->setFocus("btn_continue");
3573 formspec->doPause = true;
3576 /****************************************************************************/
3577 /****************************************************************************
3578 extern function for launching the game
3579 ****************************************************************************/
3580 /****************************************************************************/
3584 void the_game(bool *kill,
3585 InputHandler *input,
3586 const GameStartData &start_data,
3587 std::string &error_message,
3588 ChatBackend &chat_backend,
3589 bool *reconnect_requested) // Used for local game
3595 /* Make a copy of the server address because if a local singleplayer server
3596 * is created then this is updated and we don't want to change the value
3597 * passed to us by the calling function
3602 if (game.startup(kill, input, start_data, error_message,
3603 reconnect_requested, &chat_backend)) {
3607 } catch (SerializationError &e) {
3608 error_message = std::string("A serialization error occurred:\n")
3609 + e.what() + "\n\nThe server is probably "
3610 " running a different version of " PROJECT_NAME_C ".";
3611 errorstream << error_message << std::endl;
3612 } catch (ServerError &e) {
3613 error_message = e.what();
3614 errorstream << "ServerError: " << error_message << std::endl;
3615 } catch (ModError &e) {
3616 error_message = std::string("ModError: ") + e.what() +
3617 strgettext("\nCheck debug.txt for details.");
3618 errorstream << error_message << std::endl;