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, false);
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), false);
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, false);
321 static void cameraPunchRight(MtEvent *e, void *data)
323 SoundMaker *sm = (SoundMaker *)data;
324 sm->m_sound->playSound(sm->m_player_rightpunch_sound, false);
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, false);
334 static void playerDamage(MtEvent *e, void *data)
336 SoundMaker *sm = (SoundMaker *)data;
337 sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false);
340 static void playerFallingDamage(MtEvent *e, void *data)
342 SoundMaker *sm = (SoundMaker *)data;
343 sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false);
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;
427 void onSettingsChange(const std::string &name)
429 if (name == "enable_fog")
430 m_fog_enabled = g_settings->getBool("enable_fog");
433 static void settingsCallback(const std::string &name, void *userdata)
435 reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
438 void setSky(Sky *sky) { m_sky = sky; }
440 GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
441 f32 *fog_range, Client *client) :
443 m_force_fog_off(force_fog_off),
444 m_fog_range(fog_range),
445 m_sky_bg_color("skyBgColor"),
446 m_fog_distance("fogDistance"),
447 m_animation_timer_vertex("animationTimer"),
448 m_animation_timer_pixel("animationTimer"),
449 m_day_light("dayLight"),
450 m_star_color("starColor"),
451 m_eye_position_pixel("eyePosition"),
452 m_eye_position_vertex("eyePosition"),
453 m_minimap_yaw("yawVec"),
454 m_camera_offset_pixel("cameraOffset"),
455 m_camera_offset_vertex("cameraOffset"),
456 m_base_texture("baseTexture"),
457 m_normal_texture("normalTexture"),
460 g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
461 m_fog_enabled = g_settings->getBool("enable_fog");
464 ~GameGlobalShaderConstantSetter()
466 g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
469 void onSetConstants(video::IMaterialRendererServices *services) override
472 video::SColor bgcolor = m_sky->getBgColor();
473 video::SColorf bgcolorf(bgcolor);
474 float bgcolorfa[4] = {
480 m_sky_bg_color.set(bgcolorfa, services);
483 float fog_distance = 10000 * BS;
485 if (m_fog_enabled && !*m_force_fog_off)
486 fog_distance = *m_fog_range;
488 m_fog_distance.set(&fog_distance, services);
490 u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
491 video::SColorf sunlight;
492 get_sunlight_color(&sunlight, daynight_ratio);
497 m_day_light.set(dnc, services);
499 video::SColorf star_color = m_sky->getCurrentStarColor();
500 float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a};
501 m_star_color.set(clr, services);
503 u32 animation_timer = porting::getTimeMs() % 1000000;
504 float animation_timer_f = (float)animation_timer / 100000.f;
505 m_animation_timer_vertex.set(&animation_timer_f, services);
506 m_animation_timer_pixel.set(&animation_timer_f, services);
508 float eye_position_array[3];
509 v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
510 epos.getAs3Values(eye_position_array);
511 m_eye_position_pixel.set(eye_position_array, services);
512 m_eye_position_vertex.set(eye_position_array, services);
514 if (m_client->getMinimap()) {
515 float minimap_yaw_array[3];
516 v3f minimap_yaw = m_client->getMinimap()->getYawVec();
517 minimap_yaw.getAs3Values(minimap_yaw_array);
518 m_minimap_yaw.set(minimap_yaw_array, services);
521 float camera_offset_array[3];
522 v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
523 offset.getAs3Values(camera_offset_array);
524 m_camera_offset_pixel.set(camera_offset_array, services);
525 m_camera_offset_vertex.set(camera_offset_array, services);
527 SamplerLayer_t base_tex = 0, normal_tex = 1;
528 m_base_texture.set(&base_tex, services);
529 m_normal_texture.set(&normal_tex, services);
534 class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
537 bool *m_force_fog_off;
540 std::vector<GameGlobalShaderConstantSetter *> created_nosky;
542 GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
543 f32 *fog_range, Client *client) :
545 m_force_fog_off(force_fog_off),
546 m_fog_range(fog_range),
550 void setSky(Sky *sky) {
552 for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
553 ggscs->setSky(m_sky);
555 created_nosky.clear();
558 virtual IShaderConstantSetter* create()
560 auto *scs = new GameGlobalShaderConstantSetter(
561 m_sky, m_force_fog_off, m_fog_range, m_client);
563 created_nosky.push_back(scs);
568 #ifdef HAVE_TOUCHSCREENGUI
569 #define SIZE_TAG "size[11,5.5]"
571 #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
574 /****************************************************************************
575 ****************************************************************************/
577 const static float object_hit_delay = 0.2;
580 FpsControl() : last_time(0), busy_time(0), sleep_time(0) {}
584 void limit(IrrlichtDevice *device, f32 *dtime);
586 u32 getBusyMs() const { return busy_time / 1000; }
588 // all values in microseconds (us)
589 u64 last_time, busy_time, sleep_time;
593 /* The reason the following structs are not anonymous structs within the
594 * class is that they are not used by the majority of member functions and
595 * many functions that do require objects of thse types do not modify them
596 * (so they can be passed as a const qualified parameter)
602 PointedThing pointed_old;
605 bool btn_down_for_dig;
607 bool digging_blocked;
608 bool reset_jump_timer;
609 float nodig_delay_timer;
611 float dig_time_complete;
612 float repeat_place_timer;
613 float object_hit_delay_timer;
614 float time_from_last_punch;
615 ClientActiveObject *selected_object;
619 float update_draw_list_timer;
623 v3f update_draw_list_last_cam_dir;
625 float time_of_day_smooth;
630 struct ClientEventHandler
632 void (Game::*handler)(ClientEvent *, CameraOrientation *);
635 /****************************************************************************
637 ****************************************************************************/
639 using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
641 /* This is not intended to be a public class. If a public class becomes
642 * desirable then it may be better to create another 'wrapper' class that
643 * hides most of the stuff in this class (nothing in this class is required
644 * by any other file) but exposes the public methods/data only.
651 bool startup(bool *kill,
653 RenderingEngine *rendering_engine,
654 const GameStartData &game_params,
655 std::string &error_message,
657 ChatBackend *chat_backend);
664 // Basic initialisation
665 bool init(const std::string &map_dir, const std::string &address,
666 u16 port, const SubgameSpec &gamespec);
668 bool createSingleplayerServer(const std::string &map_dir,
669 const SubgameSpec &gamespec, u16 port);
672 bool createClient(const GameStartData &start_data);
676 bool connectToServer(const GameStartData &start_data,
677 bool *connect_ok, bool *aborted);
678 bool getServerContent(bool *aborted);
682 void updateInteractTimers(f32 dtime);
683 bool checkConnection();
684 bool handleCallbacks();
685 void processQueues();
686 void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
687 void updateDebugState();
688 void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
689 void updateProfilerGraphs(ProfilerGraph *graph);
692 void processUserInput(f32 dtime);
693 void processKeyInput();
694 void processItemSelection(u16 *new_playeritem);
696 void dropSelectedItem(bool single_item = false);
697 void openInventory();
698 void openConsole(float scale, const wchar_t *line=NULL);
699 void toggleFreeMove();
700 void toggleFreeMoveAlt();
701 void togglePitchMove();
704 void toggleCinematic();
705 void toggleBlockBounds();
706 void toggleAutoforward();
708 void toggleMinimap(bool shift_pressed);
711 void toggleUpdateCamera();
713 void increaseViewRange();
714 void decreaseViewRange();
715 void toggleFullViewRange();
716 void checkZoomEnabled();
718 void updateCameraDirection(CameraOrientation *cam, float dtime);
719 void updateCameraOrientation(CameraOrientation *cam, float dtime);
720 void updatePlayerControl(const CameraOrientation &cam);
721 void step(f32 *dtime);
722 void processClientEvents(CameraOrientation *cam);
723 void updateCamera(f32 dtime);
724 void updateSound(f32 dtime);
725 void processPlayerInteraction(f32 dtime, bool show_hud);
727 * Returns the object or node the player is pointing at.
728 * Also updates the selected thing in the Hud.
730 * @param[in] shootline the shootline, starting from
731 * the camera position. This also gives the maximal distance
733 * @param[in] liquids_pointable if false, liquids are ignored
734 * @param[in] look_for_object if false, objects are ignored
735 * @param[in] camera_offset offset of the camera
736 * @param[out] selected_object the selected object or
739 PointedThing updatePointedThing(
740 const core::line3d<f32> &shootline, bool liquids_pointable,
741 bool look_for_object, const v3s16 &camera_offset);
742 void handlePointingAtNothing(const ItemStack &playerItem);
743 void handlePointingAtNode(const PointedThing &pointed,
744 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
745 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
746 const v3f &player_position, bool show_debug);
747 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
748 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
749 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
750 const CameraOrientation &cam);
751 void updateShadows();
754 void showOverlayMessage(const char *msg, float dtime, int percent,
755 bool draw_clouds = true);
757 static void settingChangedCallback(const std::string &setting_name, void *data);
760 inline bool isKeyDown(GameKeyType k)
762 return input->isKeyDown(k);
764 inline bool wasKeyDown(GameKeyType k)
766 return input->wasKeyDown(k);
768 inline bool wasKeyPressed(GameKeyType k)
770 return input->wasKeyPressed(k);
772 inline bool wasKeyReleased(GameKeyType k)
774 return input->wasKeyReleased(k);
778 void handleAndroidChatInput();
783 bool force_fog_off = false;
784 bool disable_camera_update = false;
787 void showDeathFormspec();
788 void showPauseMenu();
790 void pauseAnimation();
791 void resumeAnimation();
793 // ClientEvent handlers
794 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
795 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
796 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
797 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
798 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
799 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
800 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
801 CameraOrientation *cam);
802 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
803 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
804 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
805 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
806 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
807 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
808 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
809 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
810 CameraOrientation *cam);
811 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
813 void updateChat(f32 dtime);
815 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
816 const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
817 const NodeMetadata *meta);
818 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
820 f32 getSensitivityScaleFactor() const;
822 InputHandler *input = nullptr;
824 Client *client = nullptr;
825 Server *server = nullptr;
827 IWritableTextureSource *texture_src = nullptr;
828 IWritableShaderSource *shader_src = nullptr;
830 // When created, these will be filled with data received from the server
831 IWritableItemDefManager *itemdef_manager = nullptr;
832 NodeDefManager *nodedef_manager = nullptr;
834 GameOnDemandSoundFetcher soundfetcher; // useful when testing
835 ISoundManager *sound = nullptr;
836 bool sound_is_dummy = false;
837 SoundMaker *soundmaker = nullptr;
839 ChatBackend *chat_backend = nullptr;
840 LogOutputBuffer m_chat_log_buf;
842 EventManager *eventmgr = nullptr;
843 QuicktuneShortcutter *quicktune = nullptr;
844 bool registration_confirmation_shown = false;
846 std::unique_ptr<GameUI> m_game_ui;
847 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
848 MapDrawControl *draw_control = nullptr;
849 Camera *camera = nullptr;
850 Clouds *clouds = nullptr; // Free using ->Drop()
851 Sky *sky = nullptr; // Free using ->Drop()
853 Minimap *mapper = nullptr;
855 // Map server hud ids to client hud ids
856 std::unordered_map<u32, u32> m_hud_server_to_client;
862 This class does take ownership/responsibily for cleaning up etc of any of
863 these items (e.g. device)
865 IrrlichtDevice *device;
866 RenderingEngine *m_rendering_engine;
867 video::IVideoDriver *driver;
868 scene::ISceneManager *smgr;
870 std::string *error_message;
871 bool *reconnect_requested;
872 scene::ISceneNode *skybox;
873 PausedNodesList paused_animated_nodes;
875 bool simple_singleplayer_mode;
878 /* Pre-calculated values
880 int crack_animation_length;
882 IntervalLimiter profiler_interval;
885 * TODO: Local caching of settings is not optimal and should at some stage
886 * be updated to use a global settings object for getting thse values
887 * (as opposed to the this local caching). This can be addressed in
890 bool m_cache_doubletap_jump;
891 bool m_cache_enable_clouds;
892 bool m_cache_enable_joysticks;
893 bool m_cache_enable_particles;
894 bool m_cache_enable_fog;
895 bool m_cache_enable_noclip;
896 bool m_cache_enable_free_move;
897 f32 m_cache_mouse_sensitivity;
898 f32 m_cache_joystick_frustum_sensitivity;
899 f32 m_repeat_place_time;
900 f32 m_cache_cam_smoothing;
901 f32 m_cache_fog_start;
903 bool m_invert_mouse = false;
904 bool m_first_loop_after_window_activation = false;
905 bool m_camera_offset_changed = false;
907 bool m_does_lost_focus_pause_game = false;
909 #if IRRLICHT_VERSION_MT_REVISION < 5
910 int m_reset_HW_buffer_counter = 0;
913 #ifdef HAVE_TOUCHSCREENGUI
914 bool m_cache_hold_aux1;
917 bool m_android_chat_open;
922 m_chat_log_buf(g_logger),
923 m_game_ui(new GameUI())
925 g_settings->registerChangedCallback("doubletap_jump",
926 &settingChangedCallback, this);
927 g_settings->registerChangedCallback("enable_clouds",
928 &settingChangedCallback, this);
929 g_settings->registerChangedCallback("doubletap_joysticks",
930 &settingChangedCallback, this);
931 g_settings->registerChangedCallback("enable_particles",
932 &settingChangedCallback, this);
933 g_settings->registerChangedCallback("enable_fog",
934 &settingChangedCallback, this);
935 g_settings->registerChangedCallback("mouse_sensitivity",
936 &settingChangedCallback, this);
937 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
938 &settingChangedCallback, this);
939 g_settings->registerChangedCallback("repeat_place_time",
940 &settingChangedCallback, this);
941 g_settings->registerChangedCallback("noclip",
942 &settingChangedCallback, this);
943 g_settings->registerChangedCallback("free_move",
944 &settingChangedCallback, this);
945 g_settings->registerChangedCallback("cinematic",
946 &settingChangedCallback, this);
947 g_settings->registerChangedCallback("cinematic_camera_smoothing",
948 &settingChangedCallback, this);
949 g_settings->registerChangedCallback("camera_smoothing",
950 &settingChangedCallback, this);
954 #ifdef HAVE_TOUCHSCREENGUI
955 m_cache_hold_aux1 = false; // This is initialised properly later
962 /****************************************************************************
964 ****************************************************************************/
973 delete server; // deleted first to stop all server threads
981 delete nodedef_manager;
982 delete itemdef_manager;
985 clearTextureNameCache();
987 g_settings->deregisterChangedCallback("doubletap_jump",
988 &settingChangedCallback, this);
989 g_settings->deregisterChangedCallback("enable_clouds",
990 &settingChangedCallback, this);
991 g_settings->deregisterChangedCallback("enable_particles",
992 &settingChangedCallback, this);
993 g_settings->deregisterChangedCallback("enable_fog",
994 &settingChangedCallback, this);
995 g_settings->deregisterChangedCallback("mouse_sensitivity",
996 &settingChangedCallback, this);
997 g_settings->deregisterChangedCallback("repeat_place_time",
998 &settingChangedCallback, this);
999 g_settings->deregisterChangedCallback("noclip",
1000 &settingChangedCallback, this);
1001 g_settings->deregisterChangedCallback("free_move",
1002 &settingChangedCallback, this);
1003 g_settings->deregisterChangedCallback("cinematic",
1004 &settingChangedCallback, this);
1005 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
1006 &settingChangedCallback, this);
1007 g_settings->deregisterChangedCallback("camera_smoothing",
1008 &settingChangedCallback, this);
1011 bool Game::startup(bool *kill,
1012 InputHandler *input,
1013 RenderingEngine *rendering_engine,
1014 const GameStartData &start_data,
1015 std::string &error_message,
1017 ChatBackend *chat_backend)
1021 m_rendering_engine = rendering_engine;
1022 device = m_rendering_engine->get_raw_device();
1024 this->error_message = &error_message;
1025 reconnect_requested = reconnect;
1026 this->input = input;
1027 this->chat_backend = chat_backend;
1028 simple_singleplayer_mode = start_data.isSinglePlayer();
1030 input->keycache.populate();
1032 driver = device->getVideoDriver();
1033 smgr = m_rendering_engine->get_scene_manager();
1035 smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1038 runData = GameRunData();
1039 runData.time_from_last_punch = 10.0;
1041 m_game_ui->initFlags();
1043 m_invert_mouse = g_settings->getBool("invert_mouse");
1044 m_first_loop_after_window_activation = true;
1046 g_client_translations->clear();
1048 // address can change if simple_singleplayer_mode
1049 if (!init(start_data.world_spec.path, start_data.address,
1050 start_data.socket_port, start_data.game_spec))
1053 if (!createClient(start_data))
1056 m_rendering_engine->initialize(client, hud);
1064 ProfilerGraph graph;
1065 RunStats stats = {};
1066 CameraOrientation cam_view_target = {};
1067 CameraOrientation cam_view = {};
1068 FpsControl draw_times;
1069 f32 dtime; // in seconds
1071 /* Clear the profiler */
1072 Profiler::GraphValues dummyvalues;
1073 g_profiler->graphGet(dummyvalues);
1077 set_light_table(g_settings->getFloat("display_gamma"));
1079 #ifdef HAVE_TOUCHSCREENGUI
1080 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1081 && client->checkPrivilege("fast");
1084 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1085 g_settings->getU16("screen_h"));
1087 while (m_rendering_engine->run()
1088 && !(*kill || g_gamecallback->shutdown_requested
1089 || (server && server->isShutdownRequested()))) {
1091 const irr::core::dimension2d<u32> ¤t_screen_size =
1092 m_rendering_engine->get_video_driver()->getScreenSize();
1093 // Verify if window size has changed and save it if it's the case
1094 // Ensure evaluating settings->getBool after verifying screensize
1095 // First condition is cheaper
1096 if (previous_screen_size != current_screen_size &&
1097 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1098 g_settings->getBool("autosave_screensize")) {
1099 g_settings->setU16("screen_w", current_screen_size.Width);
1100 g_settings->setU16("screen_h", current_screen_size.Height);
1101 previous_screen_size = current_screen_size;
1104 // Calculate dtime =
1105 // m_rendering_engine->run() from this iteration
1106 // + Sleep time until the wanted FPS are reached
1107 draw_times.limit(device, &dtime);
1109 // Prepare render data for next iteration
1111 updateStats(&stats, draw_times, dtime);
1112 updateInteractTimers(dtime);
1114 if (!checkConnection())
1116 if (!handleCallbacks())
1121 m_game_ui->clearInfoText();
1122 hud->resizeHotbar();
1125 updateProfilers(stats, draw_times, dtime);
1126 processUserInput(dtime);
1127 // Update camera before player movement to avoid camera lag of one frame
1128 updateCameraDirection(&cam_view_target, dtime);
1129 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1130 cam_view.camera_yaw) * m_cache_cam_smoothing;
1131 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1132 cam_view.camera_pitch) * m_cache_cam_smoothing;
1133 updatePlayerControl(cam_view);
1135 processClientEvents(&cam_view_target);
1137 updateCamera(dtime);
1139 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
1140 updateFrame(&graph, &stats, dtime, cam_view);
1141 updateProfilerGraphs(&graph);
1143 // Update if minimap has been disabled by the server
1144 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1146 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1153 void Game::shutdown()
1155 m_rendering_engine->finalize();
1156 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1157 if (g_settings->get("3d_mode") == "pageflip") {
1158 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1161 auto formspec = m_game_ui->getFormspecGUI();
1163 formspec->quitMenu();
1165 #ifdef HAVE_TOUCHSCREENGUI
1166 g_touchscreengui->hide();
1169 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1174 if (gui_chat_console)
1175 gui_chat_console->drop();
1181 while (g_menumgr.menuCount() > 0) {
1182 g_menumgr.m_stack.front()->setVisible(false);
1183 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1186 m_game_ui->deleteFormspec();
1188 chat_backend->addMessage(L"", L"# Disconnected.");
1189 chat_backend->addMessage(L"", L"");
1190 m_chat_log_buf.clear();
1194 while (!client->isShutdown()) {
1195 assert(texture_src != NULL);
1196 assert(shader_src != NULL);
1197 texture_src->processQueue();
1198 shader_src->processQueue();
1205 /****************************************************************************/
1206 /****************************************************************************
1208 ****************************************************************************/
1209 /****************************************************************************/
1212 const std::string &map_dir,
1213 const std::string &address,
1215 const SubgameSpec &gamespec)
1217 texture_src = createTextureSource();
1219 showOverlayMessage(N_("Loading..."), 0, 0);
1221 shader_src = createShaderSource();
1223 itemdef_manager = createItemDefManager();
1224 nodedef_manager = createNodeDefManager();
1226 eventmgr = new EventManager();
1227 quicktune = new QuicktuneShortcutter();
1229 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1230 && eventmgr && quicktune))
1236 // Create a server if not connecting to an existing one
1237 if (address.empty()) {
1238 if (!createSingleplayerServer(map_dir, gamespec, port))
1245 bool Game::initSound()
1248 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1249 infostream << "Attempting to use OpenAL audio" << std::endl;
1250 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1252 infostream << "Failed to initialize OpenAL audio" << std::endl;
1254 infostream << "Sound disabled." << std::endl;
1258 infostream << "Using dummy audio." << std::endl;
1259 sound = &dummySoundManager;
1260 sound_is_dummy = true;
1263 soundmaker = new SoundMaker(sound, nodedef_manager);
1267 soundmaker->registerReceiver(eventmgr);
1272 bool Game::createSingleplayerServer(const std::string &map_dir,
1273 const SubgameSpec &gamespec, u16 port)
1275 showOverlayMessage(N_("Creating server..."), 0, 5);
1277 std::string bind_str = g_settings->get("bind_address");
1278 Address bind_addr(0, 0, 0, 0, port);
1280 if (g_settings->getBool("ipv6_server")) {
1281 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1285 bind_addr.Resolve(bind_str.c_str());
1286 } catch (ResolveError &e) {
1287 infostream << "Resolving bind address \"" << bind_str
1288 << "\" failed: " << e.what()
1289 << " -- Listening on all addresses." << std::endl;
1292 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1293 *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
1294 bind_addr.serializeString().c_str());
1295 errorstream << *error_message << std::endl;
1299 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1300 false, nullptr, error_message);
1306 bool Game::createClient(const GameStartData &start_data)
1308 showOverlayMessage(N_("Creating client..."), 0, 10);
1310 draw_control = new MapDrawControl();
1314 bool could_connect, connect_aborted;
1315 #ifdef HAVE_TOUCHSCREENGUI
1316 if (g_touchscreengui) {
1317 g_touchscreengui->init(texture_src);
1318 g_touchscreengui->hide();
1321 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1324 if (!could_connect) {
1325 if (error_message->empty() && !connect_aborted) {
1326 // Should not happen if error messages are set properly
1327 *error_message = gettext("Connection failed for unknown reason");
1328 errorstream << *error_message << std::endl;
1333 if (!getServerContent(&connect_aborted)) {
1334 if (error_message->empty() && !connect_aborted) {
1335 // Should not happen if error messages are set properly
1336 *error_message = gettext("Connection failed for unknown reason");
1337 errorstream << *error_message << std::endl;
1342 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1343 &m_flags.force_fog_off, &runData.fog_range, client);
1344 shader_src->addShaderConstantSetterFactory(scsf);
1346 // Update cached textures, meshes and materials
1347 client->afterContentReceived();
1351 camera = new Camera(*draw_control, client, m_rendering_engine);
1352 if (client->modsLoaded())
1353 client->getScript()->on_camera_ready(camera);
1354 client->setCamera(camera);
1358 if (m_cache_enable_clouds)
1359 clouds = new Clouds(smgr, -1, time(0));
1363 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
1365 skybox = NULL; // This is used/set later on in the main run loop
1367 /* Pre-calculated values
1369 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1371 v2u32 size = t->getOriginalSize();
1372 crack_animation_length = size.Y / size.X;
1374 crack_animation_length = 5;
1380 /* Set window caption
1382 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1384 str += utf8_to_wide(g_version_hash);
1386 const wchar_t *text = nullptr;
1387 if (simple_singleplayer_mode)
1388 text = wgettext("Singleplayer");
1390 text = wgettext("Multiplayer");
1397 str += driver->getName();
1400 device->setWindowCaption(str.c_str());
1402 LocalPlayer *player = client->getEnv().getLocalPlayer();
1403 player->hurt_tilt_timer = 0;
1404 player->hurt_tilt_strength = 0;
1406 hud = new Hud(client, player, &player->inventory);
1408 mapper = client->getMinimap();
1410 if (mapper && client->modsLoaded())
1411 client->getScript()->on_minimap_ready(mapper);
1416 bool Game::initGui()
1420 // Remove stale "recent" chat messages from previous connections
1421 chat_backend->clearRecentChat();
1423 // Make sure the size of the recent messages buffer is right
1424 chat_backend->applySettings();
1426 // Chat backend and console
1427 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1428 -1, chat_backend, client, &g_menumgr);
1430 #ifdef HAVE_TOUCHSCREENGUI
1432 if (g_touchscreengui)
1433 g_touchscreengui->show();
1440 bool Game::connectToServer(const GameStartData &start_data,
1441 bool *connect_ok, bool *connection_aborted)
1443 *connect_ok = false; // Let's not be overly optimistic
1444 *connection_aborted = false;
1445 bool local_server_mode = false;
1447 showOverlayMessage(N_("Resolving address..."), 0, 15);
1449 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1452 connect_address.Resolve(start_data.address.c_str());
1454 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1455 if (connect_address.isIPv6()) {
1456 IPv6AddressBytes addr_bytes;
1457 addr_bytes.bytes[15] = 1;
1458 connect_address.setAddress(&addr_bytes);
1460 connect_address.setAddress(127, 0, 0, 1);
1462 local_server_mode = true;
1464 } catch (ResolveError &e) {
1465 *error_message = fmtgettext("Couldn't resolve address: %s", e.what());
1467 errorstream << *error_message << std::endl;
1471 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1472 *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
1473 errorstream << *error_message << std::endl;
1478 client = new Client(start_data.name.c_str(),
1479 start_data.password, start_data.address,
1480 *draw_control, texture_src, shader_src,
1481 itemdef_manager, nodedef_manager, sound, eventmgr,
1482 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get(),
1483 start_data.allow_login_or_register);
1484 client->migrateModStorage();
1485 } catch (const BaseException &e) {
1486 *error_message = fmtgettext("Error creating client: %s", e.what());
1487 errorstream << *error_message << std::endl;
1491 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1493 infostream << "Connecting to server at ";
1494 connect_address.print(infostream);
1495 infostream << std::endl;
1497 client->connect(connect_address,
1498 simple_singleplayer_mode || local_server_mode);
1501 Wait for server to accept connection
1507 FpsControl fps_control;
1509 f32 wait_time = 0; // in seconds
1511 fps_control.reset();
1513 while (m_rendering_engine->run()) {
1515 fps_control.limit(device, &dtime);
1517 // Update client and server
1518 client->step(dtime);
1521 server->step(dtime);
1524 if (client->getState() == LC_Init) {
1530 if (*connection_aborted)
1533 if (client->accessDenied()) {
1534 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1535 *reconnect_requested = client->reconnectRequested();
1536 errorstream << *error_message << std::endl;
1540 if (input->cancelPressed()) {
1541 *connection_aborted = true;
1542 infostream << "Connect aborted [Escape]" << std::endl;
1547 // Only time out if we aren't waiting for the server we started
1548 if (!start_data.address.empty() && wait_time > 10) {
1549 *error_message = gettext("Connection timed out.");
1550 errorstream << *error_message << std::endl;
1555 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1557 } catch (con::PeerNotFoundException &e) {
1558 // TODO: Should something be done here? At least an info/error
1566 bool Game::getServerContent(bool *aborted)
1570 FpsControl fps_control;
1571 f32 dtime; // in seconds
1573 fps_control.reset();
1575 while (m_rendering_engine->run()) {
1577 fps_control.limit(device, &dtime);
1579 // Update client and server
1580 client->step(dtime);
1583 server->step(dtime);
1586 if (client->mediaReceived() && client->itemdefReceived() &&
1587 client->nodedefReceived()) {
1592 if (!checkConnection())
1595 if (client->getState() < LC_Init) {
1596 *error_message = gettext("Client disconnected");
1597 errorstream << *error_message << std::endl;
1601 if (input->cancelPressed()) {
1603 infostream << "Connect aborted [Escape]" << std::endl;
1610 if (!client->itemdefReceived()) {
1611 const wchar_t *text = wgettext("Item definitions...");
1613 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1616 } else if (!client->nodedefReceived()) {
1617 const wchar_t *text = wgettext("Node definitions...");
1619 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1623 std::ostringstream message;
1624 std::fixed(message);
1625 message.precision(0);
1626 float receive = client->mediaReceiveProgress() * 100;
1627 message << gettext("Media...");
1629 message << " " << receive << "%";
1630 message.precision(2);
1632 if ((USE_CURL == 0) ||
1633 (!g_settings->getBool("enable_remote_media_server"))) {
1634 float cur = client->getCurRate();
1635 std::string cur_unit = gettext("KiB/s");
1639 cur_unit = gettext("MiB/s");
1642 message << " (" << cur << ' ' << cur_unit << ")";
1645 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1646 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
1647 texture_src, dtime, progress);
1655 /****************************************************************************/
1656 /****************************************************************************
1658 ****************************************************************************/
1659 /****************************************************************************/
1661 inline void Game::updateInteractTimers(f32 dtime)
1663 if (runData.nodig_delay_timer >= 0)
1664 runData.nodig_delay_timer -= dtime;
1666 if (runData.object_hit_delay_timer >= 0)
1667 runData.object_hit_delay_timer -= dtime;
1669 runData.time_from_last_punch += dtime;
1673 /* returns false if game should exit, otherwise true
1675 inline bool Game::checkConnection()
1677 if (client->accessDenied()) {
1678 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1679 *reconnect_requested = client->reconnectRequested();
1680 errorstream << *error_message << std::endl;
1688 /* returns false if game should exit, otherwise true
1690 inline bool Game::handleCallbacks()
1692 if (g_gamecallback->disconnect_requested) {
1693 g_gamecallback->disconnect_requested = false;
1697 if (g_gamecallback->changepassword_requested) {
1698 (new GUIPasswordChange(guienv, guiroot, -1,
1699 &g_menumgr, client, texture_src))->drop();
1700 g_gamecallback->changepassword_requested = false;
1703 if (g_gamecallback->changevolume_requested) {
1704 (new GUIVolumeChange(guienv, guiroot, -1,
1705 &g_menumgr, texture_src))->drop();
1706 g_gamecallback->changevolume_requested = false;
1709 if (g_gamecallback->keyconfig_requested) {
1710 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1711 &g_menumgr, texture_src))->drop();
1712 g_gamecallback->keyconfig_requested = false;
1715 if (g_gamecallback->keyconfig_changed) {
1716 input->keycache.populate(); // update the cache with new settings
1717 g_gamecallback->keyconfig_changed = false;
1724 void Game::processQueues()
1726 texture_src->processQueue();
1727 itemdef_manager->processQueue(client);
1728 shader_src->processQueue();
1731 void Game::updateDebugState()
1733 LocalPlayer *player = client->getEnv().getLocalPlayer();
1735 // debug UI and wireframe
1736 bool has_debug = client->checkPrivilege("debug");
1737 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
1739 if (m_game_ui->m_flags.show_basic_debug) {
1740 if (!has_basic_debug)
1741 m_game_ui->m_flags.show_basic_debug = false;
1742 } else if (m_game_ui->m_flags.show_minimal_debug) {
1743 if (has_basic_debug)
1744 m_game_ui->m_flags.show_basic_debug = true;
1746 if (!has_basic_debug)
1747 hud->disableBlockBounds();
1749 draw_control->show_wireframe = false;
1752 draw_control->allow_noclip = m_cache_enable_noclip && client->checkPrivilege("noclip");
1755 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1758 float profiler_print_interval =
1759 g_settings->getFloat("profiler_print_interval");
1760 bool print_to_log = true;
1762 if (profiler_print_interval == 0) {
1763 print_to_log = false;
1764 profiler_print_interval = 3;
1767 if (profiler_interval.step(dtime, profiler_print_interval)) {
1769 infostream << "Profiler:" << std::endl;
1770 g_profiler->print(infostream);
1773 m_game_ui->updateProfiler();
1774 g_profiler->clear();
1777 // Update update graphs
1778 g_profiler->graphAdd("Time non-rendering [us]",
1779 draw_times.busy_time - stats.drawtime);
1781 g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
1782 g_profiler->graphAdd("FPS", 1.0f / dtime);
1785 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1792 /* Time average and jitter calculation
1794 jp = &stats->dtime_jitter;
1795 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1797 jitter = dtime - jp->avg;
1799 if (jitter > jp->max)
1802 jp->counter += dtime;
1804 if (jp->counter > 0.0) {
1806 jp->max_sample = jp->max;
1807 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1811 /* Busytime average and jitter calculation
1813 jp = &stats->busy_time_jitter;
1814 jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
1816 jitter = draw_times.getBusyMs() - jp->avg;
1818 if (jitter > jp->max)
1820 if (jitter < jp->min)
1823 jp->counter += dtime;
1825 if (jp->counter > 0.0) {
1827 jp->max_sample = jp->max;
1828 jp->min_sample = jp->min;
1836 /****************************************************************************
1838 ****************************************************************************/
1840 void Game::processUserInput(f32 dtime)
1842 // Reset input if window not active or some menu is active
1843 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1845 #ifdef HAVE_TOUCHSCREENGUI
1846 g_touchscreengui->hide();
1849 #ifdef HAVE_TOUCHSCREENGUI
1850 else if (g_touchscreengui) {
1851 /* on touchscreengui step may generate own input events which ain't
1852 * what we want in case we just did clear them */
1853 g_touchscreengui->show();
1854 g_touchscreengui->step(dtime);
1858 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1859 gui_chat_console->closeConsoleAtOnce();
1862 // Input handler step() (used by the random input generator)
1866 auto formspec = m_game_ui->getFormspecGUI();
1868 formspec->getAndroidUIInput();
1870 handleAndroidChatInput();
1873 // Increase timer for double tap of "keymap_jump"
1874 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1875 runData.jump_timer += dtime;
1878 processItemSelection(&runData.new_playeritem);
1882 void Game::processKeyInput()
1884 if (wasKeyDown(KeyType::DROP)) {
1885 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1886 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1887 toggleAutoforward();
1888 } else if (wasKeyDown(KeyType::BACKWARD)) {
1889 if (g_settings->getBool("continuous_forward"))
1890 toggleAutoforward();
1891 } else if (wasKeyDown(KeyType::INVENTORY)) {
1893 } else if (input->cancelPressed()) {
1895 m_android_chat_open = false;
1897 if (!gui_chat_console->isOpenInhibited()) {
1900 } else if (wasKeyDown(KeyType::CHAT)) {
1901 openConsole(0.2, L"");
1902 } else if (wasKeyDown(KeyType::CMD)) {
1903 openConsole(0.2, L"/");
1904 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1905 if (client->modsLoaded())
1906 openConsole(0.2, L".");
1908 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1909 } else if (wasKeyDown(KeyType::CONSOLE)) {
1910 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1911 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1913 } else if (wasKeyDown(KeyType::JUMP)) {
1914 toggleFreeMoveAlt();
1915 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1917 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1919 } else if (wasKeyDown(KeyType::NOCLIP)) {
1922 } else if (wasKeyDown(KeyType::MUTE)) {
1923 if (g_settings->getBool("enable_sound")) {
1924 bool new_mute_sound = !g_settings->getBool("mute_sound");
1925 g_settings->setBool("mute_sound", new_mute_sound);
1927 m_game_ui->showTranslatedStatusText("Sound muted");
1929 m_game_ui->showTranslatedStatusText("Sound unmuted");
1931 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1933 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1934 if (g_settings->getBool("enable_sound")) {
1935 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1936 g_settings->setFloat("sound_volume", new_volume);
1937 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1938 m_game_ui->showStatusText(msg);
1940 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1942 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1943 if (g_settings->getBool("enable_sound")) {
1944 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1945 g_settings->setFloat("sound_volume", new_volume);
1946 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1947 m_game_ui->showStatusText(msg);
1949 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1952 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1953 || wasKeyDown(KeyType::DEC_VOLUME)) {
1954 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1956 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1958 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1959 client->makeScreenshot();
1960 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
1961 toggleBlockBounds();
1962 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1963 m_game_ui->toggleHud();
1964 } else if (wasKeyDown(KeyType::MINIMAP)) {
1965 toggleMinimap(isKeyDown(KeyType::SNEAK));
1966 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1967 m_game_ui->toggleChat();
1968 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1970 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1971 toggleUpdateCamera();
1972 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1974 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1975 m_game_ui->toggleProfiler();
1976 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1977 increaseViewRange();
1978 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1979 decreaseViewRange();
1980 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1981 toggleFullViewRange();
1982 } else if (wasKeyDown(KeyType::ZOOM)) {
1984 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1986 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1988 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1990 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1994 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1995 runData.reset_jump_timer = false;
1996 runData.jump_timer = 0.0f;
1999 if (quicktune->hasMessage()) {
2000 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2004 void Game::processItemSelection(u16 *new_playeritem)
2006 LocalPlayer *player = client->getEnv().getLocalPlayer();
2008 /* Item selection using mouse wheel
2010 *new_playeritem = player->getWieldIndex();
2012 s32 wheel = input->getMouseWheel();
2013 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2014 player->hud_hotbar_itemcount - 1);
2018 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2021 if (wasKeyDown(KeyType::HOTBAR_PREV))
2025 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2027 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2030 /* Item selection using hotbar slot keys
2032 for (u16 i = 0; i <= max_item; i++) {
2033 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2034 *new_playeritem = i;
2041 void Game::dropSelectedItem(bool single_item)
2043 IDropAction *a = new IDropAction();
2044 a->count = single_item ? 1 : 0;
2045 a->from_inv.setCurrentPlayer();
2046 a->from_list = "main";
2047 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2048 client->inventoryAction(a);
2052 void Game::openInventory()
2055 * Don't permit to open inventory is CAO or player doesn't exists.
2056 * This prevent showing an empty inventory at player load
2059 LocalPlayer *player = client->getEnv().getLocalPlayer();
2060 if (!player || !player->getCAO())
2063 infostream << "Game: Launching inventory" << std::endl;
2065 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2067 InventoryLocation inventoryloc;
2068 inventoryloc.setCurrentPlayer();
2070 if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2075 if (fs_src->getForm().empty()) {
2080 TextDest *txt_dst = new TextDestPlayerInventory(client);
2081 auto *&formspec = m_game_ui->updateFormspec("");
2082 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2083 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2085 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2089 void Game::openConsole(float scale, const wchar_t *line)
2091 assert(scale > 0.0f && scale <= 1.0f);
2094 porting::showInputDialog(gettext("ok"), "", "", 2);
2095 m_android_chat_open = true;
2097 if (gui_chat_console->isOpenInhibited())
2099 gui_chat_console->openConsole(scale);
2101 gui_chat_console->setCloseOnEnter(true);
2102 gui_chat_console->replaceAndAddToHistory(line);
2108 void Game::handleAndroidChatInput()
2110 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2111 std::string text = porting::getInputDialogValue();
2112 client->typeChatMessage(utf8_to_wide(text));
2113 m_android_chat_open = false;
2119 void Game::toggleFreeMove()
2121 bool free_move = !g_settings->getBool("free_move");
2122 g_settings->set("free_move", bool_to_cstr(free_move));
2125 if (client->checkPrivilege("fly")) {
2126 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2128 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2131 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2135 void Game::toggleFreeMoveAlt()
2137 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2140 runData.reset_jump_timer = true;
2144 void Game::togglePitchMove()
2146 bool pitch_move = !g_settings->getBool("pitch_move");
2147 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2150 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2152 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2157 void Game::toggleFast()
2159 bool fast_move = !g_settings->getBool("fast_move");
2160 bool has_fast_privs = client->checkPrivilege("fast");
2161 g_settings->set("fast_move", bool_to_cstr(fast_move));
2164 if (has_fast_privs) {
2165 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2167 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2170 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2173 #ifdef HAVE_TOUCHSCREENGUI
2174 m_cache_hold_aux1 = fast_move && has_fast_privs;
2179 void Game::toggleNoClip()
2181 bool noclip = !g_settings->getBool("noclip");
2182 g_settings->set("noclip", bool_to_cstr(noclip));
2185 if (client->checkPrivilege("noclip")) {
2186 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2188 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2191 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2195 void Game::toggleCinematic()
2197 bool cinematic = !g_settings->getBool("cinematic");
2198 g_settings->set("cinematic", bool_to_cstr(cinematic));
2201 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2203 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2206 void Game::toggleBlockBounds()
2208 LocalPlayer *player = client->getEnv().getLocalPlayer();
2209 if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
2210 m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
2213 enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
2215 case Hud::BLOCK_BOUNDS_OFF:
2216 m_game_ui->showTranslatedStatusText("Block bounds hidden");
2218 case Hud::BLOCK_BOUNDS_CURRENT:
2219 m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
2221 case Hud::BLOCK_BOUNDS_NEAR:
2222 m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
2224 case Hud::BLOCK_BOUNDS_MAX:
2225 m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
2232 // Autoforward by toggling continuous forward.
2233 void Game::toggleAutoforward()
2235 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2236 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2238 if (autorun_enabled)
2239 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2241 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2244 void Game::toggleMinimap(bool shift_pressed)
2246 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2250 mapper->toggleMinimapShape();
2254 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2256 // Not so satisying code to keep compatibility with old fixed mode system
2258 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2260 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2261 m_game_ui->m_flags.show_minimap = false;
2264 // If radar is disabled, try to find a non radar mode or fall back to 0
2265 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2266 while (mapper->getModeIndex() &&
2267 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2270 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2274 // End of 'not so satifying code'
2275 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2276 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2277 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2279 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2282 void Game::toggleFog()
2284 bool fog_enabled = g_settings->getBool("enable_fog");
2285 g_settings->setBool("enable_fog", !fog_enabled);
2287 m_game_ui->showTranslatedStatusText("Fog disabled");
2289 m_game_ui->showTranslatedStatusText("Fog enabled");
2293 void Game::toggleDebug()
2295 LocalPlayer *player = client->getEnv().getLocalPlayer();
2296 bool has_debug = client->checkPrivilege("debug");
2297 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
2298 // Initial: No debug info
2299 // 1x toggle: Debug text
2300 // 2x toggle: Debug text with profiler graph
2301 // 3x toggle: Debug text and wireframe (needs "debug" priv)
2302 // Next toggle: Back to initial
2304 // The debug text can be in 2 modes: minimal and basic.
2305 // * Minimal: Only technical client info that not gameplay-relevant
2306 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
2307 // Basic mode is used when player has the debug HUD flag set,
2308 // otherwise the Minimal mode is used.
2309 if (!m_game_ui->m_flags.show_minimal_debug) {
2310 m_game_ui->m_flags.show_minimal_debug = true;
2311 if (has_basic_debug)
2312 m_game_ui->m_flags.show_basic_debug = true;
2313 m_game_ui->m_flags.show_profiler_graph = false;
2314 draw_control->show_wireframe = false;
2315 m_game_ui->showTranslatedStatusText("Debug info shown");
2316 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2317 if (has_basic_debug)
2318 m_game_ui->m_flags.show_basic_debug = true;
2319 m_game_ui->m_flags.show_profiler_graph = true;
2320 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2321 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2322 if (has_basic_debug)
2323 m_game_ui->m_flags.show_basic_debug = true;
2324 m_game_ui->m_flags.show_profiler_graph = false;
2325 draw_control->show_wireframe = true;
2326 m_game_ui->showTranslatedStatusText("Wireframe shown");
2328 m_game_ui->m_flags.show_minimal_debug = false;
2329 m_game_ui->m_flags.show_basic_debug = false;
2330 m_game_ui->m_flags.show_profiler_graph = false;
2331 draw_control->show_wireframe = false;
2333 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2335 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2341 void Game::toggleUpdateCamera()
2343 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2344 if (m_flags.disable_camera_update)
2345 m_game_ui->showTranslatedStatusText("Camera update disabled");
2347 m_game_ui->showTranslatedStatusText("Camera update enabled");
2351 void Game::increaseViewRange()
2353 s16 range = g_settings->getS16("viewing_range");
2354 s16 range_new = range + 10;
2356 if (range_new > 4000) {
2358 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
2359 m_game_ui->showStatusText(msg);
2361 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2362 m_game_ui->showStatusText(msg);
2364 g_settings->set("viewing_range", itos(range_new));
2368 void Game::decreaseViewRange()
2370 s16 range = g_settings->getS16("viewing_range");
2371 s16 range_new = range - 10;
2373 if (range_new < 20) {
2375 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
2376 m_game_ui->showStatusText(msg);
2378 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2379 m_game_ui->showStatusText(msg);
2381 g_settings->set("viewing_range", itos(range_new));
2385 void Game::toggleFullViewRange()
2387 draw_control->range_all = !draw_control->range_all;
2388 if (draw_control->range_all)
2389 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2391 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2395 void Game::checkZoomEnabled()
2397 LocalPlayer *player = client->getEnv().getLocalPlayer();
2398 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2399 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2402 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2404 if ((device->isWindowActive() && device->isWindowFocused()
2405 && !isMenuActive()) || input->isRandom()) {
2408 if (!input->isRandom()) {
2409 // Mac OSX gets upset if this is set every frame
2410 if (device->getCursorControl()->isVisible())
2411 device->getCursorControl()->setVisible(false);
2415 if (m_first_loop_after_window_activation) {
2416 m_first_loop_after_window_activation = false;
2418 input->setMousePos(driver->getScreenSize().Width / 2,
2419 driver->getScreenSize().Height / 2);
2421 updateCameraOrientation(cam, dtime);
2427 // Mac OSX gets upset if this is set every frame
2428 if (!device->getCursorControl()->isVisible())
2429 device->getCursorControl()->setVisible(true);
2432 m_first_loop_after_window_activation = true;
2437 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2438 // responsiveness independently of FOV.
2439 f32 Game::getSensitivityScaleFactor() const
2441 f32 fov_y = client->getCamera()->getFovY();
2443 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2444 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2446 return tan(fov_y / 2.0f) * 1.3763818698f;
2449 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2451 #ifdef HAVE_TOUCHSCREENGUI
2452 if (g_touchscreengui) {
2453 cam->camera_yaw += g_touchscreengui->getYawChange();
2454 cam->camera_pitch = g_touchscreengui->getPitch();
2457 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2458 v2s32 dist = input->getMousePos() - center;
2460 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2464 f32 sens_scale = getSensitivityScaleFactor();
2465 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2466 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2468 if (dist.X != 0 || dist.Y != 0)
2469 input->setMousePos(center.X, center.Y);
2470 #ifdef HAVE_TOUCHSCREENGUI
2474 if (m_cache_enable_joysticks) {
2475 f32 sens_scale = getSensitivityScaleFactor();
2476 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
2477 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2478 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2481 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2485 void Game::updatePlayerControl(const CameraOrientation &cam)
2487 LocalPlayer *player = client->getEnv().getLocalPlayer();
2489 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2491 PlayerControl control(
2492 isKeyDown(KeyType::FORWARD),
2493 isKeyDown(KeyType::BACKWARD),
2494 isKeyDown(KeyType::LEFT),
2495 isKeyDown(KeyType::RIGHT),
2496 isKeyDown(KeyType::JUMP) || player->getAutojump(),
2497 isKeyDown(KeyType::AUX1),
2498 isKeyDown(KeyType::SNEAK),
2499 isKeyDown(KeyType::ZOOM),
2500 isKeyDown(KeyType::DIG),
2501 isKeyDown(KeyType::PLACE),
2504 input->getMovementSpeed(),
2505 input->getMovementDirection()
2508 // autoforward if set: move towards pointed position at maximum speed
2509 if (player->getPlayerSettings().continuous_forward &&
2510 client->activeObjectsReceived() && !player->isDead()) {
2511 control.movement_speed = 1.0f;
2512 control.movement_direction = 0.0f;
2515 #ifdef HAVE_TOUCHSCREENGUI
2516 /* For touch, simulate holding down AUX1 (fast move) if the user has
2517 * the fast_move setting toggled on. If there is an aux1 key defined for
2518 * touch then its meaning is inverted (i.e. holding aux1 means walk and
2521 if (m_cache_hold_aux1) {
2522 control.aux1 = control.aux1 ^ true;
2526 client->setPlayerControl(control);
2532 inline void Game::step(f32 *dtime)
2534 bool can_be_and_is_paused =
2535 (simple_singleplayer_mode && g_menumgr.pausesGame());
2537 if (can_be_and_is_paused) { // This is for a singleplayer server
2538 *dtime = 0; // No time passes
2540 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
2544 server->step(*dtime);
2546 client->step(*dtime);
2550 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2553 for (auto &&child: node->getChildren())
2554 pauseNodeAnimation(paused, child);
2555 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2557 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2558 float speed = animated_node->getAnimationSpeed();
2561 paused.push_back({grab(animated_node), speed});
2562 animated_node->setAnimationSpeed(0.0f);
2565 void Game::pauseAnimation()
2567 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2570 void Game::resumeAnimation()
2572 for (auto &&pair: paused_animated_nodes)
2573 pair.first->setAnimationSpeed(pair.second);
2574 paused_animated_nodes.clear();
2577 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2578 {&Game::handleClientEvent_None},
2579 {&Game::handleClientEvent_PlayerDamage},
2580 {&Game::handleClientEvent_PlayerForceMove},
2581 {&Game::handleClientEvent_Deathscreen},
2582 {&Game::handleClientEvent_ShowFormSpec},
2583 {&Game::handleClientEvent_ShowLocalFormSpec},
2584 {&Game::handleClientEvent_HandleParticleEvent},
2585 {&Game::handleClientEvent_HandleParticleEvent},
2586 {&Game::handleClientEvent_HandleParticleEvent},
2587 {&Game::handleClientEvent_HudAdd},
2588 {&Game::handleClientEvent_HudRemove},
2589 {&Game::handleClientEvent_HudChange},
2590 {&Game::handleClientEvent_SetSky},
2591 {&Game::handleClientEvent_SetSun},
2592 {&Game::handleClientEvent_SetMoon},
2593 {&Game::handleClientEvent_SetStars},
2594 {&Game::handleClientEvent_OverrideDayNigthRatio},
2595 {&Game::handleClientEvent_CloudParams},
2598 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2600 FATAL_ERROR("ClientEvent type None received");
2603 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2605 if (client->modsLoaded())
2606 client->getScript()->on_damage_taken(event->player_damage.amount);
2608 // Damage flash and hurt tilt are not used at death
2609 if (client->getHP() > 0) {
2610 LocalPlayer *player = client->getEnv().getLocalPlayer();
2612 f32 hp_max = player->getCAO() ?
2613 player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2614 f32 damage_ratio = event->player_damage.amount / hp_max;
2616 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2617 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2619 player->hurt_tilt_timer = 1.5f;
2620 player->hurt_tilt_strength =
2621 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2624 // Play damage sound
2625 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2628 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2630 cam->camera_yaw = event->player_force_move.yaw;
2631 cam->camera_pitch = event->player_force_move.pitch;
2634 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2636 // If client scripting is enabled, deathscreen is handled by CSM code in
2637 // builtin/client/init.lua
2638 if (client->modsLoaded())
2639 client->getScript()->on_death();
2641 showDeathFormspec();
2643 /* Handle visualization */
2644 LocalPlayer *player = client->getEnv().getLocalPlayer();
2645 runData.damage_flash = 0;
2646 player->hurt_tilt_timer = 0;
2647 player->hurt_tilt_strength = 0;
2650 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2652 if (event->show_formspec.formspec->empty()) {
2653 auto formspec = m_game_ui->getFormspecGUI();
2654 if (formspec && (event->show_formspec.formname->empty()
2655 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2656 formspec->quitMenu();
2659 FormspecFormSource *fs_src =
2660 new FormspecFormSource(*(event->show_formspec.formspec));
2661 TextDestPlayerInventory *txt_dst =
2662 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2664 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2665 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2666 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2669 delete event->show_formspec.formspec;
2670 delete event->show_formspec.formname;
2673 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2675 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2676 LocalFormspecHandler *txt_dst =
2677 new LocalFormspecHandler(*event->show_formspec.formname, client);
2678 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2679 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2681 delete event->show_formspec.formspec;
2682 delete event->show_formspec.formname;
2685 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2686 CameraOrientation *cam)
2688 LocalPlayer *player = client->getEnv().getLocalPlayer();
2689 client->getParticleManager()->handleParticleEvent(event, client, player);
2692 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2694 LocalPlayer *player = client->getEnv().getLocalPlayer();
2696 u32 server_id = event->hudadd->server_id;
2697 // ignore if we already have a HUD with that ID
2698 auto i = m_hud_server_to_client.find(server_id);
2699 if (i != m_hud_server_to_client.end()) {
2700 delete event->hudadd;
2704 HudElement *e = new HudElement;
2705 e->type = static_cast<HudElementType>(event->hudadd->type);
2706 e->pos = event->hudadd->pos;
2707 e->name = event->hudadd->name;
2708 e->scale = event->hudadd->scale;
2709 e->text = event->hudadd->text;
2710 e->number = event->hudadd->number;
2711 e->item = event->hudadd->item;
2712 e->dir = event->hudadd->dir;
2713 e->align = event->hudadd->align;
2714 e->offset = event->hudadd->offset;
2715 e->world_pos = event->hudadd->world_pos;
2716 e->size = event->hudadd->size;
2717 e->z_index = event->hudadd->z_index;
2718 e->text2 = event->hudadd->text2;
2719 e->style = event->hudadd->style;
2720 m_hud_server_to_client[server_id] = player->addHud(e);
2722 delete event->hudadd;
2725 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2727 LocalPlayer *player = client->getEnv().getLocalPlayer();
2729 auto i = m_hud_server_to_client.find(event->hudrm.id);
2730 if (i != m_hud_server_to_client.end()) {
2731 HudElement *e = player->removeHud(i->second);
2733 m_hud_server_to_client.erase(i);
2738 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2740 LocalPlayer *player = client->getEnv().getLocalPlayer();
2742 HudElement *e = nullptr;
2744 auto i = m_hud_server_to_client.find(event->hudchange->id);
2745 if (i != m_hud_server_to_client.end()) {
2746 e = player->getHud(i->second);
2750 delete event->hudchange;
2754 #define CASE_SET(statval, prop, dataprop) \
2756 e->prop = event->hudchange->dataprop; \
2759 switch (event->hudchange->stat) {
2760 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2762 CASE_SET(HUD_STAT_NAME, name, sdata);
2764 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2766 CASE_SET(HUD_STAT_TEXT, text, sdata);
2768 CASE_SET(HUD_STAT_NUMBER, number, data);
2770 CASE_SET(HUD_STAT_ITEM, item, data);
2772 CASE_SET(HUD_STAT_DIR, dir, data);
2774 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2776 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2778 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2780 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2782 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2784 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2786 CASE_SET(HUD_STAT_STYLE, style, data);
2791 delete event->hudchange;
2794 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2796 sky->setVisible(false);
2797 // Whether clouds are visible in front of a custom skybox.
2798 sky->setCloudsEnabled(event->set_sky->clouds);
2804 // Clear the old textures out in case we switch rendering type.
2805 sky->clearSkyboxTextures();
2806 // Handle according to type
2807 if (event->set_sky->type == "regular") {
2808 // Shows the mesh skybox
2809 sky->setVisible(true);
2810 // Update mesh based skybox colours if applicable.
2811 sky->setSkyColors(event->set_sky->sky_color);
2812 sky->setHorizonTint(
2813 event->set_sky->fog_sun_tint,
2814 event->set_sky->fog_moon_tint,
2815 event->set_sky->fog_tint_type
2817 } else if (event->set_sky->type == "skybox" &&
2818 event->set_sky->textures.size() == 6) {
2819 // Disable the dyanmic mesh skybox:
2820 sky->setVisible(false);
2822 sky->setFallbackBgColor(event->set_sky->bgcolor);
2823 // Set sunrise and sunset fog tinting:
2824 sky->setHorizonTint(
2825 event->set_sky->fog_sun_tint,
2826 event->set_sky->fog_moon_tint,
2827 event->set_sky->fog_tint_type
2829 // Add textures to skybox.
2830 for (int i = 0; i < 6; i++)
2831 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2833 // Handle everything else as plain color.
2834 if (event->set_sky->type != "plain")
2835 infostream << "Unknown sky type: "
2836 << (event->set_sky->type) << std::endl;
2837 sky->setVisible(false);
2838 sky->setFallbackBgColor(event->set_sky->bgcolor);
2839 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2840 sky->setHorizonTint(
2841 event->set_sky->bgcolor,
2842 event->set_sky->bgcolor,
2847 delete event->set_sky;
2850 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2852 sky->setSunVisible(event->sun_params->visible);
2853 sky->setSunTexture(event->sun_params->texture,
2854 event->sun_params->tonemap, texture_src);
2855 sky->setSunScale(event->sun_params->scale);
2856 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2857 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2858 delete event->sun_params;
2861 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2863 sky->setMoonVisible(event->moon_params->visible);
2864 sky->setMoonTexture(event->moon_params->texture,
2865 event->moon_params->tonemap, texture_src);
2866 sky->setMoonScale(event->moon_params->scale);
2867 delete event->moon_params;
2870 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2872 sky->setStarsVisible(event->star_params->visible);
2873 sky->setStarCount(event->star_params->count);
2874 sky->setStarColor(event->star_params->starcolor);
2875 sky->setStarScale(event->star_params->scale);
2876 delete event->star_params;
2879 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2880 CameraOrientation *cam)
2882 client->getEnv().setDayNightRatioOverride(
2883 event->override_day_night_ratio.do_override,
2884 event->override_day_night_ratio.ratio_f * 1000.0f);
2887 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2892 clouds->setDensity(event->cloud_params.density);
2893 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2894 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2895 clouds->setHeight(event->cloud_params.height);
2896 clouds->setThickness(event->cloud_params.thickness);
2897 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2900 void Game::processClientEvents(CameraOrientation *cam)
2902 while (client->hasClientEvents()) {
2903 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2904 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2905 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2906 (this->*evHandler.handler)(event.get(), cam);
2910 void Game::updateChat(f32 dtime)
2912 // Get new messages from error log buffer
2913 while (!m_chat_log_buf.empty())
2914 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2916 // Get new messages from client
2917 std::wstring message;
2918 while (client->getChatMessage(message)) {
2919 chat_backend->addUnparsedMessage(message);
2922 // Remove old messages
2923 chat_backend->step(dtime);
2925 // Display all messages in a static text element
2926 auto &buf = chat_backend->getRecentBuffer();
2927 if (buf.getLinesModified()) {
2928 buf.resetLinesModified();
2929 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
2932 // Make sure that the size is still correct
2933 m_game_ui->updateChatSize();
2936 void Game::updateCamera(f32 dtime)
2938 LocalPlayer *player = client->getEnv().getLocalPlayer();
2941 For interaction purposes, get info about the held item
2943 - Is it a usable item?
2944 - Can it point to liquids?
2946 ItemStack playeritem;
2948 ItemStack selected, hand;
2949 playeritem = player->getWieldedItem(&selected, &hand);
2952 ToolCapabilities playeritem_toolcap =
2953 playeritem.getToolCapabilities(itemdef_manager);
2955 v3s16 old_camera_offset = camera->getOffset();
2957 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2958 GenericCAO *playercao = player->getCAO();
2960 // If playercao not loaded, don't change camera
2964 camera->toggleCameraMode();
2966 // Make the player visible depending on camera mode.
2967 playercao->updateMeshCulling();
2968 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2971 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2972 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2974 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2975 camera->update(player, dtime, tool_reload_ratio);
2976 camera->step(dtime);
2978 v3f camera_position = camera->getPosition();
2979 v3f camera_direction = camera->getDirection();
2980 f32 camera_fov = camera->getFovMax();
2981 v3s16 camera_offset = camera->getOffset();
2983 m_camera_offset_changed = (camera_offset != old_camera_offset);
2985 if (!m_flags.disable_camera_update) {
2986 client->getEnv().getClientMap().updateCamera(camera_position,
2987 camera_direction, camera_fov, camera_offset);
2989 if (m_camera_offset_changed) {
2990 client->updateCameraOffset(camera_offset);
2991 client->getEnv().updateCameraOffset(camera_offset);
2994 clouds->updateCameraOffset(camera_offset);
3000 void Game::updateSound(f32 dtime)
3002 // Update sound listener
3003 v3s16 camera_offset = camera->getOffset();
3004 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3005 v3f(0, 0, 0), // velocity
3006 camera->getDirection(),
3007 camera->getCameraNode()->getUpVector());
3009 bool mute_sound = g_settings->getBool("mute_sound");
3011 sound->setListenerGain(0.0f);
3013 // Check if volume is in the proper range, else fix it.
3014 float old_volume = g_settings->getFloat("sound_volume");
3015 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3016 sound->setListenerGain(new_volume);
3018 if (old_volume != new_volume) {
3019 g_settings->setFloat("sound_volume", new_volume);
3023 LocalPlayer *player = client->getEnv().getLocalPlayer();
3025 // Tell the sound maker whether to make footstep sounds
3026 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3028 // Update sound maker
3029 if (player->makes_footstep_sound)
3030 soundmaker->step(dtime);
3032 ClientMap &map = client->getEnv().getClientMap();
3033 MapNode n = map.getNode(player->getFootstepNodePos());
3034 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3038 void Game::processPlayerInteraction(f32 dtime, bool show_hud)
3040 LocalPlayer *player = client->getEnv().getLocalPlayer();
3042 const v3f camera_direction = camera->getDirection();
3043 const v3s16 camera_offset = camera->getOffset();
3046 Calculate what block is the crosshair pointing to
3049 ItemStack selected_item, hand_item;
3050 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3052 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3053 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3055 core::line3d<f32> shootline;
3057 switch (camera->getCameraMode()) {
3058 case CAMERA_MODE_FIRST:
3059 // Shoot from camera position, with bobbing
3060 shootline.start = camera->getPosition();
3062 case CAMERA_MODE_THIRD:
3063 // Shoot from player head, no bobbing
3064 shootline.start = camera->getHeadPosition();
3066 case CAMERA_MODE_THIRD_FRONT:
3067 shootline.start = camera->getHeadPosition();
3068 // prevent player pointing anything in front-view
3072 shootline.end = shootline.start + camera_direction * BS * d;
3074 #ifdef HAVE_TOUCHSCREENGUI
3076 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3077 shootline = g_touchscreengui->getShootline();
3078 // Scale shootline to the acual distance the player can reach
3079 shootline.end = shootline.start
3080 + shootline.getVector().normalize() * BS * d;
3081 shootline.start += intToFloat(camera_offset, BS);
3082 shootline.end += intToFloat(camera_offset, BS);
3087 PointedThing pointed = updatePointedThing(shootline,
3088 selected_def.liquids_pointable,
3089 !runData.btn_down_for_dig,
3092 if (pointed != runData.pointed_old)
3093 infostream << "Pointing at " << pointed.dump() << std::endl;
3095 // Note that updating the selection mesh every frame is not particularly efficient,
3096 // but the halo rendering code is already inefficient so there's no point in optimizing it here
3097 hud->updateSelectionMesh(camera_offset);
3099 // Allow digging again if button is not pressed
3100 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3101 runData.digging_blocked = false;
3105 - releasing dig button
3106 - pointing away from node
3108 if (runData.digging) {
3109 if (wasKeyReleased(KeyType::DIG)) {
3110 infostream << "Dig button released (stopped digging)" << std::endl;
3111 runData.digging = false;
3112 } else if (pointed != runData.pointed_old) {
3113 if (pointed.type == POINTEDTHING_NODE
3114 && runData.pointed_old.type == POINTEDTHING_NODE
3115 && pointed.node_undersurface
3116 == runData.pointed_old.node_undersurface) {
3117 // Still pointing to the same node, but a different face.
3120 infostream << "Pointing away from node (stopped digging)" << std::endl;
3121 runData.digging = false;
3122 hud->updateSelectionMesh(camera_offset);
3126 if (!runData.digging) {
3127 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3128 client->setCrack(-1, v3s16(0, 0, 0));
3129 runData.dig_time = 0.0;
3131 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3132 // Remove e.g. torches faster when clicking instead of holding dig button
3133 runData.nodig_delay_timer = 0;
3134 runData.dig_instantly = false;
3137 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3138 runData.btn_down_for_dig = false;
3140 runData.punching = false;
3142 soundmaker->m_player_leftpunch_sound.name = "";
3144 // Prepare for repeating, unless we're not supposed to
3145 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3146 runData.repeat_place_timer += dtime;
3148 runData.repeat_place_timer = 0;
3150 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3151 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3152 !client->getScript()->on_item_use(selected_item, pointed)))
3153 client->interact(INTERACT_USE, pointed);
3154 } else if (pointed.type == POINTEDTHING_NODE) {
3155 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3156 } else if (pointed.type == POINTEDTHING_OBJECT) {
3157 v3f player_position = player->getPosition();
3158 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
3159 handlePointingAtObject(pointed, tool_item, player_position,
3160 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
3161 } else if (isKeyDown(KeyType::DIG)) {
3162 // When button is held down in air, show continuous animation
3163 runData.punching = true;
3164 // Run callback even though item is not usable
3165 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3166 client->getScript()->on_item_use(selected_item, pointed);
3167 } else if (wasKeyPressed(KeyType::PLACE)) {
3168 handlePointingAtNothing(selected_item);
3171 runData.pointed_old = pointed;
3173 if (runData.punching || wasKeyPressed(KeyType::DIG))
3174 camera->setDigging(0); // dig animation
3176 input->clearWasKeyPressed();
3177 input->clearWasKeyReleased();
3178 // Ensure DIG & PLACE are marked as handled
3179 wasKeyDown(KeyType::DIG);
3180 wasKeyDown(KeyType::PLACE);
3182 input->joystick.clearWasKeyPressed(KeyType::DIG);
3183 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3185 input->joystick.clearWasKeyReleased(KeyType::DIG);
3186 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3190 PointedThing Game::updatePointedThing(
3191 const core::line3d<f32> &shootline,
3192 bool liquids_pointable,
3193 bool look_for_object,
3194 const v3s16 &camera_offset)
3196 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3197 selectionboxes->clear();
3198 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3199 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3200 "show_entity_selectionbox");
3202 ClientEnvironment &env = client->getEnv();
3203 ClientMap &map = env.getClientMap();
3204 const NodeDefManager *nodedef = map.getNodeDefManager();
3206 runData.selected_object = NULL;
3207 hud->pointing_at_object = false;
3209 RaycastState s(shootline, look_for_object, liquids_pointable);
3210 PointedThing result;
3211 env.continueRaycast(&s, &result);
3212 if (result.type == POINTEDTHING_OBJECT) {
3213 hud->pointing_at_object = true;
3215 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3216 aabb3f selection_box;
3217 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3218 runData.selected_object->getSelectionBox(&selection_box)) {
3219 v3f pos = runData.selected_object->getPosition();
3220 selectionboxes->push_back(aabb3f(selection_box));
3221 hud->setSelectionPos(pos, camera_offset);
3223 } else if (result.type == POINTEDTHING_NODE) {
3224 // Update selection boxes
3225 MapNode n = map.getNode(result.node_undersurface);
3226 std::vector<aabb3f> boxes;
3227 n.getSelectionBoxes(nodedef, &boxes,
3228 n.getNeighbors(result.node_undersurface, &map));
3231 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3232 i != boxes.end(); ++i) {
3234 box.MinEdge -= v3f(d, d, d);
3235 box.MaxEdge += v3f(d, d, d);
3236 selectionboxes->push_back(box);
3238 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3240 hud->setSelectedFaceNormal(v3f(
3241 result.intersection_normal.X,
3242 result.intersection_normal.Y,
3243 result.intersection_normal.Z));
3246 // Update selection mesh light level and vertex colors
3247 if (!selectionboxes->empty()) {
3248 v3f pf = hud->getSelectionPos();
3249 v3s16 p = floatToInt(pf, BS);
3251 // Get selection mesh light level
3252 MapNode n = map.getNode(p);
3253 u16 node_light = getInteriorLight(n, -1, nodedef);
3254 u16 light_level = node_light;
3256 for (const v3s16 &dir : g_6dirs) {
3257 n = map.getNode(p + dir);
3258 node_light = getInteriorLight(n, -1, nodedef);
3259 if (node_light > light_level)
3260 light_level = node_light;
3263 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3265 final_color_blend(&c, light_level, daynight_ratio);
3267 // Modify final color a bit with time
3268 u32 timer = porting::getTimeMs() % 5000;
3269 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3270 float sin_r = 0.08f * std::sin(timerf);
3271 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3272 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3273 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3274 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3275 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3277 // Set mesh final color
3278 hud->setSelectionMeshColor(c);
3284 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3286 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3287 PointedThing fauxPointed;
3288 fauxPointed.type = POINTEDTHING_NOTHING;
3289 client->interact(INTERACT_ACTIVATE, fauxPointed);
3293 void Game::handlePointingAtNode(const PointedThing &pointed,
3294 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3296 v3s16 nodepos = pointed.node_undersurface;
3297 v3s16 neighbourpos = pointed.node_abovesurface;
3300 Check information text of node
3303 ClientMap &map = client->getEnv().getClientMap();
3305 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3306 && !runData.digging_blocked
3307 && client->checkPrivilege("interact")) {
3308 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3311 // This should be done after digging handling
3312 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3315 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3316 meta->getString("infotext"))));
3318 MapNode n = map.getNode(nodepos);
3320 if (nodedef_manager->get(n).name == "unknown") {
3321 m_game_ui->setInfoText(L"Unknown node");
3325 if ((wasKeyPressed(KeyType::PLACE) ||
3326 runData.repeat_place_timer >= m_repeat_place_time) &&
3327 client->checkPrivilege("interact")) {
3328 runData.repeat_place_timer = 0;
3329 infostream << "Place button pressed while looking at ground" << std::endl;
3331 // Placing animation (always shown for feedback)
3332 camera->setDigging(1);
3334 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3336 // If the wielded item has node placement prediction,
3338 // And also set the sound and send the interact
3339 // But first check for meta formspec and rightclickable
3340 auto &def = selected_item.getDefinition(itemdef_manager);
3341 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3344 if (placed && client->modsLoaded())
3345 client->getScript()->on_placenode(pointed, def);
3349 bool Game::nodePlacement(const ItemDefinition &selected_def,
3350 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3351 const PointedThing &pointed, const NodeMetadata *meta)
3353 const auto &prediction = selected_def.node_placement_prediction;
3355 const NodeDefManager *nodedef = client->ndef();
3356 ClientMap &map = client->getEnv().getClientMap();
3358 bool is_valid_position;
3360 node = map.getNode(nodepos, &is_valid_position);
3361 if (!is_valid_position) {
3362 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3367 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3368 && !isKeyDown(KeyType::SNEAK)) {
3369 // on_rightclick callbacks are called anyway
3370 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3371 client->interact(INTERACT_PLACE, pointed);
3373 infostream << "Launching custom inventory view" << std::endl;
3375 InventoryLocation inventoryloc;
3376 inventoryloc.setNodeMeta(nodepos);
3378 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3379 &client->getEnv().getClientMap(), nodepos);
3380 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3382 auto *&formspec = m_game_ui->updateFormspec("");
3383 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3384 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3386 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3390 // on_rightclick callback
3391 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3392 !isKeyDown(KeyType::SNEAK))) {
3394 client->interact(INTERACT_PLACE, pointed);
3398 verbosestream << "Node placement prediction for "
3399 << selected_def.name << " is " << prediction << std::endl;
3400 v3s16 p = neighbourpos;
3402 // Place inside node itself if buildable_to
3403 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3404 if (is_valid_position) {
3405 if (nodedef->get(n_under).buildable_to) {
3408 node = map.getNode(p, &is_valid_position);
3409 if (is_valid_position && !nodedef->get(node).buildable_to) {
3410 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3412 client->interact(INTERACT_PLACE, pointed);
3418 // Find id of predicted node
3420 bool found = nodedef->getId(prediction, id);
3423 errorstream << "Node placement prediction failed for "
3424 << selected_def.name << " (places " << prediction
3425 << ") - Name not known" << std::endl;
3426 // Handle this as if prediction was empty
3428 client->interact(INTERACT_PLACE, pointed);
3432 const ContentFeatures &predicted_f = nodedef->get(id);
3434 // Predict param2 for facedir and wallmounted nodes
3435 // Compare core.item_place_node() for what the server does
3438 const u8 place_param2 = selected_def.place_param2;
3441 param2 = place_param2;
3442 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3443 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3444 v3s16 dir = nodepos - neighbourpos;
3446 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3447 param2 = dir.Y < 0 ? 1 : 0;
3448 } else if (abs(dir.X) > abs(dir.Z)) {
3449 param2 = dir.X < 0 ? 3 : 2;
3451 param2 = dir.Z < 0 ? 5 : 4;
3453 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3454 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3455 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3457 if (abs(dir.X) > abs(dir.Z)) {
3458 param2 = dir.X < 0 ? 3 : 1;
3460 param2 = dir.Z < 0 ? 2 : 0;
3464 // Check attachment if node is in group attached_node
3465 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3466 const static v3s16 wallmounted_dirs[8] = {
3476 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3477 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3478 pp = p + wallmounted_dirs[param2];
3480 pp = p + v3s16(0, -1, 0);
3482 if (!nodedef->get(map.getNode(pp)).walkable) {
3483 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3485 client->interact(INTERACT_PLACE, pointed);
3491 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3492 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3493 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3494 const auto &indexstr = selected_item.metadata.
3495 getString("palette_index", 0);
3496 if (!indexstr.empty()) {
3497 s32 index = mystoi(indexstr);
3498 if (predicted_f.param_type_2 == CPT2_COLOR) {
3500 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3501 // param2 = pure palette index + other
3502 param2 = (index & 0xf8) | (param2 & 0x07);
3503 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3504 // param2 = pure palette index + other
3505 param2 = (index & 0xe0) | (param2 & 0x1f);
3510 // Add node to client map
3511 MapNode n(id, 0, param2);
3514 LocalPlayer *player = client->getEnv().getLocalPlayer();
3516 // Dont place node when player would be inside new node
3517 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3518 if (!nodedef->get(n).walkable ||
3519 g_settings->getBool("enable_build_where_you_stand") ||
3520 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3521 (nodedef->get(n).walkable &&
3522 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3523 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3524 // This triggers the required mesh update too
3525 client->addNode(p, n);
3527 client->interact(INTERACT_PLACE, pointed);
3528 // A node is predicted, also play a sound
3529 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3532 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3535 } catch (const InvalidPositionException &e) {
3536 errorstream << "Node placement prediction failed for "
3537 << selected_def.name << " (places "
3538 << prediction << ") - Position not loaded" << std::endl;
3539 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3544 void Game::handlePointingAtObject(const PointedThing &pointed,
3545 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3547 std::wstring infotext = unescape_translate(
3548 utf8_to_wide(runData.selected_object->infoText()));
3551 if (!infotext.empty()) {
3554 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3557 m_game_ui->setInfoText(infotext);
3559 if (isKeyDown(KeyType::DIG)) {
3560 bool do_punch = false;
3561 bool do_punch_damage = false;
3563 if (runData.object_hit_delay_timer <= 0.0) {
3565 do_punch_damage = true;
3566 runData.object_hit_delay_timer = object_hit_delay;
3569 if (wasKeyPressed(KeyType::DIG))
3573 infostream << "Punched object" << std::endl;
3574 runData.punching = true;
3577 if (do_punch_damage) {
3578 // Report direct punch
3579 v3f objpos = runData.selected_object->getPosition();
3580 v3f dir = (objpos - player_position).normalize();
3582 bool disable_send = runData.selected_object->directReportPunch(
3583 dir, &tool_item, runData.time_from_last_punch);
3584 runData.time_from_last_punch = 0;
3587 client->interact(INTERACT_START_DIGGING, pointed);
3589 } else if (wasKeyDown(KeyType::PLACE)) {
3590 infostream << "Pressed place button while pointing at object" << std::endl;
3591 client->interact(INTERACT_PLACE, pointed); // place
3596 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3597 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3599 // See also: serverpackethandle.cpp, action == 2
3600 LocalPlayer *player = client->getEnv().getLocalPlayer();
3601 ClientMap &map = client->getEnv().getClientMap();
3602 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3604 // NOTE: Similar piece of code exists on the server side for
3606 // Get digging parameters
3607 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3608 &selected_item.getToolCapabilities(itemdef_manager),
3609 selected_item.wear);
3611 // If can't dig, try hand
3612 if (!params.diggable) {
3613 params = getDigParams(nodedef_manager->get(n).groups,
3614 &hand_item.getToolCapabilities(itemdef_manager));
3617 if (!params.diggable) {
3618 // I guess nobody will wait for this long
3619 runData.dig_time_complete = 10000000.0;
3621 runData.dig_time_complete = params.time;
3623 if (m_cache_enable_particles) {
3624 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3625 client->getParticleManager()->addNodeParticle(client,
3626 player, nodepos, n, features);
3630 if (!runData.digging) {
3631 infostream << "Started digging" << std::endl;
3632 runData.dig_instantly = runData.dig_time_complete == 0;
3633 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3635 client->interact(INTERACT_START_DIGGING, pointed);
3636 runData.digging = true;
3637 runData.btn_down_for_dig = true;
3640 if (!runData.dig_instantly) {
3641 runData.dig_index = (float)crack_animation_length
3643 / runData.dig_time_complete;
3645 // This is for e.g. torches
3646 runData.dig_index = crack_animation_length;
3649 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3651 if (sound_dig.exists() && params.diggable) {
3652 if (sound_dig.name == "__group") {
3653 if (!params.main_group.empty()) {
3654 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3655 soundmaker->m_player_leftpunch_sound.name =
3656 std::string("default_dig_") +
3660 soundmaker->m_player_leftpunch_sound = sound_dig;
3664 // Don't show cracks if not diggable
3665 if (runData.dig_time_complete >= 100000.0) {
3666 } else if (runData.dig_index < crack_animation_length) {
3667 //TimeTaker timer("client.setTempMod");
3668 //infostream<<"dig_index="<<dig_index<<std::endl;
3669 client->setCrack(runData.dig_index, nodepos);
3671 infostream << "Digging completed" << std::endl;
3672 client->setCrack(-1, v3s16(0, 0, 0));
3674 runData.dig_time = 0;
3675 runData.digging = false;
3676 // we successfully dug, now block it from repeating if we want to be safe
3677 if (g_settings->getBool("safe_dig_and_place"))
3678 runData.digging_blocked = true;
3680 runData.nodig_delay_timer =
3681 runData.dig_time_complete / (float)crack_animation_length;
3683 // We don't want a corresponding delay to very time consuming nodes
3684 // and nodes without digging time (e.g. torches) get a fixed delay.
3685 if (runData.nodig_delay_timer > 0.3)
3686 runData.nodig_delay_timer = 0.3;
3687 else if (runData.dig_instantly)
3688 runData.nodig_delay_timer = 0.15;
3690 bool is_valid_position;
3691 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3692 if (is_valid_position) {
3693 if (client->modsLoaded() &&
3694 client->getScript()->on_dignode(nodepos, wasnode)) {
3698 const ContentFeatures &f = client->ndef()->get(wasnode);
3699 if (f.node_dig_prediction == "air") {
3700 client->removeNode(nodepos);
3701 } else if (!f.node_dig_prediction.empty()) {
3703 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3705 client->addNode(nodepos, id, true);
3707 // implicit else: no prediction
3710 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3712 if (m_cache_enable_particles) {
3713 const ContentFeatures &features =
3714 client->getNodeDefManager()->get(wasnode);
3715 client->getParticleManager()->addDiggingParticles(client,
3716 player, nodepos, wasnode, features);
3720 // Send event to trigger sound
3721 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3724 if (runData.dig_time_complete < 100000.0) {
3725 runData.dig_time += dtime;
3727 runData.dig_time = 0;
3728 client->setCrack(-1, nodepos);
3731 camera->setDigging(0); // Dig animation
3734 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3735 const CameraOrientation &cam)
3737 TimeTaker tt_update("Game::updateFrame()");
3738 LocalPlayer *player = client->getEnv().getLocalPlayer();
3744 if (draw_control->range_all) {
3745 runData.fog_range = 100000 * BS;
3747 runData.fog_range = draw_control->wanted_range * BS;
3751 Calculate general brightness
3753 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3754 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3755 float direct_brightness;
3758 // When in noclip mode force same sky brightness as above ground so you
3760 if (draw_control->allow_noclip && m_cache_enable_free_move &&
3761 client->checkPrivilege("fly")) {
3762 direct_brightness = time_brightness;
3763 sunlight_seen = true;
3765 float old_brightness = sky->getBrightness();
3766 direct_brightness = client->getEnv().getClientMap()
3767 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3768 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3772 float time_of_day_smooth = runData.time_of_day_smooth;
3773 float time_of_day = client->getEnv().getTimeOfDayF();
3775 static const float maxsm = 0.05f;
3776 static const float todsm = 0.05f;
3778 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3779 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3780 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3781 time_of_day_smooth = time_of_day;
3783 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3784 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3785 + (time_of_day + 1.0) * todsm;
3787 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3788 + time_of_day * todsm;
3790 runData.time_of_day_smooth = time_of_day_smooth;
3792 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3793 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3794 player->getPitch());
3800 if (sky->getCloudsVisible()) {
3801 clouds->setVisible(true);
3802 clouds->step(dtime);
3803 // camera->getPosition is not enough for 3rd person views
3804 v3f camera_node_position = camera->getCameraNode()->getPosition();
3805 v3s16 camera_offset = camera->getOffset();
3806 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3807 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3808 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3809 clouds->update(camera_node_position,
3810 sky->getCloudColor());
3811 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3812 // if inside clouds, and fog enabled, use that as sky
3814 video::SColor clouds_dark = clouds->getColor()
3815 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3816 sky->overrideColors(clouds_dark, clouds->getColor());
3817 sky->setInClouds(true);
3818 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3819 // do not draw clouds after all
3820 clouds->setVisible(false);
3823 clouds->setVisible(false);
3830 client->getParticleManager()->step(dtime);
3836 if (m_cache_enable_fog) {
3839 video::EFT_FOG_LINEAR,
3840 runData.fog_range * m_cache_fog_start,
3841 runData.fog_range * 1.0,
3849 video::EFT_FOG_LINEAR,
3861 if (player->hurt_tilt_timer > 0.0f) {
3862 player->hurt_tilt_timer -= dtime * 6.0f;
3864 if (player->hurt_tilt_timer < 0.0f)
3865 player->hurt_tilt_strength = 0.0f;
3869 Update minimap pos and rotation
3871 if (mapper && m_game_ui->m_flags.show_hud) {
3872 mapper->setPos(floatToInt(player->getPosition(), BS));
3873 mapper->setAngle(player->getYaw());
3877 Get chat messages from client
3886 if (player->getWieldIndex() != runData.new_playeritem)
3887 client->setPlayerItem(runData.new_playeritem);
3889 if (client->updateWieldedItem()) {
3890 // Update wielded tool
3891 ItemStack selected_item, hand_item;
3892 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3893 camera->wield(tool_item);
3897 Update block draw list every 200ms or when camera direction has
3900 runData.update_draw_list_timer += dtime;
3902 float update_draw_list_delta = 0.2f;
3904 v3f camera_direction = camera->getDirection();
3905 if (runData.update_draw_list_timer >= update_draw_list_delta
3906 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3907 || m_camera_offset_changed
3908 || client->getEnv().getClientMap().needsUpdateDrawList()) {
3909 runData.update_draw_list_timer = 0;
3910 client->getEnv().getClientMap().updateDrawList();
3911 runData.update_draw_list_last_cam_dir = camera_direction;
3914 if (RenderingEngine::get_shadow_renderer()) {
3918 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3921 make sure menu is on top
3922 1. Delete formspec menu reference if menu was removed
3923 2. Else, make sure formspec menu is on top
3925 auto formspec = m_game_ui->getFormspecGUI();
3926 do { // breakable. only runs for one iteration
3930 if (formspec->getReferenceCount() == 1) {
3931 m_game_ui->deleteFormspec();
3935 auto &loc = formspec->getFormspecLocation();
3936 if (loc.type == InventoryLocation::NODEMETA) {
3937 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3938 if (!meta || meta->getString("formspec").empty()) {
3939 formspec->quitMenu();
3945 guiroot->bringToFront(formspec);
3949 ==================== Drawing begins ====================
3951 const video::SColor skycolor = sky->getSkyColor();
3953 TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
3954 driver->beginScene(true, true, skycolor);
3956 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3957 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3958 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3959 bool draw_crosshair = (
3960 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3961 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3962 #ifdef HAVE_TOUCHSCREENGUI
3964 draw_crosshair = !g_settings->getBool("touchtarget");
3965 } catch (SettingNotFoundException) {
3968 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3969 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3974 v2u32 screensize = driver->getScreenSize();
3976 if (m_game_ui->m_flags.show_profiler_graph)
3977 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3982 if (runData.damage_flash > 0.0f) {
3983 video::SColor color(runData.damage_flash, 180, 0, 0);
3984 driver->draw2DRectangle(color,
3985 core::rect<s32>(0, 0, screensize.X, screensize.Y),
3988 runData.damage_flash -= 384.0f * dtime;
3992 ==================== End scene ====================
3994 #if IRRLICHT_VERSION_MT_REVISION < 5
3995 if (++m_reset_HW_buffer_counter > 500) {
3997 Periodically remove all mesh HW buffers.
3999 Work around for a quirk in Irrlicht where a HW buffer is only
4000 released after 20000 iterations (triggered from endScene()).
4002 Without this, all loaded but unused meshes will retain their HW
4003 buffers for at least 5 minutes, at which point looking up the HW buffers
4004 becomes a bottleneck and the framerate drops (as much as 30%).
4006 Tests showed that numbers between 50 and 1000 are good, so picked 500.
4007 There are no other public Irrlicht APIs that allow interacting with the
4008 HW buffers without tracking the status of every individual mesh.
4010 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4012 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4013 driver->removeAllHardwareBuffers();
4014 m_reset_HW_buffer_counter = 0;
4020 stats->drawtime = tt_draw.stop(true);
4021 g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
4022 g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
4025 /* Log times and stuff for visualization */
4026 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4028 Profiler::GraphValues values;
4029 g_profiler->graphGet(values);
4033 /****************************************************************************
4035 *****************************************************************************/
4036 void Game::updateShadows()
4038 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4042 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4044 float timeoftheday = getWickedTimeOfDay(in_timeofday);
4045 bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
4046 bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
4047 shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
4049 timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
4050 const float offset_constant = 10000.0f;
4052 v3f light(0.0f, 0.0f, -1.0f);
4053 light.rotateXZBy(90);
4054 light.rotateXYBy(timeoftheday * 360 - 90);
4055 light.rotateYZBy(sky->getSkyBodyOrbitTilt());
4057 v3f sun_pos = light * offset_constant;
4059 if (shadow->getDirectionalLightCount() == 0)
4060 shadow->addDirectionalLight();
4061 shadow->getDirectionalLight().setDirection(sun_pos);
4062 shadow->setTimeOfDay(in_timeofday);
4064 shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4067 /****************************************************************************
4069 ****************************************************************************/
4071 void FpsControl::reset()
4073 last_time = porting::getTimeUs();
4077 * On some computers framerate doesn't seem to be automatically limited
4079 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
4081 const u64 frametime_min = 1000000.0f / (
4082 device->isWindowFocused() && !g_menumgr.pausesGame()
4083 ? g_settings->getFloat("fps_max")
4084 : g_settings->getFloat("fps_max_unfocused"));
4086 u64 time = porting::getTimeUs();
4088 if (time > last_time) // Make sure time hasn't overflowed
4089 busy_time = time - last_time;
4093 if (busy_time < frametime_min) {
4094 sleep_time = frametime_min - busy_time;
4095 if (sleep_time > 1000)
4096 sleep_ms(sleep_time / 1000);
4101 // Read the timer again to accurately determine how long we actually slept,
4102 // rather than calculating it by adding sleep_time to time.
4103 time = porting::getTimeUs();
4105 if (time > last_time) // Make sure last_time hasn't overflowed
4106 *dtime = (time - last_time) / 1000000.0f;
4113 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4115 const wchar_t *wmsg = wgettext(msg);
4116 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4121 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4123 ((Game *)data)->readSettings();
4126 void Game::readSettings()
4128 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4129 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4130 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4131 m_cache_enable_particles = g_settings->getBool("enable_particles");
4132 m_cache_enable_fog = g_settings->getBool("enable_fog");
4133 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
4134 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4135 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
4137 m_cache_enable_noclip = g_settings->getBool("noclip");
4138 m_cache_enable_free_move = g_settings->getBool("free_move");
4140 m_cache_fog_start = g_settings->getFloat("fog_start");
4142 m_cache_cam_smoothing = 0;
4143 if (g_settings->getBool("cinematic"))
4144 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4146 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4148 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4149 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4150 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4152 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4155 /****************************************************************************/
4156 /****************************************************************************
4158 ****************************************************************************/
4159 /****************************************************************************/
4161 void Game::showDeathFormspec()
4163 static std::string formspec_str =
4164 std::string("formspec_version[1]") +
4166 "bgcolor[#320000b4;true]"
4167 "label[4.85,1.35;" + gettext("You died") + "]"
4168 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4172 /* Note: FormspecFormSource and LocalFormspecHandler *
4173 * are deleted by guiFormSpecMenu */
4174 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4175 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4177 auto *&formspec = m_game_ui->getFormspecGUI();
4178 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4179 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4180 formspec->setFocus("btn_respawn");
4183 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4184 void Game::showPauseMenu()
4186 #ifdef HAVE_TOUCHSCREENGUI
4187 static const std::string control_text = strgettext("Default Controls:\n"
4188 "No menu visible:\n"
4189 "- single tap: button activate\n"
4190 "- double tap: place/use\n"
4191 "- slide finger: look around\n"
4192 "Menu/Inventory visible:\n"
4193 "- double tap (outside):\n"
4195 "- touch stack, touch slot:\n"
4197 "- touch&drag, tap 2nd finger\n"
4198 " --> place single item to slot\n"
4201 static const std::string control_text_template = strgettext("Controls:\n"
4202 "- %s: move forwards\n"
4203 "- %s: move backwards\n"
4205 "- %s: move right\n"
4206 "- %s: jump/climb up\n"
4209 "- %s: sneak/climb down\n"
4212 "- Mouse: turn/look\n"
4213 "- Mouse wheel: select item\n"
4217 char control_text_buf[600];
4219 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4220 GET_KEY_NAME(keymap_forward),
4221 GET_KEY_NAME(keymap_backward),
4222 GET_KEY_NAME(keymap_left),
4223 GET_KEY_NAME(keymap_right),
4224 GET_KEY_NAME(keymap_jump),
4225 GET_KEY_NAME(keymap_dig),
4226 GET_KEY_NAME(keymap_place),
4227 GET_KEY_NAME(keymap_sneak),
4228 GET_KEY_NAME(keymap_drop),
4229 GET_KEY_NAME(keymap_inventory),
4230 GET_KEY_NAME(keymap_chat)
4233 std::string control_text = std::string(control_text_buf);
4234 str_formspec_escape(control_text);
4237 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4238 std::ostringstream os;
4240 os << "formspec_version[1]" << SIZE_TAG
4241 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4242 << strgettext("Continue") << "]";
4244 if (!simple_singleplayer_mode) {
4245 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4246 << strgettext("Change Password") << "]";
4248 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4253 if (g_settings->getBool("enable_sound")) {
4254 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4255 << strgettext("Sound Volume") << "]";
4258 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4259 << strgettext("Change Keys") << "]";
4261 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4262 << strgettext("Exit to Menu") << "]";
4263 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4264 << strgettext("Exit to OS") << "]"
4265 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4266 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4268 << strgettext("Game info:") << "\n";
4269 const std::string &address = client->getAddressName();
4270 static const std::string mode = strgettext("- Mode: ");
4271 if (!simple_singleplayer_mode) {
4272 Address serverAddress = client->getServerAddress();
4273 if (!address.empty()) {
4274 os << mode << strgettext("Remote server") << "\n"
4275 << strgettext("- Address: ") << address;
4277 os << mode << strgettext("Hosting server");
4279 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4281 os << mode << strgettext("Singleplayer") << "\n";
4283 if (simple_singleplayer_mode || address.empty()) {
4284 static const std::string on = strgettext("On");
4285 static const std::string off = strgettext("Off");
4286 // Note: Status of enable_damage and creative_mode settings is intentionally
4287 // NOT shown here because the game might roll its own damage system and/or do
4288 // a per-player Creative Mode, in which case writing it here would mislead.
4289 bool damage = g_settings->getBool("enable_damage");
4290 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4291 if (!simple_singleplayer_mode) {
4293 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4294 //~ PvP = Player versus Player
4295 os << strgettext("- PvP: ") << pvp << "\n";
4297 os << strgettext("- Public: ") << announced << "\n";
4298 std::string server_name = g_settings->get("server_name");
4299 str_formspec_escape(server_name);
4300 if (announced == on && !server_name.empty())
4301 os << strgettext("- Server Name: ") << server_name;
4308 /* Note: FormspecFormSource and LocalFormspecHandler *
4309 * are deleted by guiFormSpecMenu */
4310 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4311 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4313 auto *&formspec = m_game_ui->getFormspecGUI();
4314 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4315 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4316 formspec->setFocus("btn_continue");
4317 formspec->doPause = true;
4319 if (simple_singleplayer_mode)
4323 /****************************************************************************/
4324 /****************************************************************************
4325 extern function for launching the game
4326 ****************************************************************************/
4327 /****************************************************************************/
4329 void the_game(bool *kill,
4330 InputHandler *input,
4331 RenderingEngine *rendering_engine,
4332 const GameStartData &start_data,
4333 std::string &error_message,
4334 ChatBackend &chat_backend,
4335 bool *reconnect_requested) // Used for local game
4339 /* Make a copy of the server address because if a local singleplayer server
4340 * is created then this is updated and we don't want to change the value
4341 * passed to us by the calling function
4346 if (game.startup(kill, input, rendering_engine, start_data,
4347 error_message, reconnect_requested, &chat_backend)) {
4351 } catch (SerializationError &e) {
4352 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
4353 error_message = strgettext("A serialization error occurred:") +"\n"
4354 + e.what() + "\n\n" + ver_err;
4355 errorstream << error_message << std::endl;
4356 } catch (ServerError &e) {
4357 error_message = e.what();
4358 errorstream << "ServerError: " << error_message << std::endl;
4359 } catch (ModError &e) {
4360 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
4361 error_message = std::string("ModError: ") + e.what() +
4362 strgettext("\nCheck debug.txt for details.");
4363 errorstream << error_message << std::endl;