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 MtEvent::Type getType() const
254 return MtEvent::NODE_DUG;
260 ISoundManager *m_sound;
261 const NodeDefManager *m_ndef;
263 bool makes_footstep_sound;
264 float m_player_step_timer;
265 float m_player_jump_timer;
267 SimpleSoundSpec m_player_step_sound;
268 SimpleSoundSpec m_player_leftpunch_sound;
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);
321 static void cameraPunchRight(MtEvent *e, void *data)
323 SoundMaker *sm = (SoundMaker *)data;
324 sm->m_sound->playSound(sm->m_player_rightpunch_sound);
327 static void nodeDug(MtEvent *e, void *data)
329 SoundMaker *sm = (SoundMaker *)data;
330 NodeDugEvent *nde = (NodeDugEvent *)e;
331 sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug);
334 static void playerDamage(MtEvent *e, void *data)
336 SoundMaker *sm = (SoundMaker *)data;
337 sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5));
340 static void playerFallingDamage(MtEvent *e, void *data)
342 SoundMaker *sm = (SoundMaker *)data;
343 sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5));
346 void registerReceiver(MtEventManager *mgr)
348 mgr->reg(MtEvent::VIEW_BOBBING_STEP, SoundMaker::viewBobbingStep, this);
349 mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, this);
350 mgr->reg(MtEvent::PLAYER_JUMP, SoundMaker::playerJump, this);
351 mgr->reg(MtEvent::CAMERA_PUNCH_LEFT, SoundMaker::cameraPunchLeft, this);
352 mgr->reg(MtEvent::CAMERA_PUNCH_RIGHT, SoundMaker::cameraPunchRight, this);
353 mgr->reg(MtEvent::NODE_DUG, SoundMaker::nodeDug, this);
354 mgr->reg(MtEvent::PLAYER_DAMAGE, SoundMaker::playerDamage, this);
355 mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, this);
358 void step(float dtime)
360 m_player_step_timer -= dtime;
361 m_player_jump_timer -= dtime;
365 // Locally stored sounds don't need to be preloaded because of this
366 class GameOnDemandSoundFetcher: public OnDemandSoundFetcher
368 std::set<std::string> m_fetched;
370 void paths_insert(std::set<std::string> &dst_paths,
371 const std::string &base,
372 const std::string &name)
374 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg");
375 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg");
376 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg");
377 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg");
378 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg");
379 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg");
380 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg");
381 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg");
382 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg");
383 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg");
384 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg");
387 void fetchSounds(const std::string &name,
388 std::set<std::string> &dst_paths,
389 std::set<std::string> &dst_datas)
391 if (m_fetched.count(name))
394 m_fetched.insert(name);
396 paths_insert(dst_paths, porting::path_share, name);
397 paths_insert(dst_paths, porting::path_user, name);
402 typedef s32 SamplerLayer_t;
405 class GameGlobalShaderConstantSetter : public IShaderConstantSetter
408 bool *m_force_fog_off;
411 CachedPixelShaderSetting<float, 4> m_sky_bg_color;
412 CachedPixelShaderSetting<float> m_fog_distance;
413 CachedVertexShaderSetting<float> m_animation_timer_vertex;
414 CachedPixelShaderSetting<float> m_animation_timer_pixel;
415 CachedPixelShaderSetting<float, 3> m_day_light;
416 CachedPixelShaderSetting<float, 4> m_star_color;
417 CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
418 CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
419 CachedPixelShaderSetting<float, 3> m_minimap_yaw;
420 CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
421 CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
422 CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
423 CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture;
424 CachedPixelShaderSetting<SamplerLayer_t> m_texture_flags;
428 void onSettingsChange(const std::string &name)
430 if (name == "enable_fog")
431 m_fog_enabled = g_settings->getBool("enable_fog");
434 static void settingsCallback(const std::string &name, void *userdata)
436 reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
439 void setSky(Sky *sky) { m_sky = sky; }
441 GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
442 f32 *fog_range, Client *client) :
444 m_force_fog_off(force_fog_off),
445 m_fog_range(fog_range),
446 m_sky_bg_color("skyBgColor"),
447 m_fog_distance("fogDistance"),
448 m_animation_timer_vertex("animationTimer"),
449 m_animation_timer_pixel("animationTimer"),
450 m_day_light("dayLight"),
451 m_star_color("starColor"),
452 m_eye_position_pixel("eyePosition"),
453 m_eye_position_vertex("eyePosition"),
454 m_minimap_yaw("yawVec"),
455 m_camera_offset_pixel("cameraOffset"),
456 m_camera_offset_vertex("cameraOffset"),
457 m_base_texture("baseTexture"),
458 m_normal_texture("normalTexture"),
459 m_texture_flags("textureFlags"),
462 g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
463 m_fog_enabled = g_settings->getBool("enable_fog");
466 ~GameGlobalShaderConstantSetter()
468 g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
471 void onSetConstants(video::IMaterialRendererServices *services) override
474 video::SColor bgcolor = m_sky->getBgColor();
475 video::SColorf bgcolorf(bgcolor);
476 float bgcolorfa[4] = {
482 m_sky_bg_color.set(bgcolorfa, services);
485 float fog_distance = 10000 * BS;
487 if (m_fog_enabled && !*m_force_fog_off)
488 fog_distance = *m_fog_range;
490 m_fog_distance.set(&fog_distance, services);
492 u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
493 video::SColorf sunlight;
494 get_sunlight_color(&sunlight, daynight_ratio);
499 m_day_light.set(dnc, services);
501 video::SColorf star_color = m_sky->getCurrentStarColor();
502 float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a};
503 m_star_color.set(clr, services);
505 u32 animation_timer = m_client->getEnv().getFrameTime() % 1000000;
506 float animation_timer_f = (float)animation_timer / 100000.f;
507 m_animation_timer_vertex.set(&animation_timer_f, services);
508 m_animation_timer_pixel.set(&animation_timer_f, services);
510 float eye_position_array[3];
511 v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
512 epos.getAs3Values(eye_position_array);
513 m_eye_position_pixel.set(eye_position_array, services);
514 m_eye_position_vertex.set(eye_position_array, services);
516 if (m_client->getMinimap()) {
517 float minimap_yaw_array[3];
518 v3f minimap_yaw = m_client->getMinimap()->getYawVec();
519 minimap_yaw.getAs3Values(minimap_yaw_array);
520 m_minimap_yaw.set(minimap_yaw_array, services);
523 float camera_offset_array[3];
524 v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
525 offset.getAs3Values(camera_offset_array);
526 m_camera_offset_pixel.set(camera_offset_array, services);
527 m_camera_offset_vertex.set(camera_offset_array, services);
529 SamplerLayer_t base_tex = 0,
532 m_base_texture.set(&base_tex, services);
533 m_normal_texture.set(&normal_tex, services);
534 m_texture_flags.set(&flags_tex, services);
539 class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
542 bool *m_force_fog_off;
545 std::vector<GameGlobalShaderConstantSetter *> created_nosky;
547 GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
548 f32 *fog_range, Client *client) :
550 m_force_fog_off(force_fog_off),
551 m_fog_range(fog_range),
555 void setSky(Sky *sky) {
557 for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
558 ggscs->setSky(m_sky);
560 created_nosky.clear();
563 virtual IShaderConstantSetter* create()
565 auto *scs = new GameGlobalShaderConstantSetter(
566 m_sky, m_force_fog_off, m_fog_range, m_client);
568 created_nosky.push_back(scs);
573 #ifdef HAVE_TOUCHSCREENGUI
574 #define SIZE_TAG "size[11,5.5]"
576 #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
579 /****************************************************************************
580 ****************************************************************************/
582 const static float object_hit_delay = 0.2;
585 FpsControl() : last_time(0), busy_time(0), sleep_time(0) {}
589 void limit(IrrlichtDevice *device, f32 *dtime);
591 u32 getBusyMs() const { return busy_time / 1000; }
593 // all values in microseconds (us)
594 u64 last_time, busy_time, sleep_time;
598 /* The reason the following structs are not anonymous structs within the
599 * class is that they are not used by the majority of member functions and
600 * many functions that do require objects of thse types do not modify them
601 * (so they can be passed as a const qualified parameter)
607 PointedThing pointed_old;
610 bool btn_down_for_dig;
612 bool digging_blocked;
613 bool reset_jump_timer;
614 float nodig_delay_timer;
616 float dig_time_complete;
617 float repeat_place_timer;
618 float object_hit_delay_timer;
619 float time_from_last_punch;
620 ClientActiveObject *selected_object;
624 float update_draw_list_timer;
628 v3f update_draw_list_last_cam_dir;
630 float time_of_day_smooth;
635 struct ClientEventHandler
637 void (Game::*handler)(ClientEvent *, CameraOrientation *);
640 /****************************************************************************
642 ****************************************************************************/
644 using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
646 /* This is not intended to be a public class. If a public class becomes
647 * desirable then it may be better to create another 'wrapper' class that
648 * hides most of the stuff in this class (nothing in this class is required
649 * by any other file) but exposes the public methods/data only.
656 bool startup(bool *kill,
658 RenderingEngine *rendering_engine,
659 const GameStartData &game_params,
660 std::string &error_message,
662 ChatBackend *chat_backend);
669 // Basic initialisation
670 bool init(const std::string &map_dir, const std::string &address,
671 u16 port, const SubgameSpec &gamespec);
673 bool createSingleplayerServer(const std::string &map_dir,
674 const SubgameSpec &gamespec, u16 port);
677 bool createClient(const GameStartData &start_data);
681 bool connectToServer(const GameStartData &start_data,
682 bool *connect_ok, bool *aborted);
683 bool getServerContent(bool *aborted);
687 void updateInteractTimers(f32 dtime);
688 bool checkConnection();
689 bool handleCallbacks();
690 void processQueues();
691 void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
692 void updateDebugState();
693 void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
694 void updateProfilerGraphs(ProfilerGraph *graph);
697 void processUserInput(f32 dtime);
698 void processKeyInput();
699 void processItemSelection(u16 *new_playeritem);
701 void dropSelectedItem(bool single_item = false);
702 void openInventory();
703 void openConsole(float scale, const wchar_t *line=NULL);
704 void toggleFreeMove();
705 void toggleFreeMoveAlt();
706 void togglePitchMove();
709 void toggleCinematic();
710 void toggleBlockBounds();
711 void toggleAutoforward();
713 void toggleMinimap(bool shift_pressed);
716 void toggleUpdateCamera();
718 void increaseViewRange();
719 void decreaseViewRange();
720 void toggleFullViewRange();
721 void checkZoomEnabled();
723 void updateCameraDirection(CameraOrientation *cam, float dtime);
724 void updateCameraOrientation(CameraOrientation *cam, float dtime);
725 void updatePlayerControl(const CameraOrientation &cam);
726 void step(f32 dtime);
727 void processClientEvents(CameraOrientation *cam);
728 void updateCamera(f32 dtime);
729 void updateSound(f32 dtime);
730 void processPlayerInteraction(f32 dtime, bool show_hud);
732 * Returns the object or node the player is pointing at.
733 * Also updates the selected thing in the Hud.
735 * @param[in] shootline the shootline, starting from
736 * the camera position. This also gives the maximal distance
738 * @param[in] liquids_pointable if false, liquids are ignored
739 * @param[in] look_for_object if false, objects are ignored
740 * @param[in] camera_offset offset of the camera
741 * @param[out] selected_object the selected object or
744 PointedThing updatePointedThing(
745 const core::line3d<f32> &shootline, bool liquids_pointable,
746 bool look_for_object, const v3s16 &camera_offset);
747 void handlePointingAtNothing(const ItemStack &playerItem);
748 void handlePointingAtNode(const PointedThing &pointed,
749 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
750 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
751 const v3f &player_position, bool show_debug);
752 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
753 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
754 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
755 const CameraOrientation &cam);
756 void updateShadows();
759 void showOverlayMessage(const char *msg, float dtime, int percent,
760 bool draw_clouds = true);
762 static void settingChangedCallback(const std::string &setting_name, void *data);
765 inline bool isKeyDown(GameKeyType k)
767 return input->isKeyDown(k);
769 inline bool wasKeyDown(GameKeyType k)
771 return input->wasKeyDown(k);
773 inline bool wasKeyPressed(GameKeyType k)
775 return input->wasKeyPressed(k);
777 inline bool wasKeyReleased(GameKeyType k)
779 return input->wasKeyReleased(k);
783 void handleAndroidChatInput();
788 bool force_fog_off = false;
789 bool disable_camera_update = false;
792 void showDeathFormspec();
793 void showPauseMenu();
795 void pauseAnimation();
796 void resumeAnimation();
798 // ClientEvent handlers
799 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
800 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
801 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
802 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
803 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
804 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
805 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
806 CameraOrientation *cam);
807 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
808 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
809 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
810 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
811 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
812 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
813 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
814 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
815 CameraOrientation *cam);
816 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
818 void updateChat(f32 dtime);
820 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
821 const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
822 const NodeMetadata *meta);
823 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
825 f32 getSensitivityScaleFactor() const;
827 InputHandler *input = nullptr;
829 Client *client = nullptr;
830 Server *server = nullptr;
832 IWritableTextureSource *texture_src = nullptr;
833 IWritableShaderSource *shader_src = nullptr;
835 // When created, these will be filled with data received from the server
836 IWritableItemDefManager *itemdef_manager = nullptr;
837 NodeDefManager *nodedef_manager = nullptr;
839 GameOnDemandSoundFetcher soundfetcher; // useful when testing
840 ISoundManager *sound = nullptr;
841 bool sound_is_dummy = false;
842 SoundMaker *soundmaker = nullptr;
844 ChatBackend *chat_backend = nullptr;
845 LogOutputBuffer m_chat_log_buf;
847 EventManager *eventmgr = nullptr;
848 QuicktuneShortcutter *quicktune = nullptr;
850 std::unique_ptr<GameUI> m_game_ui;
851 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
852 MapDrawControl *draw_control = nullptr;
853 Camera *camera = nullptr;
854 Clouds *clouds = nullptr; // Free using ->Drop()
855 Sky *sky = nullptr; // Free using ->Drop()
857 Minimap *mapper = nullptr;
859 // Map server hud ids to client hud ids
860 std::unordered_map<u32, u32> m_hud_server_to_client;
866 This class does take ownership/responsibily for cleaning up etc of any of
867 these items (e.g. device)
869 IrrlichtDevice *device;
870 RenderingEngine *m_rendering_engine;
871 video::IVideoDriver *driver;
872 scene::ISceneManager *smgr;
874 std::string *error_message;
875 bool *reconnect_requested;
876 scene::ISceneNode *skybox;
877 PausedNodesList paused_animated_nodes;
879 bool simple_singleplayer_mode;
882 /* Pre-calculated values
884 int crack_animation_length;
886 IntervalLimiter profiler_interval;
889 * TODO: Local caching of settings is not optimal and should at some stage
890 * be updated to use a global settings object for getting thse values
891 * (as opposed to the this local caching). This can be addressed in
894 bool m_cache_doubletap_jump;
895 bool m_cache_enable_clouds;
896 bool m_cache_enable_joysticks;
897 bool m_cache_enable_particles;
898 bool m_cache_enable_fog;
899 bool m_cache_enable_noclip;
900 bool m_cache_enable_free_move;
901 f32 m_cache_mouse_sensitivity;
902 f32 m_cache_joystick_frustum_sensitivity;
903 f32 m_repeat_place_time;
904 f32 m_cache_cam_smoothing;
905 f32 m_cache_fog_start;
907 bool m_invert_mouse = false;
908 bool m_first_loop_after_window_activation = false;
909 bool m_camera_offset_changed = false;
911 bool m_does_lost_focus_pause_game = false;
913 // if true, (almost) the whole game is paused
914 // this happens in pause menu in singleplayer
915 bool m_is_paused = false;
917 #if IRRLICHT_VERSION_MT_REVISION < 5
918 int m_reset_HW_buffer_counter = 0;
921 #ifdef HAVE_TOUCHSCREENGUI
922 bool m_cache_hold_aux1;
925 bool m_android_chat_open;
930 m_chat_log_buf(g_logger),
931 m_game_ui(new GameUI())
933 g_settings->registerChangedCallback("doubletap_jump",
934 &settingChangedCallback, this);
935 g_settings->registerChangedCallback("enable_clouds",
936 &settingChangedCallback, this);
937 g_settings->registerChangedCallback("doubletap_joysticks",
938 &settingChangedCallback, this);
939 g_settings->registerChangedCallback("enable_particles",
940 &settingChangedCallback, this);
941 g_settings->registerChangedCallback("enable_fog",
942 &settingChangedCallback, this);
943 g_settings->registerChangedCallback("mouse_sensitivity",
944 &settingChangedCallback, this);
945 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
946 &settingChangedCallback, this);
947 g_settings->registerChangedCallback("repeat_place_time",
948 &settingChangedCallback, this);
949 g_settings->registerChangedCallback("noclip",
950 &settingChangedCallback, this);
951 g_settings->registerChangedCallback("free_move",
952 &settingChangedCallback, this);
953 g_settings->registerChangedCallback("cinematic",
954 &settingChangedCallback, this);
955 g_settings->registerChangedCallback("cinematic_camera_smoothing",
956 &settingChangedCallback, this);
957 g_settings->registerChangedCallback("camera_smoothing",
958 &settingChangedCallback, this);
962 #ifdef HAVE_TOUCHSCREENGUI
963 m_cache_hold_aux1 = false; // This is initialised properly later
970 /****************************************************************************
972 ****************************************************************************/
981 delete server; // deleted first to stop all server threads
989 delete nodedef_manager;
990 delete itemdef_manager;
993 clearTextureNameCache();
995 g_settings->deregisterChangedCallback("doubletap_jump",
996 &settingChangedCallback, this);
997 g_settings->deregisterChangedCallback("enable_clouds",
998 &settingChangedCallback, this);
999 g_settings->deregisterChangedCallback("enable_particles",
1000 &settingChangedCallback, this);
1001 g_settings->deregisterChangedCallback("enable_fog",
1002 &settingChangedCallback, this);
1003 g_settings->deregisterChangedCallback("mouse_sensitivity",
1004 &settingChangedCallback, this);
1005 g_settings->deregisterChangedCallback("repeat_place_time",
1006 &settingChangedCallback, this);
1007 g_settings->deregisterChangedCallback("noclip",
1008 &settingChangedCallback, this);
1009 g_settings->deregisterChangedCallback("free_move",
1010 &settingChangedCallback, this);
1011 g_settings->deregisterChangedCallback("cinematic",
1012 &settingChangedCallback, this);
1013 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
1014 &settingChangedCallback, this);
1015 g_settings->deregisterChangedCallback("camera_smoothing",
1016 &settingChangedCallback, this);
1019 bool Game::startup(bool *kill,
1020 InputHandler *input,
1021 RenderingEngine *rendering_engine,
1022 const GameStartData &start_data,
1023 std::string &error_message,
1025 ChatBackend *chat_backend)
1029 m_rendering_engine = rendering_engine;
1030 device = m_rendering_engine->get_raw_device();
1032 this->error_message = &error_message;
1033 reconnect_requested = reconnect;
1034 this->input = input;
1035 this->chat_backend = chat_backend;
1036 simple_singleplayer_mode = start_data.isSinglePlayer();
1038 input->keycache.populate();
1040 driver = device->getVideoDriver();
1041 smgr = m_rendering_engine->get_scene_manager();
1043 smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1046 runData = GameRunData();
1047 runData.time_from_last_punch = 10.0;
1049 m_game_ui->initFlags();
1051 m_invert_mouse = g_settings->getBool("invert_mouse");
1052 m_first_loop_after_window_activation = true;
1054 g_client_translations->clear();
1056 // address can change if simple_singleplayer_mode
1057 if (!init(start_data.world_spec.path, start_data.address,
1058 start_data.socket_port, start_data.game_spec))
1061 if (!createClient(start_data))
1064 m_rendering_engine->initialize(client, hud);
1072 ProfilerGraph graph;
1073 RunStats stats = {};
1074 CameraOrientation cam_view_target = {};
1075 CameraOrientation cam_view = {};
1076 FpsControl draw_times;
1077 f32 dtime; // in seconds
1079 /* Clear the profiler */
1080 Profiler::GraphValues dummyvalues;
1081 g_profiler->graphGet(dummyvalues);
1085 set_light_table(g_settings->getFloat("display_gamma"));
1087 #ifdef HAVE_TOUCHSCREENGUI
1088 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1089 && client->checkPrivilege("fast");
1092 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1093 g_settings->getU16("screen_h"));
1095 while (m_rendering_engine->run()
1096 && !(*kill || g_gamecallback->shutdown_requested
1097 || (server && server->isShutdownRequested()))) {
1099 const irr::core::dimension2d<u32> ¤t_screen_size =
1100 m_rendering_engine->get_video_driver()->getScreenSize();
1101 // Verify if window size has changed and save it if it's the case
1102 // Ensure evaluating settings->getBool after verifying screensize
1103 // First condition is cheaper
1104 if (previous_screen_size != current_screen_size &&
1105 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1106 g_settings->getBool("autosave_screensize")) {
1107 g_settings->setU16("screen_w", current_screen_size.Width);
1108 g_settings->setU16("screen_h", current_screen_size.Height);
1109 previous_screen_size = current_screen_size;
1112 // Calculate dtime =
1113 // m_rendering_engine->run() from this iteration
1114 // + Sleep time until the wanted FPS are reached
1115 draw_times.limit(device, &dtime);
1117 // Prepare render data for next iteration
1119 updateStats(&stats, draw_times, dtime);
1120 updateInteractTimers(dtime);
1122 if (!checkConnection())
1124 if (!handleCallbacks())
1129 m_game_ui->clearInfoText();
1130 hud->resizeHotbar();
1133 updateProfilers(stats, draw_times, dtime);
1134 processUserInput(dtime);
1135 // Update camera before player movement to avoid camera lag of one frame
1136 updateCameraDirection(&cam_view_target, dtime);
1137 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1138 cam_view.camera_yaw) * m_cache_cam_smoothing;
1139 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1140 cam_view.camera_pitch) * m_cache_cam_smoothing;
1141 updatePlayerControl(cam_view);
1144 bool was_paused = m_is_paused;
1145 m_is_paused = simple_singleplayer_mode && g_menumgr.pausesGame();
1149 if (!was_paused && m_is_paused)
1151 else if (was_paused && !m_is_paused)
1157 processClientEvents(&cam_view_target);
1159 updateCamera(dtime);
1161 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
1162 updateFrame(&graph, &stats, dtime, cam_view);
1163 updateProfilerGraphs(&graph);
1165 // Update if minimap has been disabled by the server
1166 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1168 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1175 void Game::shutdown()
1177 m_rendering_engine->finalize();
1178 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1179 if (g_settings->get("3d_mode") == "pageflip") {
1180 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1183 auto formspec = m_game_ui->getFormspecGUI();
1185 formspec->quitMenu();
1187 #ifdef HAVE_TOUCHSCREENGUI
1188 g_touchscreengui->hide();
1191 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1196 if (gui_chat_console)
1197 gui_chat_console->drop();
1203 while (g_menumgr.menuCount() > 0) {
1204 g_menumgr.m_stack.front()->setVisible(false);
1205 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1208 m_game_ui->deleteFormspec();
1210 chat_backend->addMessage(L"", L"# Disconnected.");
1211 chat_backend->addMessage(L"", L"");
1212 m_chat_log_buf.clear();
1216 while (!client->isShutdown()) {
1217 assert(texture_src != NULL);
1218 assert(shader_src != NULL);
1219 texture_src->processQueue();
1220 shader_src->processQueue();
1227 /****************************************************************************/
1228 /****************************************************************************
1230 ****************************************************************************/
1231 /****************************************************************************/
1234 const std::string &map_dir,
1235 const std::string &address,
1237 const SubgameSpec &gamespec)
1239 texture_src = createTextureSource();
1241 showOverlayMessage(N_("Loading..."), 0, 0);
1243 shader_src = createShaderSource();
1245 itemdef_manager = createItemDefManager();
1246 nodedef_manager = createNodeDefManager();
1248 eventmgr = new EventManager();
1249 quicktune = new QuicktuneShortcutter();
1251 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1252 && eventmgr && quicktune))
1258 // Create a server if not connecting to an existing one
1259 if (address.empty()) {
1260 if (!createSingleplayerServer(map_dir, gamespec, port))
1267 bool Game::initSound()
1270 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1271 infostream << "Attempting to use OpenAL audio" << std::endl;
1272 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1274 infostream << "Failed to initialize OpenAL audio" << std::endl;
1276 infostream << "Sound disabled." << std::endl;
1280 infostream << "Using dummy audio." << std::endl;
1281 sound = &dummySoundManager;
1282 sound_is_dummy = true;
1285 soundmaker = new SoundMaker(sound, nodedef_manager);
1289 soundmaker->registerReceiver(eventmgr);
1294 bool Game::createSingleplayerServer(const std::string &map_dir,
1295 const SubgameSpec &gamespec, u16 port)
1297 showOverlayMessage(N_("Creating server..."), 0, 5);
1299 std::string bind_str = g_settings->get("bind_address");
1300 Address bind_addr(0, 0, 0, 0, port);
1302 if (g_settings->getBool("ipv6_server")) {
1303 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1307 bind_addr.Resolve(bind_str.c_str());
1308 } catch (ResolveError &e) {
1309 infostream << "Resolving bind address \"" << bind_str
1310 << "\" failed: " << e.what()
1311 << " -- Listening on all addresses." << std::endl;
1314 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1315 *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
1316 bind_addr.serializeString().c_str());
1317 errorstream << *error_message << std::endl;
1321 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1322 false, nullptr, error_message);
1328 bool Game::createClient(const GameStartData &start_data)
1330 showOverlayMessage(N_("Creating client..."), 0, 10);
1332 draw_control = new MapDrawControl();
1336 bool could_connect, connect_aborted;
1337 #ifdef HAVE_TOUCHSCREENGUI
1338 if (g_touchscreengui) {
1339 g_touchscreengui->init(texture_src);
1340 g_touchscreengui->hide();
1343 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1346 if (!could_connect) {
1347 if (error_message->empty() && !connect_aborted) {
1348 // Should not happen if error messages are set properly
1349 *error_message = gettext("Connection failed for unknown reason");
1350 errorstream << *error_message << std::endl;
1355 if (!getServerContent(&connect_aborted)) {
1356 if (error_message->empty() && !connect_aborted) {
1357 // Should not happen if error messages are set properly
1358 *error_message = gettext("Connection failed for unknown reason");
1359 errorstream << *error_message << std::endl;
1364 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1365 &m_flags.force_fog_off, &runData.fog_range, client);
1366 shader_src->addShaderConstantSetterFactory(scsf);
1368 // Update cached textures, meshes and materials
1369 client->afterContentReceived();
1373 camera = new Camera(*draw_control, client, m_rendering_engine);
1374 if (client->modsLoaded())
1375 client->getScript()->on_camera_ready(camera);
1376 client->setCamera(camera);
1380 if (m_cache_enable_clouds)
1381 clouds = new Clouds(smgr, -1, time(0));
1385 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
1387 skybox = NULL; // This is used/set later on in the main run loop
1389 /* Pre-calculated values
1391 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1393 v2u32 size = t->getOriginalSize();
1394 crack_animation_length = size.Y / size.X;
1396 crack_animation_length = 5;
1402 /* Set window caption
1404 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1406 str += utf8_to_wide(g_version_hash);
1408 const wchar_t *text = nullptr;
1409 if (simple_singleplayer_mode)
1410 text = wgettext("Singleplayer");
1412 text = wgettext("Multiplayer");
1419 str += driver->getName();
1422 device->setWindowCaption(str.c_str());
1424 LocalPlayer *player = client->getEnv().getLocalPlayer();
1425 player->hurt_tilt_timer = 0;
1426 player->hurt_tilt_strength = 0;
1428 hud = new Hud(client, player, &player->inventory);
1430 mapper = client->getMinimap();
1432 if (mapper && client->modsLoaded())
1433 client->getScript()->on_minimap_ready(mapper);
1438 bool Game::initGui()
1442 // Remove stale "recent" chat messages from previous connections
1443 chat_backend->clearRecentChat();
1445 // Make sure the size of the recent messages buffer is right
1446 chat_backend->applySettings();
1448 // Chat backend and console
1449 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1450 -1, chat_backend, client, &g_menumgr);
1452 #ifdef HAVE_TOUCHSCREENGUI
1454 if (g_touchscreengui)
1455 g_touchscreengui->show();
1462 bool Game::connectToServer(const GameStartData &start_data,
1463 bool *connect_ok, bool *connection_aborted)
1465 *connect_ok = false; // Let's not be overly optimistic
1466 *connection_aborted = false;
1467 bool local_server_mode = false;
1469 showOverlayMessage(N_("Resolving address..."), 0, 15);
1471 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1474 connect_address.Resolve(start_data.address.c_str());
1476 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1477 if (connect_address.isIPv6()) {
1478 IPv6AddressBytes addr_bytes;
1479 addr_bytes.bytes[15] = 1;
1480 connect_address.setAddress(&addr_bytes);
1482 connect_address.setAddress(127, 0, 0, 1);
1484 local_server_mode = true;
1486 } catch (ResolveError &e) {
1487 *error_message = fmtgettext("Couldn't resolve address: %s", e.what());
1489 errorstream << *error_message << std::endl;
1493 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1494 *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
1495 errorstream << *error_message << std::endl;
1500 client = new Client(start_data.name.c_str(),
1501 start_data.password, start_data.address,
1502 *draw_control, texture_src, shader_src,
1503 itemdef_manager, nodedef_manager, sound, eventmgr,
1504 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get(),
1505 start_data.allow_login_or_register);
1506 client->migrateModStorage();
1507 } catch (const BaseException &e) {
1508 *error_message = fmtgettext("Error creating client: %s", e.what());
1509 errorstream << *error_message << std::endl;
1513 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1515 infostream << "Connecting to server at ";
1516 connect_address.print(infostream);
1517 infostream << std::endl;
1519 client->connect(connect_address,
1520 simple_singleplayer_mode || local_server_mode);
1523 Wait for server to accept connection
1529 FpsControl fps_control;
1531 f32 wait_time = 0; // in seconds
1533 fps_control.reset();
1535 while (m_rendering_engine->run()) {
1537 fps_control.limit(device, &dtime);
1539 // Update client and server
1540 client->step(dtime);
1543 server->step(dtime);
1546 if (client->getState() == LC_Init) {
1552 if (*connection_aborted)
1555 if (client->accessDenied()) {
1556 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1557 *reconnect_requested = client->reconnectRequested();
1558 errorstream << *error_message << std::endl;
1562 if (input->cancelPressed()) {
1563 *connection_aborted = true;
1564 infostream << "Connect aborted [Escape]" << std::endl;
1569 // Only time out if we aren't waiting for the server we started
1570 if (!start_data.address.empty() && wait_time > 10) {
1571 *error_message = gettext("Connection timed out.");
1572 errorstream << *error_message << std::endl;
1577 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1579 } catch (con::PeerNotFoundException &e) {
1580 // TODO: Should something be done here? At least an info/error
1588 bool Game::getServerContent(bool *aborted)
1592 FpsControl fps_control;
1593 f32 dtime; // in seconds
1595 fps_control.reset();
1597 while (m_rendering_engine->run()) {
1599 fps_control.limit(device, &dtime);
1601 // Update client and server
1602 client->step(dtime);
1605 server->step(dtime);
1608 if (client->mediaReceived() && client->itemdefReceived() &&
1609 client->nodedefReceived()) {
1614 if (!checkConnection())
1617 if (client->getState() < LC_Init) {
1618 *error_message = gettext("Client disconnected");
1619 errorstream << *error_message << std::endl;
1623 if (input->cancelPressed()) {
1625 infostream << "Connect aborted [Escape]" << std::endl;
1632 if (!client->itemdefReceived()) {
1633 const wchar_t *text = wgettext("Item definitions...");
1635 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1638 } else if (!client->nodedefReceived()) {
1639 const wchar_t *text = wgettext("Node definitions...");
1641 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1645 std::ostringstream message;
1646 std::fixed(message);
1647 message.precision(0);
1648 float receive = client->mediaReceiveProgress() * 100;
1649 message << gettext("Media...");
1651 message << " " << receive << "%";
1652 message.precision(2);
1654 if ((USE_CURL == 0) ||
1655 (!g_settings->getBool("enable_remote_media_server"))) {
1656 float cur = client->getCurRate();
1657 std::string cur_unit = gettext("KiB/s");
1661 cur_unit = gettext("MiB/s");
1664 message << " (" << cur << ' ' << cur_unit << ")";
1667 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1668 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
1669 texture_src, dtime, progress);
1677 /****************************************************************************/
1678 /****************************************************************************
1680 ****************************************************************************/
1681 /****************************************************************************/
1683 inline void Game::updateInteractTimers(f32 dtime)
1685 if (runData.nodig_delay_timer >= 0)
1686 runData.nodig_delay_timer -= dtime;
1688 if (runData.object_hit_delay_timer >= 0)
1689 runData.object_hit_delay_timer -= dtime;
1691 runData.time_from_last_punch += dtime;
1695 /* returns false if game should exit, otherwise true
1697 inline bool Game::checkConnection()
1699 if (client->accessDenied()) {
1700 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1701 *reconnect_requested = client->reconnectRequested();
1702 errorstream << *error_message << std::endl;
1710 /* returns false if game should exit, otherwise true
1712 inline bool Game::handleCallbacks()
1714 if (g_gamecallback->disconnect_requested) {
1715 g_gamecallback->disconnect_requested = false;
1719 if (g_gamecallback->changepassword_requested) {
1720 (new GUIPasswordChange(guienv, guiroot, -1,
1721 &g_menumgr, client, texture_src))->drop();
1722 g_gamecallback->changepassword_requested = false;
1725 if (g_gamecallback->changevolume_requested) {
1726 (new GUIVolumeChange(guienv, guiroot, -1,
1727 &g_menumgr, texture_src))->drop();
1728 g_gamecallback->changevolume_requested = false;
1731 if (g_gamecallback->keyconfig_requested) {
1732 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1733 &g_menumgr, texture_src))->drop();
1734 g_gamecallback->keyconfig_requested = false;
1737 if (g_gamecallback->keyconfig_changed) {
1738 input->keycache.populate(); // update the cache with new settings
1739 g_gamecallback->keyconfig_changed = false;
1746 void Game::processQueues()
1748 texture_src->processQueue();
1749 itemdef_manager->processQueue(client);
1750 shader_src->processQueue();
1753 void Game::updateDebugState()
1755 LocalPlayer *player = client->getEnv().getLocalPlayer();
1757 // debug UI and wireframe
1758 bool has_debug = client->checkPrivilege("debug");
1759 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
1761 if (m_game_ui->m_flags.show_basic_debug) {
1762 if (!has_basic_debug)
1763 m_game_ui->m_flags.show_basic_debug = false;
1764 } else if (m_game_ui->m_flags.show_minimal_debug) {
1765 if (has_basic_debug)
1766 m_game_ui->m_flags.show_basic_debug = true;
1768 if (!has_basic_debug)
1769 hud->disableBlockBounds();
1771 draw_control->show_wireframe = false;
1774 draw_control->allow_noclip = m_cache_enable_noclip && client->checkPrivilege("noclip");
1777 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1780 float profiler_print_interval =
1781 g_settings->getFloat("profiler_print_interval");
1782 bool print_to_log = true;
1784 if (profiler_print_interval == 0) {
1785 print_to_log = false;
1786 profiler_print_interval = 3;
1789 if (profiler_interval.step(dtime, profiler_print_interval)) {
1791 infostream << "Profiler:" << std::endl;
1792 g_profiler->print(infostream);
1795 m_game_ui->updateProfiler();
1796 g_profiler->clear();
1799 // Update update graphs
1800 g_profiler->graphAdd("Time non-rendering [us]",
1801 draw_times.busy_time - stats.drawtime);
1803 g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
1804 g_profiler->graphAdd("FPS", 1.0f / dtime);
1807 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1814 /* Time average and jitter calculation
1816 jp = &stats->dtime_jitter;
1817 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1819 jitter = dtime - jp->avg;
1821 if (jitter > jp->max)
1824 jp->counter += dtime;
1826 if (jp->counter > 0.0) {
1828 jp->max_sample = jp->max;
1829 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1833 /* Busytime average and jitter calculation
1835 jp = &stats->busy_time_jitter;
1836 jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
1838 jitter = draw_times.getBusyMs() - jp->avg;
1840 if (jitter > jp->max)
1842 if (jitter < jp->min)
1845 jp->counter += dtime;
1847 if (jp->counter > 0.0) {
1849 jp->max_sample = jp->max;
1850 jp->min_sample = jp->min;
1858 /****************************************************************************
1860 ****************************************************************************/
1862 void Game::processUserInput(f32 dtime)
1864 // Reset input if window not active or some menu is active
1865 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1867 #ifdef HAVE_TOUCHSCREENGUI
1868 g_touchscreengui->hide();
1871 #ifdef HAVE_TOUCHSCREENGUI
1872 else if (g_touchscreengui) {
1873 /* on touchscreengui step may generate own input events which ain't
1874 * what we want in case we just did clear them */
1875 g_touchscreengui->show();
1876 g_touchscreengui->step(dtime);
1880 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1881 gui_chat_console->closeConsoleAtOnce();
1884 // Input handler step() (used by the random input generator)
1888 auto formspec = m_game_ui->getFormspecGUI();
1890 formspec->getAndroidUIInput();
1892 handleAndroidChatInput();
1895 // Increase timer for double tap of "keymap_jump"
1896 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1897 runData.jump_timer += dtime;
1900 processItemSelection(&runData.new_playeritem);
1904 void Game::processKeyInput()
1906 if (wasKeyDown(KeyType::DROP)) {
1907 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1908 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1909 toggleAutoforward();
1910 } else if (wasKeyDown(KeyType::BACKWARD)) {
1911 if (g_settings->getBool("continuous_forward"))
1912 toggleAutoforward();
1913 } else if (wasKeyDown(KeyType::INVENTORY)) {
1915 } else if (input->cancelPressed()) {
1917 m_android_chat_open = false;
1919 if (!gui_chat_console->isOpenInhibited()) {
1922 } else if (wasKeyDown(KeyType::CHAT)) {
1923 openConsole(0.2, L"");
1924 } else if (wasKeyDown(KeyType::CMD)) {
1925 openConsole(0.2, L"/");
1926 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1927 if (client->modsLoaded())
1928 openConsole(0.2, L".");
1930 m_game_ui->showTranslatedStatusText("Client side scripting is disabled");
1931 } else if (wasKeyDown(KeyType::CONSOLE)) {
1932 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1933 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1935 } else if (wasKeyDown(KeyType::JUMP)) {
1936 toggleFreeMoveAlt();
1937 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1939 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1941 } else if (wasKeyDown(KeyType::NOCLIP)) {
1944 } else if (wasKeyDown(KeyType::MUTE)) {
1945 if (g_settings->getBool("enable_sound")) {
1946 bool new_mute_sound = !g_settings->getBool("mute_sound");
1947 g_settings->setBool("mute_sound", new_mute_sound);
1949 m_game_ui->showTranslatedStatusText("Sound muted");
1951 m_game_ui->showTranslatedStatusText("Sound unmuted");
1953 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1955 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1956 if (g_settings->getBool("enable_sound")) {
1957 float new_volume = g_settings->getFloat("sound_volume", 0.0f, 0.9f) + 0.1f;
1958 g_settings->setFloat("sound_volume", new_volume);
1959 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1960 m_game_ui->showStatusText(msg);
1962 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1964 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1965 if (g_settings->getBool("enable_sound")) {
1966 float new_volume = g_settings->getFloat("sound_volume", 0.1f, 1.0f) - 0.1f;
1967 g_settings->setFloat("sound_volume", new_volume);
1968 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1969 m_game_ui->showStatusText(msg);
1971 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1974 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1975 || wasKeyDown(KeyType::DEC_VOLUME)) {
1976 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1978 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1980 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1981 client->makeScreenshot();
1982 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
1983 toggleBlockBounds();
1984 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1985 m_game_ui->toggleHud();
1986 } else if (wasKeyDown(KeyType::MINIMAP)) {
1987 toggleMinimap(isKeyDown(KeyType::SNEAK));
1988 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1989 m_game_ui->toggleChat();
1990 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1992 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1993 toggleUpdateCamera();
1994 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1996 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1997 m_game_ui->toggleProfiler();
1998 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1999 increaseViewRange();
2000 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
2001 decreaseViewRange();
2002 } else if (wasKeyDown(KeyType::RANGESELECT)) {
2003 toggleFullViewRange();
2004 } else if (wasKeyDown(KeyType::ZOOM)) {
2006 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
2008 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
2010 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
2012 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
2016 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
2017 runData.reset_jump_timer = false;
2018 runData.jump_timer = 0.0f;
2021 if (quicktune->hasMessage()) {
2022 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2026 void Game::processItemSelection(u16 *new_playeritem)
2028 LocalPlayer *player = client->getEnv().getLocalPlayer();
2030 /* Item selection using mouse wheel
2032 *new_playeritem = player->getWieldIndex();
2034 s32 wheel = input->getMouseWheel();
2035 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2036 player->hud_hotbar_itemcount - 1);
2040 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2043 if (wasKeyDown(KeyType::HOTBAR_PREV))
2047 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2049 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2052 /* Item selection using hotbar slot keys
2054 for (u16 i = 0; i <= max_item; i++) {
2055 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2056 *new_playeritem = i;
2063 void Game::dropSelectedItem(bool single_item)
2065 IDropAction *a = new IDropAction();
2066 a->count = single_item ? 1 : 0;
2067 a->from_inv.setCurrentPlayer();
2068 a->from_list = "main";
2069 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2070 client->inventoryAction(a);
2074 void Game::openInventory()
2077 * Don't permit to open inventory is CAO or player doesn't exists.
2078 * This prevent showing an empty inventory at player load
2081 LocalPlayer *player = client->getEnv().getLocalPlayer();
2082 if (!player || !player->getCAO())
2085 infostream << "Game: Launching inventory" << std::endl;
2087 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2089 InventoryLocation inventoryloc;
2090 inventoryloc.setCurrentPlayer();
2092 if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2097 if (fs_src->getForm().empty()) {
2102 TextDest *txt_dst = new TextDestPlayerInventory(client);
2103 auto *&formspec = m_game_ui->updateFormspec("");
2104 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2105 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2107 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2111 void Game::openConsole(float scale, const wchar_t *line)
2113 assert(scale > 0.0f && scale <= 1.0f);
2116 porting::showInputDialog(gettext("ok"), "", "", 2);
2117 m_android_chat_open = true;
2119 if (gui_chat_console->isOpenInhibited())
2121 gui_chat_console->openConsole(scale);
2123 gui_chat_console->setCloseOnEnter(true);
2124 gui_chat_console->replaceAndAddToHistory(line);
2130 void Game::handleAndroidChatInput()
2132 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2133 std::string text = porting::getInputDialogValue();
2134 client->typeChatMessage(utf8_to_wide(text));
2135 m_android_chat_open = false;
2141 void Game::toggleFreeMove()
2143 bool free_move = !g_settings->getBool("free_move");
2144 g_settings->set("free_move", bool_to_cstr(free_move));
2147 if (client->checkPrivilege("fly")) {
2148 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2150 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2153 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2157 void Game::toggleFreeMoveAlt()
2159 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2162 runData.reset_jump_timer = true;
2166 void Game::togglePitchMove()
2168 bool pitch_move = !g_settings->getBool("pitch_move");
2169 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2172 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2174 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2179 void Game::toggleFast()
2181 bool fast_move = !g_settings->getBool("fast_move");
2182 bool has_fast_privs = client->checkPrivilege("fast");
2183 g_settings->set("fast_move", bool_to_cstr(fast_move));
2186 if (has_fast_privs) {
2187 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2189 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2192 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2195 #ifdef HAVE_TOUCHSCREENGUI
2196 m_cache_hold_aux1 = fast_move && has_fast_privs;
2201 void Game::toggleNoClip()
2203 bool noclip = !g_settings->getBool("noclip");
2204 g_settings->set("noclip", bool_to_cstr(noclip));
2207 if (client->checkPrivilege("noclip")) {
2208 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2210 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2213 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2217 void Game::toggleCinematic()
2219 bool cinematic = !g_settings->getBool("cinematic");
2220 g_settings->set("cinematic", bool_to_cstr(cinematic));
2223 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2225 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2228 void Game::toggleBlockBounds()
2230 LocalPlayer *player = client->getEnv().getLocalPlayer();
2231 if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
2232 m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
2235 enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
2237 case Hud::BLOCK_BOUNDS_OFF:
2238 m_game_ui->showTranslatedStatusText("Block bounds hidden");
2240 case Hud::BLOCK_BOUNDS_CURRENT:
2241 m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
2243 case Hud::BLOCK_BOUNDS_NEAR:
2244 m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
2246 case Hud::BLOCK_BOUNDS_MAX:
2247 m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
2254 // Autoforward by toggling continuous forward.
2255 void Game::toggleAutoforward()
2257 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2258 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2260 if (autorun_enabled)
2261 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2263 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2266 void Game::toggleMinimap(bool shift_pressed)
2268 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2272 mapper->toggleMinimapShape();
2276 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2278 // Not so satisying code to keep compatibility with old fixed mode system
2280 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2282 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2283 m_game_ui->m_flags.show_minimap = false;
2286 // If radar is disabled, try to find a non radar mode or fall back to 0
2287 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2288 while (mapper->getModeIndex() &&
2289 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2292 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2296 // End of 'not so satifying code'
2297 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2298 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2299 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2301 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2304 void Game::toggleFog()
2306 bool fog_enabled = g_settings->getBool("enable_fog");
2307 g_settings->setBool("enable_fog", !fog_enabled);
2309 m_game_ui->showTranslatedStatusText("Fog disabled");
2311 m_game_ui->showTranslatedStatusText("Fog enabled");
2315 void Game::toggleDebug()
2317 LocalPlayer *player = client->getEnv().getLocalPlayer();
2318 bool has_debug = client->checkPrivilege("debug");
2319 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
2320 // Initial: No debug info
2321 // 1x toggle: Debug text
2322 // 2x toggle: Debug text with profiler graph
2323 // 3x toggle: Debug text and wireframe (needs "debug" priv)
2324 // Next toggle: Back to initial
2326 // The debug text can be in 2 modes: minimal and basic.
2327 // * Minimal: Only technical client info that not gameplay-relevant
2328 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
2329 // Basic mode is used when player has the debug HUD flag set,
2330 // otherwise the Minimal mode is used.
2331 if (!m_game_ui->m_flags.show_minimal_debug) {
2332 m_game_ui->m_flags.show_minimal_debug = true;
2333 if (has_basic_debug)
2334 m_game_ui->m_flags.show_basic_debug = true;
2335 m_game_ui->m_flags.show_profiler_graph = false;
2336 draw_control->show_wireframe = false;
2337 m_game_ui->showTranslatedStatusText("Debug info shown");
2338 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2339 if (has_basic_debug)
2340 m_game_ui->m_flags.show_basic_debug = true;
2341 m_game_ui->m_flags.show_profiler_graph = true;
2342 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2343 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2344 if (has_basic_debug)
2345 m_game_ui->m_flags.show_basic_debug = true;
2346 m_game_ui->m_flags.show_profiler_graph = false;
2347 draw_control->show_wireframe = true;
2348 m_game_ui->showTranslatedStatusText("Wireframe shown");
2350 m_game_ui->m_flags.show_minimal_debug = false;
2351 m_game_ui->m_flags.show_basic_debug = false;
2352 m_game_ui->m_flags.show_profiler_graph = false;
2353 draw_control->show_wireframe = false;
2355 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2357 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2363 void Game::toggleUpdateCamera()
2365 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2366 if (m_flags.disable_camera_update)
2367 m_game_ui->showTranslatedStatusText("Camera update disabled");
2369 m_game_ui->showTranslatedStatusText("Camera update enabled");
2373 void Game::increaseViewRange()
2375 s16 range = g_settings->getS16("viewing_range");
2376 s16 range_new = range + 10;
2378 if (range_new > 4000) {
2380 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
2381 m_game_ui->showStatusText(msg);
2383 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2384 m_game_ui->showStatusText(msg);
2386 g_settings->set("viewing_range", itos(range_new));
2390 void Game::decreaseViewRange()
2392 s16 range = g_settings->getS16("viewing_range");
2393 s16 range_new = range - 10;
2395 if (range_new < 20) {
2397 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
2398 m_game_ui->showStatusText(msg);
2400 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2401 m_game_ui->showStatusText(msg);
2403 g_settings->set("viewing_range", itos(range_new));
2407 void Game::toggleFullViewRange()
2409 draw_control->range_all = !draw_control->range_all;
2410 if (draw_control->range_all)
2411 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2413 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2417 void Game::checkZoomEnabled()
2419 LocalPlayer *player = client->getEnv().getLocalPlayer();
2420 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2421 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2424 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2426 if ((device->isWindowActive() && device->isWindowFocused()
2427 && !isMenuActive()) || input->isRandom()) {
2430 if (!input->isRandom()) {
2431 // Mac OSX gets upset if this is set every frame
2432 if (device->getCursorControl()->isVisible())
2433 device->getCursorControl()->setVisible(false);
2437 if (m_first_loop_after_window_activation) {
2438 m_first_loop_after_window_activation = false;
2440 input->setMousePos(driver->getScreenSize().Width / 2,
2441 driver->getScreenSize().Height / 2);
2443 updateCameraOrientation(cam, dtime);
2449 // Mac OSX gets upset if this is set every frame
2450 if (!device->getCursorControl()->isVisible())
2451 device->getCursorControl()->setVisible(true);
2454 m_first_loop_after_window_activation = true;
2459 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2460 // responsiveness independently of FOV.
2461 f32 Game::getSensitivityScaleFactor() const
2463 f32 fov_y = client->getCamera()->getFovY();
2465 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2466 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2468 return tan(fov_y / 2.0f) * 1.3763818698f;
2471 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2473 #ifdef HAVE_TOUCHSCREENGUI
2474 if (g_touchscreengui) {
2475 cam->camera_yaw += g_touchscreengui->getYawChange();
2476 cam->camera_pitch = g_touchscreengui->getPitch();
2479 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2480 v2s32 dist = input->getMousePos() - center;
2482 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2486 f32 sens_scale = getSensitivityScaleFactor();
2487 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2488 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2490 if (dist.X != 0 || dist.Y != 0)
2491 input->setMousePos(center.X, center.Y);
2492 #ifdef HAVE_TOUCHSCREENGUI
2496 if (m_cache_enable_joysticks) {
2497 f32 sens_scale = getSensitivityScaleFactor();
2498 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
2499 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2500 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2503 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2507 void Game::updatePlayerControl(const CameraOrientation &cam)
2509 LocalPlayer *player = client->getEnv().getLocalPlayer();
2511 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2513 PlayerControl control(
2514 isKeyDown(KeyType::FORWARD),
2515 isKeyDown(KeyType::BACKWARD),
2516 isKeyDown(KeyType::LEFT),
2517 isKeyDown(KeyType::RIGHT),
2518 isKeyDown(KeyType::JUMP) || player->getAutojump(),
2519 isKeyDown(KeyType::AUX1),
2520 isKeyDown(KeyType::SNEAK),
2521 isKeyDown(KeyType::ZOOM),
2522 isKeyDown(KeyType::DIG),
2523 isKeyDown(KeyType::PLACE),
2526 input->getMovementSpeed(),
2527 input->getMovementDirection()
2530 // autoforward if set: move at maximum speed
2531 if (player->getPlayerSettings().continuous_forward &&
2532 client->activeObjectsReceived() && !player->isDead()) {
2533 control.movement_speed = 1.0f;
2534 // sideways movement only
2535 float dx = sin(control.movement_direction);
2536 control.movement_direction = atan2(dx, 1.0f);
2539 #ifdef HAVE_TOUCHSCREENGUI
2540 /* For touch, simulate holding down AUX1 (fast move) if the user has
2541 * the fast_move setting toggled on. If there is an aux1 key defined for
2542 * touch then its meaning is inverted (i.e. holding aux1 means walk and
2545 if (m_cache_hold_aux1) {
2546 control.aux1 = control.aux1 ^ true;
2550 client->setPlayerControl(control);
2556 inline void Game::step(f32 dtime)
2559 server->step(dtime);
2561 client->step(dtime);
2564 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2567 for (auto &&child: node->getChildren())
2568 pauseNodeAnimation(paused, child);
2569 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2571 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2572 float speed = animated_node->getAnimationSpeed();
2575 paused.push_back({grab(animated_node), speed});
2576 animated_node->setAnimationSpeed(0.0f);
2579 void Game::pauseAnimation()
2581 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2584 void Game::resumeAnimation()
2586 for (auto &&pair: paused_animated_nodes)
2587 pair.first->setAnimationSpeed(pair.second);
2588 paused_animated_nodes.clear();
2591 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2592 {&Game::handleClientEvent_None},
2593 {&Game::handleClientEvent_PlayerDamage},
2594 {&Game::handleClientEvent_PlayerForceMove},
2595 {&Game::handleClientEvent_Deathscreen},
2596 {&Game::handleClientEvent_ShowFormSpec},
2597 {&Game::handleClientEvent_ShowLocalFormSpec},
2598 {&Game::handleClientEvent_HandleParticleEvent},
2599 {&Game::handleClientEvent_HandleParticleEvent},
2600 {&Game::handleClientEvent_HandleParticleEvent},
2601 {&Game::handleClientEvent_HudAdd},
2602 {&Game::handleClientEvent_HudRemove},
2603 {&Game::handleClientEvent_HudChange},
2604 {&Game::handleClientEvent_SetSky},
2605 {&Game::handleClientEvent_SetSun},
2606 {&Game::handleClientEvent_SetMoon},
2607 {&Game::handleClientEvent_SetStars},
2608 {&Game::handleClientEvent_OverrideDayNigthRatio},
2609 {&Game::handleClientEvent_CloudParams},
2612 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2614 FATAL_ERROR("ClientEvent type None received");
2617 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2619 if (client->modsLoaded())
2620 client->getScript()->on_damage_taken(event->player_damage.amount);
2622 if (!event->player_damage.effect)
2625 // Damage flash and hurt tilt are not used at death
2626 if (client->getHP() > 0) {
2627 LocalPlayer *player = client->getEnv().getLocalPlayer();
2629 f32 hp_max = player->getCAO() ?
2630 player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2631 f32 damage_ratio = event->player_damage.amount / hp_max;
2633 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2634 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2636 player->hurt_tilt_timer = 1.5f;
2637 player->hurt_tilt_strength =
2638 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2641 // Play damage sound
2642 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2645 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2647 cam->camera_yaw = event->player_force_move.yaw;
2648 cam->camera_pitch = event->player_force_move.pitch;
2651 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2653 // If client scripting is enabled, deathscreen is handled by CSM code in
2654 // builtin/client/init.lua
2655 if (client->modsLoaded())
2656 client->getScript()->on_death();
2658 showDeathFormspec();
2660 /* Handle visualization */
2661 LocalPlayer *player = client->getEnv().getLocalPlayer();
2662 runData.damage_flash = 0;
2663 player->hurt_tilt_timer = 0;
2664 player->hurt_tilt_strength = 0;
2667 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2669 if (event->show_formspec.formspec->empty()) {
2670 auto formspec = m_game_ui->getFormspecGUI();
2671 if (formspec && (event->show_formspec.formname->empty()
2672 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2673 formspec->quitMenu();
2676 FormspecFormSource *fs_src =
2677 new FormspecFormSource(*(event->show_formspec.formspec));
2678 TextDestPlayerInventory *txt_dst =
2679 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2681 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2682 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2683 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2686 delete event->show_formspec.formspec;
2687 delete event->show_formspec.formname;
2690 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2692 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2693 LocalFormspecHandler *txt_dst =
2694 new LocalFormspecHandler(*event->show_formspec.formname, client);
2695 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2696 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2698 delete event->show_formspec.formspec;
2699 delete event->show_formspec.formname;
2702 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2703 CameraOrientation *cam)
2705 LocalPlayer *player = client->getEnv().getLocalPlayer();
2706 client->getParticleManager()->handleParticleEvent(event, client, player);
2709 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2711 LocalPlayer *player = client->getEnv().getLocalPlayer();
2713 u32 server_id = event->hudadd->server_id;
2714 // ignore if we already have a HUD with that ID
2715 auto i = m_hud_server_to_client.find(server_id);
2716 if (i != m_hud_server_to_client.end()) {
2717 delete event->hudadd;
2721 HudElement *e = new HudElement;
2722 e->type = static_cast<HudElementType>(event->hudadd->type);
2723 e->pos = event->hudadd->pos;
2724 e->name = event->hudadd->name;
2725 e->scale = event->hudadd->scale;
2726 e->text = event->hudadd->text;
2727 e->number = event->hudadd->number;
2728 e->item = event->hudadd->item;
2729 e->dir = event->hudadd->dir;
2730 e->align = event->hudadd->align;
2731 e->offset = event->hudadd->offset;
2732 e->world_pos = event->hudadd->world_pos;
2733 e->size = event->hudadd->size;
2734 e->z_index = event->hudadd->z_index;
2735 e->text2 = event->hudadd->text2;
2736 e->style = event->hudadd->style;
2737 m_hud_server_to_client[server_id] = player->addHud(e);
2739 delete event->hudadd;
2742 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2744 LocalPlayer *player = client->getEnv().getLocalPlayer();
2746 auto i = m_hud_server_to_client.find(event->hudrm.id);
2747 if (i != m_hud_server_to_client.end()) {
2748 HudElement *e = player->removeHud(i->second);
2750 m_hud_server_to_client.erase(i);
2755 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2757 LocalPlayer *player = client->getEnv().getLocalPlayer();
2759 HudElement *e = nullptr;
2761 auto i = m_hud_server_to_client.find(event->hudchange->id);
2762 if (i != m_hud_server_to_client.end()) {
2763 e = player->getHud(i->second);
2767 delete event->hudchange;
2771 #define CASE_SET(statval, prop, dataprop) \
2773 e->prop = event->hudchange->dataprop; \
2776 switch (event->hudchange->stat) {
2777 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2779 CASE_SET(HUD_STAT_NAME, name, sdata);
2781 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2783 CASE_SET(HUD_STAT_TEXT, text, sdata);
2785 CASE_SET(HUD_STAT_NUMBER, number, data);
2787 CASE_SET(HUD_STAT_ITEM, item, data);
2789 CASE_SET(HUD_STAT_DIR, dir, data);
2791 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2793 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2795 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2797 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2799 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2801 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2803 CASE_SET(HUD_STAT_STYLE, style, data);
2808 delete event->hudchange;
2811 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2813 sky->setVisible(false);
2814 // Whether clouds are visible in front of a custom skybox.
2815 sky->setCloudsEnabled(event->set_sky->clouds);
2821 // Clear the old textures out in case we switch rendering type.
2822 sky->clearSkyboxTextures();
2823 // Handle according to type
2824 if (event->set_sky->type == "regular") {
2825 // Shows the mesh skybox
2826 sky->setVisible(true);
2827 // Update mesh based skybox colours if applicable.
2828 sky->setSkyColors(event->set_sky->sky_color);
2829 sky->setHorizonTint(
2830 event->set_sky->fog_sun_tint,
2831 event->set_sky->fog_moon_tint,
2832 event->set_sky->fog_tint_type
2834 } else if (event->set_sky->type == "skybox" &&
2835 event->set_sky->textures.size() == 6) {
2836 // Disable the dyanmic mesh skybox:
2837 sky->setVisible(false);
2839 sky->setFallbackBgColor(event->set_sky->bgcolor);
2840 // Set sunrise and sunset fog tinting:
2841 sky->setHorizonTint(
2842 event->set_sky->fog_sun_tint,
2843 event->set_sky->fog_moon_tint,
2844 event->set_sky->fog_tint_type
2846 // Add textures to skybox.
2847 for (int i = 0; i < 6; i++)
2848 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2850 // Handle everything else as plain color.
2851 if (event->set_sky->type != "plain")
2852 infostream << "Unknown sky type: "
2853 << (event->set_sky->type) << std::endl;
2854 sky->setVisible(false);
2855 sky->setFallbackBgColor(event->set_sky->bgcolor);
2856 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2857 sky->setHorizonTint(
2858 event->set_sky->bgcolor,
2859 event->set_sky->bgcolor,
2864 delete event->set_sky;
2867 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2869 sky->setSunVisible(event->sun_params->visible);
2870 sky->setSunTexture(event->sun_params->texture,
2871 event->sun_params->tonemap, texture_src);
2872 sky->setSunScale(event->sun_params->scale);
2873 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2874 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2875 delete event->sun_params;
2878 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2880 sky->setMoonVisible(event->moon_params->visible);
2881 sky->setMoonTexture(event->moon_params->texture,
2882 event->moon_params->tonemap, texture_src);
2883 sky->setMoonScale(event->moon_params->scale);
2884 delete event->moon_params;
2887 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2889 sky->setStarsVisible(event->star_params->visible);
2890 sky->setStarCount(event->star_params->count);
2891 sky->setStarColor(event->star_params->starcolor);
2892 sky->setStarScale(event->star_params->scale);
2893 sky->setStarDayOpacity(event->star_params->day_opacity);
2894 delete event->star_params;
2897 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2898 CameraOrientation *cam)
2900 client->getEnv().setDayNightRatioOverride(
2901 event->override_day_night_ratio.do_override,
2902 event->override_day_night_ratio.ratio_f * 1000.0f);
2905 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2910 clouds->setDensity(event->cloud_params.density);
2911 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2912 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2913 clouds->setHeight(event->cloud_params.height);
2914 clouds->setThickness(event->cloud_params.thickness);
2915 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2918 void Game::processClientEvents(CameraOrientation *cam)
2920 while (client->hasClientEvents()) {
2921 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2922 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2923 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2924 (this->*evHandler.handler)(event.get(), cam);
2928 void Game::updateChat(f32 dtime)
2930 // Get new messages from error log buffer
2931 while (!m_chat_log_buf.empty())
2932 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2934 // Get new messages from client
2935 std::wstring message;
2936 while (client->getChatMessage(message)) {
2937 chat_backend->addUnparsedMessage(message);
2940 // Remove old messages
2941 chat_backend->step(dtime);
2943 // Display all messages in a static text element
2944 auto &buf = chat_backend->getRecentBuffer();
2945 if (buf.getLinesModified()) {
2946 buf.resetLinesModified();
2947 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
2950 // Make sure that the size is still correct
2951 m_game_ui->updateChatSize();
2954 void Game::updateCamera(f32 dtime)
2956 LocalPlayer *player = client->getEnv().getLocalPlayer();
2959 For interaction purposes, get info about the held item
2961 - Is it a usable item?
2962 - Can it point to liquids?
2964 ItemStack playeritem;
2966 ItemStack selected, hand;
2967 playeritem = player->getWieldedItem(&selected, &hand);
2970 ToolCapabilities playeritem_toolcap =
2971 playeritem.getToolCapabilities(itemdef_manager);
2973 v3s16 old_camera_offset = camera->getOffset();
2975 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2976 GenericCAO *playercao = player->getCAO();
2978 // If playercao not loaded, don't change camera
2982 camera->toggleCameraMode();
2984 // Make the player visible depending on camera mode.
2985 playercao->updateMeshCulling();
2986 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2989 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2990 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2992 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2993 camera->update(player, dtime, tool_reload_ratio);
2994 camera->step(dtime);
2996 f32 camera_fov = camera->getFovMax();
2997 v3s16 camera_offset = camera->getOffset();
2999 m_camera_offset_changed = (camera_offset != old_camera_offset);
3001 if (!m_flags.disable_camera_update) {
3002 v3f camera_position = camera->getPosition();
3003 v3f camera_direction = camera->getDirection();
3005 client->getEnv().getClientMap().updateCamera(camera_position,
3006 camera_direction, camera_fov, camera_offset);
3008 if (m_camera_offset_changed) {
3009 client->updateCameraOffset(camera_offset);
3010 client->getEnv().updateCameraOffset(camera_offset);
3013 clouds->updateCameraOffset(camera_offset);
3019 void Game::updateSound(f32 dtime)
3021 // Update sound listener
3022 v3s16 camera_offset = camera->getOffset();
3023 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3024 v3f(0, 0, 0), // velocity
3025 camera->getDirection(),
3026 camera->getCameraNode()->getUpVector());
3028 bool mute_sound = g_settings->getBool("mute_sound");
3030 sound->setListenerGain(0.0f);
3032 // Check if volume is in the proper range, else fix it.
3033 float old_volume = g_settings->getFloat("sound_volume");
3034 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3035 sound->setListenerGain(new_volume);
3037 if (old_volume != new_volume) {
3038 g_settings->setFloat("sound_volume", new_volume);
3042 LocalPlayer *player = client->getEnv().getLocalPlayer();
3044 // Tell the sound maker whether to make footstep sounds
3045 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3047 // Update sound maker
3048 if (player->makes_footstep_sound)
3049 soundmaker->step(dtime);
3051 ClientMap &map = client->getEnv().getClientMap();
3052 MapNode n = map.getNode(player->getFootstepNodePos());
3053 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3057 void Game::processPlayerInteraction(f32 dtime, bool show_hud)
3059 LocalPlayer *player = client->getEnv().getLocalPlayer();
3061 const v3f camera_direction = camera->getDirection();
3062 const v3s16 camera_offset = camera->getOffset();
3065 Calculate what block is the crosshair pointing to
3068 ItemStack selected_item, hand_item;
3069 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3071 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3072 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3074 core::line3d<f32> shootline;
3076 switch (camera->getCameraMode()) {
3077 case CAMERA_MODE_FIRST:
3078 // Shoot from camera position, with bobbing
3079 shootline.start = camera->getPosition();
3081 case CAMERA_MODE_THIRD:
3082 // Shoot from player head, no bobbing
3083 shootline.start = camera->getHeadPosition();
3085 case CAMERA_MODE_THIRD_FRONT:
3086 shootline.start = camera->getHeadPosition();
3087 // prevent player pointing anything in front-view
3091 shootline.end = shootline.start + camera_direction * BS * d;
3093 #ifdef HAVE_TOUCHSCREENGUI
3095 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3096 shootline = g_touchscreengui->getShootline();
3097 // Scale shootline to the acual distance the player can reach
3098 shootline.end = shootline.start
3099 + shootline.getVector().normalize() * BS * d;
3100 shootline.start += intToFloat(camera_offset, BS);
3101 shootline.end += intToFloat(camera_offset, BS);
3106 PointedThing pointed = updatePointedThing(shootline,
3107 selected_def.liquids_pointable,
3108 !runData.btn_down_for_dig,
3111 if (pointed != runData.pointed_old)
3112 infostream << "Pointing at " << pointed.dump() << std::endl;
3114 // Note that updating the selection mesh every frame is not particularly efficient,
3115 // but the halo rendering code is already inefficient so there's no point in optimizing it here
3116 hud->updateSelectionMesh(camera_offset);
3118 // Allow digging again if button is not pressed
3119 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3120 runData.digging_blocked = false;
3124 - releasing dig button
3125 - pointing away from node
3127 if (runData.digging) {
3128 if (wasKeyReleased(KeyType::DIG)) {
3129 infostream << "Dig button released (stopped digging)" << std::endl;
3130 runData.digging = false;
3131 } else if (pointed != runData.pointed_old) {
3132 if (pointed.type == POINTEDTHING_NODE
3133 && runData.pointed_old.type == POINTEDTHING_NODE
3134 && pointed.node_undersurface
3135 == runData.pointed_old.node_undersurface) {
3136 // Still pointing to the same node, but a different face.
3139 infostream << "Pointing away from node (stopped digging)" << std::endl;
3140 runData.digging = false;
3141 hud->updateSelectionMesh(camera_offset);
3145 if (!runData.digging) {
3146 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3147 client->setCrack(-1, v3s16(0, 0, 0));
3148 runData.dig_time = 0.0;
3150 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3151 // Remove e.g. torches faster when clicking instead of holding dig button
3152 runData.nodig_delay_timer = 0;
3153 runData.dig_instantly = false;
3156 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3157 runData.btn_down_for_dig = false;
3159 runData.punching = false;
3161 soundmaker->m_player_leftpunch_sound.name.clear();
3163 // Prepare for repeating, unless we're not supposed to
3164 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3165 runData.repeat_place_timer += dtime;
3167 runData.repeat_place_timer = 0;
3169 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3170 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3171 !client->getScript()->on_item_use(selected_item, pointed)))
3172 client->interact(INTERACT_USE, pointed);
3173 } else if (pointed.type == POINTEDTHING_NODE) {
3174 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3175 } else if (pointed.type == POINTEDTHING_OBJECT) {
3176 v3f player_position = player->getPosition();
3177 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
3178 handlePointingAtObject(pointed, tool_item, player_position,
3179 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
3180 } else if (isKeyDown(KeyType::DIG)) {
3181 // When button is held down in air, show continuous animation
3182 runData.punching = true;
3183 // Run callback even though item is not usable
3184 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3185 client->getScript()->on_item_use(selected_item, pointed);
3186 } else if (wasKeyPressed(KeyType::PLACE)) {
3187 handlePointingAtNothing(selected_item);
3190 runData.pointed_old = pointed;
3192 if (runData.punching || wasKeyPressed(KeyType::DIG))
3193 camera->setDigging(0); // dig animation
3195 input->clearWasKeyPressed();
3196 input->clearWasKeyReleased();
3197 // Ensure DIG & PLACE are marked as handled
3198 wasKeyDown(KeyType::DIG);
3199 wasKeyDown(KeyType::PLACE);
3201 input->joystick.clearWasKeyPressed(KeyType::DIG);
3202 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3204 input->joystick.clearWasKeyReleased(KeyType::DIG);
3205 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3209 PointedThing Game::updatePointedThing(
3210 const core::line3d<f32> &shootline,
3211 bool liquids_pointable,
3212 bool look_for_object,
3213 const v3s16 &camera_offset)
3215 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3216 selectionboxes->clear();
3217 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3218 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3219 "show_entity_selectionbox");
3221 ClientEnvironment &env = client->getEnv();
3222 ClientMap &map = env.getClientMap();
3223 const NodeDefManager *nodedef = map.getNodeDefManager();
3225 runData.selected_object = NULL;
3226 hud->pointing_at_object = false;
3228 RaycastState s(shootline, look_for_object, liquids_pointable);
3229 PointedThing result;
3230 env.continueRaycast(&s, &result);
3231 if (result.type == POINTEDTHING_OBJECT) {
3232 hud->pointing_at_object = true;
3234 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3235 aabb3f selection_box;
3236 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3237 runData.selected_object->getSelectionBox(&selection_box)) {
3238 v3f pos = runData.selected_object->getPosition();
3239 selectionboxes->push_back(aabb3f(selection_box));
3240 hud->setSelectionPos(pos, camera_offset);
3242 } else if (result.type == POINTEDTHING_NODE) {
3243 // Update selection boxes
3244 MapNode n = map.getNode(result.node_undersurface);
3245 std::vector<aabb3f> boxes;
3246 n.getSelectionBoxes(nodedef, &boxes,
3247 n.getNeighbors(result.node_undersurface, &map));
3250 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3251 i != boxes.end(); ++i) {
3253 box.MinEdge -= v3f(d, d, d);
3254 box.MaxEdge += v3f(d, d, d);
3255 selectionboxes->push_back(box);
3257 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3259 hud->setSelectedFaceNormal(v3f(
3260 result.intersection_normal.X,
3261 result.intersection_normal.Y,
3262 result.intersection_normal.Z));
3265 // Update selection mesh light level and vertex colors
3266 if (!selectionboxes->empty()) {
3267 v3f pf = hud->getSelectionPos();
3268 v3s16 p = floatToInt(pf, BS);
3270 // Get selection mesh light level
3271 MapNode n = map.getNode(p);
3272 u16 node_light = getInteriorLight(n, -1, nodedef);
3273 u16 light_level = node_light;
3275 for (const v3s16 &dir : g_6dirs) {
3276 n = map.getNode(p + dir);
3277 node_light = getInteriorLight(n, -1, nodedef);
3278 if (node_light > light_level)
3279 light_level = node_light;
3282 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3284 final_color_blend(&c, light_level, daynight_ratio);
3286 // Modify final color a bit with time
3287 u32 timer = client->getEnv().getFrameTime() % 5000;
3288 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3289 float sin_r = 0.08f * std::sin(timerf);
3290 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3291 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3292 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3293 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3294 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3296 // Set mesh final color
3297 hud->setSelectionMeshColor(c);
3303 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3305 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3306 PointedThing fauxPointed;
3307 fauxPointed.type = POINTEDTHING_NOTHING;
3308 client->interact(INTERACT_ACTIVATE, fauxPointed);
3312 void Game::handlePointingAtNode(const PointedThing &pointed,
3313 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3315 v3s16 nodepos = pointed.node_undersurface;
3316 v3s16 neighbourpos = pointed.node_abovesurface;
3319 Check information text of node
3322 ClientMap &map = client->getEnv().getClientMap();
3324 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3325 && !runData.digging_blocked
3326 && client->checkPrivilege("interact")) {
3327 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3330 // This should be done after digging handling
3331 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3334 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3335 meta->getString("infotext"))));
3337 MapNode n = map.getNode(nodepos);
3339 if (nodedef_manager->get(n).name == "unknown") {
3340 m_game_ui->setInfoText(L"Unknown node");
3344 if ((wasKeyPressed(KeyType::PLACE) ||
3345 runData.repeat_place_timer >= m_repeat_place_time) &&
3346 client->checkPrivilege("interact")) {
3347 runData.repeat_place_timer = 0;
3348 infostream << "Place button pressed while looking at ground" << std::endl;
3350 // Placing animation (always shown for feedback)
3351 camera->setDigging(1);
3353 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3355 // If the wielded item has node placement prediction,
3357 // And also set the sound and send the interact
3358 // But first check for meta formspec and rightclickable
3359 auto &def = selected_item.getDefinition(itemdef_manager);
3360 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3363 if (placed && client->modsLoaded())
3364 client->getScript()->on_placenode(pointed, def);
3368 bool Game::nodePlacement(const ItemDefinition &selected_def,
3369 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3370 const PointedThing &pointed, const NodeMetadata *meta)
3372 const auto &prediction = selected_def.node_placement_prediction;
3374 const NodeDefManager *nodedef = client->ndef();
3375 ClientMap &map = client->getEnv().getClientMap();
3377 bool is_valid_position;
3379 node = map.getNode(nodepos, &is_valid_position);
3380 if (!is_valid_position) {
3381 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3386 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3387 && !isKeyDown(KeyType::SNEAK)) {
3388 // on_rightclick callbacks are called anyway
3389 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3390 client->interact(INTERACT_PLACE, pointed);
3392 infostream << "Launching custom inventory view" << std::endl;
3394 InventoryLocation inventoryloc;
3395 inventoryloc.setNodeMeta(nodepos);
3397 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3398 &client->getEnv().getClientMap(), nodepos);
3399 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3401 auto *&formspec = m_game_ui->updateFormspec("");
3402 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3403 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3405 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3409 // on_rightclick callback
3410 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3411 !isKeyDown(KeyType::SNEAK))) {
3413 client->interact(INTERACT_PLACE, pointed);
3417 verbosestream << "Node placement prediction for "
3418 << selected_def.name << " is " << prediction << std::endl;
3419 v3s16 p = neighbourpos;
3421 // Place inside node itself if buildable_to
3422 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3423 if (is_valid_position) {
3424 if (nodedef->get(n_under).buildable_to) {
3427 node = map.getNode(p, &is_valid_position);
3428 if (is_valid_position && !nodedef->get(node).buildable_to) {
3429 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3431 client->interact(INTERACT_PLACE, pointed);
3437 // Find id of predicted node
3439 bool found = nodedef->getId(prediction, id);
3442 errorstream << "Node placement prediction failed for "
3443 << selected_def.name << " (places " << prediction
3444 << ") - Name not known" << std::endl;
3445 // Handle this as if prediction was empty
3447 client->interact(INTERACT_PLACE, pointed);
3451 const ContentFeatures &predicted_f = nodedef->get(id);
3453 // Predict param2 for facedir and wallmounted nodes
3454 // Compare core.item_place_node() for what the server does
3457 const u8 place_param2 = selected_def.place_param2;
3460 param2 = place_param2;
3461 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3462 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3463 v3s16 dir = nodepos - neighbourpos;
3465 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3466 param2 = dir.Y < 0 ? 1 : 0;
3467 } else if (abs(dir.X) > abs(dir.Z)) {
3468 param2 = dir.X < 0 ? 3 : 2;
3470 param2 = dir.Z < 0 ? 5 : 4;
3472 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3473 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR ||
3474 predicted_f.param_type_2 == CPT2_4DIR ||
3475 predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3476 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3478 if (abs(dir.X) > abs(dir.Z)) {
3479 param2 = dir.X < 0 ? 3 : 1;
3481 param2 = dir.Z < 0 ? 2 : 0;
3485 // Check attachment if node is in group attached_node
3486 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3487 const static v3s16 wallmounted_dirs[8] = {
3497 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3498 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3499 pp = p + wallmounted_dirs[param2];
3501 pp = p + v3s16(0, -1, 0);
3503 if (!nodedef->get(map.getNode(pp)).walkable) {
3504 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3506 client->interact(INTERACT_PLACE, pointed);
3512 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3513 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3514 || predicted_f.param_type_2 == CPT2_COLORED_4DIR
3515 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3516 const auto &indexstr = selected_item.metadata.
3517 getString("palette_index", 0);
3518 if (!indexstr.empty()) {
3519 s32 index = mystoi(indexstr);
3520 if (predicted_f.param_type_2 == CPT2_COLOR) {
3522 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3523 // param2 = pure palette index + other
3524 param2 = (index & 0xf8) | (param2 & 0x07);
3525 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3526 // param2 = pure palette index + other
3527 param2 = (index & 0xe0) | (param2 & 0x1f);
3528 } else if (predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3529 // param2 = pure palette index + other
3530 param2 = (index & 0xfc) | (param2 & 0x03);
3535 // Add node to client map
3536 MapNode n(id, 0, param2);
3539 LocalPlayer *player = client->getEnv().getLocalPlayer();
3541 // Dont place node when player would be inside new node
3542 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3543 if (!nodedef->get(n).walkable ||
3544 g_settings->getBool("enable_build_where_you_stand") ||
3545 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3546 (nodedef->get(n).walkable &&
3547 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3548 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3549 // This triggers the required mesh update too
3550 client->addNode(p, n);
3552 client->interact(INTERACT_PLACE, pointed);
3553 // A node is predicted, also play a sound
3554 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3557 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3560 } catch (const InvalidPositionException &e) {
3561 errorstream << "Node placement prediction failed for "
3562 << selected_def.name << " (places "
3563 << prediction << ") - Position not loaded" << std::endl;
3564 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3569 void Game::handlePointingAtObject(const PointedThing &pointed,
3570 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3572 std::wstring infotext = unescape_translate(
3573 utf8_to_wide(runData.selected_object->infoText()));
3576 if (!infotext.empty()) {
3579 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3582 m_game_ui->setInfoText(infotext);
3584 if (isKeyDown(KeyType::DIG)) {
3585 bool do_punch = false;
3586 bool do_punch_damage = false;
3588 if (runData.object_hit_delay_timer <= 0.0) {
3590 do_punch_damage = true;
3591 runData.object_hit_delay_timer = object_hit_delay;
3594 if (wasKeyPressed(KeyType::DIG))
3598 infostream << "Punched object" << std::endl;
3599 runData.punching = true;
3602 if (do_punch_damage) {
3603 // Report direct punch
3604 v3f objpos = runData.selected_object->getPosition();
3605 v3f dir = (objpos - player_position).normalize();
3607 bool disable_send = runData.selected_object->directReportPunch(
3608 dir, &tool_item, runData.time_from_last_punch);
3609 runData.time_from_last_punch = 0;
3612 client->interact(INTERACT_START_DIGGING, pointed);
3614 } else if (wasKeyDown(KeyType::PLACE)) {
3615 infostream << "Pressed place button while pointing at object" << std::endl;
3616 client->interact(INTERACT_PLACE, pointed); // place
3621 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3622 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3624 // See also: serverpackethandle.cpp, action == 2
3625 LocalPlayer *player = client->getEnv().getLocalPlayer();
3626 ClientMap &map = client->getEnv().getClientMap();
3627 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3629 // NOTE: Similar piece of code exists on the server side for
3631 // Get digging parameters
3632 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3633 &selected_item.getToolCapabilities(itemdef_manager),
3634 selected_item.wear);
3636 // If can't dig, try hand
3637 if (!params.diggable) {
3638 params = getDigParams(nodedef_manager->get(n).groups,
3639 &hand_item.getToolCapabilities(itemdef_manager));
3642 if (!params.diggable) {
3643 // I guess nobody will wait for this long
3644 runData.dig_time_complete = 10000000.0;
3646 runData.dig_time_complete = params.time;
3648 if (m_cache_enable_particles) {
3649 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3650 client->getParticleManager()->addNodeParticle(client,
3651 player, nodepos, n, features);
3655 if (!runData.digging) {
3656 infostream << "Started digging" << std::endl;
3657 runData.dig_instantly = runData.dig_time_complete == 0;
3658 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3660 client->interact(INTERACT_START_DIGGING, pointed);
3661 runData.digging = true;
3662 runData.btn_down_for_dig = true;
3665 if (!runData.dig_instantly) {
3666 runData.dig_index = (float)crack_animation_length
3668 / runData.dig_time_complete;
3670 // This is for e.g. torches
3671 runData.dig_index = crack_animation_length;
3674 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3676 if (sound_dig.exists() && params.diggable) {
3677 if (sound_dig.name == "__group") {
3678 if (!params.main_group.empty()) {
3679 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3680 soundmaker->m_player_leftpunch_sound.name =
3681 std::string("default_dig_") +
3685 soundmaker->m_player_leftpunch_sound = sound_dig;
3689 // Don't show cracks if not diggable
3690 if (runData.dig_time_complete >= 100000.0) {
3691 } else if (runData.dig_index < crack_animation_length) {
3692 //TimeTaker timer("client.setTempMod");
3693 //infostream<<"dig_index="<<dig_index<<std::endl;
3694 client->setCrack(runData.dig_index, nodepos);
3696 infostream << "Digging completed" << std::endl;
3697 client->setCrack(-1, v3s16(0, 0, 0));
3699 runData.dig_time = 0;
3700 runData.digging = false;
3701 // we successfully dug, now block it from repeating if we want to be safe
3702 if (g_settings->getBool("safe_dig_and_place"))
3703 runData.digging_blocked = true;
3705 runData.nodig_delay_timer =
3706 runData.dig_time_complete / (float)crack_animation_length;
3708 // We don't want a corresponding delay to very time consuming nodes
3709 // and nodes without digging time (e.g. torches) get a fixed delay.
3710 if (runData.nodig_delay_timer > 0.3)
3711 runData.nodig_delay_timer = 0.3;
3712 else if (runData.dig_instantly)
3713 runData.nodig_delay_timer = 0.15;
3715 bool is_valid_position;
3716 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3717 if (is_valid_position) {
3718 if (client->modsLoaded() &&
3719 client->getScript()->on_dignode(nodepos, wasnode)) {
3723 const ContentFeatures &f = client->ndef()->get(wasnode);
3724 if (f.node_dig_prediction == "air") {
3725 client->removeNode(nodepos);
3726 } else if (!f.node_dig_prediction.empty()) {
3728 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3730 client->addNode(nodepos, id, true);
3732 // implicit else: no prediction
3735 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3737 if (m_cache_enable_particles) {
3738 const ContentFeatures &features =
3739 client->getNodeDefManager()->get(wasnode);
3740 client->getParticleManager()->addDiggingParticles(client,
3741 player, nodepos, wasnode, features);
3745 // Send event to trigger sound
3746 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3749 if (runData.dig_time_complete < 100000.0) {
3750 runData.dig_time += dtime;
3752 runData.dig_time = 0;
3753 client->setCrack(-1, nodepos);
3756 camera->setDigging(0); // Dig animation
3759 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3760 const CameraOrientation &cam)
3762 TimeTaker tt_update("Game::updateFrame()");
3763 LocalPlayer *player = client->getEnv().getLocalPlayer();
3769 client->getEnv().updateFrameTime(m_is_paused);
3775 if (draw_control->range_all) {
3776 runData.fog_range = 100000 * BS;
3778 runData.fog_range = draw_control->wanted_range * BS;
3782 Calculate general brightness
3784 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3785 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3786 float direct_brightness;
3789 // When in noclip mode force same sky brightness as above ground so you
3791 if (draw_control->allow_noclip && m_cache_enable_free_move &&
3792 client->checkPrivilege("fly")) {
3793 direct_brightness = time_brightness;
3794 sunlight_seen = true;
3796 float old_brightness = sky->getBrightness();
3797 direct_brightness = client->getEnv().getClientMap()
3798 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3799 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3803 float time_of_day_smooth = runData.time_of_day_smooth;
3804 float time_of_day = client->getEnv().getTimeOfDayF();
3806 static const float maxsm = 0.05f;
3807 static const float todsm = 0.05f;
3809 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3810 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3811 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3812 time_of_day_smooth = time_of_day;
3814 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3815 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3816 + (time_of_day + 1.0) * todsm;
3818 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3819 + time_of_day * todsm;
3821 runData.time_of_day_smooth = time_of_day_smooth;
3823 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3824 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3825 player->getPitch());
3831 if (sky->getCloudsVisible()) {
3832 clouds->setVisible(true);
3833 clouds->step(dtime);
3834 // camera->getPosition is not enough for 3rd person views
3835 v3f camera_node_position = camera->getCameraNode()->getPosition();
3836 v3s16 camera_offset = camera->getOffset();
3837 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3838 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3839 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3840 clouds->update(camera_node_position,
3841 sky->getCloudColor());
3842 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3843 // if inside clouds, and fog enabled, use that as sky
3845 video::SColor clouds_dark = clouds->getColor()
3846 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3847 sky->overrideColors(clouds_dark, clouds->getColor());
3848 sky->setInClouds(true);
3849 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3850 // do not draw clouds after all
3851 clouds->setVisible(false);
3854 clouds->setVisible(false);
3861 client->getParticleManager()->step(dtime);
3867 if (m_cache_enable_fog) {
3870 video::EFT_FOG_LINEAR,
3871 runData.fog_range * m_cache_fog_start,
3872 runData.fog_range * 1.0,
3880 video::EFT_FOG_LINEAR,
3892 if (player->hurt_tilt_timer > 0.0f) {
3893 player->hurt_tilt_timer -= dtime * 6.0f;
3895 if (player->hurt_tilt_timer < 0.0f)
3896 player->hurt_tilt_strength = 0.0f;
3900 Update minimap pos and rotation
3902 if (mapper && m_game_ui->m_flags.show_hud) {
3903 mapper->setPos(floatToInt(player->getPosition(), BS));
3904 mapper->setAngle(player->getYaw());
3908 Get chat messages from client
3917 if (player->getWieldIndex() != runData.new_playeritem)
3918 client->setPlayerItem(runData.new_playeritem);
3920 if (client->updateWieldedItem()) {
3921 // Update wielded tool
3922 ItemStack selected_item, hand_item;
3923 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3924 camera->wield(tool_item);
3928 Update block draw list every 200ms or when camera direction has
3931 runData.update_draw_list_timer += dtime;
3933 float update_draw_list_delta = 0.2f;
3935 v3f camera_direction = camera->getDirection();
3936 if (runData.update_draw_list_timer >= update_draw_list_delta
3937 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3938 || m_camera_offset_changed
3939 || client->getEnv().getClientMap().needsUpdateDrawList()) {
3940 runData.update_draw_list_timer = 0;
3941 client->getEnv().getClientMap().updateDrawList();
3942 runData.update_draw_list_last_cam_dir = camera_direction;
3945 if (RenderingEngine::get_shadow_renderer()) {
3949 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3952 make sure menu is on top
3953 1. Delete formspec menu reference if menu was removed
3954 2. Else, make sure formspec menu is on top
3956 auto formspec = m_game_ui->getFormspecGUI();
3957 do { // breakable. only runs for one iteration
3961 if (formspec->getReferenceCount() == 1) {
3962 m_game_ui->deleteFormspec();
3966 auto &loc = formspec->getFormspecLocation();
3967 if (loc.type == InventoryLocation::NODEMETA) {
3968 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3969 if (!meta || meta->getString("formspec").empty()) {
3970 formspec->quitMenu();
3976 guiroot->bringToFront(formspec);
3980 ==================== Drawing begins ====================
3982 const video::SColor skycolor = sky->getSkyColor();
3984 TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
3985 driver->beginScene(true, true, skycolor);
3987 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3988 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3989 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3990 bool draw_crosshair = (
3991 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3992 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3993 #ifdef HAVE_TOUCHSCREENGUI
3995 draw_crosshair = !g_settings->getBool("touchtarget");
3996 } catch (SettingNotFoundException) {
3999 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
4000 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
4005 v2u32 screensize = driver->getScreenSize();
4007 if (m_game_ui->m_flags.show_profiler_graph)
4008 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
4013 if (runData.damage_flash > 0.0f) {
4014 video::SColor color(runData.damage_flash, 180, 0, 0);
4015 driver->draw2DRectangle(color,
4016 core::rect<s32>(0, 0, screensize.X, screensize.Y),
4019 runData.damage_flash -= 384.0f * dtime;
4023 ==================== End scene ====================
4025 #if IRRLICHT_VERSION_MT_REVISION < 5
4026 if (++m_reset_HW_buffer_counter > 500) {
4028 Periodically remove all mesh HW buffers.
4030 Work around for a quirk in Irrlicht where a HW buffer is only
4031 released after 20000 iterations (triggered from endScene()).
4033 Without this, all loaded but unused meshes will retain their HW
4034 buffers for at least 5 minutes, at which point looking up the HW buffers
4035 becomes a bottleneck and the framerate drops (as much as 30%).
4037 Tests showed that numbers between 50 and 1000 are good, so picked 500.
4038 There are no other public Irrlicht APIs that allow interacting with the
4039 HW buffers without tracking the status of every individual mesh.
4041 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4043 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4044 driver->removeAllHardwareBuffers();
4045 m_reset_HW_buffer_counter = 0;
4051 stats->drawtime = tt_draw.stop(true);
4052 g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
4053 g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
4056 /* Log times and stuff for visualization */
4057 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4059 Profiler::GraphValues values;
4060 g_profiler->graphGet(values);
4064 /****************************************************************************
4066 *****************************************************************************/
4067 void Game::updateShadows()
4069 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4073 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4075 float timeoftheday = getWickedTimeOfDay(in_timeofday);
4076 bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
4077 bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
4078 shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
4080 timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
4081 const float offset_constant = 10000.0f;
4083 v3f light = is_day ? sky->getSunDirection() : sky->getMoonDirection();
4085 v3f sun_pos = light * offset_constant;
4087 if (shadow->getDirectionalLightCount() == 0)
4088 shadow->addDirectionalLight();
4089 shadow->getDirectionalLight().setDirection(sun_pos);
4090 shadow->setTimeOfDay(in_timeofday);
4092 shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4095 /****************************************************************************
4097 ****************************************************************************/
4099 void FpsControl::reset()
4101 last_time = porting::getTimeUs();
4105 * On some computers framerate doesn't seem to be automatically limited
4107 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
4109 const float fps_limit = (device->isWindowFocused() && !g_menumgr.pausesGame())
4110 ? g_settings->getFloat("fps_max")
4111 : g_settings->getFloat("fps_max_unfocused");
4112 const u64 frametime_min = 1000000.0f / std::max(fps_limit, 1.0f);
4114 u64 time = porting::getTimeUs();
4116 if (time > last_time) // Make sure time hasn't overflowed
4117 busy_time = time - last_time;
4121 if (busy_time < frametime_min) {
4122 sleep_time = frametime_min - busy_time;
4123 if (sleep_time > 1000)
4124 sleep_ms(sleep_time / 1000);
4129 // Read the timer again to accurately determine how long we actually slept,
4130 // rather than calculating it by adding sleep_time to time.
4131 time = porting::getTimeUs();
4133 if (time > last_time) // Make sure last_time hasn't overflowed
4134 *dtime = (time - last_time) / 1000000.0f;
4141 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4143 const wchar_t *wmsg = wgettext(msg);
4144 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4149 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4151 ((Game *)data)->readSettings();
4154 void Game::readSettings()
4156 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4157 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4158 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4159 m_cache_enable_particles = g_settings->getBool("enable_particles");
4160 m_cache_enable_fog = g_settings->getBool("enable_fog");
4161 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f);
4162 m_cache_joystick_frustum_sensitivity = std::max(g_settings->getFloat("joystick_frustum_sensitivity"), 0.001f);
4163 m_repeat_place_time = g_settings->getFloat("repeat_place_time", 0.25f, 2.0);
4165 m_cache_enable_noclip = g_settings->getBool("noclip");
4166 m_cache_enable_free_move = g_settings->getBool("free_move");
4168 m_cache_fog_start = g_settings->getFloat("fog_start");
4170 m_cache_cam_smoothing = 0;
4171 if (g_settings->getBool("cinematic"))
4172 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4174 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4176 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4177 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4178 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4180 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4183 /****************************************************************************/
4184 /****************************************************************************
4186 ****************************************************************************/
4187 /****************************************************************************/
4189 void Game::showDeathFormspec()
4191 static std::string formspec_str =
4192 std::string("formspec_version[1]") +
4194 "bgcolor[#320000b4;true]"
4195 "label[4.85,1.35;" + gettext("You died") + "]"
4196 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4200 /* Note: FormspecFormSource and LocalFormspecHandler *
4201 * are deleted by guiFormSpecMenu */
4202 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4203 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4205 auto *&formspec = m_game_ui->getFormspecGUI();
4206 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4207 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4208 formspec->setFocus("btn_respawn");
4211 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4212 void Game::showPauseMenu()
4214 #ifdef HAVE_TOUCHSCREENGUI
4215 static const std::string control_text = strgettext("Default Controls:\n"
4216 "No menu visible:\n"
4217 "- single tap: button activate\n"
4218 "- double tap: place/use\n"
4219 "- slide finger: look around\n"
4220 "Menu/Inventory visible:\n"
4221 "- double tap (outside):\n"
4223 "- touch stack, touch slot:\n"
4225 "- touch&drag, tap 2nd finger\n"
4226 " --> place single item to slot\n"
4229 static const std::string control_text_template = strgettext("Controls:\n"
4230 "- %s: move forwards\n"
4231 "- %s: move backwards\n"
4233 "- %s: move right\n"
4234 "- %s: jump/climb up\n"
4237 "- %s: sneak/climb down\n"
4240 "- Mouse: turn/look\n"
4241 "- Mouse wheel: select item\n"
4245 char control_text_buf[600];
4247 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4248 GET_KEY_NAME(keymap_forward),
4249 GET_KEY_NAME(keymap_backward),
4250 GET_KEY_NAME(keymap_left),
4251 GET_KEY_NAME(keymap_right),
4252 GET_KEY_NAME(keymap_jump),
4253 GET_KEY_NAME(keymap_dig),
4254 GET_KEY_NAME(keymap_place),
4255 GET_KEY_NAME(keymap_sneak),
4256 GET_KEY_NAME(keymap_drop),
4257 GET_KEY_NAME(keymap_inventory),
4258 GET_KEY_NAME(keymap_chat)
4261 std::string control_text = std::string(control_text_buf);
4262 str_formspec_escape(control_text);
4265 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4266 std::ostringstream os;
4268 os << "formspec_version[1]" << SIZE_TAG
4269 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4270 << strgettext("Continue") << "]";
4272 if (!simple_singleplayer_mode) {
4273 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4274 << strgettext("Change Password") << "]";
4276 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4281 if (g_settings->getBool("enable_sound")) {
4282 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4283 << strgettext("Sound Volume") << "]";
4286 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4287 << strgettext("Change Keys") << "]";
4289 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4290 << strgettext("Exit to Menu") << "]";
4291 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4292 << strgettext("Exit to OS") << "]"
4293 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4294 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4296 << strgettext("Game info:") << "\n";
4297 const std::string &address = client->getAddressName();
4298 static const std::string mode = strgettext("- Mode: ");
4299 if (!simple_singleplayer_mode) {
4300 Address serverAddress = client->getServerAddress();
4301 if (!address.empty()) {
4302 os << mode << strgettext("Remote server") << "\n"
4303 << strgettext("- Address: ") << address;
4305 os << mode << strgettext("Hosting server");
4307 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4309 os << mode << strgettext("Singleplayer") << "\n";
4311 if (simple_singleplayer_mode || address.empty()) {
4312 static const std::string on = strgettext("On");
4313 static const std::string off = strgettext("Off");
4314 // Note: Status of enable_damage and creative_mode settings is intentionally
4315 // NOT shown here because the game might roll its own damage system and/or do
4316 // a per-player Creative Mode, in which case writing it here would mislead.
4317 bool damage = g_settings->getBool("enable_damage");
4318 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4319 if (!simple_singleplayer_mode) {
4321 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4322 //~ PvP = Player versus Player
4323 os << strgettext("- PvP: ") << pvp << "\n";
4325 os << strgettext("- Public: ") << announced << "\n";
4326 std::string server_name = g_settings->get("server_name");
4327 str_formspec_escape(server_name);
4328 if (announced == on && !server_name.empty())
4329 os << strgettext("- Server Name: ") << server_name;
4336 /* Note: FormspecFormSource and LocalFormspecHandler *
4337 * are deleted by guiFormSpecMenu */
4338 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4339 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4341 auto *&formspec = m_game_ui->getFormspecGUI();
4342 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4343 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4344 formspec->setFocus("btn_continue");
4345 // game will be paused in next step, if in singleplayer (see m_is_paused)
4346 formspec->doPause = true;
4349 /****************************************************************************/
4350 /****************************************************************************
4351 extern function for launching the game
4352 ****************************************************************************/
4353 /****************************************************************************/
4355 void the_game(bool *kill,
4356 InputHandler *input,
4357 RenderingEngine *rendering_engine,
4358 const GameStartData &start_data,
4359 std::string &error_message,
4360 ChatBackend &chat_backend,
4361 bool *reconnect_requested) // Used for local game
4365 /* Make a copy of the server address because if a local singleplayer server
4366 * is created then this is updated and we don't want to change the value
4367 * passed to us by the calling function
4372 if (game.startup(kill, input, rendering_engine, start_data,
4373 error_message, reconnect_requested, &chat_backend)) {
4377 } catch (SerializationError &e) {
4378 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
4379 error_message = strgettext("A serialization error occurred:") +"\n"
4380 + e.what() + "\n\n" + ver_err;
4381 errorstream << error_message << std::endl;
4382 } catch (ServerError &e) {
4383 error_message = e.what();
4384 errorstream << "ServerError: " << error_message << std::endl;
4385 } catch (ModError &e) {
4386 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
4387 error_message = std::string("ModError: ") + e.what() +
4388 strgettext("\nCheck debug.txt for details.");
4389 errorstream << error_message << std::endl;