lua api: add register_on_item_activate
[waspsaliva.git] / src / client / game.cpp
blob6ec4efa35a7ed225c7040e6b330115e27c4a7a75
1 /*
2 Minetest
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.
20 #include "game.h"
22 #include <iomanip>
23 #include <cmath>
24 #include "client/renderingengine.h"
25 #include "camera.h"
26 #include "client.h"
27 #include "client/clientevent.h"
28 #include "client/gameui.h"
29 #include "client/inputhandler.h"
30 #include "client/tile.h" // For TextureSource
31 #include "client/keys.h"
32 #include "client/joystick_controller.h"
33 #include "clientmap.h"
34 #include "clouds.h"
35 #include "config.h"
36 #include "content_cao.h"
37 #include "content/subgames.h"
38 #include "client/event_manager.h"
39 #include "fontengine.h"
40 #include "itemdef.h"
41 #include "log.h"
42 #include "filesys.h"
43 #include "gameparams.h"
44 #include "gettext.h"
45 #include "gui/guiChatConsole.h"
46 #include "gui/guiConfirmRegistration.h"
47 #include "gui/guiFormSpecMenu.h"
48 #include "gui/guiKeyChangeMenu.h"
49 #include "gui/guiPasswordChange.h"
50 #include "gui/guiVolumeChange.h"
51 #include "gui/mainmenumanager.h"
52 #include "gui/profilergraph.h"
53 #include "mapblock.h"
54 #include "minimap.h"
55 #include "nodedef.h" // Needed for determining pointing to nodes
56 #include "nodemetadata.h"
57 #include "particles.h"
58 #include "porting.h"
59 #include "profiler.h"
60 #include "raycast.h"
61 #include "server.h"
62 #include "settings.h"
63 #include "shader.h"
64 #include "sky.h"
65 #include "translation.h"
66 #include "util/basic_macros.h"
67 #include "util/directiontables.h"
68 #include "util/pointedthing.h"
69 #include "util/quicktune_shortcutter.h"
70 #include "irrlicht_changes/static_text.h"
71 #include "version.h"
72 #include "script/scripting_client.h"
73 #include "hud.h"
75 #if USE_SOUND
76 #include "client/sound_openal.h"
77 #else
78 #include "client/sound.h"
79 #endif
81 Game::Game() :
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);
122 readSettings();
124 #ifdef __ANDROID__
125 m_cache_hold_aux1 = false; // This is initialised properly later
126 #endif
132 /****************************************************************************
133 MinetestApp Public
134 ****************************************************************************/
136 Game::~Game()
138 delete client;
139 delete soundmaker;
140 if (!sound_is_dummy)
141 delete sound;
143 delete server; // deleted first to stop all server threads
145 delete hud;
146 delete camera;
147 delete quicktune;
148 delete eventmgr;
149 delete texture_src;
150 delete shader_src;
151 delete nodedef_manager;
152 delete itemdef_manager;
153 delete draw_control;
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,
192 InputHandler *input,
193 const GameStartData &start_data,
194 std::string &error_message,
195 bool *reconnect,
196 ChatBackend *chat_backend)
199 // "cache"
200 this->device = RenderingEngine::get_raw_device();
201 this->kill = kill;
202 this->error_message = &error_message;
203 this->reconnect_requested = reconnect;
204 this->input = input;
205 this->chat_backend = chat_backend;
206 this->simple_singleplayer_mode = start_data.isSinglePlayer();
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);
216 // Reinit runData
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))
230 return false;
232 if (!createClient(start_data))
233 return false;
235 RenderingEngine::initialize(client, hud);
237 return true;
241 void Game::run()
243 ProfilerGraph graph;
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"));
256 #ifdef __ANDROID__
257 m_cache_hold_aux1 = g_settings->getBool("fast_move")
258 && client->checkPrivilege("fast");
259 #endif
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> &current_screen_size =
269 RenderingEngine::get_video_driver()->getScreenSize();
270 // Verify if window size has changed and save it if it's the case
271 // Ensure evaluating settings->getBool after verifying screensize
272 // First condition is cheaper
273 if (previous_screen_size != current_screen_size &&
274 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
275 g_settings->getBool("autosave_screensize")) {
276 g_settings->setU16("screen_w", current_screen_size.Width);
277 g_settings->setU16("screen_h", current_screen_size.Height);
278 previous_screen_size = current_screen_size;
281 // Calculate dtime =
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())
292 break;
293 if (!handleCallbacks())
294 break;
296 processQueues();
298 m_game_ui->clearInfoText();
299 hud->resizeHotbar();
301 updateProfilers(stats, draw_times, dtime);
302 processUserInput(dtime);
303 // Update camera before player movement to avoid camera lag of one frame
304 updateCameraDirection(&cam_view_target, dtime);
305 cam_view.camera_yaw += (cam_view_target.camera_yaw -
306 cam_view.camera_yaw) * m_cache_cam_smoothing;
307 cam_view.camera_pitch += (cam_view_target.camera_pitch -
308 cam_view.camera_pitch) * m_cache_cam_smoothing;
309 updatePlayerControl(cam_view);
310 step(&dtime);
311 processClientEvents(&cam_view_target);
312 updateCamera(draw_times.busy_time, dtime);
313 updateSound(dtime);
314 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
315 m_game_ui->m_flags.show_debug);
316 updateFrame(&graph, &stats, dtime, cam_view);
317 updateProfilerGraphs(&graph);
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()) {
323 showPauseMenu();
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);
336 #endif
337 auto formspec = m_game_ui->getFormspecGUI();
338 if (formspec)
339 formspec->quitMenu();
341 #ifdef HAVE_TOUCHSCREENGUI
342 g_touchscreengui->hide();
343 #endif
345 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
347 if (clouds)
348 clouds->drop();
350 if (gui_chat_console)
351 gui_chat_console->drop();
353 if (m_cheat_menu)
354 delete m_cheat_menu;
356 if (sky)
357 sky->drop();
359 /* cleanup menus */
360 while (g_menumgr.menuCount() > 0) {
361 g_menumgr.m_stack.front()->setVisible(false);
362 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
365 m_game_ui->deleteFormspec();
367 chat_backend->addMessage(L"", L"# Disconnected.");
368 chat_backend->addMessage(L"", L"");
369 m_chat_log_buf.clear();
371 if (client) {
372 client->Stop();
373 while (!client->isShutdown()) {
374 assert(texture_src != NULL);
375 assert(shader_src != NULL);
376 texture_src->processQueue();
377 shader_src->processQueue();
378 sleep_ms(100);
384 /****************************************************************************/
385 /****************************************************************************
386 Startup
387 ****************************************************************************/
388 /****************************************************************************/
390 bool Game::init(
391 const std::string &map_dir,
392 const std::string &address,
393 u16 port,
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))
410 return false;
412 if (!initSound())
413 return false;
415 // Create a server if not connecting to an existing one
416 if (address.empty()) {
417 if (!createSingleplayerServer(map_dir, gamespec, port))
418 return false;
421 return true;
424 bool Game::initSound()
426 #if USE_SOUND
427 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
428 infostream << "Attempting to use OpenAL audio" << std::endl;
429 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
430 if (!sound)
431 infostream << "Failed to initialize OpenAL audio" << std::endl;
432 } else
433 infostream << "Sound disabled." << std::endl;
434 #endif
436 if (!sound) {
437 infostream << "Using dummy audio." << std::endl;
438 sound = &dummySoundManager;
439 sound_is_dummy = true;
442 soundmaker = new SoundMaker(sound, nodedef_manager);
443 if (!soundmaker)
444 return false;
446 soundmaker->registerReceiver(eventmgr);
448 return true;
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);
463 try {
464 bind_addr.Resolve(bind_str.c_str());
465 } catch (ResolveError &e) {
466 infostream << "Resolving bind address \"" << bind_str
467 << "\" failed: " << e.what()
468 << " -- Listening on all addresses." << std::endl;
471 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
472 *error_message = "Unable to listen on " +
473 bind_addr.serializeString() +
474 " because IPv6 is disabled";
475 errorstream << *error_message << std::endl;
476 return false;
479 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
480 false, nullptr, error_message);
481 server->start();
483 return true;
486 bool Game::createClient(const GameStartData &start_data)
488 showOverlayMessage(N_("Creating client..."), 0, 10);
490 draw_control = new MapDrawControl;
491 if (!draw_control)
492 return false;
494 bool could_connect, connect_aborted;
495 #ifdef HAVE_TOUCHSCREENGUI
496 if (g_touchscreengui) {
497 g_touchscreengui->init(texture_src);
498 g_touchscreengui->hide();
500 #endif
501 if (!connectToServer(start_data, &could_connect, &connect_aborted))
502 return false;
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;
510 return false;
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;
519 return false;
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();
529 /* Camera
531 camera = new Camera(*draw_control, client);
532 if (!camera || !camera->successfullyCreated(*error_message))
533 return false;
534 client->setCamera(camera);
536 /* Clouds
538 if (m_cache_enable_clouds) {
539 clouds = new Clouds(smgr, -1, time(0));
540 if (!clouds) {
541 *error_message = "Memory allocation error (clouds)";
542 errorstream << *error_message << std::endl;
543 return false;
547 /* Skybox
549 sky = new Sky(-1, texture_src, shader_src);
550 scsf->setSky(sky);
551 skybox = NULL; // This is used/set later on in the main run loop
553 if (!sky) {
554 *error_message = "Memory allocation error sky";
555 errorstream << *error_message << std::endl;
556 return false;
559 /* Pre-calculated values
561 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
562 if (t) {
563 v2u32 size = t->getOriginalSize();
564 crack_animation_length = size.Y / size.X;
565 } else {
566 crack_animation_length = 5;
569 if (!initGui())
570 return false;
572 /* Set window caption
574 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
575 str += L" ";
576 str += utf8_to_wide(g_version_hash);
577 str += L" [";
578 str += L"Minetest Hackclient";
579 str += L"]";
580 device->setWindowCaption(str.c_str());
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);
588 if (!hud) {
589 *error_message = "Memory error: could not create HUD";
590 errorstream << *error_message << std::endl;
591 return false;
594 mapper = client->getMinimap();
596 if (mapper && client->modsLoaded())
597 client->getScript()->on_minimap_ready(mapper);
599 return true;
602 bool Game::initGui()
604 m_game_ui->init();
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;
619 return false;
622 m_cheat_menu = new CheatMenu(client);
624 if (!m_cheat_menu) {
625 *error_message = "Could not allocate memory for cheat menu";
626 errorstream << *error_message << std::endl;
627 return false;
630 #ifdef HAVE_TOUCHSCREENGUI
632 if (g_touchscreengui)
633 g_touchscreengui->show();
635 #endif
637 return true;
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);
651 try {
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);
660 } else {
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;
668 return false;
671 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
672 *error_message = "Unable to connect to " +
673 connect_address.serializeString() +
674 " because IPv6 is disabled";
675 errorstream << *error_message << std::endl;
676 return false;
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());
685 if (!client)
686 return false;
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
701 try {
702 input->clear();
704 FpsControl fps_control = { 0 };
705 f32 dtime;
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
715 client->step(dtime);
717 if (server != NULL)
718 server->step(dtime);
720 // End condition
721 if (client->getState() == LC_Init) {
722 *connect_ok = true;
723 break;
726 // Break conditions
727 if (*connection_aborted)
728 break;
730 if (client->accessDenied()) {
731 *error_message = "Access denied. Reason: "
732 + client->accessDeniedReason();
733 *reconnect_requested = client->reconnectRequested();
734 errorstream << *error_message << std::endl;
735 break;
738 if (input->cancelPressed()) {
739 *connection_aborted = true;
740 infostream << "Connect aborted [Escape]" << std::endl;
741 break;
744 if (client->m_is_registration_confirmation_state) {
745 if (registration_confirmation_shown) {
746 // Keep drawing the GUI
747 RenderingEngine::draw_menu_scene(guienv, dtime, true);
748 } else {
749 registration_confirmation_shown = true;
750 (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
751 &g_menumgr, client, start_data.name, start_data.password,
752 connection_aborted, texture_src))->drop();
754 } else {
755 wait_time += dtime;
756 // Only time out if we aren't waiting for the server we started
757 if (!start_data.isSinglePlayer() && wait_time > 10) {
758 *error_message = "Connection timed out.";
759 errorstream << *error_message << std::endl;
760 break;
763 // Update status
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
769 // message?
770 return false;
773 return true;
776 bool Game::getServerContent(bool *aborted)
778 input->clear();
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
790 client->step(dtime);
792 if (server != NULL)
793 server->step(dtime);
795 // End condition
796 if (client->mediaReceived() && client->itemdefReceived() &&
797 client->nodedefReceived()) {
798 break;
801 // Error conditions
802 if (!checkConnection())
803 return false;
805 if (client->getState() < LC_Init) {
806 *error_message = "Client disconnected";
807 errorstream << *error_message << std::endl;
808 return false;
811 if (input->cancelPressed()) {
812 *aborted = true;
813 infostream << "Connect aborted [Escape]" << std::endl;
814 return false;
817 // Display status
818 int progress = 25;
820 if (!client->itemdefReceived()) {
821 const wchar_t *text = wgettext("Item definitions...");
822 progress = 25;
823 RenderingEngine::draw_load_screen(text, guienv, texture_src,
824 dtime, progress);
825 delete[] text;
826 } else if (!client->nodedefReceived()) {
827 const wchar_t *text = wgettext("Node definitions...");
828 progress = 30;
829 RenderingEngine::draw_load_screen(text, guienv, texture_src,
830 dtime, progress);
831 delete[] text;
832 } else {
833 std::stringstream message;
834 std::fixed(message);
835 message.precision(0);
836 float receive = client->mediaReceiveProgress() * 100;
837 message << gettext("Media...");
838 if (receive > 0)
839 message << " " << receive << "%";
840 message.precision(2);
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");
847 if (cur > 900) {
848 cur /= 1024.0;
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);
861 return true;
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;
892 return false;
895 return true;
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;
905 return 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;
931 return true;
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,
944 f32 dtime)
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)) {
956 if (print_to_log) {
957 infostream << "Profiler:" << std::endl;
958 g_profiler->print(infostream);
961 m_game_ui->updateProfiler();
962 g_profiler->clear();
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,
974 f32 dtime)
977 f32 jitter;
978 Jitter *jp;
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)
988 jp->max = jitter;
990 jp->counter += dtime;
992 if (jp->counter > 0.0) {
993 jp->counter -= 3.0;
994 jp->max_sample = jp->max;
995 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
996 jp->max = 0.0;
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)
1007 jp->max = jitter;
1008 if (jitter < jp->min)
1009 jp->min = jitter;
1011 jp->counter += dtime;
1013 if (jp->counter > 0.0) {
1014 jp->counter -= 3.0;
1015 jp->max_sample = jp->max;
1016 jp->min_sample = jp->min;
1017 jp->max = 0.0;
1018 jp->min = 0.0;
1024 /****************************************************************************
1025 Input handling
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)) {
1032 input->clear();
1033 #ifdef HAVE_TOUCHSCREENGUI
1034 g_touchscreengui->hide();
1035 #endif
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);
1043 #endif
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)
1050 input->step(dtime);
1052 #ifdef __ANDROID__
1053 auto formspec = m_game_ui->getFormspecGUI();
1054 if (formspec)
1055 formspec->getAndroidUIInput();
1056 else
1057 handleAndroidChatInput();
1058 #endif
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;
1064 processKeyInput();
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)) {
1091 openInventory();
1092 } else if (wasKeyDown(KeyType::ENDERCHEST)) {
1093 openEnderchest();
1094 } else if (input->cancelPressed()) {
1095 #ifdef __ANDROID__
1096 m_android_chat_open = false;
1097 #endif
1098 if (!gui_chat_console->isOpenInhibited()) {
1099 showPauseMenu();
1101 } else if (wasKeyDown(KeyType::CHAT)) {
1102 openConsole(0.2, L"");
1103 } else if (wasKeyDown(KeyType::CMD)) {
1104 openConsole(0.2, L"/");
1105 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1106 if (client->modsLoaded())
1107 openConsole(0.2, L".");
1108 else
1109 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1110 } else if (wasKeyDown(KeyType::CONSOLE)) {
1111 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1112 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1113 toggleFreeMove();
1114 } else if (wasKeyDown(KeyType::JUMP)) {
1115 toggleFreeMoveAlt();
1116 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1117 togglePitchMove();
1118 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1119 toggleFast();
1120 } else if (wasKeyDown(KeyType::NOCLIP)) {
1121 toggleNoClip();
1122 } else if (wasKeyDown(KeyType::KILLAURA)) {
1123 toggleKillaura();
1124 } else if (wasKeyDown(KeyType::FREECAM)) {
1125 toggleFreecam();
1126 } else if (wasKeyDown(KeyType::SCAFFOLD)) {
1127 toggleScaffold();
1128 #if USE_SOUND
1129 } else if (wasKeyDown(KeyType::MUTE)) {
1130 if (g_settings->getBool("enable_sound")) {
1131 bool new_mute_sound = !g_settings->getBool("mute_sound");
1132 g_settings->setBool("mute_sound", new_mute_sound);
1133 if (new_mute_sound)
1134 m_game_ui->showTranslatedStatusText("Sound muted");
1135 else
1136 m_game_ui->showTranslatedStatusText("Sound unmuted");
1137 } else {
1138 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1140 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1141 if (g_settings->getBool("enable_sound")) {
1142 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1143 wchar_t buf[100];
1144 g_settings->setFloat("sound_volume", new_volume);
1145 const wchar_t *str = wgettext("Volume changed to %d%%");
1146 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1147 delete[] str;
1148 m_game_ui->showStatusText(buf);
1149 } else {
1150 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1152 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1153 if (g_settings->getBool("enable_sound")) {
1154 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1155 wchar_t buf[100];
1156 g_settings->setFloat("sound_volume", new_volume);
1157 const wchar_t *str = wgettext("Volume changed to %d%%");
1158 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1159 delete[] str;
1160 m_game_ui->showStatusText(buf);
1161 } else {
1162 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1164 #else
1165 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1166 || wasKeyDown(KeyType::DEC_VOLUME)) {
1167 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1168 #endif
1169 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1170 toggleCinematic();
1171 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1172 client->makeScreenshot();
1173 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1174 m_game_ui->toggleHud();
1175 } else if (wasKeyDown(KeyType::MINIMAP)) {
1176 toggleMinimap(isKeyDown(KeyType::SNEAK));
1177 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1178 m_game_ui->toggleChat();
1179 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1180 toggleFog();
1181 } else if (wasKeyDown(KeyType::TOGGLE_CHEAT_MENU)) {
1182 m_game_ui->toggleCheatMenu();
1183 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1184 toggleUpdateCamera();
1185 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1186 toggleDebug();
1187 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1188 m_game_ui->toggleProfiler();
1189 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1190 increaseViewRange();
1191 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1192 decreaseViewRange();
1193 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1194 toggleFullViewRange();
1195 } else if (wasKeyDown(KeyType::ZOOM)) {
1196 checkZoomEnabled();
1197 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1198 quicktune->next();
1199 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1200 quicktune->prev();
1201 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1202 quicktune->inc();
1203 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1204 quicktune->dec();
1207 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1208 runData.reset_jump_timer = false;
1209 runData.jump_timer = 0.0f;
1212 if (quicktune->hasMessage()) {
1213 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
1217 void Game::processItemSelection(u16 *new_playeritem)
1219 LocalPlayer *player = client->getEnv().getLocalPlayer();
1221 /* Item selection using mouse wheel
1223 *new_playeritem = player->getWieldIndex();
1225 s32 wheel = input->getMouseWheel();
1226 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
1227 player->hud_hotbar_itemcount - 1);
1229 s32 dir = wheel;
1231 if (wasKeyDown(KeyType::HOTBAR_NEXT))
1232 dir = -1;
1234 if (wasKeyDown(KeyType::HOTBAR_PREV))
1235 dir = 1;
1237 if (dir < 0)
1238 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
1239 else if (dir > 0)
1240 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
1241 // else dir == 0
1243 /* Item selection using hotbar slot keys
1245 for (u16 i = 0; i <= max_item; i++) {
1246 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
1247 *new_playeritem = i;
1248 break;
1254 void Game::dropSelectedItem(bool single_item)
1256 IDropAction *a = new IDropAction();
1257 a->count = single_item ? 1 : 0;
1258 a->from_inv.setCurrentPlayer();
1259 a->from_list = "main";
1260 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
1261 client->inventoryAction(a);
1265 void Game::openInventory()
1268 * Don't permit to open inventory is CAO or player doesn't exists.
1269 * This prevent showing an empty inventory at player load
1272 LocalPlayer *player = client->getEnv().getLocalPlayer();
1273 if (!player || !player->getCAO())
1274 return;
1276 infostream << "Game: Launching inventory" << std::endl;
1278 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
1280 InventoryLocation inventoryloc;
1281 inventoryloc.setCurrentPlayer();
1283 if (!client->modsLoaded()
1284 || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
1285 TextDest *txt_dst = new TextDestPlayerInventory(client);
1286 auto *&formspec = m_game_ui->updateFormspec("");
1287 GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
1288 txt_dst, client->getFormspecPrepend(), sound);
1290 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
1294 void Game::openEnderchest()
1296 LocalPlayer *player = client->getEnv().getLocalPlayer();
1297 if (!player || !player->getCAO())
1298 return;
1300 infostream << "Game: Launching special inventory" << std::endl;
1302 if (client->modsLoaded())
1303 client->getScript()->open_enderchest();
1307 void Game::openConsole(float scale, const wchar_t *line)
1309 assert(scale > 0.0f && scale <= 1.0f);
1311 #ifdef __ANDROID__
1312 porting::showInputDialog(gettext("ok"), "", "", 2);
1313 m_android_chat_open = true;
1314 #else
1315 if (gui_chat_console->isOpenInhibited())
1316 return;
1317 gui_chat_console->openConsole(scale);
1318 if (line) {
1319 gui_chat_console->setCloseOnEnter(true);
1320 gui_chat_console->replaceAndAddToHistory(line);
1322 #endif
1325 #ifdef __ANDROID__
1326 void Game::handleAndroidChatInput()
1328 if (m_android_chat_open && porting::getInputDialogState() == 0) {
1329 std::string text = porting::getInputDialogValue();
1330 client->typeChatMessage(utf8_to_wide(text));
1331 m_android_chat_open = false;
1334 #endif
1337 void Game::toggleFreeMove()
1339 bool free_move = !g_settings->getBool("free_move");
1340 g_settings->set("free_move", bool_to_cstr(free_move));
1342 if (free_move) {
1343 if (client->checkPrivilege("fly")) {
1344 m_game_ui->showTranslatedStatusText("Fly mode enabled");
1345 } else {
1346 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
1348 } else {
1349 m_game_ui->showTranslatedStatusText("Fly mode disabled");
1353 void Game::toggleFreeMoveAlt()
1355 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
1356 toggleFreeMove();
1358 runData.reset_jump_timer = true;
1362 void Game::togglePitchMove()
1364 bool pitch_move = !g_settings->getBool("pitch_move");
1365 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
1367 if (pitch_move) {
1368 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
1369 } else {
1370 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
1375 void Game::toggleFast()
1377 bool fast_move = !g_settings->getBool("fast_move");
1378 bool has_fast_privs = client->checkPrivilege("fast");
1379 g_settings->set("fast_move", bool_to_cstr(fast_move));
1381 if (fast_move) {
1382 if (has_fast_privs) {
1383 m_game_ui->showTranslatedStatusText("Fast mode enabled");
1384 } else {
1385 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
1387 } else {
1388 m_game_ui->showTranslatedStatusText("Fast mode disabled");
1391 #ifdef __ANDROID__
1392 m_cache_hold_aux1 = fast_move && has_fast_privs;
1393 #endif
1397 void Game::toggleNoClip()
1399 bool noclip = !g_settings->getBool("noclip");
1400 g_settings->set("noclip", bool_to_cstr(noclip));
1402 if (noclip) {
1403 if (client->checkPrivilege("noclip")) {
1404 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
1405 } else {
1406 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
1408 } else {
1409 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
1413 void Game::toggleKillaura()
1415 bool killaura = ! g_settings->getBool("killaura");
1416 g_settings->set("killaura", bool_to_cstr(killaura));
1418 if (killaura) {
1419 m_game_ui->showTranslatedStatusText("Killaura enabled");
1420 } else {
1421 m_game_ui->showTranslatedStatusText("Killaura disabled");
1425 void Game::toggleFreecam()
1427 bool freecam = ! g_settings->getBool("freecam");
1428 g_settings->set("freecam", bool_to_cstr(freecam));
1430 if (freecam) {
1431 m_game_ui->showTranslatedStatusText("Freecam enabled");
1432 } else {
1433 m_game_ui->showTranslatedStatusText("Freecam disabled");
1437 void Game::toggleScaffold()
1439 bool scaffold = ! g_settings->getBool("scaffold");
1440 g_settings->set("scaffold", bool_to_cstr(scaffold));
1442 if (scaffold) {
1443 m_game_ui->showTranslatedStatusText("Scaffold enabled");
1444 } else {
1445 m_game_ui->showTranslatedStatusText("Scaffold disabled");
1449 void Game::toggleCinematic()
1451 bool cinematic = !g_settings->getBool("cinematic");
1452 g_settings->set("cinematic", bool_to_cstr(cinematic));
1454 if (cinematic)
1455 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
1456 else
1457 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
1460 // Autoforward by toggling continuous forward.
1461 void Game::toggleAutoforward()
1463 bool autorun_enabled = !g_settings->getBool("continuous_forward");
1464 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
1466 if (autorun_enabled)
1467 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
1468 else
1469 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
1472 void Game::toggleMinimap(bool shift_pressed)
1474 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
1475 return;
1477 if (shift_pressed)
1478 mapper->toggleMinimapShape();
1479 else
1480 mapper->nextMode();
1482 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
1484 // Not so satisying code to keep compatibility with old fixed mode system
1485 // -->
1486 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
1488 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
1489 m_game_ui->m_flags.show_minimap = false;
1490 } else {
1492 // If radar is disabled, try to find a non radar mode or fall back to 0
1493 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
1494 while (mapper->getModeIndex() &&
1495 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
1496 mapper->nextMode();
1498 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
1499 MINIMAP_TYPE_OFF;
1501 // <--
1502 // End of 'not so satifying code'
1503 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
1504 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
1505 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
1506 else
1507 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
1510 void Game::toggleFog()
1512 bool fog_enabled = g_settings->getBool("enable_fog");
1513 g_settings->setBool("enable_fog", !fog_enabled);
1514 if (fog_enabled)
1515 m_game_ui->showTranslatedStatusText("Fog disabled");
1516 else
1517 m_game_ui->showTranslatedStatusText("Fog enabled");
1521 void Game::toggleDebug()
1523 // Initial / 4x toggle: Chat only
1524 // 1x toggle: Debug text with chat
1525 // 2x toggle: Debug text with profiler graph
1526 // 3x toggle: Debug text and wireframe
1527 if (!m_game_ui->m_flags.show_debug) {
1528 m_game_ui->m_flags.show_debug = true;
1529 m_game_ui->m_flags.show_profiler_graph = false;
1530 draw_control->show_wireframe = false;
1531 m_game_ui->showTranslatedStatusText("Debug info shown");
1532 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
1533 m_game_ui->m_flags.show_profiler_graph = true;
1534 m_game_ui->showTranslatedStatusText("Profiler graph shown");
1535 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
1536 m_game_ui->m_flags.show_profiler_graph = false;
1537 draw_control->show_wireframe = true;
1538 m_game_ui->showTranslatedStatusText("Wireframe shown");
1539 } else {
1540 m_game_ui->m_flags.show_debug = false;
1541 m_game_ui->m_flags.show_profiler_graph = false;
1542 draw_control->show_wireframe = false;
1543 if (client->checkPrivilege("debug")) {
1544 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
1545 } else {
1546 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
1552 void Game::toggleUpdateCamera()
1554 if (g_settings->getBool("freecam"))
1555 return;
1556 m_flags.disable_camera_update = !m_flags.disable_camera_update;
1557 if (m_flags.disable_camera_update)
1558 m_game_ui->showTranslatedStatusText("Camera update disabled");
1559 else
1560 m_game_ui->showTranslatedStatusText("Camera update enabled");
1563 void Game::show_huds()
1565 m_game_ui->m_flags.show_hud=true;
1566 m_game_ui->m_flags.show_cheat_menu=true;
1567 m_game_ui->m_flags.show_chat=true;
1569 void Game::hide_huds()
1571 m_game_ui->m_flags.show_hud=false;
1572 m_game_ui->m_flags.show_cheat_menu=false;
1573 m_game_ui->m_flags.show_chat=false;
1577 void Game::increaseViewRange()
1579 s16 range = g_settings->getS16("viewing_range");
1580 s16 range_new = range + 10;
1582 wchar_t buf[255];
1583 const wchar_t *str;
1584 if (range_new > 4000) {
1585 range_new = 4000;
1586 str = wgettext("Viewing range is at maximum: %d");
1587 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
1588 delete[] str;
1589 m_game_ui->showStatusText(buf);
1591 } else {
1592 str = wgettext("Viewing range changed to %d");
1593 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
1594 delete[] str;
1595 m_game_ui->showStatusText(buf);
1597 g_settings->set("viewing_range", itos(range_new));
1601 void Game::decreaseViewRange()
1603 s16 range = g_settings->getS16("viewing_range");
1604 s16 range_new = range - 10;
1606 wchar_t buf[255];
1607 const wchar_t *str;
1608 if (range_new < 20) {
1609 range_new = 20;
1610 str = wgettext("Viewing range is at minimum: %d");
1611 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
1612 delete[] str;
1613 m_game_ui->showStatusText(buf);
1614 } else {
1615 str = wgettext("Viewing range changed to %d");
1616 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
1617 delete[] str;
1618 m_game_ui->showStatusText(buf);
1620 g_settings->set("viewing_range", itos(range_new));
1624 void Game::toggleFullViewRange()
1626 draw_control->range_all = !draw_control->range_all;
1627 if (draw_control->range_all)
1628 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
1629 else
1630 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
1634 void Game::checkZoomEnabled()
1636 LocalPlayer *player = client->getEnv().getLocalPlayer();
1637 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
1638 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
1642 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
1644 if ((device->isWindowActive() && device->isWindowFocused()
1645 && !isMenuActive()) || input->isRandom()) {
1647 #ifndef __ANDROID__
1648 if (!input->isRandom()) {
1649 // Mac OSX gets upset if this is set every frame
1650 if (device->getCursorControl()->isVisible())
1651 device->getCursorControl()->setVisible(false);
1653 #endif
1655 if (m_first_loop_after_window_activation) {
1656 m_first_loop_after_window_activation = false;
1658 input->setMousePos(driver->getScreenSize().Width / 2,
1659 driver->getScreenSize().Height / 2);
1660 } else {
1661 updateCameraOrientation(cam, dtime);
1664 } else {
1666 #ifndef ANDROID
1667 // Mac OSX gets upset if this is set every frame
1668 if (!device->getCursorControl()->isVisible())
1669 device->getCursorControl()->setVisible(true);
1670 #endif
1672 m_first_loop_after_window_activation = true;
1677 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
1679 #ifdef HAVE_TOUCHSCREENGUI
1680 if (g_touchscreengui) {
1681 cam->camera_yaw += g_touchscreengui->getYawChange();
1682 cam->camera_pitch = g_touchscreengui->getPitch();
1683 } else {
1684 #endif
1685 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
1686 v2s32 dist = input->getMousePos() - center;
1688 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
1689 dist.Y = -dist.Y;
1692 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity;
1693 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity;
1695 if (dist.X != 0 || dist.Y != 0)
1696 input->setMousePos(center.X, center.Y);
1697 #ifdef HAVE_TOUCHSCREENGUI
1699 #endif
1701 if (m_cache_enable_joysticks) {
1702 f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime;
1703 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
1704 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
1707 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
1711 void Game::updatePlayerControl(const CameraOrientation &cam)
1713 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
1715 // DO NOT use the isKeyDown method for the forward, backward, left, right
1716 // buttons, as the code that uses the controls needs to be able to
1717 // distinguish between the two in order to know when to use joysticks.
1719 PlayerControl control(
1720 input->isKeyDown(KeyType::FORWARD),
1721 input->isKeyDown(KeyType::BACKWARD),
1722 input->isKeyDown(KeyType::LEFT),
1723 input->isKeyDown(KeyType::RIGHT),
1724 isKeyDown(KeyType::JUMP),
1725 isKeyDown(KeyType::SPECIAL1),
1726 isKeyDown(KeyType::SNEAK),
1727 isKeyDown(KeyType::ZOOM),
1728 isKeyDown(KeyType::DIG),
1729 isKeyDown(KeyType::PLACE),
1730 cam.camera_pitch,
1731 cam.camera_yaw,
1732 input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE),
1733 input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE)
1736 u32 keypress_bits = (
1737 ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) |
1738 ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) |
1739 ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) |
1740 ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) |
1741 ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) |
1742 ( (u32)(isKeyDown(KeyType::SPECIAL1) & 0x1) << 5) |
1743 ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) |
1744 ( (u32)(isKeyDown(KeyType::DIG) & 0x1) << 7) |
1745 ( (u32)(isKeyDown(KeyType::PLACE) & 0x1) << 8) |
1746 ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9)
1749 #ifdef ANDROID
1750 /* For Android, simulate holding down AUX1 (fast move) if the user has
1751 * the fast_move setting toggled on. If there is an aux1 key defined for
1752 * Android then its meaning is inverted (i.e. holding aux1 means walk and
1753 * not fast)
1755 if (m_cache_hold_aux1) {
1756 control.aux1 = control.aux1 ^ true;
1757 keypress_bits ^= ((u32)(1U << 5));
1759 #endif
1761 LocalPlayer *player = client->getEnv().getLocalPlayer();
1763 // autojump if set: simulate "jump" key
1764 if (player->getAutojump()) {
1765 control.jump = true;
1766 keypress_bits |= 1U << 4;
1769 // autoforward if set: simulate "up" key
1770 if (player->getPlayerSettings().continuous_forward &&
1771 client->activeObjectsReceived() && !player->isDead()) {
1772 control.up = true;
1773 keypress_bits |= 1U << 0;
1776 client->setPlayerControl(control);
1777 player->keyPressed = keypress_bits;
1779 //tt.stop();
1783 inline void Game::step(f32 *dtime)
1785 bool can_be_and_is_paused =
1786 (simple_singleplayer_mode && g_menumgr.pausesGame());
1788 if (can_be_and_is_paused) { // This is for a singleplayer server
1789 *dtime = 0; // No time passes
1790 } else {
1791 if (server)
1792 server->step(*dtime);
1794 client->step(*dtime);
1798 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
1799 {&Game::handleClientEvent_None},
1800 {&Game::handleClientEvent_PlayerDamage},
1801 {&Game::handleClientEvent_PlayerForceMove},
1802 {&Game::handleClientEvent_Deathscreen},
1803 {&Game::handleClientEvent_ShowFormSpec},
1804 {&Game::handleClientEvent_ShowLocalFormSpec},
1805 {&Game::handleClientEvent_HandleParticleEvent},
1806 {&Game::handleClientEvent_HandleParticleEvent},
1807 {&Game::handleClientEvent_HandleParticleEvent},
1808 {&Game::handleClientEvent_HudAdd},
1809 {&Game::handleClientEvent_HudRemove},
1810 {&Game::handleClientEvent_HudChange},
1811 {&Game::handleClientEvent_SetSky},
1812 {&Game::handleClientEvent_SetSun},
1813 {&Game::handleClientEvent_SetMoon},
1814 {&Game::handleClientEvent_SetStars},
1815 {&Game::handleClientEvent_OverrideDayNigthRatio},
1816 {&Game::handleClientEvent_CloudParams},
1819 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
1821 FATAL_ERROR("ClientEvent type None received");
1824 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
1826 if (client->modsLoaded())
1827 client->getScript()->on_damage_taken(event->player_damage.amount);
1829 // Damage flash and hurt tilt are not used at death
1830 if (client->getHP() > 0) {
1831 runData.damage_flash += 95.0f + 3.2f * event->player_damage.amount;
1832 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
1834 LocalPlayer *player = client->getEnv().getLocalPlayer();
1836 player->hurt_tilt_timer = 1.5f;
1837 player->hurt_tilt_strength =
1838 rangelim(event->player_damage.amount / 4.0f, 1.0f, 4.0f);
1841 // Play damage sound
1842 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
1845 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
1847 cam->camera_yaw = event->player_force_move.yaw;
1848 cam->camera_pitch = event->player_force_move.pitch;
1851 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
1853 // If client scripting is enabled, deathscreen is handled by CSM code in
1854 // builtin/client/init.lua
1855 if (client->modsLoaded())
1856 client->getScript()->on_death();
1857 else
1858 showDeathFormspec();
1860 /* Handle visualization */
1861 LocalPlayer *player = client->getEnv().getLocalPlayer();
1862 runData.damage_flash = 0;
1863 player->hurt_tilt_timer = 0;
1864 player->hurt_tilt_strength = 0;
1867 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
1869 if (event->show_formspec.formspec->empty()) {
1870 auto formspec = m_game_ui->getFormspecGUI();
1871 if (formspec && (event->show_formspec.formname->empty()
1872 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
1873 formspec->quitMenu();
1875 } else {
1876 FormspecFormSource *fs_src =
1877 new FormspecFormSource(*(event->show_formspec.formspec));
1878 TextDestPlayerInventory *txt_dst =
1879 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
1881 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
1882 GUIFormSpecMenu::create(formspec, client, &input->joystick,
1883 fs_src, txt_dst, client->getFormspecPrepend(), sound);
1886 delete event->show_formspec.formspec;
1887 delete event->show_formspec.formname;
1890 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
1892 if (event->show_formspec.formspec->empty()) {
1893 auto formspec = m_game_ui->getFormspecGUI();
1894 if (formspec && (event->show_formspec.formname->empty()
1895 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
1896 formspec->quitMenu();
1898 } else {
1899 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
1900 LocalFormspecHandler *txt_dst =
1901 new LocalFormspecHandler(*event->show_formspec.formname, client);
1902 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, &input->joystick,
1903 fs_src, txt_dst, client->getFormspecPrepend(), sound);
1906 delete event->show_formspec.formspec;
1907 delete event->show_formspec.formname;
1910 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
1911 CameraOrientation *cam)
1913 LocalPlayer *player = client->getEnv().getLocalPlayer();
1914 client->getParticleManager()->handleParticleEvent(event, client, player);
1917 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
1919 LocalPlayer *player = client->getEnv().getLocalPlayer();
1920 auto &hud_server_to_client = client->getHUDTranslationMap();
1922 u32 server_id = event->hudadd.server_id;
1923 // ignore if we already have a HUD with that ID
1924 auto i = hud_server_to_client.find(server_id);
1925 if (i != hud_server_to_client.end()) {
1926 delete event->hudadd.pos;
1927 delete event->hudadd.name;
1928 delete event->hudadd.scale;
1929 delete event->hudadd.text;
1930 delete event->hudadd.align;
1931 delete event->hudadd.offset;
1932 delete event->hudadd.world_pos;
1933 delete event->hudadd.size;
1934 delete event->hudadd.text2;
1935 return;
1938 HudElement *e = new HudElement;
1939 e->type = (HudElementType)event->hudadd.type;
1940 e->pos = *event->hudadd.pos;
1941 e->name = *event->hudadd.name;
1942 e->scale = *event->hudadd.scale;
1943 e->text = *event->hudadd.text;
1944 e->number = event->hudadd.number;
1945 e->item = event->hudadd.item;
1946 e->dir = event->hudadd.dir;
1947 e->align = *event->hudadd.align;
1948 e->offset = *event->hudadd.offset;
1949 e->world_pos = *event->hudadd.world_pos;
1950 e->size = *event->hudadd.size;
1951 e->z_index = event->hudadd.z_index;
1952 e->text2 = *event->hudadd.text2;
1953 hud_server_to_client[server_id] = player->addHud(e);
1955 delete event->hudadd.pos;
1956 delete event->hudadd.name;
1957 delete event->hudadd.scale;
1958 delete event->hudadd.text;
1959 delete event->hudadd.align;
1960 delete event->hudadd.offset;
1961 delete event->hudadd.world_pos;
1962 delete event->hudadd.size;
1963 delete event->hudadd.text2;
1966 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
1968 LocalPlayer *player = client->getEnv().getLocalPlayer();
1969 HudElement *e = player->removeHud(event->hudrm.id);
1970 delete e;
1973 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
1975 LocalPlayer *player = client->getEnv().getLocalPlayer();
1977 u32 id = event->hudchange.id;
1978 HudElement *e = player->getHud(id);
1980 if (e == NULL) {
1981 delete event->hudchange.v3fdata;
1982 delete event->hudchange.v2fdata;
1983 delete event->hudchange.sdata;
1984 delete event->hudchange.v2s32data;
1985 return;
1988 switch (event->hudchange.stat) {
1989 case HUD_STAT_POS:
1990 e->pos = *event->hudchange.v2fdata;
1991 break;
1993 case HUD_STAT_NAME:
1994 e->name = *event->hudchange.sdata;
1995 break;
1997 case HUD_STAT_SCALE:
1998 e->scale = *event->hudchange.v2fdata;
1999 break;
2001 case HUD_STAT_TEXT:
2002 e->text = *event->hudchange.sdata;
2003 break;
2005 case HUD_STAT_NUMBER:
2006 e->number = event->hudchange.data;
2007 break;
2009 case HUD_STAT_ITEM:
2010 e->item = event->hudchange.data;
2011 break;
2013 case HUD_STAT_DIR:
2014 e->dir = event->hudchange.data;
2015 break;
2017 case HUD_STAT_ALIGN:
2018 e->align = *event->hudchange.v2fdata;
2019 break;
2021 case HUD_STAT_OFFSET:
2022 e->offset = *event->hudchange.v2fdata;
2023 break;
2025 case HUD_STAT_WORLD_POS:
2026 e->world_pos = *event->hudchange.v3fdata;
2027 break;
2029 case HUD_STAT_SIZE:
2030 e->size = *event->hudchange.v2s32data;
2031 break;
2033 case HUD_STAT_Z_INDEX:
2034 e->z_index = event->hudchange.data;
2035 break;
2037 case HUD_STAT_TEXT2:
2038 e->text2 = *event->hudchange.sdata;
2039 break;
2042 delete event->hudchange.v3fdata;
2043 delete event->hudchange.v2fdata;
2044 delete event->hudchange.sdata;
2045 delete event->hudchange.v2s32data;
2048 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2050 sky->setVisible(false);
2051 // Whether clouds are visible in front of a custom skybox.
2052 sky->setCloudsEnabled(event->set_sky->clouds);
2054 if (skybox) {
2055 skybox->remove();
2056 skybox = NULL;
2058 // Clear the old textures out in case we switch rendering type.
2059 sky->clearSkyboxTextures();
2060 // Handle according to type
2061 if (event->set_sky->type == "regular") {
2062 // Shows the mesh skybox
2063 sky->setVisible(true);
2064 // Update mesh based skybox colours if applicable.
2065 sky->setSkyColors(event->set_sky->sky_color);
2066 sky->setHorizonTint(
2067 event->set_sky->fog_sun_tint,
2068 event->set_sky->fog_moon_tint,
2069 event->set_sky->fog_tint_type
2071 } else if (event->set_sky->type == "skybox" &&
2072 event->set_sky->textures.size() == 6) {
2073 // Disable the dyanmic mesh skybox:
2074 sky->setVisible(false);
2075 // Set fog colors:
2076 sky->setFallbackBgColor(event->set_sky->bgcolor);
2077 // Set sunrise and sunset fog tinting:
2078 sky->setHorizonTint(
2079 event->set_sky->fog_sun_tint,
2080 event->set_sky->fog_moon_tint,
2081 event->set_sky->fog_tint_type
2083 // Add textures to skybox.
2084 for (int i = 0; i < 6; i++)
2085 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2086 } else {
2087 // Handle everything else as plain color.
2088 if (event->set_sky->type != "plain")
2089 infostream << "Unknown sky type: "
2090 << (event->set_sky->type) << std::endl;
2091 sky->setVisible(false);
2092 sky->setFallbackBgColor(event->set_sky->bgcolor);
2093 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2094 sky->setHorizonTint(
2095 event->set_sky->bgcolor,
2096 event->set_sky->bgcolor,
2097 "custom"
2100 delete event->set_sky;
2103 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2105 sky->setSunVisible(event->sun_params->visible);
2106 sky->setSunTexture(event->sun_params->texture,
2107 event->sun_params->tonemap, texture_src);
2108 sky->setSunScale(event->sun_params->scale);
2109 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2110 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2111 delete event->sun_params;
2114 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2116 sky->setMoonVisible(event->moon_params->visible);
2117 sky->setMoonTexture(event->moon_params->texture,
2118 event->moon_params->tonemap, texture_src);
2119 sky->setMoonScale(event->moon_params->scale);
2120 delete event->moon_params;
2123 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2125 sky->setStarsVisible(event->star_params->visible);
2126 sky->setStarCount(event->star_params->count, false);
2127 sky->setStarColor(event->star_params->starcolor);
2128 sky->setStarScale(event->star_params->scale);
2129 delete event->star_params;
2132 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2133 CameraOrientation *cam)
2135 client->getEnv().setDayNightRatioOverride(
2136 event->override_day_night_ratio.do_override,
2137 event->override_day_night_ratio.ratio_f * 1000.0f);
2140 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2142 if (!clouds)
2143 return;
2145 clouds->setDensity(event->cloud_params.density);
2146 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2147 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2148 clouds->setHeight(event->cloud_params.height);
2149 clouds->setThickness(event->cloud_params.thickness);
2150 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2153 void Game::processClientEvents(CameraOrientation *cam)
2155 while (client->hasClientEvents()) {
2156 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2157 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2158 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2159 (this->*evHandler.handler)(event.get(), cam);
2163 void Game::updateChat(f32 dtime, const v2u32 &screensize)
2165 // Get new messages from error log buffer
2166 while (!m_chat_log_buf.empty())
2167 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2169 // Get new messages from client
2170 std::wstring message;
2171 while (client->getChatMessage(message)) {
2172 chat_backend->addUnparsedMessage(message);
2175 // Remove old messages
2176 chat_backend->step(dtime);
2178 // Display all messages in a static text element
2179 m_game_ui->setChatText(chat_backend->getRecentChat(),
2180 chat_backend->getRecentBuffer().getLineCount());
2183 void Game::updateCamera(u32 busy_time, f32 dtime)
2185 LocalPlayer *player = client->getEnv().getLocalPlayer();
2188 For interaction purposes, get info about the held item
2189 - What item is it?
2190 - Is it a usable item?
2191 - Can it point to liquids?
2193 ItemStack playeritem;
2195 ItemStack selected, hand;
2196 playeritem = player->getWieldedItem(&selected, &hand);
2199 ToolCapabilities playeritem_toolcap =
2200 playeritem.getToolCapabilities(itemdef_manager);
2202 v3s16 old_camera_offset = camera->getOffset();
2204 if (wasKeyDown(KeyType::CAMERA_MODE) && ! g_settings->getBool("freecam")) {
2205 camera->toggleCameraMode();
2206 updatePlayerCAOVisibility();
2209 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2210 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2212 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2213 camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
2214 camera->step(dtime);
2216 v3f camera_position = camera->getPosition();
2217 v3f camera_direction = camera->getDirection();
2218 f32 camera_fov = camera->getFovMax();
2219 v3s16 camera_offset = camera->getOffset();
2221 m_camera_offset_changed = (camera_offset != old_camera_offset);
2223 if (!m_flags.disable_camera_update) {
2224 client->getEnv().getClientMap().updateCamera(camera_position,
2225 camera_direction, camera_fov, camera_offset);
2227 if (m_camera_offset_changed) {
2228 client->updateCameraOffset(camera_offset);
2229 client->getEnv().updateCameraOffset(camera_offset);
2231 if (clouds)
2232 clouds->updateCameraOffset(camera_offset);
2237 void Game::updatePlayerCAOVisibility()
2239 // Make the player visible depending on camera mode.
2240 LocalPlayer *player = client->getEnv().getLocalPlayer();
2241 GenericCAO *playercao = player->getCAO();
2242 if (!playercao)
2243 return;
2244 playercao->updateMeshCulling();
2245 bool is_visible = camera->getCameraMode() > CAMERA_MODE_FIRST || g_settings->getBool("freecam");
2246 playercao->setChildrenVisible(is_visible);
2249 void Game::updateSound(f32 dtime)
2251 // Update sound listener
2252 v3s16 camera_offset = camera->getOffset();
2253 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
2254 v3f(0, 0, 0), // velocity
2255 camera->getDirection(),
2256 camera->getCameraNode()->getUpVector());
2258 bool mute_sound = g_settings->getBool("mute_sound");
2259 if (mute_sound) {
2260 sound->setListenerGain(0.0f);
2261 } else {
2262 // Check if volume is in the proper range, else fix it.
2263 float old_volume = g_settings->getFloat("sound_volume");
2264 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
2265 sound->setListenerGain(new_volume);
2267 if (old_volume != new_volume) {
2268 g_settings->setFloat("sound_volume", new_volume);
2272 LocalPlayer *player = client->getEnv().getLocalPlayer();
2274 // Tell the sound maker whether to make footstep sounds
2275 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
2277 // Update sound maker
2278 if (player->makes_footstep_sound)
2279 soundmaker->step(dtime);
2281 ClientMap &map = client->getEnv().getClientMap();
2282 MapNode n = map.getNode(player->getFootstepNodePos());
2283 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
2287 void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
2289 LocalPlayer *player = client->getEnv().getLocalPlayer();
2291 const v3f camera_direction = camera->getDirection();
2292 const v3s16 camera_offset = camera->getOffset();
2295 Calculate what block is the crosshair pointing to
2298 ItemStack selected_item, hand_item;
2299 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
2301 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
2302 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
2304 if (g_settings->getBool("increase_tool_range"))
2305 d += 2;
2306 if (g_settings->getBool("increase_tool_range_plus"))
2307 d = 1000;
2309 core::line3d<f32> shootline;
2311 switch (camera->getCameraMode()) {
2312 case CAMERA_MODE_FIRST:
2313 // Shoot from camera position, with bobbing
2314 shootline.start = camera->getPosition();
2315 break;
2316 case CAMERA_MODE_THIRD:
2317 // Shoot from player head, no bobbing
2318 shootline.start = camera->getHeadPosition();
2319 break;
2320 case CAMERA_MODE_THIRD_FRONT:
2321 shootline.start = camera->getHeadPosition();
2322 // prevent player pointing anything in front-view
2323 d = 0;
2324 break;
2326 shootline.end = shootline.start + camera_direction * BS * d;
2328 #ifdef HAVE_TOUCHSCREENGUI
2330 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
2331 shootline = g_touchscreengui->getShootline();
2332 // Scale shootline to the acual distance the player can reach
2333 shootline.end = shootline.start
2334 + shootline.getVector().normalize() * BS * d;
2335 shootline.start += intToFloat(camera_offset, BS);
2336 shootline.end += intToFloat(camera_offset, BS);
2339 #endif
2341 PointedThing pointed = updatePointedThing(shootline,
2342 selected_def.liquids_pointable,
2343 !runData.btn_down_for_dig,
2344 camera_offset);
2346 if (pointed != runData.pointed_old) {
2347 infostream << "Pointing at " << pointed.dump() << std::endl;
2348 hud->updateSelectionMesh(camera_offset);
2351 // Allow digging again if button is not pressed
2352 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
2353 runData.digging_blocked = false;
2356 Stop digging when
2357 - releasing dig button
2358 - pointing away from node
2360 if (runData.digging) {
2361 if (wasKeyReleased(KeyType::DIG)) {
2362 infostream << "Dig button released (stopped digging)" << std::endl;
2363 runData.digging = false;
2364 } else if (pointed != runData.pointed_old) {
2365 if (pointed.type == POINTEDTHING_NODE
2366 && runData.pointed_old.type == POINTEDTHING_NODE
2367 && pointed.node_undersurface
2368 == runData.pointed_old.node_undersurface) {
2369 // Still pointing to the same node, but a different face.
2370 // Don't reset.
2371 } else {
2372 infostream << "Pointing away from node (stopped digging)" << std::endl;
2373 runData.digging = false;
2374 hud->updateSelectionMesh(camera_offset);
2378 if (!runData.digging) {
2379 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
2380 client->setCrack(-1, v3s16(0, 0, 0));
2381 runData.dig_time = 0.0;
2383 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
2384 // Remove e.g. torches faster when clicking instead of holding dig button
2385 runData.nodig_delay_timer = 0;
2386 runData.dig_instantly = false;
2389 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
2390 runData.btn_down_for_dig = false;
2392 runData.punching = false;
2394 soundmaker->m_player_leftpunch_sound.name = "";
2396 // Prepare for repeating, unless we're not supposed to
2397 if ((isKeyDown(KeyType::PLACE) || g_settings->getBool("autoplace")) && !g_settings->getBool("safe_dig_and_place"))
2398 runData.repeat_place_timer += dtime;
2399 else
2400 runData.repeat_place_timer = 0;
2402 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
2403 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
2404 !client->getScript()->on_item_use(selected_item, pointed)))
2405 client->interact(INTERACT_USE, pointed);
2406 } else if (pointed.type == POINTEDTHING_NODE) {
2407 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
2408 } else if (pointed.type == POINTEDTHING_OBJECT) {
2409 v3f player_position = player->getPosition();
2410 handlePointingAtObject(pointed, tool_item, player_position, show_debug);
2411 } else if (isKeyDown(KeyType::DIG)) {
2412 // When button is held down in air, show continuous animation
2413 runData.punching = true;
2414 // Run callback even though item is not usable
2415 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
2416 client->getScript()->on_item_use(selected_item, pointed);
2417 } else if (wasKeyPressed(KeyType::PLACE) && (!client->modsLoaded() ||
2418 !client->getScript()->on_item_activate(selected_item, pointed))) {
2419 handlePointingAtNothing(selected_item);
2422 runData.pointed_old = pointed;
2424 if (runData.punching || wasKeyPressed(KeyType::DIG))
2425 camera->setDigging(0); // dig animation
2427 input->clearWasKeyPressed();
2428 input->clearWasKeyReleased();
2430 input->joystick.clearWasKeyDown(KeyType::DIG);
2431 input->joystick.clearWasKeyDown(KeyType::PLACE);
2433 input->joystick.clearWasKeyReleased(KeyType::DIG);
2434 input->joystick.clearWasKeyReleased(KeyType::PLACE);
2438 PointedThing Game::updatePointedThing(
2439 const core::line3d<f32> &shootline,
2440 bool liquids_pointable,
2441 bool look_for_object,
2442 const v3s16 &camera_offset)
2444 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
2445 selectionboxes->clear();
2446 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
2447 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
2448 "show_entity_selectionbox");
2450 ClientEnvironment &env = client->getEnv();
2451 ClientMap &map = env.getClientMap();
2452 const NodeDefManager *nodedef = map.getNodeDefManager();
2454 runData.selected_object = NULL;
2455 hud->pointing_at_object = false;
2456 RaycastState s(shootline, look_for_object, liquids_pointable, ! g_settings->getBool("dont_point_nodes"));
2457 PointedThing result;
2458 env.continueRaycast(&s, &result);
2459 if (result.type == POINTEDTHING_OBJECT) {
2460 hud->pointing_at_object = true;
2462 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
2463 aabb3f selection_box;
2464 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
2465 runData.selected_object->getSelectionBox(&selection_box)) {
2466 v3f pos = runData.selected_object->getPosition();
2467 selectionboxes->push_back(aabb3f(selection_box));
2468 hud->setSelectionPos(pos, camera_offset);
2470 } else if (result.type == POINTEDTHING_NODE) {
2471 // Update selection boxes
2472 MapNode n = map.getNode(result.node_undersurface);
2473 std::vector<aabb3f> boxes;
2474 n.getSelectionBoxes(nodedef, &boxes,
2475 n.getNeighbors(result.node_undersurface, &map));
2477 f32 d = 0.002 * BS;
2478 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
2479 i != boxes.end(); ++i) {
2480 aabb3f box = *i;
2481 box.MinEdge -= v3f(d, d, d);
2482 box.MaxEdge += v3f(d, d, d);
2483 selectionboxes->push_back(box);
2485 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
2486 camera_offset);
2487 hud->setSelectedFaceNormal(v3f(
2488 result.intersection_normal.X,
2489 result.intersection_normal.Y,
2490 result.intersection_normal.Z));
2493 // Update selection mesh light level and vertex colors
2494 if (!selectionboxes->empty()) {
2495 v3f pf = hud->getSelectionPos();
2496 v3s16 p = floatToInt(pf, BS);
2498 // Get selection mesh light level
2499 MapNode n = map.getNode(p);
2500 u16 node_light = getInteriorLight(n, -1, nodedef);
2501 u16 light_level = node_light;
2503 for (const v3s16 &dir : g_6dirs) {
2504 n = map.getNode(p + dir);
2505 node_light = getInteriorLight(n, -1, nodedef);
2506 if (node_light > light_level)
2507 light_level = node_light;
2510 u32 daynight_ratio = client->getEnv().getDayNightRatio();
2511 video::SColor c;
2512 final_color_blend(&c, light_level, daynight_ratio);
2514 // Modify final color a bit with time
2515 u32 timer = porting::getTimeMs() % 5000;
2516 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
2517 float sin_r = 0.08f * std::sin(timerf);
2518 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
2519 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
2520 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
2521 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
2522 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
2524 // Set mesh final color
2525 hud->setSelectionMeshColor(c);
2527 return result;
2530 void Game::handlePointingAtNothing(const ItemStack &playerItem)
2532 infostream << "Attempted to place item while pointing at nothing" << std::endl;
2533 PointedThing fauxPointed;
2534 fauxPointed.type = POINTEDTHING_NOTHING;
2535 client->interact(INTERACT_ACTIVATE, fauxPointed);
2539 void Game::handlePointingAtNode(const PointedThing &pointed,
2540 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
2542 v3s16 nodepos = pointed.node_undersurface;
2543 v3s16 neighbourpos = pointed.node_abovesurface;
2546 Check information text of node
2549 ClientMap &map = client->getEnv().getClientMap();
2551 if (((runData.nodig_delay_timer <= 0.0 || g_settings->getBool("fastdig")) && (isKeyDown(KeyType::DIG) || g_settings->getBool("autodig"))
2552 && !runData.digging_blocked
2553 && client->checkPrivilege("interact"))
2555 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
2558 // This should be done after digging handling
2559 NodeMetadata *meta = map.getNodeMetadata(nodepos);
2561 if (meta) {
2562 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
2563 meta->getString("infotext"))));
2564 } else {
2565 MapNode n = map.getNode(nodepos);
2567 if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
2568 m_game_ui->setInfoText(L"Unknown node: " +
2569 utf8_to_wide(nodedef_manager->get(n).name));
2574 if ((wasKeyPressed(KeyType::PLACE) ||
2575 (runData.repeat_place_timer >= (g_settings->getBool("fastplace") ? 0.001 : m_repeat_place_time))) &&
2576 client->checkPrivilege("interact")) {
2577 runData.repeat_place_timer = 0;
2578 infostream << "Place button pressed while looking at ground" << std::endl;
2580 // Placing animation (always shown for feedback)
2581 camera->setDigging(1);
2583 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
2585 // If the wielded item has node placement prediction,
2586 // make that happen
2587 // And also set the sound and send the interact
2588 // But first check for meta formspec and rightclickable
2589 auto &def = selected_item.getDefinition(itemdef_manager);
2590 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
2591 pointed, meta);
2593 if (placed && client->modsLoaded())
2594 client->getScript()->on_placenode(pointed, def);
2598 bool Game::nodePlacement(const ItemDefinition &selected_def,
2599 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
2600 const PointedThing &pointed, const NodeMetadata *meta, bool force_sneak)
2602 std::string prediction = selected_def.node_placement_prediction;
2603 const NodeDefManager *nodedef = client->ndef();
2604 ClientMap &map = client->getEnv().getClientMap();
2605 MapNode node;
2606 bool is_valid_position;
2607 bool sneaking = force_sneak or isKeyDown(KeyType::SNEAK);
2609 node = map.getNode(nodepos, &is_valid_position);
2610 if (!is_valid_position) {
2611 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2612 return false;
2615 // formspec in meta
2616 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
2617 && !sneaking) {
2618 // on_rightclick callbacks are called anyway
2619 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
2620 client->interact(INTERACT_PLACE, pointed);
2622 std::string formspec_str = meta->getString("formspec");
2624 if (!client->getScript()->on_nodemeta_form_open(nodepos, "", formspec_str)) {
2625 infostream << "Launching custom inventory view" << std::endl;
2627 InventoryLocation inventoryloc;
2628 inventoryloc.setNodeMeta(nodepos);
2630 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
2631 &client->getEnv().getClientMap(), nodepos);
2632 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
2634 auto *&formspec = m_game_ui->updateFormspec("");
2635 GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
2636 txt_dst, client->getFormspecPrepend(), sound);
2638 formspec->setFormSpec(formspec_str, inventoryloc);
2640 return false;
2643 // on_rightclick callback
2644 if (prediction.empty() || (nodedef->get(node).rightclickable
2645 && !sneaking)) {
2646 // Report to server
2647 client->interact(INTERACT_PLACE, pointed);
2648 return false;
2651 verbosestream << "Node placement prediction for "
2652 << selected_def.name << " is " << prediction << std::endl;
2653 v3s16 p = neighbourpos;
2655 // Place inside node itself if buildable_to
2656 MapNode n_under = map.getNode(nodepos, &is_valid_position);
2657 if (is_valid_position) {
2658 if (nodedef->get(n_under).buildable_to) {
2659 p = nodepos;
2660 } else {
2661 node = map.getNode(p, &is_valid_position);
2662 if (is_valid_position && !nodedef->get(node).buildable_to) {
2663 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2664 // Report to server
2665 client->interact(INTERACT_PLACE, pointed);
2666 return false;
2671 // Find id of predicted node
2672 content_t id;
2673 bool found = nodedef->getId(prediction, id);
2675 if (!found) {
2676 errorstream << "Node placement prediction failed for "
2677 << selected_def.name << " (places "
2678 << prediction
2679 << ") - Name not known" << std::endl;
2680 // Handle this as if prediction was empty
2681 // Report to server
2682 client->interact(INTERACT_PLACE, pointed);
2683 return false;
2686 const ContentFeatures &predicted_f = nodedef->get(id);
2688 // Predict param2 for facedir and wallmounted nodes
2689 u8 param2 = 0;
2691 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
2692 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
2693 v3s16 dir = nodepos - neighbourpos;
2695 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
2696 param2 = dir.Y < 0 ? 1 : 0;
2697 } else if (abs(dir.X) > abs(dir.Z)) {
2698 param2 = dir.X < 0 ? 3 : 2;
2699 } else {
2700 param2 = dir.Z < 0 ? 5 : 4;
2704 if (predicted_f.param_type_2 == CPT2_FACEDIR ||
2705 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
2706 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
2708 if (abs(dir.X) > abs(dir.Z)) {
2709 param2 = dir.X < 0 ? 3 : 1;
2710 } else {
2711 param2 = dir.Z < 0 ? 2 : 0;
2715 assert(param2 <= 5);
2717 //Check attachment if node is in group attached_node
2718 if (((ItemGroupList) predicted_f.groups)["attached_node"] != 0) {
2719 static v3s16 wallmounted_dirs[8] = {
2720 v3s16(0, 1, 0),
2721 v3s16(0, -1, 0),
2722 v3s16(1, 0, 0),
2723 v3s16(-1, 0, 0),
2724 v3s16(0, 0, 1),
2725 v3s16(0, 0, -1),
2727 v3s16 pp;
2729 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
2730 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
2731 pp = p + wallmounted_dirs[param2];
2732 else
2733 pp = p + v3s16(0, -1, 0);
2735 if (!nodedef->get(map.getNode(pp)).walkable) {
2736 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2737 // Report to server
2738 client->interact(INTERACT_PLACE, pointed);
2739 return false;
2743 // Apply color
2744 if ((predicted_f.param_type_2 == CPT2_COLOR
2745 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
2746 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
2747 const std::string &indexstr = selected_item.metadata.getString(
2748 "palette_index", 0);
2749 if (!indexstr.empty()) {
2750 s32 index = mystoi(indexstr);
2751 if (predicted_f.param_type_2 == CPT2_COLOR) {
2752 param2 = index;
2753 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
2754 // param2 = pure palette index + other
2755 param2 = (index & 0xf8) | (param2 & 0x07);
2756 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
2757 // param2 = pure palette index + other
2758 param2 = (index & 0xe0) | (param2 & 0x1f);
2763 // Add node to client map
2764 MapNode n(id, 0, param2);
2766 try {
2767 LocalPlayer *player = client->getEnv().getLocalPlayer();
2769 // Dont place node when player would be inside new node
2770 // NOTE: This is to be eventually implemented by a mod as client-side Lua
2771 if (!nodedef->get(n).walkable ||
2772 g_settings->getBool("enable_build_where_you_stand") ||
2773 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
2774 (nodedef->get(n).walkable &&
2775 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
2776 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
2777 // This triggers the required mesh update too
2778 client->addNode(p, n);
2779 // Report to server
2780 client->interact(INTERACT_PLACE, pointed);
2781 // A node is predicted, also play a sound
2782 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
2783 return true;
2784 } else {
2785 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2786 return false;
2788 } catch (InvalidPositionException &e) {
2789 errorstream << "Node placement prediction failed for "
2790 << selected_def.name << " (places "
2791 << prediction
2792 << ") - Position not loaded" << std::endl;
2793 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
2794 return false;
2798 void Game::handlePointingAtObject(const PointedThing &pointed,
2799 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
2801 std::wstring infotext = unescape_translate(
2802 utf8_to_wide(runData.selected_object->infoText()));
2804 if (show_debug) {
2805 if (!infotext.empty()) {
2806 infotext += L"\n";
2808 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
2811 m_game_ui->setInfoText(infotext);
2813 if (isKeyDown(KeyType::DIG) || g_settings->getBool("autohit")) {
2814 bool do_punch = false;
2815 bool do_punch_damage = false;
2817 if (runData.object_hit_delay_timer <= 0.0 || g_settings->getBool("spamclick")) {
2818 do_punch = true;
2819 do_punch_damage = true;
2820 runData.object_hit_delay_timer = object_hit_delay;
2823 if (wasKeyPressed(KeyType::DIG))
2824 do_punch = true;
2826 if (do_punch) {
2827 infostream << "Punched object" << std::endl;
2828 runData.punching = true;
2831 if (do_punch_damage) {
2832 // Report direct punch
2833 v3f objpos = runData.selected_object->getPosition();
2834 v3f dir = (objpos - player_position).normalize();
2836 bool disable_send = runData.selected_object->directReportPunch(
2837 dir, &tool_item, runData.time_from_last_punch);
2838 runData.time_from_last_punch = 0;
2840 if (!disable_send) {
2841 client->interact(INTERACT_START_DIGGING, pointed);
2844 } else if (wasKeyDown(KeyType::PLACE)) {
2845 infostream << "Pressed place button while pointing at object" << std::endl;
2846 client->interact(INTERACT_PLACE, pointed); // place
2851 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
2852 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
2854 // See also: serverpackethandle.cpp, action == 2
2855 LocalPlayer *player = client->getEnv().getLocalPlayer();
2856 ClientMap &map = client->getEnv().getClientMap();
2857 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
2859 // NOTE: Similar piece of code exists on the server side for
2860 // cheat detection.
2861 // Get digging parameters
2862 DigParams params = getDigParams(nodedef_manager->get(n).groups,
2863 &selected_item.getToolCapabilities(itemdef_manager));
2865 // If can't dig, try hand
2866 if (!params.diggable) {
2867 params = getDigParams(nodedef_manager->get(n).groups,
2868 &hand_item.getToolCapabilities(itemdef_manager));
2871 if (!params.diggable) {
2872 // I guess nobody will wait for this long
2873 runData.dig_time_complete = 10000000.0;
2874 } else {
2875 runData.dig_time_complete = params.time;
2877 if (m_cache_enable_particles) {
2878 const ContentFeatures &features = client->getNodeDefManager()->get(n);
2879 client->getParticleManager()->addNodeParticle(client,
2880 player, nodepos, n, features);
2884 if(g_settings->getBool("instant_break")) {
2885 runData.dig_time_complete = 0;
2886 runData.dig_instantly = true;
2888 if (!runData.digging) {
2889 infostream << "Started digging" << std::endl;
2890 runData.dig_instantly = runData.dig_time_complete == 0;
2891 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
2892 return;
2893 client->interact(INTERACT_START_DIGGING, pointed);
2894 runData.digging = true;
2895 runData.btn_down_for_dig = true;
2898 if (!runData.dig_instantly) {
2899 runData.dig_index = (float)crack_animation_length
2900 * runData.dig_time
2901 / runData.dig_time_complete;
2902 } else {
2903 // This is for e.g. torches
2904 runData.dig_index = crack_animation_length;
2907 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
2909 if (sound_dig.exists() && params.diggable) {
2910 if (sound_dig.name == "__group") {
2911 if (!params.main_group.empty()) {
2912 soundmaker->m_player_leftpunch_sound.gain = 0.5;
2913 soundmaker->m_player_leftpunch_sound.name =
2914 std::string("default_dig_") +
2915 params.main_group;
2917 } else {
2918 soundmaker->m_player_leftpunch_sound = sound_dig;
2922 // Don't show cracks if not diggable
2923 if (runData.dig_time_complete >= 100000.0) {
2924 } else if (runData.dig_index < crack_animation_length) {
2925 //TimeTaker timer("client.setTempMod");
2926 //infostream<<"dig_index="<<dig_index<<std::endl;
2927 client->setCrack(runData.dig_index, nodepos);
2928 } else {
2929 infostream << "Digging completed" << std::endl;
2930 client->setCrack(-1, v3s16(0, 0, 0));
2932 runData.dig_time = 0;
2933 runData.digging = false;
2934 // we successfully dug, now block it from repeating if we want to be safe
2935 if (g_settings->getBool("safe_dig_and_place"))
2936 runData.digging_blocked = true;
2938 runData.nodig_delay_timer =
2939 runData.dig_time_complete / (float)crack_animation_length;
2941 // We don't want a corresponding delay to very time consuming nodes
2942 // and nodes without digging time (e.g. torches) get a fixed delay.
2943 if (runData.nodig_delay_timer > 0.3)
2944 runData.nodig_delay_timer = 0.3;
2945 else if (runData.dig_instantly)
2946 runData.nodig_delay_timer = 0.15;
2948 bool is_valid_position;
2949 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
2950 if (is_valid_position) {
2951 if (client->modsLoaded() &&
2952 client->getScript()->on_dignode(nodepos, wasnode)) {
2953 return;
2956 const ContentFeatures &f = client->ndef()->get(wasnode);
2957 if (f.node_dig_prediction == "air") {
2958 client->removeNode(nodepos);
2959 } else if (!f.node_dig_prediction.empty()) {
2960 content_t id;
2961 bool found = client->ndef()->getId(f.node_dig_prediction, id);
2962 if (found)
2963 client->addNode(nodepos, id, true);
2965 // implicit else: no prediction
2968 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
2970 if (m_cache_enable_particles) {
2971 const ContentFeatures &features =
2972 client->getNodeDefManager()->get(wasnode);
2973 client->getParticleManager()->addDiggingParticles(client,
2974 player, nodepos, wasnode, features);
2978 // Send event to trigger sound
2979 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
2982 if (runData.dig_time_complete < 100000.0) {
2983 runData.dig_time += dtime;
2984 } else {
2985 runData.dig_time = 0;
2986 client->setCrack(-1, nodepos);
2989 camera->setDigging(0); // Dig animation
2992 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
2993 const CameraOrientation &cam)
2995 TimeTaker tt_update("Game::updateFrame()");
2996 LocalPlayer *player = client->getEnv().getLocalPlayer();
2999 Fog range
3002 if (draw_control->range_all) {
3003 runData.fog_range = 100000 * BS;
3004 } else {
3005 runData.fog_range = draw_control->wanted_range * BS;
3009 Calculate general brightness
3011 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3012 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3013 float direct_brightness;
3014 bool sunlight_seen;
3016 if ((m_cache_enable_noclip && m_cache_enable_free_move) || g_settings->getBool("freecam")) {
3017 direct_brightness = time_brightness;
3018 sunlight_seen = true;
3019 } else {
3020 float old_brightness = sky->getBrightness();
3021 direct_brightness = client->getEnv().getClientMap()
3022 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3023 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3024 / 255.0;
3027 float time_of_day_smooth = runData.time_of_day_smooth;
3028 float time_of_day = client->getEnv().getTimeOfDayF();
3030 static const float maxsm = 0.05f;
3031 static const float todsm = 0.05f;
3033 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3034 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3035 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3036 time_of_day_smooth = time_of_day;
3038 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3039 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3040 + (time_of_day + 1.0) * todsm;
3041 else
3042 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3043 + time_of_day * todsm;
3045 runData.time_of_day_smooth = time_of_day_smooth;
3047 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3048 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3049 player->getPitch());
3052 Update clouds
3054 if (clouds) {
3055 if (sky->getCloudsVisible()) {
3056 clouds->setVisible(true);
3057 clouds->step(dtime);
3058 // camera->getPosition is not enough for 3rd person views
3059 v3f camera_node_position = camera->getCameraNode()->getPosition();
3060 v3s16 camera_offset = camera->getOffset();
3061 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3062 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3063 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3064 clouds->update(camera_node_position,
3065 sky->getCloudColor());
3066 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3067 // if inside clouds, and fog enabled, use that as sky
3068 // color(s)
3069 video::SColor clouds_dark = clouds->getColor()
3070 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3071 sky->overrideColors(clouds_dark, clouds->getColor());
3072 sky->setInClouds(true);
3073 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3074 // do not draw clouds after all
3075 clouds->setVisible(false);
3077 } else {
3078 clouds->setVisible(false);
3083 Update particles
3085 client->getParticleManager()->step(dtime);
3091 if (m_cache_enable_fog) {
3092 driver->setFog(
3093 sky->getBgColor(),
3094 video::EFT_FOG_LINEAR,
3095 runData.fog_range * m_cache_fog_start,
3096 runData.fog_range * 1.0,
3097 0.01,
3098 false, // pixel fog
3099 true // range fog
3101 } else {
3102 driver->setFog(
3103 sky->getBgColor(),
3104 video::EFT_FOG_LINEAR,
3105 100000 * BS,
3106 110000 * BS,
3107 0.01f,
3108 false, // pixel fog
3109 false // range fog
3114 Get chat messages from client
3117 v2u32 screensize = driver->getScreenSize();
3119 updateChat(dtime, screensize);
3122 Inventory
3125 if (player->getWieldIndex() != runData.new_playeritem)
3126 client->setPlayerItem(runData.new_playeritem);
3128 if (client->updateWieldedItem()) {
3129 // Update wielded tool
3130 ItemStack selected_item, hand_item;
3131 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3132 camera->wield(tool_item);
3136 Update block draw list every 200ms or when camera direction has
3137 changed much
3139 runData.update_draw_list_timer += dtime;
3141 v3f camera_direction = camera->getDirection();
3142 if (runData.update_draw_list_timer >= 0.2
3143 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3144 || m_camera_offset_changed) {
3145 runData.update_draw_list_timer = 0;
3146 client->getEnv().getClientMap().updateDrawList();
3147 runData.update_draw_list_last_cam_dir = camera_direction;
3150 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3153 make sure menu is on top
3154 1. Delete formspec menu reference if menu was removed
3155 2. Else, make sure formspec menu is on top
3157 auto formspec = m_game_ui->getFormspecGUI();
3158 do { // breakable. only runs for one iteration
3159 if (!formspec)
3160 break;
3162 if (formspec->getReferenceCount() == 1) {
3163 m_game_ui->deleteFormspec();
3164 break;
3167 auto &loc = formspec->getFormspecLocation();
3168 if (loc.type == InventoryLocation::NODEMETA) {
3169 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3170 if (!meta || meta->getString("formspec").empty()) {
3171 formspec->quitMenu();
3172 break;
3176 if (isMenuActive())
3177 guiroot->bringToFront(formspec);
3178 } while (false);
3181 Drawing begins
3183 const video::SColor &skycolor = sky->getSkyColor();
3185 TimeTaker tt_draw("Draw scene");
3186 driver->beginScene(true, true, skycolor);
3188 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3189 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3190 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3191 bool draw_crosshair = (
3192 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3193 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3194 #ifdef HAVE_TOUCHSCREENGUI
3195 try {
3196 draw_crosshair = !g_settings->getBool("touchtarget");
3197 } catch (SettingNotFoundException) {
3199 #endif
3200 RenderingEngine::draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3201 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3204 Profiler graph
3206 if (m_game_ui->m_flags.show_profiler_graph)
3207 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3210 Cheat menu
3213 if (! gui_chat_console->isOpen()) {
3214 if (m_game_ui->m_flags.show_cheat_menu)
3215 m_cheat_menu->draw(driver, m_game_ui->m_flags.show_debug);
3216 if (g_settings->getBool("cheat_hud"))
3217 m_cheat_menu->drawHUD(driver, dtime);
3220 Damage flash
3222 if (runData.damage_flash > 0.0f) {
3223 video::SColor color(runData.damage_flash, 180, 0, 0);
3224 if (! g_settings->getBool("no_hurt_cam"))
3225 driver->draw2DRectangle(color, core::rect<s32>(0, 0, screensize.X, screensize.Y), NULL);
3227 runData.damage_flash -= 384.0f * dtime;
3231 Damage camera tilt
3233 if (player->hurt_tilt_timer > 0.0f) {
3234 player->hurt_tilt_timer -= dtime * 6.0f;
3236 if (player->hurt_tilt_timer < 0.0f || g_settings->getBool("no_hurt_cam"))
3237 player->hurt_tilt_strength = 0.0f;
3241 Update minimap pos and rotation
3243 if (mapper && m_game_ui->m_flags.show_hud) {
3244 mapper->setPos(floatToInt(player->getPosition(), BS));
3245 mapper->setAngle(player->getYaw());
3249 End scene
3251 if (++m_reset_HW_buffer_counter > 500) {
3253 Periodically remove all mesh HW buffers.
3255 Work around for a quirk in Irrlicht where a HW buffer is only
3256 released after 20000 iterations (triggered from endScene()).
3258 Without this, all loaded but unused meshes will retain their HW
3259 buffers for at least 5 minutes, at which point looking up the HW buffers
3260 becomes a bottleneck and the framerate drops (as much as 30%).
3262 Tests showed that numbers between 50 and 1000 are good, so picked 500.
3263 There are no other public Irrlicht APIs that allow interacting with the
3264 HW buffers without tracking the status of every individual mesh.
3266 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
3268 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
3269 driver->removeAllHardwareBuffers();
3270 m_reset_HW_buffer_counter = 0;
3272 driver->endScene();
3274 stats->drawtime = tt_draw.stop(true);
3275 g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
3276 g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
3279 /* Log times and stuff for visualization */
3280 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
3282 Profiler::GraphValues values;
3283 g_profiler->graphGet(values);
3284 graph->put(values);
3289 /****************************************************************************
3290 Misc
3291 ****************************************************************************/
3293 /* On some computers framerate doesn't seem to be automatically limited
3295 inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
3297 // not using getRealTime is necessary for wine
3298 device->getTimer()->tick(); // Maker sure device time is up-to-date
3299 u32 time = device->getTimer()->getTime();
3300 u32 last_time = fps_timings->last_time;
3302 if (time > last_time) // Make sure time hasn't overflowed
3303 fps_timings->busy_time = time - last_time;
3304 else
3305 fps_timings->busy_time = 0;
3307 u32 frametime_min = 1000 / (
3308 device->isWindowFocused() && !g_menumgr.pausesGame()
3309 ? g_settings->getFloat("fps_max")
3310 : g_settings->getFloat("fps_max_unfocused"));
3312 if (fps_timings->busy_time < frametime_min) {
3313 fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
3314 device->sleep(fps_timings->sleep_time);
3315 } else {
3316 fps_timings->sleep_time = 0;
3319 /* Get the new value of the device timer. Note that device->sleep() may
3320 * not sleep for the entire requested time as sleep may be interrupted and
3321 * therefore it is arguably more accurate to get the new time from the
3322 * device rather than calculating it by adding sleep_time to time.
3325 device->getTimer()->tick(); // Update device timer
3326 time = device->getTimer()->getTime();
3328 if (time > last_time) // Make sure last_time hasn't overflowed
3329 *dtime = (time - last_time) / 1000.0;
3330 else
3331 *dtime = 0;
3333 fps_timings->last_time = time;
3336 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
3338 const wchar_t *wmsg = wgettext(msg);
3339 RenderingEngine::draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
3340 draw_clouds);
3341 delete[] wmsg;
3344 void Game::settingChangedCallback(const std::string &setting_name, void *data)
3346 ((Game *)data)->readSettings();
3349 void Game::updateAllMapBlocksCallback(const std::string &setting_name, void *data)
3351 ((Game *) data)->client->updateAllMapBlocks();
3354 void Game::freecamChangedCallback(const std::string &setting_name, void *data)
3356 Game *game = (Game *) data;
3357 LocalPlayer *player = game->client->getEnv().getLocalPlayer();
3358 if (g_settings->getBool("freecam")) {
3359 game->camera->setCameraMode(CAMERA_MODE_FIRST);
3360 player->freecamEnable();
3361 } else {
3362 player->freecamDisable();
3364 game->updatePlayerCAOVisibility();
3367 void Game::readSettings()
3369 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
3370 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
3371 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
3372 m_cache_enable_particles = g_settings->getBool("enable_particles");
3373 m_cache_enable_fog = g_settings->getBool("enable_fog");
3374 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
3375 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
3376 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
3378 m_cache_enable_noclip = g_settings->getBool("noclip");
3379 m_cache_enable_free_move = g_settings->getBool("free_move");
3381 m_cache_fog_start = g_settings->getFloat("fog_start");
3383 m_cache_cam_smoothing = 0;
3384 if (g_settings->getBool("cinematic"))
3385 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
3386 else
3387 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
3389 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
3390 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
3391 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
3393 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
3396 /****************************************************************************/
3397 /****************************************************************************
3398 Shutdown / cleanup
3399 ****************************************************************************/
3400 /****************************************************************************/
3402 void Game::extendedResourceCleanup()
3404 // Extended resource accounting
3405 infostream << "Irrlicht resources after cleanup:" << std::endl;
3406 infostream << "\tRemaining meshes : "
3407 << RenderingEngine::get_mesh_cache()->getMeshCount() << std::endl;
3408 infostream << "\tRemaining textures : "
3409 << driver->getTextureCount() << std::endl;
3411 for (unsigned int i = 0; i < driver->getTextureCount(); i++) {
3412 irr::video::ITexture *texture = driver->getTextureByIndex(i);
3413 infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str()
3414 << std::endl;
3417 clearTextureNameCache();
3418 infostream << "\tRemaining materials: "
3419 << driver-> getMaterialRendererCount()
3420 << " (note: irrlicht doesn't support removing renderers)" << std::endl;
3423 void Game::showDeathFormspec()
3425 static std::string formspec_str =
3426 std::string("formspec_version[1]") +
3427 SIZE_TAG
3428 "bgcolor[#320000b4;true]"
3429 "label[4.85,1.35;" + gettext("You died") + "]"
3430 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
3433 /* Create menu */
3434 /* Note: FormspecFormSource and LocalFormspecHandler *
3435 * are deleted by guiFormSpecMenu */
3436 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
3437 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
3439 auto *&formspec = m_game_ui->getFormspecGUI();
3440 GUIFormSpecMenu::create(formspec, client, &input->joystick,
3441 fs_src, txt_dst, client->getFormspecPrepend(), sound);
3442 formspec->setFocus("btn_respawn");
3445 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
3446 void Game::showPauseMenu()
3448 #ifdef __ANDROID__
3449 static const std::string control_text = strgettext("Default Controls:\n"
3450 "No menu visible:\n"
3451 "- single tap: button activate\n"
3452 "- double tap: place/use\n"
3453 "- slide finger: look around\n"
3454 "Menu/Inventory visible:\n"
3455 "- double tap (outside):\n"
3456 " -->close\n"
3457 "- touch stack, touch slot:\n"
3458 " --> move stack\n"
3459 "- touch&drag, tap 2nd finger\n"
3460 " --> place single item to slot\n"
3462 #else
3463 static const std::string control_text_template = strgettext("Controls:\n"
3464 "- %s: move forwards\n"
3465 "- %s: move backwards\n"
3466 "- %s: move left\n"
3467 "- %s: move right\n"
3468 "- %s: jump/climb up\n"
3469 "- %s: dig/punch\n"
3470 "- %s: place/use\n"
3471 "- %s: sneak/climb down\n"
3472 "- %s: drop item\n"
3473 "- %s: inventory\n"
3474 "- %s: enderchest\n"
3475 "- Mouse: turn/look\n"
3476 "- Mouse wheel: select item\n"
3477 "- %s: chat\n"
3478 "- %s: Killaura\n"
3479 "- %s: Freecam\n"
3480 "- %s: Scaffold\n"
3483 char control_text_buf[600];
3485 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
3486 GET_KEY_NAME(keymap_forward),
3487 GET_KEY_NAME(keymap_backward),
3488 GET_KEY_NAME(keymap_left),
3489 GET_KEY_NAME(keymap_right),
3490 GET_KEY_NAME(keymap_jump),
3491 GET_KEY_NAME(keymap_dig),
3492 GET_KEY_NAME(keymap_place),
3493 GET_KEY_NAME(keymap_sneak),
3494 GET_KEY_NAME(keymap_drop),
3495 GET_KEY_NAME(keymap_inventory),
3496 GET_KEY_NAME(keymap_enderchest),
3497 GET_KEY_NAME(keymap_chat),
3498 GET_KEY_NAME(keymap_toggle_killaura),
3499 GET_KEY_NAME(keymap_toggle_freecam),
3500 GET_KEY_NAME(keymap_toggle_scaffold)
3503 std::string control_text = std::string(control_text_buf);
3504 str_formspec_escape(control_text);
3505 #endif
3507 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
3508 std::ostringstream os;
3510 os << "formspec_version[1]" << SIZE_TAG
3511 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
3512 << strgettext("Continue") << "]";
3514 if (!simple_singleplayer_mode) {
3515 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
3516 << strgettext("Change Password") << "]";
3517 } else {
3518 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
3521 #ifndef __ANDROID__
3522 #if USE_SOUND
3523 if (g_settings->getBool("enable_sound")) {
3524 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
3525 << strgettext("Sound Volume") << "]";
3527 #endif
3528 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
3529 << strgettext("Change Keys") << "]";
3530 #endif
3531 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
3532 << strgettext("Exit to Menu") << "]";
3533 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
3534 << strgettext("Exit to OS") << "]"
3535 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
3536 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
3537 << "\n"
3538 << strgettext("Game info:") << "\n";
3539 const std::string &address = client->getAddressName();
3540 static const std::string mode = strgettext("- Mode: ");
3541 if (!simple_singleplayer_mode) {
3542 Address serverAddress = client->getServerAddress();
3543 if (!address.empty()) {
3544 os << mode << strgettext("Remote server") << "\n"
3545 << strgettext("- Address: ") << address;
3546 } else {
3547 os << mode << strgettext("Hosting server");
3549 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
3550 } else {
3551 os << mode << strgettext("Singleplayer") << "\n";
3553 if (simple_singleplayer_mode || address.empty()) {
3554 static const std::string on = strgettext("On");
3555 static const std::string off = strgettext("Off");
3556 const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
3557 const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
3558 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
3559 os << strgettext("- Damage: ") << damage << "\n"
3560 << strgettext("- Creative Mode: ") << creative << "\n";
3561 if (!simple_singleplayer_mode) {
3562 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
3563 //~ PvP = Player versus Player
3564 os << strgettext("- PvP: ") << pvp << "\n"
3565 << strgettext("- Public: ") << announced << "\n";
3566 std::string server_name = g_settings->get("server_name");
3567 str_formspec_escape(server_name);
3568 if (announced == on && !server_name.empty())
3569 os << strgettext("- Server Name: ") << server_name;
3573 os << ";]";
3575 /* Create menu */
3576 /* Note: FormspecFormSource and LocalFormspecHandler *
3577 * are deleted by guiFormSpecMenu */
3578 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
3579 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
3581 auto *&formspec = m_game_ui->getFormspecGUI();
3582 GUIFormSpecMenu::create(formspec, client, &input->joystick,
3583 fs_src, txt_dst, client->getFormspecPrepend(), sound);
3584 formspec->setFocus("btn_continue");
3585 formspec->doPause = true;
3588 /****************************************************************************/
3589 /****************************************************************************
3590 extern function for launching the game
3591 ****************************************************************************/
3592 /****************************************************************************/
3594 Game *g_game;
3596 void the_game(bool *kill,
3597 InputHandler *input,
3598 const GameStartData &start_data,
3599 std::string &error_message,
3600 ChatBackend &chat_backend,
3601 bool *reconnect_requested) // Used for local game
3603 Game game;
3605 g_game = &game;
3607 /* Make a copy of the server address because if a local singleplayer server
3608 * is created then this is updated and we don't want to change the value
3609 * passed to us by the calling function
3612 try {
3614 if (game.startup(kill, input, start_data, error_message,
3615 reconnect_requested, &chat_backend)) {
3616 game.run();
3619 } catch (SerializationError &e) {
3620 error_message = std::string("A serialization error occurred:\n")
3621 + e.what() + "\n\nThe server is probably "
3622 " running a different version of " PROJECT_NAME_C ".";
3623 errorstream << error_message << std::endl;
3624 } catch (ServerError &e) {
3625 error_message = e.what();
3626 errorstream << "ServerError: " << error_message << std::endl;
3627 } catch (ModError &e) {
3628 error_message = std::string("ModError: ") + e.what() +
3629 strgettext("\nCheck debug.txt for details.");
3630 errorstream << error_message << std::endl;
3632 game.shutdown();