3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "client/renderingengine.h"
27 #include "client/clientevent.h"
28 #include "client/gameui.h"
29 #include "client/inputhandler.h"
30 #include "client/tile.h" // For TextureSource
31 #include "client/keys.h"
32 #include "client/joystick_controller.h"
33 #include "clientmap.h"
36 #include "content_cao.h"
37 #include "content/subgames.h"
38 #include "client/event_manager.h"
39 #include "fontengine.h"
43 #include "gameparams.h"
45 #include "gui/guiChatConsole.h"
46 #include "gui/guiConfirmRegistration.h"
47 #include "gui/guiFormSpecMenu.h"
48 #include "gui/guiKeyChangeMenu.h"
49 #include "gui/guiPasswordChange.h"
50 #include "gui/guiVolumeChange.h"
51 #include "gui/mainmenumanager.h"
52 #include "gui/profilergraph.h"
55 #include "nodedef.h" // Needed for determining pointing to nodes
56 #include "nodemetadata.h"
57 #include "particles.h"
65 #include "translation.h"
66 #include "util/basic_macros.h"
67 #include "util/directiontables.h"
68 #include "util/pointedthing.h"
69 #include "util/quicktune_shortcutter.h"
70 #include "irrlicht_changes/static_text.h"
72 #include "script/scripting_client.h"
76 #include "client/sound_openal.h"
78 #include "client/sound.h"
82 m_chat_log_buf(g_logger
),
83 m_game_ui(new GameUI())
85 g_settings
->registerChangedCallback("doubletap_jump",
86 &settingChangedCallback
, this);
87 g_settings
->registerChangedCallback("enable_clouds",
88 &settingChangedCallback
, this);
89 g_settings
->registerChangedCallback("doubletap_joysticks",
90 &settingChangedCallback
, this);
91 g_settings
->registerChangedCallback("enable_particles",
92 &settingChangedCallback
, this);
93 g_settings
->registerChangedCallback("enable_fog",
94 &settingChangedCallback
, this);
95 g_settings
->registerChangedCallback("mouse_sensitivity",
96 &settingChangedCallback
, this);
97 g_settings
->registerChangedCallback("joystick_frustum_sensitivity",
98 &settingChangedCallback
, this);
99 g_settings
->registerChangedCallback("repeat_place_time",
100 &settingChangedCallback
, this);
101 g_settings
->registerChangedCallback("noclip",
102 &settingChangedCallback
, this);
103 g_settings
->registerChangedCallback("free_move",
104 &settingChangedCallback
, this);
105 g_settings
->registerChangedCallback("cinematic",
106 &settingChangedCallback
, this);
107 g_settings
->registerChangedCallback("cinematic_camera_smoothing",
108 &settingChangedCallback
, this);
109 g_settings
->registerChangedCallback("camera_smoothing",
110 &settingChangedCallback
, this);
111 g_settings
->registerChangedCallback("freecam",
112 &freecamChangedCallback
, this);
113 g_settings
->registerChangedCallback("xray",
114 &updateAllMapBlocksCallback
, this);
115 g_settings
->registerChangedCallback("xray_nodes",
116 &updateAllMapBlocksCallback
, this);
117 g_settings
->registerChangedCallback("fullbright",
118 &updateAllMapBlocksCallback
, this);
119 g_settings
->registerChangedCallback("node_esp_nodes",
120 &updateAllMapBlocksCallback
, this);
125 m_cache_hold_aux1
= false; // This is initialised properly later
132 /****************************************************************************
134 ****************************************************************************/
143 delete server
; // deleted first to stop all server threads
151 delete nodedef_manager
;
152 delete itemdef_manager
;
155 extendedResourceCleanup();
157 g_settings
->deregisterChangedCallback("doubletap_jump",
158 &settingChangedCallback
, this);
159 g_settings
->deregisterChangedCallback("enable_clouds",
160 &settingChangedCallback
, this);
161 g_settings
->deregisterChangedCallback("enable_particles",
162 &settingChangedCallback
, this);
163 g_settings
->deregisterChangedCallback("enable_fog",
164 &settingChangedCallback
, this);
165 g_settings
->deregisterChangedCallback("mouse_sensitivity",
166 &settingChangedCallback
, this);
167 g_settings
->deregisterChangedCallback("repeat_place_time",
168 &settingChangedCallback
, this);
169 g_settings
->deregisterChangedCallback("noclip",
170 &settingChangedCallback
, this);
171 g_settings
->deregisterChangedCallback("free_move",
172 &settingChangedCallback
, this);
173 g_settings
->deregisterChangedCallback("cinematic",
174 &settingChangedCallback
, this);
175 g_settings
->deregisterChangedCallback("cinematic_camera_smoothing",
176 &settingChangedCallback
, this);
177 g_settings
->deregisterChangedCallback("camera_smoothing",
178 &settingChangedCallback
, this);
179 g_settings
->deregisterChangedCallback("freecam",
180 &freecamChangedCallback
, this);
181 g_settings
->deregisterChangedCallback("xray",
182 &updateAllMapBlocksCallback
, this);
183 g_settings
->deregisterChangedCallback("xray_nodes",
184 &updateAllMapBlocksCallback
, this);
185 g_settings
->deregisterChangedCallback("fullbright",
186 &updateAllMapBlocksCallback
, this);
187 g_settings
->deregisterChangedCallback("node_esp_nodes",
188 &updateAllMapBlocksCallback
, this);
191 bool Game::startup(bool *kill
,
193 const GameStartData
&start_data
,
194 std::string
&error_message
,
196 ChatBackend
*chat_backend
)
200 this->device
= RenderingEngine::get_raw_device();
202 this->error_message
= &error_message
;
203 this->reconnect_requested
= reconnect
;
205 this->chat_backend
= chat_backend
;
206 this->simple_singleplayer_mode
= start_data
.isSinglePlayer();
208 input
->keycache
.populate();
210 driver
= device
->getVideoDriver();
211 smgr
= RenderingEngine::get_scene_manager();
213 RenderingEngine::get_scene_manager()->getParameters()->
214 setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES
, true);
217 runData
= GameRunData();
218 runData
.time_from_last_punch
= 10.0;
220 m_game_ui
->initFlags();
222 m_invert_mouse
= g_settings
->getBool("invert_mouse");
223 m_first_loop_after_window_activation
= true;
225 g_client_translations
->clear();
227 // address can change if simple_singleplayer_mode
228 if (!init(start_data
.world_spec
.path
, start_data
.address
,
229 start_data
.socket_port
, start_data
.game_spec
))
232 if (!createClient(start_data
))
235 RenderingEngine::initialize(client
, hud
);
244 RunStats stats
= { 0 };
245 FpsControl draw_times
= { 0 };
246 f32 dtime
; // in seconds
248 /* Clear the profiler */
249 Profiler::GraphValues dummyvalues
;
250 g_profiler
->graphGet(dummyvalues
);
252 draw_times
.last_time
= RenderingEngine::get_timer_time();
254 set_light_table(g_settings
->getFloat("display_gamma"));
257 m_cache_hold_aux1
= g_settings
->getBool("fast_move")
258 && client
->checkPrivilege("fast");
261 irr::core::dimension2d
<u32
> previous_screen_size(g_settings
->getU16("screen_w"),
262 g_settings
->getU16("screen_h"));
264 while (RenderingEngine::run()
265 && !(*kill
|| g_gamecallback
->shutdown_requested
266 || (server
&& server
->isShutdownRequested()))) {
268 const irr::core::dimension2d
<u32
> ¤t_screen_size
=
269 RenderingEngine::get_video_driver()->getScreenSize();
270 // Verify if window size has changed and save it if it's the case
271 // Ensure evaluating settings->getBool after verifying screensize
272 // First condition is cheaper
273 if (previous_screen_size
!= current_screen_size
&&
274 current_screen_size
!= irr::core::dimension2d
<u32
>(0,0) &&
275 g_settings
->getBool("autosave_screensize")) {
276 g_settings
->setU16("screen_w", current_screen_size
.Width
);
277 g_settings
->setU16("screen_h", current_screen_size
.Height
);
278 previous_screen_size
= current_screen_size
;
282 // RenderingEngine::run() from this iteration
283 // + Sleep time until the wanted FPS are reached
284 limitFps(&draw_times
, &dtime
);
286 // Prepare render data for next iteration
288 updateStats(&stats
, draw_times
, dtime
);
289 updateInteractTimers(dtime
);
291 if (!checkConnection())
293 if (!handleCallbacks())
298 m_game_ui
->clearInfoText();
301 updateProfilers(stats
, draw_times
, dtime
);
302 processUserInput(dtime
);
303 // Update camera before player movement to avoid camera lag of one frame
304 updateCameraDirection(&cam_view_target
, dtime
);
305 cam_view
.camera_yaw
+= (cam_view_target
.camera_yaw
-
306 cam_view
.camera_yaw
) * m_cache_cam_smoothing
;
307 cam_view
.camera_pitch
+= (cam_view_target
.camera_pitch
-
308 cam_view
.camera_pitch
) * m_cache_cam_smoothing
;
309 updatePlayerControl(cam_view
);
311 processClientEvents(&cam_view_target
);
312 updateCamera(draw_times
.busy_time
, dtime
);
314 processPlayerInteraction(dtime
, m_game_ui
->m_flags
.show_hud
,
315 m_game_ui
->m_flags
.show_debug
);
316 updateFrame(&graph
, &stats
, dtime
, cam_view
);
317 updateProfilerGraphs(&graph
);
319 // Update if minimap has been disabled by the server
320 m_game_ui
->m_flags
.show_minimap
&= client
->shouldShowMinimap();
322 if (m_does_lost_focus_pause_game
&& !device
->isWindowFocused() && !isMenuActive()) {
329 void Game::shutdown()
331 RenderingEngine::finalize();
332 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
333 if (g_settings
->get("3d_mode") == "pageflip") {
334 driver
->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS
);
337 auto formspec
= m_game_ui
->getFormspecGUI();
339 formspec
->quitMenu();
341 #ifdef HAVE_TOUCHSCREENGUI
342 g_touchscreengui
->hide();
345 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
350 if (gui_chat_console
)
351 gui_chat_console
->drop();
360 while (g_menumgr
.menuCount() > 0) {
361 g_menumgr
.m_stack
.front()->setVisible(false);
362 g_menumgr
.deletingMenu(g_menumgr
.m_stack
.front());
365 m_game_ui
->deleteFormspec();
367 chat_backend
->addMessage(L
"", L
"# Disconnected.");
368 chat_backend
->addMessage(L
"", L
"");
369 m_chat_log_buf
.clear();
373 while (!client
->isShutdown()) {
374 assert(texture_src
!= NULL
);
375 assert(shader_src
!= NULL
);
376 texture_src
->processQueue();
377 shader_src
->processQueue();
384 /****************************************************************************/
385 /****************************************************************************
387 ****************************************************************************/
388 /****************************************************************************/
391 const std::string
&map_dir
,
392 const std::string
&address
,
394 const SubgameSpec
&gamespec
)
396 texture_src
= createTextureSource();
398 showOverlayMessage(N_("Loading..."), 0, 0);
400 shader_src
= createShaderSource();
402 itemdef_manager
= createItemDefManager();
403 nodedef_manager
= createNodeDefManager();
405 eventmgr
= new EventManager();
406 quicktune
= new QuicktuneShortcutter();
408 if (!(texture_src
&& shader_src
&& itemdef_manager
&& nodedef_manager
409 && eventmgr
&& quicktune
))
415 // Create a server if not connecting to an existing one
416 if (address
.empty()) {
417 if (!createSingleplayerServer(map_dir
, gamespec
, port
))
424 bool Game::initSound()
427 if (g_settings
->getBool("enable_sound") && g_sound_manager_singleton
.get()) {
428 infostream
<< "Attempting to use OpenAL audio" << std::endl
;
429 sound
= createOpenALSoundManager(g_sound_manager_singleton
.get(), &soundfetcher
);
431 infostream
<< "Failed to initialize OpenAL audio" << std::endl
;
433 infostream
<< "Sound disabled." << std::endl
;
437 infostream
<< "Using dummy audio." << std::endl
;
438 sound
= &dummySoundManager
;
439 sound_is_dummy
= true;
442 soundmaker
= new SoundMaker(sound
, nodedef_manager
);
446 soundmaker
->registerReceiver(eventmgr
);
451 bool Game::createSingleplayerServer(const std::string
&map_dir
,
452 const SubgameSpec
&gamespec
, u16 port
)
454 showOverlayMessage(N_("Creating server..."), 0, 5);
456 std::string bind_str
= g_settings
->get("bind_address");
457 Address
bind_addr(0, 0, 0, 0, port
);
459 if (g_settings
->getBool("ipv6_server")) {
460 bind_addr
.setAddress((IPv6AddressBytes
*) NULL
);
464 bind_addr
.Resolve(bind_str
.c_str());
465 } catch (ResolveError
&e
) {
466 infostream
<< "Resolving bind address \"" << bind_str
467 << "\" failed: " << e
.what()
468 << " -- Listening on all addresses." << std::endl
;
471 if (bind_addr
.isIPv6() && !g_settings
->getBool("enable_ipv6")) {
472 *error_message
= "Unable to listen on " +
473 bind_addr
.serializeString() +
474 " because IPv6 is disabled";
475 errorstream
<< *error_message
<< std::endl
;
479 server
= new Server(map_dir
, gamespec
, simple_singleplayer_mode
, bind_addr
,
480 false, nullptr, error_message
);
486 bool Game::createClient(const GameStartData
&start_data
)
488 showOverlayMessage(N_("Creating client..."), 0, 10);
490 draw_control
= new MapDrawControl
;
494 bool could_connect
, connect_aborted
;
495 #ifdef HAVE_TOUCHSCREENGUI
496 if (g_touchscreengui
) {
497 g_touchscreengui
->init(texture_src
);
498 g_touchscreengui
->hide();
501 if (!connectToServer(start_data
, &could_connect
, &connect_aborted
))
504 if (!could_connect
) {
505 if (error_message
->empty() && !connect_aborted
) {
506 // Should not happen if error messages are set properly
507 *error_message
= "Connection failed for unknown reason";
508 errorstream
<< *error_message
<< std::endl
;
513 if (!getServerContent(&connect_aborted
)) {
514 if (error_message
->empty() && !connect_aborted
) {
515 // Should not happen if error messages are set properly
516 *error_message
= "Connection failed for unknown reason";
517 errorstream
<< *error_message
<< std::endl
;
522 GameGlobalShaderConstantSetterFactory
*scsf
= new GameGlobalShaderConstantSetterFactory(
523 &m_flags
.force_fog_off
, &runData
.fog_range
, client
);
524 shader_src
->addShaderConstantSetterFactory(scsf
);
526 // Update cached textures, meshes and materials
527 client
->afterContentReceived();
531 camera
= new Camera(*draw_control
, client
);
532 if (!camera
|| !camera
->successfullyCreated(*error_message
))
534 client
->setCamera(camera
);
538 if (m_cache_enable_clouds
) {
539 clouds
= new Clouds(smgr
, -1, time(0));
541 *error_message
= "Memory allocation error (clouds)";
542 errorstream
<< *error_message
<< std::endl
;
549 sky
= new Sky(-1, texture_src
, shader_src
);
551 skybox
= NULL
; // This is used/set later on in the main run loop
554 *error_message
= "Memory allocation error sky";
555 errorstream
<< *error_message
<< std::endl
;
559 /* Pre-calculated values
561 video::ITexture
*t
= texture_src
->getTexture("crack_anylength.png");
563 v2u32 size
= t
->getOriginalSize();
564 crack_animation_length
= size
.Y
/ size
.X
;
566 crack_animation_length
= 5;
572 /* Set window caption
574 std::wstring str
= utf8_to_wide(PROJECT_NAME_C
);
576 str
+= utf8_to_wide(g_version_hash
);
578 str
+= L
"Minetest Hackclient";
580 device
->setWindowCaption(str
.c_str());
582 LocalPlayer
*player
= client
->getEnv().getLocalPlayer();
583 player
->hurt_tilt_timer
= 0;
584 player
->hurt_tilt_strength
= 0;
586 hud
= new Hud(guienv
, client
, player
, &player
->inventory
);
589 *error_message
= "Memory error: could not create HUD";
590 errorstream
<< *error_message
<< std::endl
;
594 mapper
= client
->getMinimap();
596 if (mapper
&& client
->modsLoaded())
597 client
->getScript()->on_minimap_ready(mapper
);
606 // Remove stale "recent" chat messages from previous connections
607 chat_backend
->clearRecentChat();
609 // Make sure the size of the recent messages buffer is right
610 chat_backend
->applySettings();
612 // Chat backend and console
613 gui_chat_console
= new GUIChatConsole(guienv
, guienv
->getRootGUIElement(),
614 -1, chat_backend
, client
, &g_menumgr
);
616 if (!gui_chat_console
) {
617 *error_message
= "Could not allocate memory for chat console";
618 errorstream
<< *error_message
<< std::endl
;
622 m_cheat_menu
= new CheatMenu(client
);
625 *error_message
= "Could not allocate memory for cheat menu";
626 errorstream
<< *error_message
<< std::endl
;
630 #ifdef HAVE_TOUCHSCREENGUI
632 if (g_touchscreengui
)
633 g_touchscreengui
->show();
640 bool Game::connectToServer(const GameStartData
&start_data
,
641 bool *connect_ok
, bool *connection_aborted
)
643 *connect_ok
= false; // Let's not be overly optimistic
644 *connection_aborted
= false;
645 bool local_server_mode
= false;
647 showOverlayMessage(N_("Resolving address..."), 0, 15);
649 Address
connect_address(0, 0, 0, 0, start_data
.socket_port
);
652 connect_address
.Resolve(start_data
.address
.c_str());
654 if (connect_address
.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
655 //connect_address.Resolve("localhost");
656 if (connect_address
.isIPv6()) {
657 IPv6AddressBytes addr_bytes
;
658 addr_bytes
.bytes
[15] = 1;
659 connect_address
.setAddress(&addr_bytes
);
661 connect_address
.setAddress(127, 0, 0, 1);
663 local_server_mode
= true;
665 } catch (ResolveError
&e
) {
666 *error_message
= std::string("Couldn't resolve address: ") + e
.what();
667 errorstream
<< *error_message
<< std::endl
;
671 if (connect_address
.isIPv6() && !g_settings
->getBool("enable_ipv6")) {
672 *error_message
= "Unable to connect to " +
673 connect_address
.serializeString() +
674 " because IPv6 is disabled";
675 errorstream
<< *error_message
<< std::endl
;
679 client
= new Client(start_data
.name
.c_str(),
680 start_data
.password
, start_data
.address
,
681 *draw_control
, texture_src
, shader_src
,
682 itemdef_manager
, nodedef_manager
, sound
, eventmgr
,
683 connect_address
.isIPv6(), m_game_ui
.get());
688 client
->m_simple_singleplayer_mode
= simple_singleplayer_mode
;
690 infostream
<< "Connecting to server at ";
691 connect_address
.print(&infostream
);
692 infostream
<< std::endl
;
694 client
->connect(connect_address
,
695 simple_singleplayer_mode
|| local_server_mode
);
698 Wait for server to accept connection
704 FpsControl fps_control
= { 0 };
706 f32 wait_time
= 0; // in seconds
708 fps_control
.last_time
= RenderingEngine::get_timer_time();
710 while (RenderingEngine::run()) {
712 limitFps(&fps_control
, &dtime
);
714 // Update client and server
721 if (client
->getState() == LC_Init
) {
727 if (*connection_aborted
)
730 if (client
->accessDenied()) {
731 *error_message
= "Access denied. Reason: "
732 + client
->accessDeniedReason();
733 *reconnect_requested
= client
->reconnectRequested();
734 errorstream
<< *error_message
<< std::endl
;
738 if (input
->cancelPressed()) {
739 *connection_aborted
= true;
740 infostream
<< "Connect aborted [Escape]" << std::endl
;
744 if (client
->m_is_registration_confirmation_state
) {
745 if (registration_confirmation_shown
) {
746 // Keep drawing the GUI
747 RenderingEngine::draw_menu_scene(guienv
, dtime
, true);
749 registration_confirmation_shown
= true;
750 (new GUIConfirmRegistration(guienv
, guienv
->getRootGUIElement(), -1,
751 &g_menumgr
, client
, start_data
.name
, start_data
.password
,
752 connection_aborted
, texture_src
))->drop();
756 // Only time out if we aren't waiting for the server we started
757 if (!start_data
.isSinglePlayer() && wait_time
> 10) {
758 *error_message
= "Connection timed out.";
759 errorstream
<< *error_message
<< std::endl
;
764 showOverlayMessage(N_("Connecting to server..."), dtime
, 20);
767 } catch (con::PeerNotFoundException
&e
) {
768 // TODO: Should something be done here? At least an info/error
776 bool Game::getServerContent(bool *aborted
)
780 FpsControl fps_control
= { 0 };
781 f32 dtime
; // in seconds
783 fps_control
.last_time
= RenderingEngine::get_timer_time();
785 while (RenderingEngine::run()) {
787 limitFps(&fps_control
, &dtime
);
789 // Update client and server
796 if (client
->mediaReceived() && client
->itemdefReceived() &&
797 client
->nodedefReceived()) {
802 if (!checkConnection())
805 if (client
->getState() < LC_Init
) {
806 *error_message
= "Client disconnected";
807 errorstream
<< *error_message
<< std::endl
;
811 if (input
->cancelPressed()) {
813 infostream
<< "Connect aborted [Escape]" << std::endl
;
820 if (!client
->itemdefReceived()) {
821 const wchar_t *text
= wgettext("Item definitions...");
823 RenderingEngine::draw_load_screen(text
, guienv
, texture_src
,
826 } else if (!client
->nodedefReceived()) {
827 const wchar_t *text
= wgettext("Node definitions...");
829 RenderingEngine::draw_load_screen(text
, guienv
, texture_src
,
833 std::stringstream message
;
835 message
.precision(0);
836 float receive
= client
->mediaReceiveProgress() * 100;
837 message
<< gettext("Media...");
839 message
<< " " << receive
<< "%";
840 message
.precision(2);
842 if ((USE_CURL
== 0) ||
843 (!g_settings
->getBool("enable_remote_media_server"))) {
844 float cur
= client
->getCurRate();
845 std::string cur_unit
= gettext("KiB/s");
849 cur_unit
= gettext("MiB/s");
852 message
<< " (" << cur
<< ' ' << cur_unit
<< ")";
855 progress
= 30 + client
->mediaReceiveProgress() * 35 + 0.5;
856 RenderingEngine::draw_load_screen(utf8_to_wide(message
.str()), guienv
,
857 texture_src
, dtime
, progress
);
865 /****************************************************************************/
866 /****************************************************************************
868 ****************************************************************************/
869 /****************************************************************************/
871 inline void Game::updateInteractTimers(f32 dtime
)
873 if (runData
.nodig_delay_timer
>= 0)
874 runData
.nodig_delay_timer
-= dtime
;
876 if (runData
.object_hit_delay_timer
>= 0)
877 runData
.object_hit_delay_timer
-= dtime
;
879 runData
.time_from_last_punch
+= dtime
;
883 /* returns false if game should exit, otherwise true
885 inline bool Game::checkConnection()
887 if (client
->accessDenied()) {
888 *error_message
= "Access denied. Reason: "
889 + client
->accessDeniedReason();
890 *reconnect_requested
= client
->reconnectRequested();
891 errorstream
<< *error_message
<< std::endl
;
899 /* returns false if game should exit, otherwise true
901 inline bool Game::handleCallbacks()
903 if (g_gamecallback
->disconnect_requested
) {
904 g_gamecallback
->disconnect_requested
= false;
908 if (g_gamecallback
->changepassword_requested
) {
909 (new GUIPasswordChange(guienv
, guiroot
, -1,
910 &g_menumgr
, client
, texture_src
))->drop();
911 g_gamecallback
->changepassword_requested
= false;
914 if (g_gamecallback
->changevolume_requested
) {
915 (new GUIVolumeChange(guienv
, guiroot
, -1,
916 &g_menumgr
, texture_src
))->drop();
917 g_gamecallback
->changevolume_requested
= false;
920 if (g_gamecallback
->keyconfig_requested
) {
921 (new GUIKeyChangeMenu(guienv
, guiroot
, -1,
922 &g_menumgr
, texture_src
))->drop();
923 g_gamecallback
->keyconfig_requested
= false;
926 if (g_gamecallback
->keyconfig_changed
) {
927 input
->keycache
.populate(); // update the cache with new settings
928 g_gamecallback
->keyconfig_changed
= false;
935 void Game::processQueues()
937 texture_src
->processQueue();
938 itemdef_manager
->processQueue(client
);
939 shader_src
->processQueue();
943 void Game::updateProfilers(const RunStats
&stats
, const FpsControl
&draw_times
,
946 float profiler_print_interval
=
947 g_settings
->getFloat("profiler_print_interval");
948 bool print_to_log
= true;
950 if (profiler_print_interval
== 0) {
951 print_to_log
= false;
952 profiler_print_interval
= 3;
955 if (profiler_interval
.step(dtime
, profiler_print_interval
)) {
957 infostream
<< "Profiler:" << std::endl
;
958 g_profiler
->print(infostream
);
961 m_game_ui
->updateProfiler();
965 // Update update graphs
966 g_profiler
->graphAdd("Time non-rendering [ms]",
967 draw_times
.busy_time
- stats
.drawtime
);
969 g_profiler
->graphAdd("Sleep [ms]", draw_times
.sleep_time
);
970 g_profiler
->graphAdd("FPS", 1.0f
/ dtime
);
973 void Game::updateStats(RunStats
*stats
, const FpsControl
&draw_times
,
980 /* Time average and jitter calculation
982 jp
= &stats
->dtime_jitter
;
983 jp
->avg
= jp
->avg
* 0.96 + dtime
* 0.04;
985 jitter
= dtime
- jp
->avg
;
987 if (jitter
> jp
->max
)
990 jp
->counter
+= dtime
;
992 if (jp
->counter
> 0.0) {
994 jp
->max_sample
= jp
->max
;
995 jp
->max_fraction
= jp
->max_sample
/ (jp
->avg
+ 0.001);
999 /* Busytime average and jitter calculation
1001 jp
= &stats
->busy_time_jitter
;
1002 jp
->avg
= jp
->avg
+ draw_times
.busy_time
* 0.02;
1004 jitter
= draw_times
.busy_time
- jp
->avg
;
1006 if (jitter
> jp
->max
)
1008 if (jitter
< jp
->min
)
1011 jp
->counter
+= dtime
;
1013 if (jp
->counter
> 0.0) {
1015 jp
->max_sample
= jp
->max
;
1016 jp
->min_sample
= jp
->min
;
1024 /****************************************************************************
1026 ****************************************************************************/
1028 void Game::processUserInput(f32 dtime
)
1030 // Reset input if window not active or some menu is active
1031 if (!device
->isWindowActive() || isMenuActive() || guienv
->hasFocus(gui_chat_console
)) {
1033 #ifdef HAVE_TOUCHSCREENGUI
1034 g_touchscreengui
->hide();
1037 #ifdef HAVE_TOUCHSCREENGUI
1038 else if (g_touchscreengui
) {
1039 /* on touchscreengui step may generate own input events which ain't
1040 * what we want in case we just did clear them */
1041 g_touchscreengui
->step(dtime
);
1045 if (!guienv
->hasFocus(gui_chat_console
) && gui_chat_console
->isOpen()) {
1046 gui_chat_console
->closeConsoleAtOnce();
1049 // Input handler step() (used by the random input generator)
1053 auto formspec
= m_game_ui
->getFormspecGUI();
1055 formspec
->getAndroidUIInput();
1057 handleAndroidChatInput();
1060 // Increase timer for double tap of "keymap_jump"
1061 if (m_cache_doubletap_jump
&& runData
.jump_timer
<= 0.2f
)
1062 runData
.jump_timer
+= dtime
;
1065 processItemSelection(&runData
.new_playeritem
);
1069 void Game::processKeyInput()
1071 if (wasKeyDown(KeyType::SELECT_UP
)) {
1072 m_cheat_menu
->selectUp();
1073 } else if (wasKeyDown(KeyType::SELECT_DOWN
)) {
1074 m_cheat_menu
->selectDown();
1075 } else if (wasKeyDown(KeyType::SELECT_LEFT
)) {
1076 m_cheat_menu
->selectLeft();
1077 } else if (wasKeyDown(KeyType::SELECT_RIGHT
)) {
1078 m_cheat_menu
->selectRight();
1079 } else if (wasKeyDown(KeyType::SELECT_CONFIRM
)) {
1080 m_cheat_menu
->selectConfirm();
1083 if (wasKeyDown(KeyType::DROP
)) {
1084 dropSelectedItem(isKeyDown(KeyType::SNEAK
));
1085 } else if (wasKeyDown(KeyType::AUTOFORWARD
)) {
1086 toggleAutoforward();
1087 } else if (wasKeyDown(KeyType::BACKWARD
)) {
1088 if (g_settings
->getBool("continuous_forward"))
1089 toggleAutoforward();
1090 } else if (wasKeyDown(KeyType::INVENTORY
)) {
1092 } else if (wasKeyDown(KeyType::ENDERCHEST
)) {
1094 } else if (input
->cancelPressed()) {
1096 m_android_chat_open
= false;
1098 if (!gui_chat_console
->isOpenInhibited()) {
1101 } else if (wasKeyDown(KeyType::CHAT
)) {
1102 openConsole(0.2, L
"");
1103 } else if (wasKeyDown(KeyType::CMD
)) {
1104 openConsole(0.2, L
"/");
1105 } else if (wasKeyDown(KeyType::CMD_LOCAL
)) {
1106 if (client
->modsLoaded())
1107 openConsole(0.2, L
".");
1109 m_game_ui
->showStatusText(wgettext("Client side scripting is disabled"));
1110 } else if (wasKeyDown(KeyType::CONSOLE
)) {
1111 openConsole(core::clamp(g_settings
->getFloat("console_height"), 0.1f
, 1.0f
));
1112 } else if (wasKeyDown(KeyType::FREEMOVE
)) {
1114 } else if (wasKeyDown(KeyType::JUMP
)) {
1115 toggleFreeMoveAlt();
1116 } else if (wasKeyDown(KeyType::PITCHMOVE
)) {
1118 } else if (wasKeyDown(KeyType::FASTMOVE
)) {
1120 } else if (wasKeyDown(KeyType::NOCLIP
)) {
1122 } else if (wasKeyDown(KeyType::KILLAURA
)) {
1124 } else if (wasKeyDown(KeyType::FREECAM
)) {
1126 } else if (wasKeyDown(KeyType::SCAFFOLD
)) {
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
);
1134 m_game_ui
->showTranslatedStatusText("Sound muted");
1136 m_game_ui
->showTranslatedStatusText("Sound unmuted");
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
);
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));
1148 m_game_ui
->showStatusText(buf
);
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
);
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));
1160 m_game_ui
->showStatusText(buf
);
1162 m_game_ui
->showTranslatedStatusText("Sound system is disabled");
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");
1169 } else if (wasKeyDown(KeyType::CINEMATIC
)) {
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
)) {
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
)) {
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
)) {
1197 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT
)) {
1199 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV
)) {
1201 } else if (wasKeyDown(KeyType::QUICKTUNE_INC
)) {
1203 } else if (wasKeyDown(KeyType::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);
1231 if (wasKeyDown(KeyType::HOTBAR_NEXT
))
1234 if (wasKeyDown(KeyType::HOTBAR_PREV
))
1238 *new_playeritem
= *new_playeritem
< max_item
? *new_playeritem
+ 1 : 0;
1240 *new_playeritem
= *new_playeritem
> 0 ? *new_playeritem
- 1 : max_item
;
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
;
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())
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())
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
);
1312 porting::showInputDialog(gettext("ok"), "", "", 2);
1313 m_android_chat_open
= true;
1315 if (gui_chat_console
->isOpenInhibited())
1317 gui_chat_console
->openConsole(scale
);
1319 gui_chat_console
->setCloseOnEnter(true);
1320 gui_chat_console
->replaceAndAddToHistory(line
);
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;
1337 void Game::toggleFreeMove()
1339 bool free_move
= !g_settings
->getBool("free_move");
1340 g_settings
->set("free_move", bool_to_cstr(free_move
));
1343 if (client
->checkPrivilege("fly")) {
1344 m_game_ui
->showTranslatedStatusText("Fly mode enabled");
1346 m_game_ui
->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
1349 m_game_ui
->showTranslatedStatusText("Fly mode disabled");
1353 void Game::toggleFreeMoveAlt()
1355 if (m_cache_doubletap_jump
&& runData
.jump_timer
< 0.2f
)
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
));
1368 m_game_ui
->showTranslatedStatusText("Pitch move mode enabled");
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
));
1382 if (has_fast_privs
) {
1383 m_game_ui
->showTranslatedStatusText("Fast mode enabled");
1385 m_game_ui
->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
1388 m_game_ui
->showTranslatedStatusText("Fast mode disabled");
1392 m_cache_hold_aux1
= fast_move
&& has_fast_privs
;
1397 void Game::toggleNoClip()
1399 bool noclip
= !g_settings
->getBool("noclip");
1400 g_settings
->set("noclip", bool_to_cstr(noclip
));
1403 if (client
->checkPrivilege("noclip")) {
1404 m_game_ui
->showTranslatedStatusText("Noclip mode enabled");
1406 m_game_ui
->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
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
));
1419 m_game_ui
->showTranslatedStatusText("Killaura enabled");
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
));
1431 m_game_ui
->showTranslatedStatusText("Freecam enabled");
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
));
1443 m_game_ui
->showTranslatedStatusText("Scaffold enabled");
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
));
1455 m_game_ui
->showTranslatedStatusText("Cinematic mode enabled");
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");
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"))
1478 mapper
->toggleMinimapShape();
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
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;
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
)
1498 m_game_ui
->m_flags
.show_minimap
= mapper
->getModeDef().type
!=
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
));
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
);
1515 m_game_ui
->showTranslatedStatusText("Fog disabled");
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");
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");
1546 m_game_ui
->showTranslatedStatusText("Debug info and profiler graph hidden");
1552 void Game::toggleUpdateCamera()
1554 if (g_settings
->getBool("freecam"))
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");
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;
1584 if (range_new
> 4000) {
1586 str
= wgettext("Viewing range is at maximum: %d");
1587 swprintf(buf
, sizeof(buf
) / sizeof(wchar_t), str
, range_new
);
1589 m_game_ui
->showStatusText(buf
);
1592 str
= wgettext("Viewing range changed to %d");
1593 swprintf(buf
, sizeof(buf
) / sizeof(wchar_t), str
, range_new
);
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;
1608 if (range_new
< 20) {
1610 str
= wgettext("Viewing range is at minimum: %d");
1611 swprintf(buf
, sizeof(buf
) / sizeof(wchar_t), str
, range_new
);
1613 m_game_ui
->showStatusText(buf
);
1615 str
= wgettext("Viewing range changed to %d");
1616 swprintf(buf
, sizeof(buf
) / sizeof(wchar_t), str
, range_new
);
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");
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()) {
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);
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);
1661 updateCameraOrientation(cam
, dtime
);
1667 // Mac OSX gets upset if this is set every frame
1668 if (!device
->getCursorControl()->isVisible())
1669 device
->getCursorControl()->setVisible(true);
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();
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
) {
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
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
),
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)
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
1755 if (m_cache_hold_aux1
) {
1756 control
.aux1
= control
.aux1
^ true;
1757 keypress_bits
^= ((u32
)(1U << 5));
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()) {
1773 keypress_bits
|= 1U << 0;
1776 client
->setPlayerControl(control
);
1777 player
->keyPressed
= keypress_bits
;
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
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();
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();
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();
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
;
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
);
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
);
1981 delete event
->hudchange
.v3fdata
;
1982 delete event
->hudchange
.v2fdata
;
1983 delete event
->hudchange
.sdata
;
1984 delete event
->hudchange
.v2s32data
;
1988 switch (event
->hudchange
.stat
) {
1990 e
->pos
= *event
->hudchange
.v2fdata
;
1994 e
->name
= *event
->hudchange
.sdata
;
1997 case HUD_STAT_SCALE
:
1998 e
->scale
= *event
->hudchange
.v2fdata
;
2002 e
->text
= *event
->hudchange
.sdata
;
2005 case HUD_STAT_NUMBER
:
2006 e
->number
= event
->hudchange
.data
;
2010 e
->item
= event
->hudchange
.data
;
2014 e
->dir
= event
->hudchange
.data
;
2017 case HUD_STAT_ALIGN
:
2018 e
->align
= *event
->hudchange
.v2fdata
;
2021 case HUD_STAT_OFFSET
:
2022 e
->offset
= *event
->hudchange
.v2fdata
;
2025 case HUD_STAT_WORLD_POS
:
2026 e
->world_pos
= *event
->hudchange
.v3fdata
;
2030 e
->size
= *event
->hudchange
.v2s32data
;
2033 case HUD_STAT_Z_INDEX
:
2034 e
->z_index
= event
->hudchange
.data
;
2037 case HUD_STAT_TEXT2
:
2038 e
->text2
= *event
->hudchange
.sdata
;
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
);
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);
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
);
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
,
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
)
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
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
);
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();
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");
2260 sound
->setListenerGain(0.0f
);
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"))
2306 if (g_settings
->getBool("increase_tool_range_plus"))
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();
2316 case CAMERA_MODE_THIRD
:
2317 // Shoot from player head, no bobbing
2318 shootline
.start
= camera
->getHeadPosition();
2320 case CAMERA_MODE_THIRD_FRONT
:
2321 shootline
.start
= camera
->getHeadPosition();
2322 // prevent player pointing anything in front-view
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
);
2341 PointedThing pointed
= updatePointedThing(shootline
,
2342 selected_def
.liquids_pointable
,
2343 !runData
.btn_down_for_dig
,
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;
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.
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
;
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
));
2478 for (std::vector
<aabb3f
>::const_iterator i
= boxes
.begin();
2479 i
!= boxes
.end(); ++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
),
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();
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
);
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
);
2562 m_game_ui
->setInfoText(unescape_translate(utf8_to_wide(
2563 meta
->getString("infotext"))));
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,
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
,
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();
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
;
2616 if (meta
&& !meta
->getString("formspec").empty() && !input
->isRandom()
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
);
2643 // on_rightclick callback
2644 if (prediction
.empty() || (nodedef
->get(node
).rightclickable
2647 client
->interact(INTERACT_PLACE
, pointed
);
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
) {
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
;
2665 client
->interact(INTERACT_PLACE
, pointed
);
2671 // Find id of predicted node
2673 bool found
= nodedef
->getId(prediction
, id
);
2676 errorstream
<< "Node placement prediction failed for "
2677 << selected_def
.name
<< " (places "
2679 << ") - Name not known" << std::endl
;
2680 // Handle this as if prediction was empty
2682 client
->interact(INTERACT_PLACE
, pointed
);
2686 const ContentFeatures
&predicted_f
= nodedef
->get(id
);
2688 // Predict param2 for facedir and wallmounted nodes
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;
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;
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] = {
2729 if (predicted_f
.param_type_2
== CPT2_WALLMOUNTED
||
2730 predicted_f
.param_type_2
== CPT2_COLORED_WALLMOUNTED
)
2731 pp
= p
+ wallmounted_dirs
[param2
];
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
;
2738 client
->interact(INTERACT_PLACE
, pointed
);
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
) {
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
);
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
);
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
;
2785 soundmaker
->m_player_rightpunch_sound
= selected_def
.sound_place_failed
;
2788 } catch (InvalidPositionException
&e
) {
2789 errorstream
<< "Node placement prediction failed for "
2790 << selected_def
.name
<< " (places "
2792 << ") - Position not loaded" << std::endl
;
2793 soundmaker
->m_player_rightpunch_sound
= selected_def
.sound_place_failed
;
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()));
2805 if (!infotext
.empty()) {
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")) {
2819 do_punch_damage
= true;
2820 runData
.object_hit_delay_timer
= object_hit_delay
;
2823 if (wasKeyPressed(KeyType::DIG
))
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
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;
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
))
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
2901 / runData
.dig_time_complete
;
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_") +
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
);
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
)) {
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()) {
2961 bool found
= client
->ndef()->getId(f
.node_dig_prediction
, id
);
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
;
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();
3002 if (draw_control
->range_all
) {
3003 runData
.fog_range
= 100000 * BS
;
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
;
3016 if ((m_cache_enable_noclip
&& m_cache_enable_free_move
) || g_settings
->getBool("freecam")) {
3017 direct_brightness
= time_brightness
;
3018 sunlight_seen
= true;
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
)
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
;
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());
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
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);
3078 clouds
->setVisible(false);
3085 client
->getParticleManager()->step(dtime
);
3091 if (m_cache_enable_fog
) {
3094 video::EFT_FOG_LINEAR
,
3095 runData
.fog_range
* m_cache_fog_start
,
3096 runData
.fog_range
* 1.0,
3104 video::EFT_FOG_LINEAR
,
3114 Get chat messages from client
3117 v2u32 screensize
= driver
->getScreenSize();
3119 updateChat(dtime
, screensize
);
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
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
3162 if (formspec
->getReferenceCount() == 1) {
3163 m_game_ui
->deleteFormspec();
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();
3177 guiroot
->bringToFront(formspec
);
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
3196 draw_crosshair
= !g_settings
->getBool("touchtarget");
3197 } catch (SettingNotFoundException
) {
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
);
3206 if (m_game_ui
->m_flags
.show_profiler_graph
)
3207 graph
->draw(10, screensize
.Y
- 10, driver
, g_fontengine
->getFont());
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
);
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
;
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());
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;
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
);
3289 /****************************************************************************
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
;
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
);
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;
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
,
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();
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");
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 /****************************************************************************
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()
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]") +
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") + "]"
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()
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"
3457 "- touch stack, touch slot:\n"
3459 "- touch&drag, tap 2nd finger\n"
3460 " --> place single item to slot\n"
3463 static const std::string control_text_template
= strgettext("Controls:\n"
3464 "- %s: move forwards\n"
3465 "- %s: move backwards\n"
3467 "- %s: move right\n"
3468 "- %s: jump/climb up\n"
3471 "- %s: sneak/climb down\n"
3474 "- %s: enderchest\n"
3475 "- Mouse: turn/look\n"
3476 "- Mouse wheel: select item\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
);
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") << "]";
3518 os
<< "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
3523 if (g_settings
->getBool("enable_sound")) {
3524 os
<< "button_exit[4," << (ypos
++) << ";3,0.5;btn_sound;"
3525 << strgettext("Sound Volume") << "]";
3528 os
<< "button_exit[4," << (ypos
++) << ";3,0.5;btn_key_config;"
3529 << strgettext("Change Keys") << "]";
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"
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
;
3547 os
<< mode
<< strgettext("Hosting server");
3549 os
<< "\n" << strgettext("- Port: ") << serverAddress
.getPort() << "\n";
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
;
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 /****************************************************************************/
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
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
3614 if (game
.startup(kill
, input
, start_data
, error_message
,
3615 reconnect_requested
, &chat_backend
)) {
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
;