3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "client/renderingengine.h"
27 #include "client/clientevent.h"
28 #include "client/gameui.h"
29 #include "client/inputhandler.h"
30 #include "client/tile.h" // For TextureSource
31 #include "client/keys.h"
32 #include "client/joystick_controller.h"
33 #include "clientmap.h"
36 #include "content_cao.h"
37 #include "content/subgames.h"
38 #include "client/event_manager.h"
39 #include "fontengine.h"
43 #include "gameparams.h"
45 #include "gui/guiChatConsole.h"
46 #include "gui/guiFormSpecMenu.h"
47 #include "gui/guiKeyChangeMenu.h"
48 #include "gui/guiPasswordChange.h"
49 #include "gui/guiVolumeChange.h"
50 #include "gui/mainmenumanager.h"
51 #include "gui/profilergraph.h"
54 #include "nodedef.h" // Needed for determining pointing to nodes
55 #include "nodemetadata.h"
56 #include "particles.h"
64 #include "translation.h"
65 #include "util/basic_macros.h"
66 #include "util/directiontables.h"
67 #include "util/pointedthing.h"
68 #include "util/quicktune_shortcutter.h"
69 #include "irrlicht_changes/static_text.h"
72 #include "script/scripting_client.h"
76 #include "client/sound_openal.h"
78 #include "client/sound.h"
84 struct TextDestNodeMetadata : public TextDest
86 TextDestNodeMetadata(v3s16 p, Client *client)
91 // This is deprecated I guess? -celeron55
92 void gotText(const std::wstring &text)
94 std::string ntext = wide_to_utf8(text);
95 infostream << "Submitting 'text' field of node at (" << m_p.X << ","
96 << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
98 fields["text"] = ntext;
99 m_client->sendNodemetaFields(m_p, "", fields);
101 void gotText(const StringMap &fields)
103 m_client->sendNodemetaFields(m_p, "", fields);
110 struct TextDestPlayerInventory : public TextDest
112 TextDestPlayerInventory(Client *client)
117 TextDestPlayerInventory(Client *client, const std::string &formname)
120 m_formname = formname;
122 void gotText(const StringMap &fields)
124 m_client->sendInventoryFields(m_formname, fields);
130 struct LocalFormspecHandler : public TextDest
132 LocalFormspecHandler(const std::string &formname)
134 m_formname = formname;
137 LocalFormspecHandler(const std::string &formname, Client *client):
140 m_formname = formname;
143 void gotText(const StringMap &fields)
145 if (m_formname == "MT_PAUSE_MENU") {
146 if (fields.find("btn_sound") != fields.end()) {
147 g_gamecallback->changeVolume();
151 if (fields.find("btn_key_config") != fields.end()) {
152 g_gamecallback->keyConfig();
156 if (fields.find("btn_exit_menu") != fields.end()) {
157 g_gamecallback->disconnect();
161 if (fields.find("btn_exit_os") != fields.end()) {
162 g_gamecallback->exitToOS();
164 RenderingEngine::get_raw_device()->closeDevice();
169 if (fields.find("btn_change_password") != fields.end()) {
170 g_gamecallback->changePassword();
177 if (m_formname == "MT_DEATH_SCREEN") {
178 assert(m_client != 0);
179 m_client->sendRespawn();
183 if (m_client->modsLoaded())
184 m_client->getScript()->on_formspec_input(m_formname, fields);
187 Client *m_client = nullptr;
190 /* Form update callback */
192 class NodeMetadataFormSource: public IFormSource
195 NodeMetadataFormSource(ClientMap *map, v3s16 p):
200 const std::string &getForm() const
202 static const std::string empty_string = "";
203 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
208 return meta->getString("formspec");
211 virtual std::string resolveText(const std::string &str)
213 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
218 return meta->resolveString(str);
225 class PlayerInventoryFormSource: public IFormSource
228 PlayerInventoryFormSource(Client *client):
233 const std::string &getForm() const
235 LocalPlayer *player = m_client->getEnv().getLocalPlayer();
236 return player->inventory_formspec;
242 class NodeDugEvent : public MtEvent
248 NodeDugEvent(v3s16 p, MapNode n):
252 Type getType() const { return NODE_DUG; }
257 ISoundManager *m_sound;
258 const NodeDefManager *m_ndef;
261 bool makes_footstep_sound;
262 float m_player_step_timer;
263 float m_player_jump_timer;
265 SimpleSoundSpec m_player_step_sound;
266 SimpleSoundSpec m_player_leftpunch_sound;
267 // Second sound made on left punch, currently used for item 'use' sound
268 SimpleSoundSpec m_player_leftpunch_sound2;
269 SimpleSoundSpec m_player_rightpunch_sound;
271 SoundMaker(ISoundManager *sound, const NodeDefManager *ndef):
274 makes_footstep_sound(true),
275 m_player_step_timer(0.0f),
276 m_player_jump_timer(0.0f)
280 void playPlayerStep()
282 if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
283 m_player_step_timer = 0.03;
284 if (makes_footstep_sound)
285 m_sound->playSound(m_player_step_sound);
289 void playPlayerJump()
291 if (m_player_jump_timer <= 0.0f) {
292 m_player_jump_timer = 0.2f;
293 m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f));
297 static void viewBobbingStep(MtEvent *e, void *data)
299 SoundMaker *sm = (SoundMaker *)data;
300 sm->playPlayerStep();
303 static void playerRegainGround(MtEvent *e, void *data)
305 SoundMaker *sm = (SoundMaker *)data;
306 sm->playPlayerStep();
309 static void playerJump(MtEvent *e, void *data)
311 SoundMaker *sm = (SoundMaker *)data;
312 sm->playPlayerJump();
315 static void cameraPunchLeft(MtEvent *e, void *data)
317 SoundMaker *sm = (SoundMaker *)data;
318 sm->m_sound->playSound(sm->m_player_leftpunch_sound);
319 sm->m_sound->playSound(sm->m_player_leftpunch_sound2);
322 static void cameraPunchRight(MtEvent *e, void *data)
324 SoundMaker *sm = (SoundMaker *)data;
325 sm->m_sound->playSound(sm->m_player_rightpunch_sound);
328 static void nodeDug(MtEvent *e, void *data)
330 SoundMaker *sm = (SoundMaker *)data;
331 NodeDugEvent *nde = (NodeDugEvent *)e;
332 sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug);
335 static void playerDamage(MtEvent *e, void *data)
337 SoundMaker *sm = (SoundMaker *)data;
338 sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5));
341 static void playerFallingDamage(MtEvent *e, void *data)
343 SoundMaker *sm = (SoundMaker *)data;
344 sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5));
347 void registerReceiver(MtEventManager *mgr)
349 mgr->reg(MtEvent::VIEW_BOBBING_STEP, SoundMaker::viewBobbingStep, this);
350 mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, this);
351 mgr->reg(MtEvent::PLAYER_JUMP, SoundMaker::playerJump, this);
352 mgr->reg(MtEvent::CAMERA_PUNCH_LEFT, SoundMaker::cameraPunchLeft, this);
353 mgr->reg(MtEvent::CAMERA_PUNCH_RIGHT, SoundMaker::cameraPunchRight, this);
354 mgr->reg(MtEvent::NODE_DUG, SoundMaker::nodeDug, this);
355 mgr->reg(MtEvent::PLAYER_DAMAGE, SoundMaker::playerDamage, this);
356 mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, this);
359 void step(float dtime)
361 m_player_step_timer -= dtime;
362 m_player_jump_timer -= dtime;
366 // Locally stored sounds don't need to be preloaded because of this
367 class GameOnDemandSoundFetcher: public OnDemandSoundFetcher
369 std::set<std::string> m_fetched;
371 void paths_insert(std::set<std::string> &dst_paths,
372 const std::string &base,
373 const std::string &name)
375 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg");
376 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg");
377 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg");
378 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg");
379 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg");
380 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg");
381 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg");
382 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg");
383 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg");
384 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg");
385 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg");
388 void fetchSounds(const std::string &name,
389 std::set<std::string> &dst_paths,
390 std::set<std::string> &dst_datas)
392 if (m_fetched.count(name))
395 m_fetched.insert(name);
397 paths_insert(dst_paths, porting::path_share, name);
398 paths_insert(dst_paths, porting::path_user, name);
403 typedef s32 SamplerLayer_t;
406 class GameGlobalShaderConstantSetter : public IShaderConstantSetter
410 bool *m_force_fog_off;
413 CachedPixelShaderSetting<float, 4> m_sky_bg_color;
414 CachedPixelShaderSetting<float> m_fog_distance;
415 CachedVertexShaderSetting<float> m_animation_timer_vertex;
416 CachedPixelShaderSetting<float> m_animation_timer_pixel;
417 CachedPixelShaderSetting<float, 3> m_day_light;
418 CachedPixelShaderSetting<float, 4> m_star_color;
419 CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
420 CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
421 CachedPixelShaderSetting<float, 3> m_minimap_yaw;
422 CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
423 CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
424 CachedPixelShaderSetting<SamplerLayer_t> m_texture0;
425 CachedPixelShaderSetting<SamplerLayer_t> m_texture1;
426 CachedPixelShaderSetting<SamplerLayer_t> m_texture2;
427 CachedPixelShaderSetting<SamplerLayer_t> m_texture3;
428 CachedPixelShaderSetting<float, 2> m_texel_size0;
429 std::array<float, 2> m_texel_size0_values;
430 CachedPixelShaderSetting<float> m_exposure_factor_pixel;
431 float m_user_exposure_factor;
432 bool m_bloom_enabled;
433 CachedPixelShaderSetting<float> m_bloom_intensity_pixel;
434 float m_bloom_intensity;
435 CachedPixelShaderSetting<float> m_bloom_strength_pixel;
436 float m_bloom_strength;
437 CachedPixelShaderSetting<float> m_bloom_radius_pixel;
438 float m_bloom_radius;
439 CachedPixelShaderSetting<float> m_saturation_pixel;
442 void onSettingsChange(const std::string &name)
444 if (name == "enable_fog")
445 m_fog_enabled = g_settings->getBool("enable_fog");
446 if (name == "exposure_factor")
447 m_user_exposure_factor = g_settings->getFloat("exposure_factor", 0.1f, 10.0f);
448 if (name == "bloom_intensity")
449 m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f);
450 if (name == "bloom_strength_factor")
451 m_bloom_strength = RenderingEngine::BASE_BLOOM_STRENGTH * g_settings->getFloat("bloom_strength_factor", 0.1f, 10.0f);
452 if (name == "bloom_radius")
453 m_bloom_radius = g_settings->getFloat("bloom_radius", 0.1f, 8.0f);
456 static void settingsCallback(const std::string &name, void *userdata)
458 reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
461 void setSky(Sky *sky) { m_sky = sky; }
463 GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
464 f32 *fog_range, Client *client) :
467 m_force_fog_off(force_fog_off),
468 m_fog_range(fog_range),
469 m_sky_bg_color("skyBgColor"),
470 m_fog_distance("fogDistance"),
471 m_animation_timer_vertex("animationTimer"),
472 m_animation_timer_pixel("animationTimer"),
473 m_day_light("dayLight"),
474 m_star_color("starColor"),
475 m_eye_position_pixel("eyePosition"),
476 m_eye_position_vertex("eyePosition"),
477 m_minimap_yaw("yawVec"),
478 m_camera_offset_pixel("cameraOffset"),
479 m_camera_offset_vertex("cameraOffset"),
480 m_texture0("texture0"),
481 m_texture1("texture1"),
482 m_texture2("texture2"),
483 m_texture3("texture3"),
484 m_texel_size0("texelSize0"),
485 m_exposure_factor_pixel("exposureFactor"),
486 m_bloom_intensity_pixel("bloomIntensity"),
487 m_bloom_strength_pixel("bloomStrength"),
488 m_bloom_radius_pixel("bloomRadius"),
489 m_saturation_pixel("saturation")
491 g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
492 g_settings->registerChangedCallback("exposure_factor", settingsCallback, this);
493 g_settings->registerChangedCallback("bloom_intensity", settingsCallback, this);
494 g_settings->registerChangedCallback("bloom_strength_factor", settingsCallback, this);
495 g_settings->registerChangedCallback("bloom_radius", settingsCallback, this);
496 g_settings->registerChangedCallback("saturation", settingsCallback, this);
497 m_fog_enabled = g_settings->getBool("enable_fog");
498 m_user_exposure_factor = g_settings->getFloat("exposure_factor", 0.1f, 10.0f);
499 m_bloom_enabled = g_settings->getBool("enable_bloom");
500 m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f);
501 m_bloom_strength = RenderingEngine::BASE_BLOOM_STRENGTH * g_settings->getFloat("bloom_strength_factor", 0.1f, 10.0f);
502 m_bloom_radius = g_settings->getFloat("bloom_radius", 0.1f, 8.0f);
505 ~GameGlobalShaderConstantSetter()
507 g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
510 void onSetConstants(video::IMaterialRendererServices *services) override
513 video::SColor bgcolor = m_sky->getBgColor();
514 video::SColorf bgcolorf(bgcolor);
515 float bgcolorfa[4] = {
521 m_sky_bg_color.set(bgcolorfa, services);
524 float fog_distance = 10000 * BS;
526 if (m_fog_enabled && !*m_force_fog_off)
527 fog_distance = *m_fog_range;
529 m_fog_distance.set(&fog_distance, services);
531 u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
532 video::SColorf sunlight;
533 get_sunlight_color(&sunlight, daynight_ratio);
538 m_day_light.set(dnc, services);
540 video::SColorf star_color = m_sky->getCurrentStarColor();
541 float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a};
542 m_star_color.set(clr, services);
544 u32 animation_timer = m_client->getEnv().getFrameTime() % 1000000;
545 float animation_timer_f = (float)animation_timer / 100000.f;
546 m_animation_timer_vertex.set(&animation_timer_f, services);
547 m_animation_timer_pixel.set(&animation_timer_f, services);
549 float eye_position_array[3];
550 v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
551 epos.getAs3Values(eye_position_array);
552 m_eye_position_pixel.set(eye_position_array, services);
553 m_eye_position_vertex.set(eye_position_array, services);
555 if (m_client->getMinimap()) {
556 float minimap_yaw_array[3];
557 v3f minimap_yaw = m_client->getMinimap()->getYawVec();
558 minimap_yaw.getAs3Values(minimap_yaw_array);
559 m_minimap_yaw.set(minimap_yaw_array, services);
562 float camera_offset_array[3];
563 v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
564 offset.getAs3Values(camera_offset_array);
565 m_camera_offset_pixel.set(camera_offset_array, services);
566 m_camera_offset_vertex.set(camera_offset_array, services);
568 SamplerLayer_t tex_id;
570 m_texture0.set(&tex_id, services);
572 m_texture1.set(&tex_id, services);
574 m_texture2.set(&tex_id, services);
576 m_texture3.set(&tex_id, services);
578 m_texel_size0.set(m_texel_size0_values.data(), services);
580 float exposure_factor = m_user_exposure_factor;
581 if (std::isnan(exposure_factor))
582 exposure_factor = 1.0f;
583 m_exposure_factor_pixel.set(&exposure_factor, services);
585 if (m_bloom_enabled) {
586 m_bloom_intensity_pixel.set(&m_bloom_intensity, services);
587 m_bloom_radius_pixel.set(&m_bloom_radius, services);
588 m_bloom_strength_pixel.set(&m_bloom_strength, services);
590 float saturation = m_client->getEnv().getLocalPlayer()->getLighting().saturation;
591 m_saturation_pixel.set(&saturation, services);
594 void onSetMaterial(const video::SMaterial &material)
596 video::ITexture *texture = material.getTexture(0);
598 core::dimension2du size = texture->getSize();
599 m_texel_size0_values[0] = 1.f / size.Width;
600 m_texel_size0_values[1] = 1.f / size.Height;
603 m_texel_size0_values[0] = 0.f;
604 m_texel_size0_values[1] = 0.f;
610 class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
613 bool *m_force_fog_off;
616 std::vector<GameGlobalShaderConstantSetter *> created_nosky;
618 GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
619 f32 *fog_range, Client *client) :
621 m_force_fog_off(force_fog_off),
622 m_fog_range(fog_range),
626 void setSky(Sky *sky) {
628 for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
629 ggscs->setSky(m_sky);
631 created_nosky.clear();
634 virtual IShaderConstantSetter* create()
636 auto *scs = new GameGlobalShaderConstantSetter(
637 m_sky, m_force_fog_off, m_fog_range, m_client);
639 created_nosky.push_back(scs);
644 #ifdef HAVE_TOUCHSCREENGUI
645 #define SIZE_TAG "size[11,5.5]"
647 #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
650 /****************************************************************************
651 ****************************************************************************/
653 const static float object_hit_delay = 0.2;
656 FpsControl() : last_time(0), busy_time(0), sleep_time(0) {}
660 void limit(IrrlichtDevice *device, f32 *dtime);
662 u32 getBusyMs() const { return busy_time / 1000; }
664 // all values in microseconds (us)
665 u64 last_time, busy_time, sleep_time;
669 /* The reason the following structs are not anonymous structs within the
670 * class is that they are not used by the majority of member functions and
671 * many functions that do require objects of thse types do not modify them
672 * (so they can be passed as a const qualified parameter)
678 PointedThing pointed_old;
681 bool btn_down_for_dig;
683 bool digging_blocked;
684 bool reset_jump_timer;
685 float nodig_delay_timer;
687 float dig_time_complete;
688 float repeat_place_timer;
689 float object_hit_delay_timer;
690 float time_from_last_punch;
691 ClientActiveObject *selected_object;
693 float jump_timer_up; // from key up until key down
694 float jump_timer_down; // since last key down
695 float jump_timer_down_before; // from key down until key down again
698 float update_draw_list_timer;
699 float touch_blocks_timer;
703 v3f update_draw_list_last_cam_dir;
705 float time_of_day_smooth;
710 struct ClientEventHandler
712 void (Game::*handler)(ClientEvent *, CameraOrientation *);
715 /****************************************************************************
717 ****************************************************************************/
719 using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
721 /* This is not intended to be a public class. If a public class becomes
722 * desirable then it may be better to create another 'wrapper' class that
723 * hides most of the stuff in this class (nothing in this class is required
724 * by any other file) but exposes the public methods/data only.
731 bool startup(bool *kill,
733 RenderingEngine *rendering_engine,
734 const GameStartData &game_params,
735 std::string &error_message,
737 ChatBackend *chat_backend);
744 // Basic initialisation
745 bool init(const std::string &map_dir, const std::string &address,
746 u16 port, const SubgameSpec &gamespec);
748 bool createSingleplayerServer(const std::string &map_dir,
749 const SubgameSpec &gamespec, u16 port);
752 bool createClient(const GameStartData &start_data);
756 bool connectToServer(const GameStartData &start_data,
757 bool *connect_ok, bool *aborted);
758 bool getServerContent(bool *aborted);
762 void updateInteractTimers(f32 dtime);
763 bool checkConnection();
764 bool handleCallbacks();
765 void processQueues();
766 void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
767 void updateDebugState();
768 void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
769 void updateProfilerGraphs(ProfilerGraph *graph);
772 void processUserInput(f32 dtime);
773 void processKeyInput();
774 void processItemSelection(u16 *new_playeritem);
776 void dropSelectedItem(bool single_item = false);
777 void openInventory();
778 void openConsole(float scale, const wchar_t *line=NULL);
779 void toggleFreeMove();
780 void toggleFreeMoveAlt();
781 void togglePitchMove();
784 void toggleCinematic();
785 void toggleBlockBounds();
786 void toggleAutoforward();
788 void toggleMinimap(bool shift_pressed);
791 void toggleUpdateCamera();
793 void increaseViewRange();
794 void decreaseViewRange();
795 void toggleFullViewRange();
796 void checkZoomEnabled();
798 void updateCameraDirection(CameraOrientation *cam, float dtime);
799 void updateCameraOrientation(CameraOrientation *cam, float dtime);
800 void updatePlayerControl(const CameraOrientation &cam);
801 void step(f32 dtime);
802 void processClientEvents(CameraOrientation *cam);
803 void updateCamera(f32 dtime);
804 void updateSound(f32 dtime);
805 void processPlayerInteraction(f32 dtime, bool show_hud);
807 * Returns the object or node the player is pointing at.
808 * Also updates the selected thing in the Hud.
810 * @param[in] shootline the shootline, starting from
811 * the camera position. This also gives the maximal distance
813 * @param[in] liquids_pointable if false, liquids are ignored
814 * @param[in] look_for_object if false, objects are ignored
815 * @param[in] camera_offset offset of the camera
816 * @param[out] selected_object the selected object or
819 PointedThing updatePointedThing(
820 const core::line3d<f32> &shootline, bool liquids_pointable,
821 bool look_for_object, const v3s16 &camera_offset);
822 void handlePointingAtNothing(const ItemStack &playerItem);
823 void handlePointingAtNode(const PointedThing &pointed,
824 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
825 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
826 const v3f &player_position, bool show_debug);
827 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
828 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
829 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
830 const CameraOrientation &cam);
831 void updateShadows();
834 void showOverlayMessage(const char *msg, float dtime, int percent,
835 bool draw_clouds = true);
837 static void settingChangedCallback(const std::string &setting_name, void *data);
840 inline bool isKeyDown(GameKeyType k)
842 return input->isKeyDown(k);
844 inline bool wasKeyDown(GameKeyType k)
846 return input->wasKeyDown(k);
848 inline bool wasKeyPressed(GameKeyType k)
850 return input->wasKeyPressed(k);
852 inline bool wasKeyReleased(GameKeyType k)
854 return input->wasKeyReleased(k);
858 void handleAndroidChatInput();
863 bool force_fog_off = false;
864 bool disable_camera_update = false;
867 void showDeathFormspec();
868 void showPauseMenu();
870 void pauseAnimation();
871 void resumeAnimation();
873 // ClientEvent handlers
874 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
875 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
876 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
877 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
878 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
879 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
880 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
881 CameraOrientation *cam);
882 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
883 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
884 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
885 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
886 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
887 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
888 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
889 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
890 CameraOrientation *cam);
891 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
893 void updateChat(f32 dtime);
895 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
896 const v3s16 &nodepos, const v3s16 &neighborpos, const PointedThing &pointed,
897 const NodeMetadata *meta);
898 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
900 f32 getSensitivityScaleFactor() const;
902 InputHandler *input = nullptr;
904 Client *client = nullptr;
905 Server *server = nullptr;
907 IWritableTextureSource *texture_src = nullptr;
908 IWritableShaderSource *shader_src = nullptr;
910 // When created, these will be filled with data received from the server
911 IWritableItemDefManager *itemdef_manager = nullptr;
912 NodeDefManager *nodedef_manager = nullptr;
914 GameOnDemandSoundFetcher soundfetcher; // useful when testing
915 ISoundManager *sound = nullptr;
916 bool sound_is_dummy = false;
917 SoundMaker *soundmaker = nullptr;
919 ChatBackend *chat_backend = nullptr;
920 LogOutputBuffer m_chat_log_buf;
922 EventManager *eventmgr = nullptr;
923 QuicktuneShortcutter *quicktune = nullptr;
925 std::unique_ptr<GameUI> m_game_ui;
926 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
927 MapDrawControl *draw_control = nullptr;
928 Camera *camera = nullptr;
929 Clouds *clouds = nullptr; // Free using ->Drop()
930 Sky *sky = nullptr; // Free using ->Drop()
932 Minimap *mapper = nullptr;
934 // Map server hud ids to client hud ids
935 std::unordered_map<u32, u32> m_hud_server_to_client;
941 This class does take ownership/responsibily for cleaning up etc of any of
942 these items (e.g. device)
944 IrrlichtDevice *device;
945 RenderingEngine *m_rendering_engine;
946 video::IVideoDriver *driver;
947 scene::ISceneManager *smgr;
949 std::string *error_message;
950 bool *reconnect_requested;
951 scene::ISceneNode *skybox;
952 PausedNodesList paused_animated_nodes;
954 bool simple_singleplayer_mode;
957 /* Pre-calculated values
959 int crack_animation_length;
961 IntervalLimiter profiler_interval;
964 * TODO: Local caching of settings is not optimal and should at some stage
965 * be updated to use a global settings object for getting thse values
966 * (as opposed to the this local caching). This can be addressed in
969 bool m_cache_doubletap_jump;
970 bool m_cache_enable_clouds;
971 bool m_cache_enable_joysticks;
972 bool m_cache_enable_particles;
973 bool m_cache_enable_fog;
974 bool m_cache_enable_noclip;
975 bool m_cache_enable_free_move;
976 f32 m_cache_mouse_sensitivity;
977 f32 m_cache_joystick_frustum_sensitivity;
978 f32 m_repeat_place_time;
979 f32 m_cache_cam_smoothing;
980 f32 m_cache_fog_start;
982 bool m_invert_mouse = false;
983 bool m_first_loop_after_window_activation = false;
984 bool m_camera_offset_changed = false;
986 bool m_does_lost_focus_pause_game = false;
988 // if true, (almost) the whole game is paused
989 // this happens in pause menu in singleplayer
990 bool m_is_paused = false;
992 #if IRRLICHT_VERSION_MT_REVISION < 5
993 int m_reset_HW_buffer_counter = 0;
996 #ifdef HAVE_TOUCHSCREENGUI
997 bool m_cache_hold_aux1;
998 bool m_touch_use_crosshair;
999 inline bool isNoCrosshairAllowed() {
1000 return !m_touch_use_crosshair && camera->getCameraMode() == CAMERA_MODE_FIRST;
1004 bool m_android_chat_open;
1009 m_chat_log_buf(g_logger),
1010 m_game_ui(new GameUI())
1012 g_settings->registerChangedCallback("doubletap_jump",
1013 &settingChangedCallback, this);
1014 g_settings->registerChangedCallback("enable_clouds",
1015 &settingChangedCallback, this);
1016 g_settings->registerChangedCallback("doubletap_joysticks",
1017 &settingChangedCallback, this);
1018 g_settings->registerChangedCallback("enable_particles",
1019 &settingChangedCallback, this);
1020 g_settings->registerChangedCallback("enable_fog",
1021 &settingChangedCallback, this);
1022 g_settings->registerChangedCallback("mouse_sensitivity",
1023 &settingChangedCallback, this);
1024 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
1025 &settingChangedCallback, this);
1026 g_settings->registerChangedCallback("repeat_place_time",
1027 &settingChangedCallback, this);
1028 g_settings->registerChangedCallback("noclip",
1029 &settingChangedCallback, this);
1030 g_settings->registerChangedCallback("free_move",
1031 &settingChangedCallback, this);
1032 g_settings->registerChangedCallback("cinematic",
1033 &settingChangedCallback, this);
1034 g_settings->registerChangedCallback("cinematic_camera_smoothing",
1035 &settingChangedCallback, this);
1036 g_settings->registerChangedCallback("camera_smoothing",
1037 &settingChangedCallback, this);
1041 #ifdef HAVE_TOUCHSCREENGUI
1042 m_cache_hold_aux1 = false; // This is initialised properly later
1049 /****************************************************************************
1051 ****************************************************************************/
1057 if (!sound_is_dummy)
1060 delete server; // deleted first to stop all server threads
1068 delete nodedef_manager;
1069 delete itemdef_manager;
1070 delete draw_control;
1072 clearTextureNameCache();
1074 g_settings->deregisterChangedCallback("doubletap_jump",
1075 &settingChangedCallback, this);
1076 g_settings->deregisterChangedCallback("enable_clouds",
1077 &settingChangedCallback, this);
1078 g_settings->deregisterChangedCallback("enable_particles",
1079 &settingChangedCallback, this);
1080 g_settings->deregisterChangedCallback("enable_fog",
1081 &settingChangedCallback, this);
1082 g_settings->deregisterChangedCallback("mouse_sensitivity",
1083 &settingChangedCallback, this);
1084 g_settings->deregisterChangedCallback("repeat_place_time",
1085 &settingChangedCallback, this);
1086 g_settings->deregisterChangedCallback("noclip",
1087 &settingChangedCallback, this);
1088 g_settings->deregisterChangedCallback("free_move",
1089 &settingChangedCallback, this);
1090 g_settings->deregisterChangedCallback("cinematic",
1091 &settingChangedCallback, this);
1092 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
1093 &settingChangedCallback, this);
1094 g_settings->deregisterChangedCallback("camera_smoothing",
1095 &settingChangedCallback, this);
1098 bool Game::startup(bool *kill,
1099 InputHandler *input,
1100 RenderingEngine *rendering_engine,
1101 const GameStartData &start_data,
1102 std::string &error_message,
1104 ChatBackend *chat_backend)
1108 m_rendering_engine = rendering_engine;
1109 device = m_rendering_engine->get_raw_device();
1111 this->error_message = &error_message;
1112 reconnect_requested = reconnect;
1113 this->input = input;
1114 this->chat_backend = chat_backend;
1115 simple_singleplayer_mode = start_data.isSinglePlayer();
1117 input->keycache.populate();
1119 driver = device->getVideoDriver();
1120 smgr = m_rendering_engine->get_scene_manager();
1122 smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1125 runData = GameRunData();
1126 runData.time_from_last_punch = 10.0;
1128 m_game_ui->initFlags();
1130 m_invert_mouse = g_settings->getBool("invert_mouse");
1131 m_first_loop_after_window_activation = true;
1133 #ifdef HAVE_TOUCHSCREENGUI
1134 m_touch_use_crosshair = g_settings->getBool("touch_use_crosshair");
1137 g_client_translations->clear();
1139 // address can change if simple_singleplayer_mode
1140 if (!init(start_data.world_spec.path, start_data.address,
1141 start_data.socket_port, start_data.game_spec))
1144 if (!createClient(start_data))
1147 m_rendering_engine->initialize(client, hud);
1155 ProfilerGraph graph;
1156 RunStats stats = {};
1157 CameraOrientation cam_view_target = {};
1158 CameraOrientation cam_view = {};
1159 FpsControl draw_times;
1160 f32 dtime; // in seconds
1162 /* Clear the profiler */
1163 Profiler::GraphValues dummyvalues;
1164 g_profiler->graphGet(dummyvalues);
1168 set_light_table(g_settings->getFloat("display_gamma"));
1170 #ifdef HAVE_TOUCHSCREENGUI
1171 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1172 && client->checkPrivilege("fast");
1175 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1176 g_settings->getU16("screen_h"));
1178 while (m_rendering_engine->run()
1179 && !(*kill || g_gamecallback->shutdown_requested
1180 || (server && server->isShutdownRequested()))) {
1182 const irr::core::dimension2d<u32> ¤t_screen_size =
1183 m_rendering_engine->get_video_driver()->getScreenSize();
1184 // Verify if window size has changed and save it if it's the case
1185 // Ensure evaluating settings->getBool after verifying screensize
1186 // First condition is cheaper
1187 if (previous_screen_size != current_screen_size &&
1188 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1189 g_settings->getBool("autosave_screensize")) {
1190 g_settings->setU16("screen_w", current_screen_size.Width);
1191 g_settings->setU16("screen_h", current_screen_size.Height);
1192 previous_screen_size = current_screen_size;
1195 // Calculate dtime =
1196 // m_rendering_engine->run() from this iteration
1197 // + Sleep time until the wanted FPS are reached
1198 draw_times.limit(device, &dtime);
1200 // Prepare render data for next iteration
1202 updateStats(&stats, draw_times, dtime);
1203 updateInteractTimers(dtime);
1205 if (!checkConnection())
1207 if (!handleCallbacks())
1212 m_game_ui->clearInfoText();
1214 updateProfilers(stats, draw_times, dtime);
1215 processUserInput(dtime);
1216 // Update camera before player movement to avoid camera lag of one frame
1217 updateCameraDirection(&cam_view_target, dtime);
1218 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1219 cam_view.camera_yaw) * m_cache_cam_smoothing;
1220 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1221 cam_view.camera_pitch) * m_cache_cam_smoothing;
1222 updatePlayerControl(cam_view);
1225 bool was_paused = m_is_paused;
1226 m_is_paused = simple_singleplayer_mode && g_menumgr.pausesGame();
1230 if (!was_paused && m_is_paused)
1232 else if (was_paused && !m_is_paused)
1238 processClientEvents(&cam_view_target);
1240 updateCamera(dtime);
1242 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
1243 updateFrame(&graph, &stats, dtime, cam_view);
1244 updateProfilerGraphs(&graph);
1246 // Update if minimap has been disabled by the server
1247 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1249 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1256 void Game::shutdown()
1258 m_rendering_engine->finalize();
1260 auto formspec = m_game_ui->getFormspecGUI();
1262 formspec->quitMenu();
1264 #ifdef HAVE_TOUCHSCREENGUI
1265 g_touchscreengui->hide();
1268 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1273 if (gui_chat_console)
1274 gui_chat_console->drop();
1280 while (g_menumgr.menuCount() > 0) {
1281 g_menumgr.m_stack.front()->setVisible(false);
1282 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1285 m_game_ui->deleteFormspec();
1287 chat_backend->addMessage(L"", L"# Disconnected.");
1288 chat_backend->addMessage(L"", L"");
1289 m_chat_log_buf.clear();
1293 while (!client->isShutdown()) {
1294 assert(texture_src != NULL);
1295 assert(shader_src != NULL);
1296 texture_src->processQueue();
1297 shader_src->processQueue();
1304 /****************************************************************************/
1305 /****************************************************************************
1307 ****************************************************************************/
1308 /****************************************************************************/
1311 const std::string &map_dir,
1312 const std::string &address,
1314 const SubgameSpec &gamespec)
1316 texture_src = createTextureSource();
1318 showOverlayMessage(N_("Loading..."), 0, 0);
1320 shader_src = createShaderSource();
1322 itemdef_manager = createItemDefManager();
1323 nodedef_manager = createNodeDefManager();
1325 eventmgr = new EventManager();
1326 quicktune = new QuicktuneShortcutter();
1328 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1329 && eventmgr && quicktune))
1335 // Create a server if not connecting to an existing one
1336 if (address.empty()) {
1337 if (!createSingleplayerServer(map_dir, gamespec, port))
1344 bool Game::initSound()
1347 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1348 infostream << "Attempting to use OpenAL audio" << std::endl;
1349 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1351 infostream << "Failed to initialize OpenAL audio" << std::endl;
1353 infostream << "Sound disabled." << std::endl;
1357 infostream << "Using dummy audio." << std::endl;
1358 sound = &dummySoundManager;
1359 sound_is_dummy = true;
1362 soundmaker = new SoundMaker(sound, nodedef_manager);
1366 soundmaker->registerReceiver(eventmgr);
1371 bool Game::createSingleplayerServer(const std::string &map_dir,
1372 const SubgameSpec &gamespec, u16 port)
1374 showOverlayMessage(N_("Creating server..."), 0, 5);
1376 std::string bind_str = g_settings->get("bind_address");
1377 Address bind_addr(0, 0, 0, 0, port);
1379 if (g_settings->getBool("ipv6_server")) {
1380 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1384 bind_addr.Resolve(bind_str.c_str());
1385 } catch (ResolveError &e) {
1386 infostream << "Resolving bind address \"" << bind_str
1387 << "\" failed: " << e.what()
1388 << " -- Listening on all addresses." << std::endl;
1391 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1392 *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
1393 bind_addr.serializeString().c_str());
1394 errorstream << *error_message << std::endl;
1398 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1399 false, nullptr, error_message);
1405 bool Game::createClient(const GameStartData &start_data)
1407 showOverlayMessage(N_("Creating client..."), 0, 10);
1409 draw_control = new MapDrawControl();
1413 bool could_connect, connect_aborted;
1414 #ifdef HAVE_TOUCHSCREENGUI
1415 if (g_touchscreengui) {
1416 g_touchscreengui->init(texture_src);
1417 g_touchscreengui->hide();
1420 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1423 if (!could_connect) {
1424 if (error_message->empty() && !connect_aborted) {
1425 // Should not happen if error messages are set properly
1426 *error_message = gettext("Connection failed for unknown reason");
1427 errorstream << *error_message << std::endl;
1432 if (!getServerContent(&connect_aborted)) {
1433 if (error_message->empty() && !connect_aborted) {
1434 // Should not happen if error messages are set properly
1435 *error_message = gettext("Connection failed for unknown reason");
1436 errorstream << *error_message << std::endl;
1441 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1442 &m_flags.force_fog_off, &runData.fog_range, client);
1443 shader_src->addShaderConstantSetterFactory(scsf);
1445 // Update cached textures, meshes and materials
1446 client->afterContentReceived();
1450 camera = new Camera(*draw_control, client, m_rendering_engine);
1451 if (client->modsLoaded())
1452 client->getScript()->on_camera_ready(camera);
1453 client->setCamera(camera);
1454 #ifdef HAVE_TOUCHSCREENGUI
1455 if (g_touchscreengui) {
1456 g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed());
1462 if (m_cache_enable_clouds)
1463 clouds = new Clouds(smgr, -1, time(0));
1467 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
1469 skybox = NULL; // This is used/set later on in the main run loop
1471 /* Pre-calculated values
1473 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1475 v2u32 size = t->getOriginalSize();
1476 crack_animation_length = size.Y / size.X;
1478 crack_animation_length = 5;
1484 /* Set window caption
1486 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1488 str += utf8_to_wide(g_version_hash);
1490 const wchar_t *text = nullptr;
1491 if (simple_singleplayer_mode)
1492 text = wgettext("Singleplayer");
1494 text = wgettext("Multiplayer");
1501 str += driver->getName();
1504 device->setWindowCaption(str.c_str());
1506 LocalPlayer *player = client->getEnv().getLocalPlayer();
1507 player->hurt_tilt_timer = 0;
1508 player->hurt_tilt_strength = 0;
1510 hud = new Hud(client, player, &player->inventory);
1512 mapper = client->getMinimap();
1514 if (mapper && client->modsLoaded())
1515 client->getScript()->on_minimap_ready(mapper);
1520 bool Game::initGui()
1524 // Remove stale "recent" chat messages from previous connections
1525 chat_backend->clearRecentChat();
1527 // Make sure the size of the recent messages buffer is right
1528 chat_backend->applySettings();
1530 // Chat backend and console
1531 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1532 -1, chat_backend, client, &g_menumgr);
1534 #ifdef HAVE_TOUCHSCREENGUI
1536 if (g_touchscreengui)
1537 g_touchscreengui->show();
1544 bool Game::connectToServer(const GameStartData &start_data,
1545 bool *connect_ok, bool *connection_aborted)
1547 *connect_ok = false; // Let's not be overly optimistic
1548 *connection_aborted = false;
1549 bool local_server_mode = false;
1551 showOverlayMessage(N_("Resolving address..."), 0, 15);
1553 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1556 connect_address.Resolve(start_data.address.c_str());
1558 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1559 if (connect_address.isIPv6()) {
1560 IPv6AddressBytes addr_bytes;
1561 addr_bytes.bytes[15] = 1;
1562 connect_address.setAddress(&addr_bytes);
1564 connect_address.setAddress(127, 0, 0, 1);
1566 local_server_mode = true;
1568 } catch (ResolveError &e) {
1569 *error_message = fmtgettext("Couldn't resolve address: %s", e.what());
1571 errorstream << *error_message << std::endl;
1575 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1576 *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
1577 errorstream << *error_message << std::endl;
1582 client = new Client(start_data.name.c_str(),
1583 start_data.password, start_data.address,
1584 *draw_control, texture_src, shader_src,
1585 itemdef_manager, nodedef_manager, sound, eventmgr,
1586 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get(),
1587 start_data.allow_login_or_register);
1588 client->migrateModStorage();
1589 } catch (const BaseException &e) {
1590 *error_message = fmtgettext("Error creating client: %s", e.what());
1591 errorstream << *error_message << std::endl;
1595 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1597 infostream << "Connecting to server at ";
1598 connect_address.print(infostream);
1599 infostream << std::endl;
1601 client->connect(connect_address,
1602 simple_singleplayer_mode || local_server_mode);
1605 Wait for server to accept connection
1611 FpsControl fps_control;
1613 f32 wait_time = 0; // in seconds
1615 fps_control.reset();
1617 while (m_rendering_engine->run()) {
1619 fps_control.limit(device, &dtime);
1621 // Update client and server
1622 client->step(dtime);
1625 server->step(dtime);
1628 if (client->getState() == LC_Init) {
1634 if (*connection_aborted)
1637 if (client->accessDenied()) {
1638 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1639 *reconnect_requested = client->reconnectRequested();
1640 errorstream << *error_message << std::endl;
1644 if (input->cancelPressed()) {
1645 *connection_aborted = true;
1646 infostream << "Connect aborted [Escape]" << std::endl;
1651 // Only time out if we aren't waiting for the server we started
1652 if (!start_data.address.empty() && wait_time > 10) {
1653 *error_message = gettext("Connection timed out.");
1654 errorstream << *error_message << std::endl;
1659 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1661 } catch (con::PeerNotFoundException &e) {
1662 // TODO: Should something be done here? At least an info/error
1670 bool Game::getServerContent(bool *aborted)
1674 FpsControl fps_control;
1675 f32 dtime; // in seconds
1677 fps_control.reset();
1679 while (m_rendering_engine->run()) {
1681 fps_control.limit(device, &dtime);
1683 // Update client and server
1684 client->step(dtime);
1687 server->step(dtime);
1690 if (client->mediaReceived() && client->itemdefReceived() &&
1691 client->nodedefReceived()) {
1696 if (!checkConnection())
1699 if (client->getState() < LC_Init) {
1700 *error_message = gettext("Client disconnected");
1701 errorstream << *error_message << std::endl;
1705 if (input->cancelPressed()) {
1707 infostream << "Connect aborted [Escape]" << std::endl;
1714 if (!client->itemdefReceived()) {
1715 const wchar_t *text = wgettext("Item definitions...");
1717 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1720 } else if (!client->nodedefReceived()) {
1721 const wchar_t *text = wgettext("Node definitions...");
1723 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1727 std::ostringstream message;
1728 std::fixed(message);
1729 message.precision(0);
1730 float receive = client->mediaReceiveProgress() * 100;
1731 message << gettext("Media...");
1733 message << " " << receive << "%";
1734 message.precision(2);
1736 if ((USE_CURL == 0) ||
1737 (!g_settings->getBool("enable_remote_media_server"))) {
1738 float cur = client->getCurRate();
1739 std::string cur_unit = gettext("KiB/s");
1743 cur_unit = gettext("MiB/s");
1746 message << " (" << cur << ' ' << cur_unit << ")";
1749 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1750 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
1751 texture_src, dtime, progress);
1759 /****************************************************************************/
1760 /****************************************************************************
1762 ****************************************************************************/
1763 /****************************************************************************/
1765 inline void Game::updateInteractTimers(f32 dtime)
1767 if (runData.nodig_delay_timer >= 0)
1768 runData.nodig_delay_timer -= dtime;
1770 if (runData.object_hit_delay_timer >= 0)
1771 runData.object_hit_delay_timer -= dtime;
1773 runData.time_from_last_punch += dtime;
1777 /* returns false if game should exit, otherwise true
1779 inline bool Game::checkConnection()
1781 if (client->accessDenied()) {
1782 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1783 *reconnect_requested = client->reconnectRequested();
1784 errorstream << *error_message << std::endl;
1792 /* returns false if game should exit, otherwise true
1794 inline bool Game::handleCallbacks()
1796 if (g_gamecallback->disconnect_requested) {
1797 g_gamecallback->disconnect_requested = false;
1801 if (g_gamecallback->changepassword_requested) {
1802 (new GUIPasswordChange(guienv, guiroot, -1,
1803 &g_menumgr, client, texture_src))->drop();
1804 g_gamecallback->changepassword_requested = false;
1807 if (g_gamecallback->changevolume_requested) {
1808 (new GUIVolumeChange(guienv, guiroot, -1,
1809 &g_menumgr, texture_src))->drop();
1810 g_gamecallback->changevolume_requested = false;
1813 if (g_gamecallback->keyconfig_requested) {
1814 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1815 &g_menumgr, texture_src))->drop();
1816 g_gamecallback->keyconfig_requested = false;
1819 if (g_gamecallback->keyconfig_changed) {
1820 input->keycache.populate(); // update the cache with new settings
1821 g_gamecallback->keyconfig_changed = false;
1828 void Game::processQueues()
1830 texture_src->processQueue();
1831 itemdef_manager->processQueue(client);
1832 shader_src->processQueue();
1835 void Game::updateDebugState()
1837 LocalPlayer *player = client->getEnv().getLocalPlayer();
1839 // debug UI and wireframe
1840 bool has_debug = client->checkPrivilege("debug");
1841 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
1843 if (m_game_ui->m_flags.show_basic_debug) {
1844 if (!has_basic_debug)
1845 m_game_ui->m_flags.show_basic_debug = false;
1846 } else if (m_game_ui->m_flags.show_minimal_debug) {
1847 if (has_basic_debug)
1848 m_game_ui->m_flags.show_basic_debug = true;
1850 if (!has_basic_debug)
1851 hud->disableBlockBounds();
1853 draw_control->show_wireframe = false;
1856 draw_control->allow_noclip = m_cache_enable_noclip && client->checkPrivilege("noclip");
1859 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1862 float profiler_print_interval =
1863 g_settings->getFloat("profiler_print_interval");
1864 bool print_to_log = true;
1866 if (profiler_print_interval == 0) {
1867 print_to_log = false;
1868 profiler_print_interval = 3;
1871 if (profiler_interval.step(dtime, profiler_print_interval)) {
1873 infostream << "Profiler:" << std::endl;
1874 g_profiler->print(infostream);
1877 m_game_ui->updateProfiler();
1878 g_profiler->clear();
1881 // Update update graphs
1882 g_profiler->graphAdd("Time non-rendering [us]",
1883 draw_times.busy_time - stats.drawtime);
1885 g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
1886 g_profiler->graphAdd("FPS", 1.0f / dtime);
1889 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1896 /* Time average and jitter calculation
1898 jp = &stats->dtime_jitter;
1899 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1901 jitter = dtime - jp->avg;
1903 if (jitter > jp->max)
1906 jp->counter += dtime;
1908 if (jp->counter > 0.0) {
1910 jp->max_sample = jp->max;
1911 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1915 /* Busytime average and jitter calculation
1917 jp = &stats->busy_time_jitter;
1918 jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
1920 jitter = draw_times.getBusyMs() - jp->avg;
1922 if (jitter > jp->max)
1924 if (jitter < jp->min)
1927 jp->counter += dtime;
1929 if (jp->counter > 0.0) {
1931 jp->max_sample = jp->max;
1932 jp->min_sample = jp->min;
1940 /****************************************************************************
1942 ****************************************************************************/
1944 void Game::processUserInput(f32 dtime)
1946 // Reset input if window not active or some menu is active
1947 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1949 #ifdef HAVE_TOUCHSCREENGUI
1950 g_touchscreengui->hide();
1953 #ifdef HAVE_TOUCHSCREENGUI
1954 else if (g_touchscreengui) {
1955 /* on touchscreengui step may generate own input events which ain't
1956 * what we want in case we just did clear them */
1957 g_touchscreengui->show();
1958 g_touchscreengui->step(dtime);
1962 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1963 gui_chat_console->closeConsoleAtOnce();
1966 // Input handler step() (used by the random input generator)
1970 auto formspec = m_game_ui->getFormspecGUI();
1972 formspec->getAndroidUIInput();
1974 handleAndroidChatInput();
1977 // Increase timer for double tap of "keymap_jump"
1978 if (m_cache_doubletap_jump && runData.jump_timer_up <= 0.2f)
1979 runData.jump_timer_up += dtime;
1980 if (m_cache_doubletap_jump && runData.jump_timer_down <= 0.4f)
1981 runData.jump_timer_down += dtime;
1984 processItemSelection(&runData.new_playeritem);
1988 void Game::processKeyInput()
1990 if (wasKeyDown(KeyType::DROP)) {
1991 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1992 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1993 toggleAutoforward();
1994 } else if (wasKeyDown(KeyType::BACKWARD)) {
1995 if (g_settings->getBool("continuous_forward"))
1996 toggleAutoforward();
1997 } else if (wasKeyDown(KeyType::INVENTORY)) {
1999 } else if (input->cancelPressed()) {
2001 m_android_chat_open = false;
2003 if (!gui_chat_console->isOpenInhibited()) {
2006 } else if (wasKeyDown(KeyType::CHAT)) {
2007 openConsole(0.2, L"");
2008 } else if (wasKeyDown(KeyType::CMD)) {
2009 openConsole(0.2, L"/");
2010 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
2011 if (client->modsLoaded())
2012 openConsole(0.2, L".");
2014 m_game_ui->showTranslatedStatusText("Client side scripting is disabled");
2015 } else if (wasKeyDown(KeyType::CONSOLE)) {
2016 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
2017 } else if (wasKeyDown(KeyType::FREEMOVE)) {
2019 } else if (wasKeyDown(KeyType::JUMP)) {
2020 toggleFreeMoveAlt();
2021 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
2023 } else if (wasKeyDown(KeyType::FASTMOVE)) {
2025 } else if (wasKeyDown(KeyType::NOCLIP)) {
2028 } else if (wasKeyDown(KeyType::MUTE)) {
2029 if (g_settings->getBool("enable_sound")) {
2030 bool new_mute_sound = !g_settings->getBool("mute_sound");
2031 g_settings->setBool("mute_sound", new_mute_sound);
2033 m_game_ui->showTranslatedStatusText("Sound muted");
2035 m_game_ui->showTranslatedStatusText("Sound unmuted");
2037 m_game_ui->showTranslatedStatusText("Sound system is disabled");
2039 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
2040 if (g_settings->getBool("enable_sound")) {
2041 float new_volume = g_settings->getFloat("sound_volume", 0.0f, 0.9f) + 0.1f;
2042 g_settings->setFloat("sound_volume", new_volume);
2043 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
2044 m_game_ui->showStatusText(msg);
2046 m_game_ui->showTranslatedStatusText("Sound system is disabled");
2048 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
2049 if (g_settings->getBool("enable_sound")) {
2050 float new_volume = g_settings->getFloat("sound_volume", 0.1f, 1.0f) - 0.1f;
2051 g_settings->setFloat("sound_volume", new_volume);
2052 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
2053 m_game_ui->showStatusText(msg);
2055 m_game_ui->showTranslatedStatusText("Sound system is disabled");
2058 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
2059 || wasKeyDown(KeyType::DEC_VOLUME)) {
2060 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
2062 } else if (wasKeyDown(KeyType::CINEMATIC)) {
2064 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
2065 client->makeScreenshot();
2066 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
2067 toggleBlockBounds();
2068 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
2069 m_game_ui->toggleHud();
2070 } else if (wasKeyDown(KeyType::MINIMAP)) {
2071 toggleMinimap(isKeyDown(KeyType::SNEAK));
2072 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
2073 m_game_ui->toggleChat();
2074 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
2076 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
2077 toggleUpdateCamera();
2078 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
2080 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
2081 m_game_ui->toggleProfiler();
2082 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
2083 increaseViewRange();
2084 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
2085 decreaseViewRange();
2086 } else if (wasKeyDown(KeyType::RANGESELECT)) {
2087 toggleFullViewRange();
2088 } else if (wasKeyDown(KeyType::ZOOM)) {
2090 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
2092 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
2094 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
2096 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
2100 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
2101 runData.reset_jump_timer = false;
2102 runData.jump_timer_up = 0.0f;
2105 if (quicktune->hasMessage()) {
2106 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2110 void Game::processItemSelection(u16 *new_playeritem)
2112 LocalPlayer *player = client->getEnv().getLocalPlayer();
2114 /* Item selection using mouse wheel
2116 *new_playeritem = player->getWieldIndex();
2118 s32 wheel = input->getMouseWheel();
2119 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2120 player->hud_hotbar_itemcount - 1);
2124 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2127 if (wasKeyDown(KeyType::HOTBAR_PREV))
2131 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2133 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2136 /* Item selection using hotbar slot keys
2138 for (u16 i = 0; i <= max_item; i++) {
2139 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2140 *new_playeritem = i;
2147 void Game::dropSelectedItem(bool single_item)
2149 IDropAction *a = new IDropAction();
2150 a->count = single_item ? 1 : 0;
2151 a->from_inv.setCurrentPlayer();
2152 a->from_list = "main";
2153 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2154 client->inventoryAction(a);
2158 void Game::openInventory()
2161 * Don't permit to open inventory is CAO or player doesn't exists.
2162 * This prevent showing an empty inventory at player load
2165 LocalPlayer *player = client->getEnv().getLocalPlayer();
2166 if (!player || !player->getCAO())
2169 infostream << "Game: Launching inventory" << std::endl;
2171 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2173 InventoryLocation inventoryloc;
2174 inventoryloc.setCurrentPlayer();
2176 if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2181 if (fs_src->getForm().empty()) {
2186 TextDest *txt_dst = new TextDestPlayerInventory(client);
2187 auto *&formspec = m_game_ui->updateFormspec("");
2188 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2189 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2191 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2195 void Game::openConsole(float scale, const wchar_t *line)
2197 assert(scale > 0.0f && scale <= 1.0f);
2200 porting::showInputDialog(gettext("ok"), "", "", 2);
2201 m_android_chat_open = true;
2203 if (gui_chat_console->isOpenInhibited())
2205 gui_chat_console->openConsole(scale);
2207 gui_chat_console->setCloseOnEnter(true);
2208 gui_chat_console->replaceAndAddToHistory(line);
2214 void Game::handleAndroidChatInput()
2216 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2217 std::string text = porting::getInputDialogValue();
2218 client->typeChatMessage(utf8_to_wide(text));
2219 m_android_chat_open = false;
2225 void Game::toggleFreeMove()
2227 bool free_move = !g_settings->getBool("free_move");
2228 g_settings->set("free_move", bool_to_cstr(free_move));
2231 if (client->checkPrivilege("fly")) {
2232 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2234 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2237 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2241 void Game::toggleFreeMoveAlt()
2243 if (!runData.reset_jump_timer) {
2244 runData.jump_timer_down_before = runData.jump_timer_down;
2245 runData.jump_timer_down = 0.0f;
2248 // key down (0.2 s max.), then key up (0.2 s max.), then key down
2249 if (m_cache_doubletap_jump && runData.jump_timer_up < 0.2f &&
2250 runData.jump_timer_down_before < 0.4f) // 0.2 + 0.2
2253 runData.reset_jump_timer = true;
2257 void Game::togglePitchMove()
2259 bool pitch_move = !g_settings->getBool("pitch_move");
2260 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2263 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2265 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2270 void Game::toggleFast()
2272 bool fast_move = !g_settings->getBool("fast_move");
2273 bool has_fast_privs = client->checkPrivilege("fast");
2274 g_settings->set("fast_move", bool_to_cstr(fast_move));
2277 if (has_fast_privs) {
2278 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2280 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2283 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2286 #ifdef HAVE_TOUCHSCREENGUI
2287 m_cache_hold_aux1 = fast_move && has_fast_privs;
2292 void Game::toggleNoClip()
2294 bool noclip = !g_settings->getBool("noclip");
2295 g_settings->set("noclip", bool_to_cstr(noclip));
2298 if (client->checkPrivilege("noclip")) {
2299 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2301 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2304 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2308 void Game::toggleCinematic()
2310 bool cinematic = !g_settings->getBool("cinematic");
2311 g_settings->set("cinematic", bool_to_cstr(cinematic));
2314 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2316 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2319 void Game::toggleBlockBounds()
2321 LocalPlayer *player = client->getEnv().getLocalPlayer();
2322 if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
2323 m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
2326 enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
2328 case Hud::BLOCK_BOUNDS_OFF:
2329 m_game_ui->showTranslatedStatusText("Block bounds hidden");
2331 case Hud::BLOCK_BOUNDS_CURRENT:
2332 m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
2334 case Hud::BLOCK_BOUNDS_NEAR:
2335 m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
2337 case Hud::BLOCK_BOUNDS_MAX:
2338 m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
2345 // Autoforward by toggling continuous forward.
2346 void Game::toggleAutoforward()
2348 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2349 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2351 if (autorun_enabled)
2352 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2354 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2357 void Game::toggleMinimap(bool shift_pressed)
2359 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2363 mapper->toggleMinimapShape();
2367 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2369 // Not so satisying code to keep compatibility with old fixed mode system
2371 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2373 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2374 m_game_ui->m_flags.show_minimap = false;
2377 // If radar is disabled, try to find a non radar mode or fall back to 0
2378 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2379 while (mapper->getModeIndex() &&
2380 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2383 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2387 // End of 'not so satifying code'
2388 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2389 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2390 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2392 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2395 void Game::toggleFog()
2397 bool fog_enabled = g_settings->getBool("enable_fog");
2398 g_settings->setBool("enable_fog", !fog_enabled);
2400 m_game_ui->showTranslatedStatusText("Fog disabled");
2402 m_game_ui->showTranslatedStatusText("Fog enabled");
2406 void Game::toggleDebug()
2408 LocalPlayer *player = client->getEnv().getLocalPlayer();
2409 bool has_debug = client->checkPrivilege("debug");
2410 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
2411 // Initial: No debug info
2412 // 1x toggle: Debug text
2413 // 2x toggle: Debug text with profiler graph
2414 // 3x toggle: Debug text and wireframe (needs "debug" priv)
2415 // Next toggle: Back to initial
2417 // The debug text can be in 2 modes: minimal and basic.
2418 // * Minimal: Only technical client info that not gameplay-relevant
2419 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
2420 // Basic mode is used when player has the debug HUD flag set,
2421 // otherwise the Minimal mode is used.
2422 if (!m_game_ui->m_flags.show_minimal_debug) {
2423 m_game_ui->m_flags.show_minimal_debug = true;
2424 if (has_basic_debug)
2425 m_game_ui->m_flags.show_basic_debug = true;
2426 m_game_ui->m_flags.show_profiler_graph = false;
2427 draw_control->show_wireframe = false;
2428 m_game_ui->showTranslatedStatusText("Debug info shown");
2429 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2430 if (has_basic_debug)
2431 m_game_ui->m_flags.show_basic_debug = true;
2432 m_game_ui->m_flags.show_profiler_graph = true;
2433 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2434 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2435 if (has_basic_debug)
2436 m_game_ui->m_flags.show_basic_debug = true;
2437 m_game_ui->m_flags.show_profiler_graph = false;
2438 draw_control->show_wireframe = true;
2439 m_game_ui->showTranslatedStatusText("Wireframe shown");
2441 m_game_ui->m_flags.show_minimal_debug = false;
2442 m_game_ui->m_flags.show_basic_debug = false;
2443 m_game_ui->m_flags.show_profiler_graph = false;
2444 draw_control->show_wireframe = false;
2446 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2448 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2454 void Game::toggleUpdateCamera()
2456 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2457 if (m_flags.disable_camera_update)
2458 m_game_ui->showTranslatedStatusText("Camera update disabled");
2460 m_game_ui->showTranslatedStatusText("Camera update enabled");
2464 void Game::increaseViewRange()
2466 s16 range = g_settings->getS16("viewing_range");
2467 s16 range_new = range + 10;
2469 if (range_new > 4000) {
2471 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
2472 m_game_ui->showStatusText(msg);
2474 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2475 m_game_ui->showStatusText(msg);
2477 g_settings->set("viewing_range", itos(range_new));
2481 void Game::decreaseViewRange()
2483 s16 range = g_settings->getS16("viewing_range");
2484 s16 range_new = range - 10;
2486 if (range_new < 20) {
2488 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
2489 m_game_ui->showStatusText(msg);
2491 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2492 m_game_ui->showStatusText(msg);
2494 g_settings->set("viewing_range", itos(range_new));
2498 void Game::toggleFullViewRange()
2500 draw_control->range_all = !draw_control->range_all;
2501 if (draw_control->range_all)
2502 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2504 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2508 void Game::checkZoomEnabled()
2510 LocalPlayer *player = client->getEnv().getLocalPlayer();
2511 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2512 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2515 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2517 #if IRRLICHT_VERSION_MT_REVISION >= 9
2519 device->getCursorControl()->setRelativeMode(false);
2521 device->getCursorControl()->setRelativeMode(true);
2523 if ((device->isWindowActive() && device->isWindowFocused()
2524 && !isMenuActive()) || input->isRandom()) {
2527 if (!input->isRandom()) {
2528 // Mac OSX gets upset if this is set every frame
2529 if (device->getCursorControl()->isVisible())
2530 device->getCursorControl()->setVisible(false);
2534 if (m_first_loop_after_window_activation) {
2535 m_first_loop_after_window_activation = false;
2537 input->setMousePos(driver->getScreenSize().Width / 2,
2538 driver->getScreenSize().Height / 2);
2540 updateCameraOrientation(cam, dtime);
2546 // Mac OSX gets upset if this is set every frame
2547 if (!device->getCursorControl()->isVisible())
2548 device->getCursorControl()->setVisible(true);
2551 m_first_loop_after_window_activation = true;
2556 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2557 // responsiveness independently of FOV.
2558 f32 Game::getSensitivityScaleFactor() const
2560 f32 fov_y = client->getCamera()->getFovY();
2562 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2563 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2565 return tan(fov_y / 2.0f) * 1.3763818698f;
2568 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2570 #ifdef HAVE_TOUCHSCREENGUI
2571 if (g_touchscreengui) {
2572 cam->camera_yaw += g_touchscreengui->getYawChange();
2573 cam->camera_pitch = g_touchscreengui->getPitch();
2576 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2577 v2s32 dist = input->getMousePos() - center;
2579 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2583 f32 sens_scale = getSensitivityScaleFactor();
2584 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2585 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2587 if (dist.X != 0 || dist.Y != 0)
2588 input->setMousePos(center.X, center.Y);
2589 #ifdef HAVE_TOUCHSCREENGUI
2593 if (m_cache_enable_joysticks) {
2594 f32 sens_scale = getSensitivityScaleFactor();
2595 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
2596 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2597 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2600 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2604 void Game::updatePlayerControl(const CameraOrientation &cam)
2606 LocalPlayer *player = client->getEnv().getLocalPlayer();
2608 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2610 PlayerControl control(
2611 isKeyDown(KeyType::FORWARD),
2612 isKeyDown(KeyType::BACKWARD),
2613 isKeyDown(KeyType::LEFT),
2614 isKeyDown(KeyType::RIGHT),
2615 isKeyDown(KeyType::JUMP) || player->getAutojump(),
2616 isKeyDown(KeyType::AUX1),
2617 isKeyDown(KeyType::SNEAK),
2618 isKeyDown(KeyType::ZOOM),
2619 isKeyDown(KeyType::DIG),
2620 isKeyDown(KeyType::PLACE),
2623 input->getMovementSpeed(),
2624 input->getMovementDirection()
2627 // autoforward if set: move at maximum speed
2628 if (player->getPlayerSettings().continuous_forward &&
2629 client->activeObjectsReceived() && !player->isDead()) {
2630 control.movement_speed = 1.0f;
2631 // sideways movement only
2632 float dx = sin(control.movement_direction);
2633 control.movement_direction = atan2(dx, 1.0f);
2636 #ifdef HAVE_TOUCHSCREENGUI
2637 /* For touch, simulate holding down AUX1 (fast move) if the user has
2638 * the fast_move setting toggled on. If there is an aux1 key defined for
2639 * touch then its meaning is inverted (i.e. holding aux1 means walk and
2642 if (m_cache_hold_aux1) {
2643 control.aux1 = control.aux1 ^ true;
2647 client->setPlayerControl(control);
2653 inline void Game::step(f32 dtime)
2656 server->step(dtime);
2658 client->step(dtime);
2661 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2664 for (auto &&child: node->getChildren())
2665 pauseNodeAnimation(paused, child);
2666 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2668 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2669 float speed = animated_node->getAnimationSpeed();
2672 paused.push_back({grab(animated_node), speed});
2673 animated_node->setAnimationSpeed(0.0f);
2676 void Game::pauseAnimation()
2678 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2681 void Game::resumeAnimation()
2683 for (auto &&pair: paused_animated_nodes)
2684 pair.first->setAnimationSpeed(pair.second);
2685 paused_animated_nodes.clear();
2688 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2689 {&Game::handleClientEvent_None},
2690 {&Game::handleClientEvent_PlayerDamage},
2691 {&Game::handleClientEvent_PlayerForceMove},
2692 {&Game::handleClientEvent_Deathscreen},
2693 {&Game::handleClientEvent_ShowFormSpec},
2694 {&Game::handleClientEvent_ShowLocalFormSpec},
2695 {&Game::handleClientEvent_HandleParticleEvent},
2696 {&Game::handleClientEvent_HandleParticleEvent},
2697 {&Game::handleClientEvent_HandleParticleEvent},
2698 {&Game::handleClientEvent_HudAdd},
2699 {&Game::handleClientEvent_HudRemove},
2700 {&Game::handleClientEvent_HudChange},
2701 {&Game::handleClientEvent_SetSky},
2702 {&Game::handleClientEvent_SetSun},
2703 {&Game::handleClientEvent_SetMoon},
2704 {&Game::handleClientEvent_SetStars},
2705 {&Game::handleClientEvent_OverrideDayNigthRatio},
2706 {&Game::handleClientEvent_CloudParams},
2709 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2711 FATAL_ERROR("ClientEvent type None received");
2714 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2716 if (client->modsLoaded())
2717 client->getScript()->on_damage_taken(event->player_damage.amount);
2719 if (!event->player_damage.effect)
2722 // Damage flash and hurt tilt are not used at death
2723 if (client->getHP() > 0) {
2724 LocalPlayer *player = client->getEnv().getLocalPlayer();
2726 f32 hp_max = player->getCAO() ?
2727 player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2728 f32 damage_ratio = event->player_damage.amount / hp_max;
2730 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2731 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2733 player->hurt_tilt_timer = 1.5f;
2734 player->hurt_tilt_strength =
2735 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2738 // Play damage sound
2739 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2742 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2744 cam->camera_yaw = event->player_force_move.yaw;
2745 cam->camera_pitch = event->player_force_move.pitch;
2748 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2750 // If client scripting is enabled, deathscreen is handled by CSM code in
2751 // builtin/client/init.lua
2752 if (client->modsLoaded())
2753 client->getScript()->on_death();
2755 showDeathFormspec();
2757 /* Handle visualization */
2758 LocalPlayer *player = client->getEnv().getLocalPlayer();
2759 runData.damage_flash = 0;
2760 player->hurt_tilt_timer = 0;
2761 player->hurt_tilt_strength = 0;
2764 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2766 if (event->show_formspec.formspec->empty()) {
2767 auto formspec = m_game_ui->getFormspecGUI();
2768 if (formspec && (event->show_formspec.formname->empty()
2769 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2770 formspec->quitMenu();
2773 FormspecFormSource *fs_src =
2774 new FormspecFormSource(*(event->show_formspec.formspec));
2775 TextDestPlayerInventory *txt_dst =
2776 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2778 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2779 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2780 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2783 delete event->show_formspec.formspec;
2784 delete event->show_formspec.formname;
2787 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2789 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2790 LocalFormspecHandler *txt_dst =
2791 new LocalFormspecHandler(*event->show_formspec.formname, client);
2792 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2793 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2795 delete event->show_formspec.formspec;
2796 delete event->show_formspec.formname;
2799 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2800 CameraOrientation *cam)
2802 LocalPlayer *player = client->getEnv().getLocalPlayer();
2803 client->getParticleManager()->handleParticleEvent(event, client, player);
2806 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2808 LocalPlayer *player = client->getEnv().getLocalPlayer();
2810 u32 server_id = event->hudadd->server_id;
2811 // ignore if we already have a HUD with that ID
2812 auto i = m_hud_server_to_client.find(server_id);
2813 if (i != m_hud_server_to_client.end()) {
2814 delete event->hudadd;
2818 HudElement *e = new HudElement;
2819 e->type = static_cast<HudElementType>(event->hudadd->type);
2820 e->pos = event->hudadd->pos;
2821 e->name = event->hudadd->name;
2822 e->scale = event->hudadd->scale;
2823 e->text = event->hudadd->text;
2824 e->number = event->hudadd->number;
2825 e->item = event->hudadd->item;
2826 e->dir = event->hudadd->dir;
2827 e->align = event->hudadd->align;
2828 e->offset = event->hudadd->offset;
2829 e->world_pos = event->hudadd->world_pos;
2830 e->size = event->hudadd->size;
2831 e->z_index = event->hudadd->z_index;
2832 e->text2 = event->hudadd->text2;
2833 e->style = event->hudadd->style;
2834 m_hud_server_to_client[server_id] = player->addHud(e);
2836 delete event->hudadd;
2839 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2841 LocalPlayer *player = client->getEnv().getLocalPlayer();
2843 auto i = m_hud_server_to_client.find(event->hudrm.id);
2844 if (i != m_hud_server_to_client.end()) {
2845 HudElement *e = player->removeHud(i->second);
2847 m_hud_server_to_client.erase(i);
2852 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2854 LocalPlayer *player = client->getEnv().getLocalPlayer();
2856 HudElement *e = nullptr;
2858 auto i = m_hud_server_to_client.find(event->hudchange->id);
2859 if (i != m_hud_server_to_client.end()) {
2860 e = player->getHud(i->second);
2864 delete event->hudchange;
2868 #define CASE_SET(statval, prop, dataprop) \
2870 e->prop = event->hudchange->dataprop; \
2873 switch (event->hudchange->stat) {
2874 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2876 CASE_SET(HUD_STAT_NAME, name, sdata);
2878 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2880 CASE_SET(HUD_STAT_TEXT, text, sdata);
2882 CASE_SET(HUD_STAT_NUMBER, number, data);
2884 CASE_SET(HUD_STAT_ITEM, item, data);
2886 CASE_SET(HUD_STAT_DIR, dir, data);
2888 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2890 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2892 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2894 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2896 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2898 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2900 CASE_SET(HUD_STAT_STYLE, style, data);
2905 delete event->hudchange;
2908 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2910 sky->setVisible(false);
2911 // Whether clouds are visible in front of a custom skybox.
2912 sky->setCloudsEnabled(event->set_sky->clouds);
2918 // Clear the old textures out in case we switch rendering type.
2919 sky->clearSkyboxTextures();
2920 // Handle according to type
2921 if (event->set_sky->type == "regular") {
2922 // Shows the mesh skybox
2923 sky->setVisible(true);
2924 // Update mesh based skybox colours if applicable.
2925 sky->setSkyColors(event->set_sky->sky_color);
2926 sky->setHorizonTint(
2927 event->set_sky->fog_sun_tint,
2928 event->set_sky->fog_moon_tint,
2929 event->set_sky->fog_tint_type
2931 } else if (event->set_sky->type == "skybox" &&
2932 event->set_sky->textures.size() == 6) {
2933 // Disable the dyanmic mesh skybox:
2934 sky->setVisible(false);
2936 sky->setFallbackBgColor(event->set_sky->bgcolor);
2937 // Set sunrise and sunset fog tinting:
2938 sky->setHorizonTint(
2939 event->set_sky->fog_sun_tint,
2940 event->set_sky->fog_moon_tint,
2941 event->set_sky->fog_tint_type
2943 // Add textures to skybox.
2944 for (int i = 0; i < 6; i++)
2945 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2947 // Handle everything else as plain color.
2948 if (event->set_sky->type != "plain")
2949 infostream << "Unknown sky type: "
2950 << (event->set_sky->type) << std::endl;
2951 sky->setVisible(false);
2952 sky->setFallbackBgColor(event->set_sky->bgcolor);
2953 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2954 sky->setHorizonTint(
2955 event->set_sky->bgcolor,
2956 event->set_sky->bgcolor,
2961 delete event->set_sky;
2964 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2966 sky->setSunVisible(event->sun_params->visible);
2967 sky->setSunTexture(event->sun_params->texture,
2968 event->sun_params->tonemap, texture_src);
2969 sky->setSunScale(event->sun_params->scale);
2970 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2971 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2972 delete event->sun_params;
2975 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2977 sky->setMoonVisible(event->moon_params->visible);
2978 sky->setMoonTexture(event->moon_params->texture,
2979 event->moon_params->tonemap, texture_src);
2980 sky->setMoonScale(event->moon_params->scale);
2981 delete event->moon_params;
2984 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2986 sky->setStarsVisible(event->star_params->visible);
2987 sky->setStarCount(event->star_params->count);
2988 sky->setStarColor(event->star_params->starcolor);
2989 sky->setStarScale(event->star_params->scale);
2990 sky->setStarDayOpacity(event->star_params->day_opacity);
2991 delete event->star_params;
2994 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2995 CameraOrientation *cam)
2997 client->getEnv().setDayNightRatioOverride(
2998 event->override_day_night_ratio.do_override,
2999 event->override_day_night_ratio.ratio_f * 1000.0f);
3002 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
3007 clouds->setDensity(event->cloud_params.density);
3008 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
3009 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
3010 clouds->setHeight(event->cloud_params.height);
3011 clouds->setThickness(event->cloud_params.thickness);
3012 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
3015 void Game::processClientEvents(CameraOrientation *cam)
3017 while (client->hasClientEvents()) {
3018 std::unique_ptr<ClientEvent> event(client->getClientEvent());
3019 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
3020 const ClientEventHandler& evHandler = clientEventHandler[event->type];
3021 (this->*evHandler.handler)(event.get(), cam);
3025 void Game::updateChat(f32 dtime)
3027 // Get new messages from error log buffer
3028 while (!m_chat_log_buf.empty())
3029 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
3031 // Get new messages from client
3032 std::wstring message;
3033 while (client->getChatMessage(message)) {
3034 chat_backend->addUnparsedMessage(message);
3037 // Remove old messages
3038 chat_backend->step(dtime);
3040 // Display all messages in a static text element
3041 auto &buf = chat_backend->getRecentBuffer();
3042 if (buf.getLinesModified()) {
3043 buf.resetLinesModified();
3044 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
3047 // Make sure that the size is still correct
3048 m_game_ui->updateChatSize();
3051 void Game::updateCamera(f32 dtime)
3053 LocalPlayer *player = client->getEnv().getLocalPlayer();
3056 For interaction purposes, get info about the held item
3058 - Is it a usable item?
3059 - Can it point to liquids?
3061 ItemStack playeritem;
3063 ItemStack selected, hand;
3064 playeritem = player->getWieldedItem(&selected, &hand);
3067 ToolCapabilities playeritem_toolcap =
3068 playeritem.getToolCapabilities(itemdef_manager);
3070 v3s16 old_camera_offset = camera->getOffset();
3072 if (wasKeyDown(KeyType::CAMERA_MODE)) {
3073 GenericCAO *playercao = player->getCAO();
3075 // If playercao not loaded, don't change camera
3079 camera->toggleCameraMode();
3081 #ifdef HAVE_TOUCHSCREENGUI
3082 if (g_touchscreengui)
3083 g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed());
3086 // Make the player visible depending on camera mode.
3087 playercao->updateMeshCulling();
3088 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
3091 float full_punch_interval = playeritem_toolcap.full_punch_interval;
3092 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
3094 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
3095 camera->update(player, dtime, tool_reload_ratio);
3096 camera->step(dtime);
3098 f32 camera_fov = camera->getFovMax();
3099 v3s16 camera_offset = camera->getOffset();
3101 m_camera_offset_changed = (camera_offset != old_camera_offset);
3103 if (!m_flags.disable_camera_update) {
3104 v3f camera_position = camera->getPosition();
3105 v3f camera_direction = camera->getDirection();
3107 client->getEnv().getClientMap().updateCamera(camera_position,
3108 camera_direction, camera_fov, camera_offset);
3110 if (m_camera_offset_changed) {
3111 client->updateCameraOffset(camera_offset);
3112 client->getEnv().updateCameraOffset(camera_offset);
3115 clouds->updateCameraOffset(camera_offset);
3121 void Game::updateSound(f32 dtime)
3123 // Update sound listener
3124 v3s16 camera_offset = camera->getOffset();
3125 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3126 v3f(0, 0, 0), // velocity
3127 camera->getDirection(),
3128 camera->getCameraNode()->getUpVector());
3130 bool mute_sound = g_settings->getBool("mute_sound");
3132 sound->setListenerGain(0.0f);
3134 // Check if volume is in the proper range, else fix it.
3135 float old_volume = g_settings->getFloat("sound_volume");
3136 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3137 sound->setListenerGain(new_volume);
3139 if (old_volume != new_volume) {
3140 g_settings->setFloat("sound_volume", new_volume);
3144 LocalPlayer *player = client->getEnv().getLocalPlayer();
3146 // Tell the sound maker whether to make footstep sounds
3147 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3149 // Update sound maker
3150 if (player->makes_footstep_sound)
3151 soundmaker->step(dtime);
3153 ClientMap &map = client->getEnv().getClientMap();
3154 MapNode n = map.getNode(player->getFootstepNodePos());
3155 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3159 void Game::processPlayerInteraction(f32 dtime, bool show_hud)
3161 LocalPlayer *player = client->getEnv().getLocalPlayer();
3163 const v3f camera_direction = camera->getDirection();
3164 const v3s16 camera_offset = camera->getOffset();
3167 Calculate what block is the crosshair pointing to
3170 ItemStack selected_item, hand_item;
3171 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3173 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3174 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3176 core::line3d<f32> shootline;
3178 switch (camera->getCameraMode()) {
3179 case CAMERA_MODE_FIRST:
3180 // Shoot from camera position, with bobbing
3181 shootline.start = camera->getPosition();
3183 case CAMERA_MODE_THIRD:
3184 // Shoot from player head, no bobbing
3185 shootline.start = camera->getHeadPosition();
3187 case CAMERA_MODE_THIRD_FRONT:
3188 shootline.start = camera->getHeadPosition();
3189 // prevent player pointing anything in front-view
3193 shootline.end = shootline.start + camera_direction * BS * d;
3195 #ifdef HAVE_TOUCHSCREENGUI
3196 if (g_touchscreengui && isNoCrosshairAllowed()) {
3197 shootline = g_touchscreengui->getShootline();
3198 // Scale shootline to the acual distance the player can reach
3199 shootline.end = shootline.start +
3200 shootline.getVector().normalize() * BS * d;
3201 shootline.start += intToFloat(camera_offset, BS);
3202 shootline.end += intToFloat(camera_offset, BS);
3206 PointedThing pointed = updatePointedThing(shootline,
3207 selected_def.liquids_pointable,
3208 !runData.btn_down_for_dig,
3211 if (pointed != runData.pointed_old)
3212 infostream << "Pointing at " << pointed.dump() << std::endl;
3214 // Note that updating the selection mesh every frame is not particularly efficient,
3215 // but the halo rendering code is already inefficient so there's no point in optimizing it here
3216 hud->updateSelectionMesh(camera_offset);
3218 // Allow digging again if button is not pressed
3219 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3220 runData.digging_blocked = false;
3224 - releasing dig button
3225 - pointing away from node
3227 if (runData.digging) {
3228 if (wasKeyReleased(KeyType::DIG)) {
3229 infostream << "Dig button released (stopped digging)" << std::endl;
3230 runData.digging = false;
3231 } else if (pointed != runData.pointed_old) {
3232 if (pointed.type == POINTEDTHING_NODE
3233 && runData.pointed_old.type == POINTEDTHING_NODE
3234 && pointed.node_undersurface
3235 == runData.pointed_old.node_undersurface) {
3236 // Still pointing to the same node, but a different face.
3239 infostream << "Pointing away from node (stopped digging)" << std::endl;
3240 runData.digging = false;
3241 hud->updateSelectionMesh(camera_offset);
3245 if (!runData.digging) {
3246 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3247 client->setCrack(-1, v3s16(0, 0, 0));
3248 runData.dig_time = 0.0;
3250 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3251 // Remove e.g. torches faster when clicking instead of holding dig button
3252 runData.nodig_delay_timer = 0;
3253 runData.dig_instantly = false;
3256 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3257 runData.btn_down_for_dig = false;
3259 runData.punching = false;
3261 soundmaker->m_player_leftpunch_sound = SimpleSoundSpec();
3262 soundmaker->m_player_leftpunch_sound2 = pointed.type != POINTEDTHING_NOTHING ?
3263 selected_def.sound_use : selected_def.sound_use_air;
3265 // Prepare for repeating, unless we're not supposed to
3266 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3267 runData.repeat_place_timer += dtime;
3269 runData.repeat_place_timer = 0;
3271 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3272 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3273 !client->getScript()->on_item_use(selected_item, pointed)))
3274 client->interact(INTERACT_USE, pointed);
3275 } else if (pointed.type == POINTEDTHING_NODE) {
3276 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3277 } else if (pointed.type == POINTEDTHING_OBJECT) {
3278 v3f player_position = player->getPosition();
3279 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
3280 handlePointingAtObject(pointed, tool_item, player_position,
3281 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
3282 } else if (isKeyDown(KeyType::DIG)) {
3283 // When button is held down in air, show continuous animation
3284 runData.punching = true;
3285 // Run callback even though item is not usable
3286 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3287 client->getScript()->on_item_use(selected_item, pointed);
3288 } else if (wasKeyPressed(KeyType::PLACE)) {
3289 handlePointingAtNothing(selected_item);
3292 runData.pointed_old = pointed;
3294 if (runData.punching || wasKeyPressed(KeyType::DIG))
3295 camera->setDigging(0); // dig animation
3297 input->clearWasKeyPressed();
3298 input->clearWasKeyReleased();
3299 // Ensure DIG & PLACE are marked as handled
3300 wasKeyDown(KeyType::DIG);
3301 wasKeyDown(KeyType::PLACE);
3303 input->joystick.clearWasKeyPressed(KeyType::DIG);
3304 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3306 input->joystick.clearWasKeyReleased(KeyType::DIG);
3307 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3311 PointedThing Game::updatePointedThing(
3312 const core::line3d<f32> &shootline,
3313 bool liquids_pointable,
3314 bool look_for_object,
3315 const v3s16 &camera_offset)
3317 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3318 selectionboxes->clear();
3319 hud->setSelectedFaceNormal(v3f());
3320 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3321 "show_entity_selectionbox");
3323 ClientEnvironment &env = client->getEnv();
3324 ClientMap &map = env.getClientMap();
3325 const NodeDefManager *nodedef = map.getNodeDefManager();
3327 runData.selected_object = NULL;
3328 hud->pointing_at_object = false;
3330 RaycastState s(shootline, look_for_object, liquids_pointable);
3331 PointedThing result;
3332 env.continueRaycast(&s, &result);
3333 if (result.type == POINTEDTHING_OBJECT) {
3334 hud->pointing_at_object = true;
3336 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3337 aabb3f selection_box;
3338 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3339 runData.selected_object->getSelectionBox(&selection_box)) {
3340 v3f pos = runData.selected_object->getPosition();
3341 selectionboxes->push_back(aabb3f(selection_box));
3342 hud->setSelectionPos(pos, camera_offset);
3343 GenericCAO* gcao = dynamic_cast<GenericCAO*>(runData.selected_object);
3344 if (gcao != nullptr && gcao->getProperties().rotate_selectionbox)
3345 hud->setSelectionRotation(gcao->getSceneNode()->getAbsoluteTransformation().getRotationDegrees());
3347 hud->setSelectionRotation(v3f());
3349 hud->setSelectedFaceNormal(result.raw_intersection_normal);
3350 } else if (result.type == POINTEDTHING_NODE) {
3351 // Update selection boxes
3352 MapNode n = map.getNode(result.node_undersurface);
3353 std::vector<aabb3f> boxes;
3354 n.getSelectionBoxes(nodedef, &boxes,
3355 n.getNeighbors(result.node_undersurface, &map));
3358 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3359 i != boxes.end(); ++i) {
3361 box.MinEdge -= v3f(d, d, d);
3362 box.MaxEdge += v3f(d, d, d);
3363 selectionboxes->push_back(box);
3365 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3367 hud->setSelectionRotation(v3f());
3368 hud->setSelectedFaceNormal(result.intersection_normal);
3371 // Update selection mesh light level and vertex colors
3372 if (!selectionboxes->empty()) {
3373 v3f pf = hud->getSelectionPos();
3374 v3s16 p = floatToInt(pf, BS);
3376 // Get selection mesh light level
3377 MapNode n = map.getNode(p);
3378 u16 node_light = getInteriorLight(n, -1, nodedef);
3379 u16 light_level = node_light;
3381 for (const v3s16 &dir : g_6dirs) {
3382 n = map.getNode(p + dir);
3383 node_light = getInteriorLight(n, -1, nodedef);
3384 if (node_light > light_level)
3385 light_level = node_light;
3388 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3390 final_color_blend(&c, light_level, daynight_ratio);
3392 // Modify final color a bit with time
3393 u32 timer = client->getEnv().getFrameTime() % 5000;
3394 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3395 float sin_r = 0.08f * std::sin(timerf);
3396 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3397 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3398 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3399 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3400 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3402 // Set mesh final color
3403 hud->setSelectionMeshColor(c);
3409 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3411 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3412 PointedThing fauxPointed;
3413 fauxPointed.type = POINTEDTHING_NOTHING;
3414 client->interact(INTERACT_ACTIVATE, fauxPointed);
3418 void Game::handlePointingAtNode(const PointedThing &pointed,
3419 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3421 v3s16 nodepos = pointed.node_undersurface;
3422 v3s16 neighborpos = pointed.node_abovesurface;
3425 Check information text of node
3428 ClientMap &map = client->getEnv().getClientMap();
3430 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3431 && !runData.digging_blocked
3432 && client->checkPrivilege("interact")) {
3433 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3436 // This should be done after digging handling
3437 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3440 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3441 meta->getString("infotext"))));
3443 MapNode n = map.getNode(nodepos);
3445 if (nodedef_manager->get(n).name == "unknown") {
3446 m_game_ui->setInfoText(L"Unknown node");
3450 if ((wasKeyPressed(KeyType::PLACE) ||
3451 runData.repeat_place_timer >= m_repeat_place_time) &&
3452 client->checkPrivilege("interact")) {
3453 runData.repeat_place_timer = 0;
3454 infostream << "Place button pressed while looking at ground" << std::endl;
3456 // Placing animation (always shown for feedback)
3457 camera->setDigging(1);
3459 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3461 // If the wielded item has node placement prediction,
3463 // And also set the sound and send the interact
3464 // But first check for meta formspec and rightclickable
3465 auto &def = selected_item.getDefinition(itemdef_manager);
3466 bool placed = nodePlacement(def, selected_item, nodepos, neighborpos,
3469 if (placed && client->modsLoaded())
3470 client->getScript()->on_placenode(pointed, def);
3474 bool Game::nodePlacement(const ItemDefinition &selected_def,
3475 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighborpos,
3476 const PointedThing &pointed, const NodeMetadata *meta)
3478 const auto &prediction = selected_def.node_placement_prediction;
3480 const NodeDefManager *nodedef = client->ndef();
3481 ClientMap &map = client->getEnv().getClientMap();
3483 bool is_valid_position;
3485 node = map.getNode(nodepos, &is_valid_position);
3486 if (!is_valid_position) {
3487 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3492 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3493 && !isKeyDown(KeyType::SNEAK)) {
3494 // on_rightclick callbacks are called anyway
3495 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3496 client->interact(INTERACT_PLACE, pointed);
3498 infostream << "Launching custom inventory view" << std::endl;
3500 InventoryLocation inventoryloc;
3501 inventoryloc.setNodeMeta(nodepos);
3503 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3504 &client->getEnv().getClientMap(), nodepos);
3505 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3507 auto *&formspec = m_game_ui->updateFormspec("");
3508 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3509 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3511 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3515 // on_rightclick callback
3516 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3517 !isKeyDown(KeyType::SNEAK))) {
3519 client->interact(INTERACT_PLACE, pointed);
3523 verbosestream << "Node placement prediction for "
3524 << selected_def.name << " is " << prediction << std::endl;
3525 v3s16 p = neighborpos;
3527 // Place inside node itself if buildable_to
3528 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3529 if (is_valid_position) {
3530 if (nodedef->get(n_under).buildable_to) {
3533 node = map.getNode(p, &is_valid_position);
3534 if (is_valid_position && !nodedef->get(node).buildable_to) {
3535 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3537 client->interact(INTERACT_PLACE, pointed);
3543 // Find id of predicted node
3545 bool found = nodedef->getId(prediction, id);
3548 errorstream << "Node placement prediction failed for "
3549 << selected_def.name << " (places " << prediction
3550 << ") - Name not known" << std::endl;
3551 // Handle this as if prediction was empty
3553 client->interact(INTERACT_PLACE, pointed);
3557 const ContentFeatures &predicted_f = nodedef->get(id);
3559 // Compare core.item_place_node() for what the server does with param2
3560 MapNode predicted_node(id, 0, 0);
3562 const u8 place_param2 = selected_def.place_param2;
3565 predicted_node.setParam2(place_param2);
3566 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3567 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3568 v3s16 dir = nodepos - neighborpos;
3570 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3571 predicted_node.setParam2(dir.Y < 0 ? 1 : 0);
3572 } else if (abs(dir.X) > abs(dir.Z)) {
3573 predicted_node.setParam2(dir.X < 0 ? 3 : 2);
3575 predicted_node.setParam2(dir.Z < 0 ? 5 : 4);
3577 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3578 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR ||
3579 predicted_f.param_type_2 == CPT2_4DIR ||
3580 predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3581 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3583 if (abs(dir.X) > abs(dir.Z)) {
3584 predicted_node.setParam2(dir.X < 0 ? 3 : 1);
3586 predicted_node.setParam2(dir.Z < 0 ? 2 : 0);
3590 // Check attachment if node is in group attached_node
3591 int an = itemgroup_get(predicted_f.groups, "attached_node");
3596 pp = p + v3s16(0, -1, 0);
3597 } else if (an == 4) {
3598 pp = p + v3s16(0, 1, 0);
3599 } else if (an == 2) {
3600 if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3601 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR ||
3602 predicted_f.param_type_2 == CPT2_4DIR ||
3603 predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3604 pp = p + facedir_dirs[predicted_node.getFaceDir(nodedef)];
3608 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3609 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3610 pp = p + predicted_node.getWallMountedDir(nodedef);
3612 pp = p + v3s16(0, -1, 0);
3615 if (!nodedef->get(map.getNode(pp)).walkable) {
3616 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3618 client->interact(INTERACT_PLACE, pointed);
3624 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3625 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3626 || predicted_f.param_type_2 == CPT2_COLORED_4DIR
3627 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3628 const auto &indexstr = selected_item.metadata.
3629 getString("palette_index", 0);
3630 if (!indexstr.empty()) {
3631 s32 index = mystoi(indexstr);
3632 if (predicted_f.param_type_2 == CPT2_COLOR) {
3633 predicted_node.setParam2(index);
3634 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3635 // param2 = pure palette index + other
3636 predicted_node.setParam2((index & 0xf8) | (predicted_node.getParam2() & 0x07));
3637 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3638 // param2 = pure palette index + other
3639 predicted_node.setParam2((index & 0xe0) | (predicted_node.getParam2() & 0x1f));
3640 } else if (predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3641 // param2 = pure palette index + other
3642 predicted_node.setParam2((index & 0xfc) | (predicted_node.getParam2() & 0x03));
3647 // Add node to client map
3649 LocalPlayer *player = client->getEnv().getLocalPlayer();
3651 // Don't place node when player would be inside new node
3652 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3653 if (!predicted_f.walkable ||
3654 g_settings->getBool("enable_build_where_you_stand") ||
3655 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3656 (predicted_f.walkable &&
3657 neighborpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3658 neighborpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3659 // This triggers the required mesh update too
3660 client->addNode(p, predicted_node);
3662 client->interact(INTERACT_PLACE, pointed);
3663 // A node is predicted, also play a sound
3664 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3667 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3670 } catch (const InvalidPositionException &e) {
3671 errorstream << "Node placement prediction failed for "
3672 << selected_def.name << " (places "
3673 << prediction << ") - Position not loaded" << std::endl;
3674 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3679 void Game::handlePointingAtObject(const PointedThing &pointed,
3680 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3682 std::wstring infotext = unescape_translate(
3683 utf8_to_wide(runData.selected_object->infoText()));
3686 if (!infotext.empty()) {
3689 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3692 m_game_ui->setInfoText(infotext);
3694 if (isKeyDown(KeyType::DIG)) {
3695 bool do_punch = false;
3696 bool do_punch_damage = false;
3698 if (runData.object_hit_delay_timer <= 0.0) {
3700 do_punch_damage = true;
3701 runData.object_hit_delay_timer = object_hit_delay;
3704 if (wasKeyPressed(KeyType::DIG))
3708 infostream << "Punched object" << std::endl;
3709 runData.punching = true;
3712 if (do_punch_damage) {
3713 // Report direct punch
3714 v3f objpos = runData.selected_object->getPosition();
3715 v3f dir = (objpos - player_position).normalize();
3717 bool disable_send = runData.selected_object->directReportPunch(
3718 dir, &tool_item, runData.time_from_last_punch);
3719 runData.time_from_last_punch = 0;
3722 client->interact(INTERACT_START_DIGGING, pointed);
3724 } else if (wasKeyDown(KeyType::PLACE)) {
3725 infostream << "Pressed place button while pointing at object" << std::endl;
3726 client->interact(INTERACT_PLACE, pointed); // place
3731 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3732 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3734 // See also: serverpackethandle.cpp, action == 2
3735 LocalPlayer *player = client->getEnv().getLocalPlayer();
3736 ClientMap &map = client->getEnv().getClientMap();
3737 MapNode n = map.getNode(nodepos);
3738 const auto &features = nodedef_manager->get(n);
3740 // NOTE: Similar piece of code exists on the server side for
3742 // Get digging parameters
3743 DigParams params = getDigParams(features.groups,
3744 &selected_item.getToolCapabilities(itemdef_manager),
3745 selected_item.wear);
3747 // If can't dig, try hand
3748 if (!params.diggable) {
3749 params = getDigParams(features.groups,
3750 &hand_item.getToolCapabilities(itemdef_manager));
3753 if (!params.diggable) {
3754 // I guess nobody will wait for this long
3755 runData.dig_time_complete = 10000000.0;
3757 runData.dig_time_complete = params.time;
3759 if (m_cache_enable_particles) {
3760 client->getParticleManager()->addNodeParticle(client,
3761 player, nodepos, n, features);
3765 if (!runData.digging) {
3766 infostream << "Started digging" << std::endl;
3767 runData.dig_instantly = runData.dig_time_complete == 0;
3768 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3771 client->interact(INTERACT_START_DIGGING, pointed);
3772 runData.digging = true;
3773 runData.btn_down_for_dig = true;
3776 if (!runData.dig_instantly) {
3777 runData.dig_index = (float)crack_animation_length
3779 / runData.dig_time_complete;
3781 // This is for e.g. torches
3782 runData.dig_index = crack_animation_length;
3785 const auto &sound_dig = features.sound_dig;
3787 if (sound_dig.exists() && params.diggable) {
3788 if (sound_dig.name == "__group") {
3789 if (!params.main_group.empty()) {
3790 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3791 soundmaker->m_player_leftpunch_sound.name =
3792 std::string("default_dig_") +
3796 soundmaker->m_player_leftpunch_sound = sound_dig;
3800 // Don't show cracks if not diggable
3801 if (runData.dig_time_complete >= 100000.0) {
3802 } else if (runData.dig_index < crack_animation_length) {
3803 client->setCrack(runData.dig_index, nodepos);
3805 infostream << "Digging completed" << std::endl;
3806 client->setCrack(-1, v3s16(0, 0, 0));
3808 runData.dig_time = 0;
3809 runData.digging = false;
3810 // we successfully dug, now block it from repeating if we want to be safe
3811 if (g_settings->getBool("safe_dig_and_place"))
3812 runData.digging_blocked = true;
3814 runData.nodig_delay_timer =
3815 runData.dig_time_complete / (float)crack_animation_length;
3817 // We don't want a corresponding delay to very time consuming nodes
3818 // and nodes without digging time (e.g. torches) get a fixed delay.
3819 if (runData.nodig_delay_timer > 0.3)
3820 runData.nodig_delay_timer = 0.3;
3821 else if (runData.dig_instantly)
3822 runData.nodig_delay_timer = 0.15;
3824 if (client->modsLoaded() &&
3825 client->getScript()->on_dignode(nodepos, n)) {
3829 if (features.node_dig_prediction == "air") {
3830 client->removeNode(nodepos);
3831 } else if (!features.node_dig_prediction.empty()) {
3833 bool found = nodedef_manager->getId(features.node_dig_prediction, id);
3835 client->addNode(nodepos, id, true);
3837 // implicit else: no prediction
3839 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3841 if (m_cache_enable_particles) {
3842 client->getParticleManager()->addDiggingParticles(client,
3843 player, nodepos, n, features);
3847 // Send event to trigger sound
3848 client->getEventManager()->put(new NodeDugEvent(nodepos, n));
3851 if (runData.dig_time_complete < 100000.0) {
3852 runData.dig_time += dtime;
3854 runData.dig_time = 0;
3855 client->setCrack(-1, nodepos);
3858 camera->setDigging(0); // Dig animation
3861 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3862 const CameraOrientation &cam)
3864 TimeTaker tt_update("Game::updateFrame()");
3865 LocalPlayer *player = client->getEnv().getLocalPlayer();
3871 client->getEnv().updateFrameTime(m_is_paused);
3877 if (draw_control->range_all) {
3878 runData.fog_range = 100000 * BS;
3880 runData.fog_range = draw_control->wanted_range * BS;
3884 Calculate general brightness
3886 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3887 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3888 float direct_brightness;
3891 // When in noclip mode force same sky brightness as above ground so you
3893 if (draw_control->allow_noclip && m_cache_enable_free_move &&
3894 client->checkPrivilege("fly")) {
3895 direct_brightness = time_brightness;
3896 sunlight_seen = true;
3898 float old_brightness = sky->getBrightness();
3899 direct_brightness = client->getEnv().getClientMap()
3900 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3901 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3905 float time_of_day_smooth = runData.time_of_day_smooth;
3906 float time_of_day = client->getEnv().getTimeOfDayF();
3908 static const float maxsm = 0.05f;
3909 static const float todsm = 0.05f;
3911 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3912 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3913 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3914 time_of_day_smooth = time_of_day;
3916 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3917 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3918 + (time_of_day + 1.0) * todsm;
3920 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3921 + time_of_day * todsm;
3923 runData.time_of_day_smooth = time_of_day_smooth;
3925 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3926 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3927 player->getPitch());
3933 if (sky->getCloudsVisible()) {
3934 clouds->setVisible(true);
3935 clouds->step(dtime);
3936 // camera->getPosition is not enough for 3rd person views
3937 v3f camera_node_position = camera->getCameraNode()->getPosition();
3938 v3s16 camera_offset = camera->getOffset();
3939 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3940 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3941 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3942 clouds->update(camera_node_position,
3943 sky->getCloudColor());
3944 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3945 // if inside clouds, and fog enabled, use that as sky
3947 video::SColor clouds_dark = clouds->getColor()
3948 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3949 sky->overrideColors(clouds_dark, clouds->getColor());
3950 sky->setInClouds(true);
3951 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3952 // do not draw clouds after all
3953 clouds->setVisible(false);
3956 clouds->setVisible(false);
3963 client->getParticleManager()->step(dtime);
3969 if (m_cache_enable_fog) {
3972 video::EFT_FOG_LINEAR,
3973 runData.fog_range * m_cache_fog_start,
3974 runData.fog_range * 1.0,
3982 video::EFT_FOG_LINEAR,
3994 if (player->hurt_tilt_timer > 0.0f) {
3995 player->hurt_tilt_timer -= dtime * 6.0f;
3997 if (player->hurt_tilt_timer < 0.0f)
3998 player->hurt_tilt_strength = 0.0f;
4002 Update minimap pos and rotation
4004 if (mapper && m_game_ui->m_flags.show_hud) {
4005 mapper->setPos(floatToInt(player->getPosition(), BS));
4006 mapper->setAngle(player->getYaw());
4010 Get chat messages from client
4019 if (player->getWieldIndex() != runData.new_playeritem)
4020 client->setPlayerItem(runData.new_playeritem);
4022 if (client->updateWieldedItem()) {
4023 // Update wielded tool
4024 ItemStack selected_item, hand_item;
4025 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
4026 camera->wield(tool_item);
4030 Update block draw list every 200ms or when camera direction has
4033 runData.update_draw_list_timer += dtime;
4034 runData.touch_blocks_timer += dtime;
4036 bool draw_list_updated = false;
4038 float update_draw_list_delta = 0.2f;
4040 v3f camera_direction = camera->getDirection();
4041 if (runData.update_draw_list_timer >= update_draw_list_delta
4042 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
4043 || m_camera_offset_changed
4044 || client->getEnv().getClientMap().needsUpdateDrawList()) {
4045 runData.update_draw_list_timer = 0;
4046 client->getEnv().getClientMap().updateDrawList();
4047 runData.update_draw_list_last_cam_dir = camera_direction;
4048 draw_list_updated = true;
4051 if (runData.touch_blocks_timer > update_draw_list_delta && !draw_list_updated) {
4052 client->getEnv().getClientMap().touchMapBlocks();
4053 runData.touch_blocks_timer = 0;
4056 if (RenderingEngine::get_shadow_renderer()) {
4060 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
4063 make sure menu is on top
4064 1. Delete formspec menu reference if menu was removed
4065 2. Else, make sure formspec menu is on top
4067 auto formspec = m_game_ui->getFormspecGUI();
4068 do { // breakable. only runs for one iteration
4072 if (formspec->getReferenceCount() == 1) {
4073 m_game_ui->deleteFormspec();
4077 auto &loc = formspec->getFormspecLocation();
4078 if (loc.type == InventoryLocation::NODEMETA) {
4079 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
4080 if (!meta || meta->getString("formspec").empty()) {
4081 formspec->quitMenu();
4087 guiroot->bringToFront(formspec);
4091 ==================== Drawing begins ====================
4093 const video::SColor skycolor = sky->getSkyColor();
4095 TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
4096 driver->beginScene(true, true, skycolor);
4098 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
4099 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
4100 (camera->getCameraMode() == CAMERA_MODE_FIRST));
4101 bool draw_crosshair = (
4102 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
4103 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
4104 #ifdef HAVE_TOUCHSCREENGUI
4105 if (isNoCrosshairAllowed())
4106 draw_crosshair = false;
4108 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
4109 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
4114 v2u32 screensize = driver->getScreenSize();
4116 if (m_game_ui->m_flags.show_profiler_graph)
4117 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
4122 if (runData.damage_flash > 0.0f) {
4123 video::SColor color(runData.damage_flash, 180, 0, 0);
4124 driver->draw2DRectangle(color,
4125 core::rect<s32>(0, 0, screensize.X, screensize.Y),
4128 runData.damage_flash -= 384.0f * dtime;
4132 ==================== End scene ====================
4134 #if IRRLICHT_VERSION_MT_REVISION < 5
4135 if (++m_reset_HW_buffer_counter > 500) {
4137 Periodically remove all mesh HW buffers.
4139 Work around for a quirk in Irrlicht where a HW buffer is only
4140 released after 20000 iterations (triggered from endScene()).
4142 Without this, all loaded but unused meshes will retain their HW
4143 buffers for at least 5 minutes, at which point looking up the HW buffers
4144 becomes a bottleneck and the framerate drops (as much as 30%).
4146 Tests showed that numbers between 50 and 1000 are good, so picked 500.
4147 There are no other public Irrlicht APIs that allow interacting with the
4148 HW buffers without tracking the status of every individual mesh.
4150 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4152 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4153 driver->removeAllHardwareBuffers();
4154 m_reset_HW_buffer_counter = 0;
4160 stats->drawtime = tt_draw.stop(true);
4161 g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
4162 g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
4165 /* Log times and stuff for visualization */
4166 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4168 Profiler::GraphValues values;
4169 g_profiler->graphGet(values);
4173 /****************************************************************************
4175 *****************************************************************************/
4176 void Game::updateShadows()
4178 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4182 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4184 float timeoftheday = getWickedTimeOfDay(in_timeofday);
4185 bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
4186 bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
4187 shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
4189 timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
4190 const float offset_constant = 10000.0f;
4192 v3f light = is_day ? sky->getSunDirection() : sky->getMoonDirection();
4194 v3f sun_pos = light * offset_constant;
4196 if (shadow->getDirectionalLightCount() == 0)
4197 shadow->addDirectionalLight();
4198 shadow->getDirectionalLight().setDirection(sun_pos);
4199 shadow->setTimeOfDay(in_timeofday);
4201 shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4204 /****************************************************************************
4206 ****************************************************************************/
4208 void FpsControl::reset()
4210 last_time = porting::getTimeUs();
4214 * On some computers framerate doesn't seem to be automatically limited
4216 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
4218 const float fps_limit = (device->isWindowFocused() && !g_menumgr.pausesGame())
4219 ? g_settings->getFloat("fps_max")
4220 : g_settings->getFloat("fps_max_unfocused");
4221 const u64 frametime_min = 1000000.0f / std::max(fps_limit, 1.0f);
4223 u64 time = porting::getTimeUs();
4225 if (time > last_time) // Make sure time hasn't overflowed
4226 busy_time = time - last_time;
4230 if (busy_time < frametime_min) {
4231 sleep_time = frametime_min - busy_time;
4232 if (sleep_time > 1000)
4233 sleep_ms(sleep_time / 1000);
4238 // Read the timer again to accurately determine how long we actually slept,
4239 // rather than calculating it by adding sleep_time to time.
4240 time = porting::getTimeUs();
4242 if (time > last_time) // Make sure last_time hasn't overflowed
4243 *dtime = (time - last_time) / 1000000.0f;
4250 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4252 const wchar_t *wmsg = wgettext(msg);
4253 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4258 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4260 ((Game *)data)->readSettings();
4263 void Game::readSettings()
4265 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4266 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4267 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4268 m_cache_enable_particles = g_settings->getBool("enable_particles");
4269 m_cache_enable_fog = g_settings->getBool("enable_fog");
4270 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f);
4271 m_cache_joystick_frustum_sensitivity = std::max(g_settings->getFloat("joystick_frustum_sensitivity"), 0.001f);
4272 m_repeat_place_time = g_settings->getFloat("repeat_place_time", 0.25f, 2.0);
4274 m_cache_enable_noclip = g_settings->getBool("noclip");
4275 m_cache_enable_free_move = g_settings->getBool("free_move");
4277 m_cache_fog_start = g_settings->getFloat("fog_start");
4279 m_cache_cam_smoothing = 0;
4280 if (g_settings->getBool("cinematic"))
4281 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4283 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4285 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4286 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4287 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4289 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4292 /****************************************************************************/
4293 /****************************************************************************
4295 ****************************************************************************/
4296 /****************************************************************************/
4298 void Game::showDeathFormspec()
4300 static std::string formspec_str =
4301 std::string("formspec_version[1]") +
4303 "bgcolor[#320000b4;true]"
4304 "label[4.85,1.35;" + gettext("You died") + "]"
4305 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4309 /* Note: FormspecFormSource and LocalFormspecHandler *
4310 * are deleted by guiFormSpecMenu */
4311 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4312 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4314 auto *&formspec = m_game_ui->getFormspecGUI();
4315 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4316 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4317 formspec->setFocus("btn_respawn");
4320 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4321 void Game::showPauseMenu()
4323 #ifdef HAVE_TOUCHSCREENGUI
4324 static const std::string control_text = strgettext("Default Controls:\n"
4325 "No menu visible:\n"
4326 "- single tap: button activate\n"
4327 "- double tap: place/use\n"
4328 "- slide finger: look around\n"
4329 "Menu/Inventory visible:\n"
4330 "- double tap (outside):\n"
4332 "- touch stack, touch slot:\n"
4334 "- touch&drag, tap 2nd finger\n"
4335 " --> place single item to slot\n"
4338 static const std::string control_text_template = strgettext("Controls:\n"
4339 "- %s: move forwards\n"
4340 "- %s: move backwards\n"
4342 "- %s: move right\n"
4343 "- %s: jump/climb up\n"
4346 "- %s: sneak/climb down\n"
4349 "- Mouse: turn/look\n"
4350 "- Mouse wheel: select item\n"
4354 char control_text_buf[600];
4356 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4357 GET_KEY_NAME(keymap_forward),
4358 GET_KEY_NAME(keymap_backward),
4359 GET_KEY_NAME(keymap_left),
4360 GET_KEY_NAME(keymap_right),
4361 GET_KEY_NAME(keymap_jump),
4362 GET_KEY_NAME(keymap_dig),
4363 GET_KEY_NAME(keymap_place),
4364 GET_KEY_NAME(keymap_sneak),
4365 GET_KEY_NAME(keymap_drop),
4366 GET_KEY_NAME(keymap_inventory),
4367 GET_KEY_NAME(keymap_chat)
4370 std::string control_text = std::string(control_text_buf);
4371 str_formspec_escape(control_text);
4374 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4375 std::ostringstream os;
4377 os << "formspec_version[1]" << SIZE_TAG
4378 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4379 << strgettext("Continue") << "]";
4381 if (!simple_singleplayer_mode) {
4382 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4383 << strgettext("Change Password") << "]";
4385 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4390 if (g_settings->getBool("enable_sound")) {
4391 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4392 << strgettext("Sound Volume") << "]";
4395 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4396 << strgettext("Change Keys") << "]";
4398 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4399 << strgettext("Exit to Menu") << "]";
4400 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4401 << strgettext("Exit to OS") << "]"
4402 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4403 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4405 << strgettext("Game info:") << "\n";
4406 const std::string &address = client->getAddressName();
4407 static const std::string mode = strgettext("- Mode: ");
4408 if (!simple_singleplayer_mode) {
4409 Address serverAddress = client->getServerAddress();
4410 if (!address.empty()) {
4411 os << mode << strgettext("Remote server") << "\n"
4412 << strgettext("- Address: ") << address;
4414 os << mode << strgettext("Hosting server");
4416 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4418 os << mode << strgettext("Singleplayer") << "\n";
4420 if (simple_singleplayer_mode || address.empty()) {
4421 static const std::string on = strgettext("On");
4422 static const std::string off = strgettext("Off");
4423 // Note: Status of enable_damage and creative_mode settings is intentionally
4424 // NOT shown here because the game might roll its own damage system and/or do
4425 // a per-player Creative Mode, in which case writing it here would mislead.
4426 bool damage = g_settings->getBool("enable_damage");
4427 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4428 if (!simple_singleplayer_mode) {
4430 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4431 //~ PvP = Player versus Player
4432 os << strgettext("- PvP: ") << pvp << "\n";
4434 os << strgettext("- Public: ") << announced << "\n";
4435 std::string server_name = g_settings->get("server_name");
4436 str_formspec_escape(server_name);
4437 if (announced == on && !server_name.empty())
4438 os << strgettext("- Server Name: ") << server_name;
4445 /* Note: FormspecFormSource and LocalFormspecHandler *
4446 * are deleted by guiFormSpecMenu */
4447 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4448 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4450 auto *&formspec = m_game_ui->getFormspecGUI();
4451 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4452 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4453 formspec->setFocus("btn_continue");
4454 // game will be paused in next step, if in singleplayer (see m_is_paused)
4455 formspec->doPause = true;
4458 /****************************************************************************/
4459 /****************************************************************************
4460 extern function for launching the game
4461 ****************************************************************************/
4462 /****************************************************************************/
4464 void the_game(bool *kill,
4465 InputHandler *input,
4466 RenderingEngine *rendering_engine,
4467 const GameStartData &start_data,
4468 std::string &error_message,
4469 ChatBackend &chat_backend,
4470 bool *reconnect_requested) // Used for local game
4474 /* Make a copy of the server address because if a local singleplayer server
4475 * is created then this is updated and we don't want to change the value
4476 * passed to us by the calling function
4481 if (game.startup(kill, input, rendering_engine, start_data,
4482 error_message, reconnect_requested, &chat_backend)) {
4486 } catch (SerializationError &e) {
4487 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
4488 error_message = strgettext("A serialization error occurred:") +"\n"
4489 + e.what() + "\n\n" + ver_err;
4490 errorstream << error_message << std::endl;
4491 } catch (ServerError &e) {
4492 error_message = e.what();
4493 errorstream << "ServerError: " << error_message << std::endl;
4494 } catch (ModError &e) {
4495 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
4496 error_message = std::string("ModError: ") + e.what() +
4497 strgettext("\nCheck debug.txt for details.");
4498 errorstream << error_message << std::endl;