3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "client/renderingengine.h"
27 #include "client/clientevent.h"
28 #include "client/gameui.h"
29 #include "client/inputhandler.h"
30 #include "client/tile.h" // For TextureSource
31 #include "client/keys.h"
32 #include "client/joystick_controller.h"
33 #include "clientmap.h"
36 #include "content_cao.h"
37 #include "content/subgames.h"
38 #include "client/event_manager.h"
39 #include "fontengine.h"
43 #include "gameparams.h"
45 #include "gui/guiChatConsole.h"
46 #include "gui/guiFormSpecMenu.h"
47 #include "gui/guiKeyChangeMenu.h"
48 #include "gui/guiPasswordChange.h"
49 #include "gui/guiVolumeChange.h"
50 #include "gui/mainmenumanager.h"
51 #include "gui/profilergraph.h"
54 #include "nodedef.h" // Needed for determining pointing to nodes
55 #include "nodemetadata.h"
56 #include "particles.h"
64 #include "translation.h"
65 #include "util/basic_macros.h"
66 #include "util/directiontables.h"
67 #include "util/pointedthing.h"
68 #include "util/quicktune_shortcutter.h"
69 #include "irrlicht_changes/static_text.h"
72 #include "script/scripting_client.h"
76 #include "client/sound_openal.h"
78 #include "client/sound.h"
84 struct TextDestNodeMetadata : public TextDest
86 TextDestNodeMetadata(v3s16 p, Client *client)
91 // This is deprecated I guess? -celeron55
92 void gotText(const std::wstring &text)
94 std::string ntext = wide_to_utf8(text);
95 infostream << "Submitting 'text' field of node at (" << m_p.X << ","
96 << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
98 fields["text"] = ntext;
99 m_client->sendNodemetaFields(m_p, "", fields);
101 void gotText(const StringMap &fields)
103 m_client->sendNodemetaFields(m_p, "", fields);
110 struct TextDestPlayerInventory : public TextDest
112 TextDestPlayerInventory(Client *client)
117 TextDestPlayerInventory(Client *client, const std::string &formname)
120 m_formname = formname;
122 void gotText(const StringMap &fields)
124 m_client->sendInventoryFields(m_formname, fields);
130 struct LocalFormspecHandler : public TextDest
132 LocalFormspecHandler(const std::string &formname)
134 m_formname = formname;
137 LocalFormspecHandler(const std::string &formname, Client *client):
140 m_formname = formname;
143 void gotText(const StringMap &fields)
145 if (m_formname == "MT_PAUSE_MENU") {
146 if (fields.find("btn_sound") != fields.end()) {
147 g_gamecallback->changeVolume();
151 if (fields.find("btn_key_config") != fields.end()) {
152 g_gamecallback->keyConfig();
156 if (fields.find("btn_exit_menu") != fields.end()) {
157 g_gamecallback->disconnect();
161 if (fields.find("btn_exit_os") != fields.end()) {
162 g_gamecallback->exitToOS();
164 RenderingEngine::get_raw_device()->closeDevice();
169 if (fields.find("btn_change_password") != fields.end()) {
170 g_gamecallback->changePassword();
177 if (m_formname == "MT_DEATH_SCREEN") {
178 assert(m_client != 0);
179 m_client->sendRespawn();
183 if (m_client->modsLoaded())
184 m_client->getScript()->on_formspec_input(m_formname, fields);
187 Client *m_client = nullptr;
190 /* Form update callback */
192 class NodeMetadataFormSource: public IFormSource
195 NodeMetadataFormSource(ClientMap *map, v3s16 p):
200 const std::string &getForm() const
202 static const std::string empty_string = "";
203 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
208 return meta->getString("formspec");
211 virtual std::string resolveText(const std::string &str)
213 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
218 return meta->resolveString(str);
225 class PlayerInventoryFormSource: public IFormSource
228 PlayerInventoryFormSource(Client *client):
233 const std::string &getForm() const
235 LocalPlayer *player = m_client->getEnv().getLocalPlayer();
236 return player->inventory_formspec;
242 class NodeDugEvent: public MtEvent
248 NodeDugEvent(v3s16 p, MapNode n):
252 MtEvent::Type getType() const
254 return MtEvent::NODE_DUG;
260 ISoundManager *m_sound;
261 const NodeDefManager *m_ndef;
263 bool makes_footstep_sound;
264 float m_player_step_timer;
265 float m_player_jump_timer;
267 SimpleSoundSpec m_player_step_sound;
268 SimpleSoundSpec m_player_leftpunch_sound;
269 SimpleSoundSpec m_player_rightpunch_sound;
271 SoundMaker(ISoundManager *sound, const NodeDefManager *ndef):
274 makes_footstep_sound(true),
275 m_player_step_timer(0.0f),
276 m_player_jump_timer(0.0f)
280 void playPlayerStep()
282 if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
283 m_player_step_timer = 0.03;
284 if (makes_footstep_sound)
285 m_sound->playSound(m_player_step_sound);
289 void playPlayerJump()
291 if (m_player_jump_timer <= 0.0f) {
292 m_player_jump_timer = 0.2f;
293 m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f));
297 static void viewBobbingStep(MtEvent *e, void *data)
299 SoundMaker *sm = (SoundMaker *)data;
300 sm->playPlayerStep();
303 static void playerRegainGround(MtEvent *e, void *data)
305 SoundMaker *sm = (SoundMaker *)data;
306 sm->playPlayerStep();
309 static void playerJump(MtEvent *e, void *data)
311 SoundMaker *sm = (SoundMaker *)data;
312 sm->playPlayerJump();
315 static void cameraPunchLeft(MtEvent *e, void *data)
317 SoundMaker *sm = (SoundMaker *)data;
318 sm->m_sound->playSound(sm->m_player_leftpunch_sound);
321 static void cameraPunchRight(MtEvent *e, void *data)
323 SoundMaker *sm = (SoundMaker *)data;
324 sm->m_sound->playSound(sm->m_player_rightpunch_sound);
327 static void nodeDug(MtEvent *e, void *data)
329 SoundMaker *sm = (SoundMaker *)data;
330 NodeDugEvent *nde = (NodeDugEvent *)e;
331 sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug);
334 static void playerDamage(MtEvent *e, void *data)
336 SoundMaker *sm = (SoundMaker *)data;
337 sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5));
340 static void playerFallingDamage(MtEvent *e, void *data)
342 SoundMaker *sm = (SoundMaker *)data;
343 sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5));
346 void registerReceiver(MtEventManager *mgr)
348 mgr->reg(MtEvent::VIEW_BOBBING_STEP, SoundMaker::viewBobbingStep, this);
349 mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, this);
350 mgr->reg(MtEvent::PLAYER_JUMP, SoundMaker::playerJump, this);
351 mgr->reg(MtEvent::CAMERA_PUNCH_LEFT, SoundMaker::cameraPunchLeft, this);
352 mgr->reg(MtEvent::CAMERA_PUNCH_RIGHT, SoundMaker::cameraPunchRight, this);
353 mgr->reg(MtEvent::NODE_DUG, SoundMaker::nodeDug, this);
354 mgr->reg(MtEvent::PLAYER_DAMAGE, SoundMaker::playerDamage, this);
355 mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, this);
358 void step(float dtime)
360 m_player_step_timer -= dtime;
361 m_player_jump_timer -= dtime;
365 // Locally stored sounds don't need to be preloaded because of this
366 class GameOnDemandSoundFetcher: public OnDemandSoundFetcher
368 std::set<std::string> m_fetched;
370 void paths_insert(std::set<std::string> &dst_paths,
371 const std::string &base,
372 const std::string &name)
374 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg");
375 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg");
376 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg");
377 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg");
378 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg");
379 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg");
380 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg");
381 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg");
382 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg");
383 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg");
384 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg");
387 void fetchSounds(const std::string &name,
388 std::set<std::string> &dst_paths,
389 std::set<std::string> &dst_datas)
391 if (m_fetched.count(name))
394 m_fetched.insert(name);
396 paths_insert(dst_paths, porting::path_share, name);
397 paths_insert(dst_paths, porting::path_user, name);
402 typedef s32 SamplerLayer_t;
405 class GameGlobalShaderConstantSetter : public IShaderConstantSetter
408 bool *m_force_fog_off;
411 CachedPixelShaderSetting<float, 4> m_sky_bg_color;
412 CachedPixelShaderSetting<float> m_fog_distance;
413 CachedVertexShaderSetting<float> m_animation_timer_vertex;
414 CachedPixelShaderSetting<float> m_animation_timer_pixel;
415 CachedPixelShaderSetting<float, 3> m_day_light;
416 CachedPixelShaderSetting<float, 4> m_star_color;
417 CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
418 CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
419 CachedPixelShaderSetting<float, 3> m_minimap_yaw;
420 CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
421 CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
422 CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
423 CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture;
424 CachedPixelShaderSetting<SamplerLayer_t> m_texture_flags;
428 void onSettingsChange(const std::string &name)
430 if (name == "enable_fog")
431 m_fog_enabled = g_settings->getBool("enable_fog");
434 static void settingsCallback(const std::string &name, void *userdata)
436 reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
439 void setSky(Sky *sky) { m_sky = sky; }
441 GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
442 f32 *fog_range, Client *client) :
444 m_force_fog_off(force_fog_off),
445 m_fog_range(fog_range),
446 m_sky_bg_color("skyBgColor"),
447 m_fog_distance("fogDistance"),
448 m_animation_timer_vertex("animationTimer"),
449 m_animation_timer_pixel("animationTimer"),
450 m_day_light("dayLight"),
451 m_star_color("starColor"),
452 m_eye_position_pixel("eyePosition"),
453 m_eye_position_vertex("eyePosition"),
454 m_minimap_yaw("yawVec"),
455 m_camera_offset_pixel("cameraOffset"),
456 m_camera_offset_vertex("cameraOffset"),
457 m_base_texture("baseTexture"),
458 m_normal_texture("normalTexture"),
459 m_texture_flags("textureFlags"),
462 g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
463 m_fog_enabled = g_settings->getBool("enable_fog");
466 ~GameGlobalShaderConstantSetter()
468 g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
471 void onSetConstants(video::IMaterialRendererServices *services) override
474 video::SColor bgcolor = m_sky->getBgColor();
475 video::SColorf bgcolorf(bgcolor);
476 float bgcolorfa[4] = {
482 m_sky_bg_color.set(bgcolorfa, services);
485 float fog_distance = 10000 * BS;
487 if (m_fog_enabled && !*m_force_fog_off)
488 fog_distance = *m_fog_range;
490 m_fog_distance.set(&fog_distance, services);
492 u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
493 video::SColorf sunlight;
494 get_sunlight_color(&sunlight, daynight_ratio);
499 m_day_light.set(dnc, services);
501 video::SColorf star_color = m_sky->getCurrentStarColor();
502 float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a};
503 m_star_color.set(clr, services);
505 u32 animation_timer = m_client->getEnv().getFrameTime() % 1000000;
506 float animation_timer_f = (float)animation_timer / 100000.f;
507 m_animation_timer_vertex.set(&animation_timer_f, services);
508 m_animation_timer_pixel.set(&animation_timer_f, services);
510 float eye_position_array[3];
511 v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
512 epos.getAs3Values(eye_position_array);
513 m_eye_position_pixel.set(eye_position_array, services);
514 m_eye_position_vertex.set(eye_position_array, services);
516 if (m_client->getMinimap()) {
517 float minimap_yaw_array[3];
518 v3f minimap_yaw = m_client->getMinimap()->getYawVec();
519 minimap_yaw.getAs3Values(minimap_yaw_array);
520 m_minimap_yaw.set(minimap_yaw_array, services);
523 float camera_offset_array[3];
524 v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
525 offset.getAs3Values(camera_offset_array);
526 m_camera_offset_pixel.set(camera_offset_array, services);
527 m_camera_offset_vertex.set(camera_offset_array, services);
529 SamplerLayer_t base_tex = 0,
532 m_base_texture.set(&base_tex, services);
533 m_normal_texture.set(&normal_tex, services);
534 m_texture_flags.set(&flags_tex, services);
539 class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
542 bool *m_force_fog_off;
545 std::vector<GameGlobalShaderConstantSetter *> created_nosky;
547 GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
548 f32 *fog_range, Client *client) :
550 m_force_fog_off(force_fog_off),
551 m_fog_range(fog_range),
555 void setSky(Sky *sky) {
557 for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
558 ggscs->setSky(m_sky);
560 created_nosky.clear();
563 virtual IShaderConstantSetter* create()
565 auto *scs = new GameGlobalShaderConstantSetter(
566 m_sky, m_force_fog_off, m_fog_range, m_client);
568 created_nosky.push_back(scs);
573 #ifdef HAVE_TOUCHSCREENGUI
574 #define SIZE_TAG "size[11,5.5]"
576 #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
579 /****************************************************************************
580 ****************************************************************************/
582 const static float object_hit_delay = 0.2;
585 FpsControl() : last_time(0), busy_time(0), sleep_time(0) {}
589 void limit(IrrlichtDevice *device, f32 *dtime);
591 u32 getBusyMs() const { return busy_time / 1000; }
593 // all values in microseconds (us)
594 u64 last_time, busy_time, sleep_time;
598 /* The reason the following structs are not anonymous structs within the
599 * class is that they are not used by the majority of member functions and
600 * many functions that do require objects of thse types do not modify them
601 * (so they can be passed as a const qualified parameter)
607 PointedThing pointed_old;
610 bool btn_down_for_dig;
612 bool digging_blocked;
613 bool reset_jump_timer;
614 float nodig_delay_timer;
616 float dig_time_complete;
617 float repeat_place_timer;
618 float object_hit_delay_timer;
619 float time_from_last_punch;
620 ClientActiveObject *selected_object;
624 float update_draw_list_timer;
628 v3f update_draw_list_last_cam_dir;
630 float time_of_day_smooth;
635 struct ClientEventHandler
637 void (Game::*handler)(ClientEvent *, CameraOrientation *);
640 /****************************************************************************
642 ****************************************************************************/
644 using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
646 /* This is not intended to be a public class. If a public class becomes
647 * desirable then it may be better to create another 'wrapper' class that
648 * hides most of the stuff in this class (nothing in this class is required
649 * by any other file) but exposes the public methods/data only.
656 bool startup(bool *kill,
658 RenderingEngine *rendering_engine,
659 const GameStartData &game_params,
660 std::string &error_message,
662 ChatBackend *chat_backend);
669 // Basic initialisation
670 bool init(const std::string &map_dir, const std::string &address,
671 u16 port, const SubgameSpec &gamespec);
673 bool createSingleplayerServer(const std::string &map_dir,
674 const SubgameSpec &gamespec, u16 port);
677 bool createClient(const GameStartData &start_data);
681 bool connectToServer(const GameStartData &start_data,
682 bool *connect_ok, bool *aborted);
683 bool getServerContent(bool *aborted);
687 void updateInteractTimers(f32 dtime);
688 bool checkConnection();
689 bool handleCallbacks();
690 void processQueues();
691 void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
692 void updateDebugState();
693 void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
694 void updateProfilerGraphs(ProfilerGraph *graph);
697 void processUserInput(f32 dtime);
698 void processKeyInput();
699 void processItemSelection(u16 *new_playeritem);
701 void dropSelectedItem(bool single_item = false);
702 void openInventory();
703 void openConsole(float scale, const wchar_t *line=NULL);
704 void toggleFreeMove();
705 void toggleFreeMoveAlt();
706 void togglePitchMove();
709 void toggleCinematic();
710 void toggleBlockBounds();
711 void toggleAutoforward();
713 void toggleMinimap(bool shift_pressed);
716 void toggleUpdateCamera();
718 void increaseViewRange();
719 void decreaseViewRange();
720 void toggleFullViewRange();
721 void checkZoomEnabled();
723 void updateCameraDirection(CameraOrientation *cam, float dtime);
724 void updateCameraOrientation(CameraOrientation *cam, float dtime);
725 void updatePlayerControl(const CameraOrientation &cam);
726 void step(f32 *dtime);
727 void processClientEvents(CameraOrientation *cam);
728 void updateCamera(f32 dtime);
729 void updateSound(f32 dtime);
730 void processPlayerInteraction(f32 dtime, bool show_hud);
732 * Returns the object or node the player is pointing at.
733 * Also updates the selected thing in the Hud.
735 * @param[in] shootline the shootline, starting from
736 * the camera position. This also gives the maximal distance
738 * @param[in] liquids_pointable if false, liquids are ignored
739 * @param[in] look_for_object if false, objects are ignored
740 * @param[in] camera_offset offset of the camera
741 * @param[out] selected_object the selected object or
744 PointedThing updatePointedThing(
745 const core::line3d<f32> &shootline, bool liquids_pointable,
746 bool look_for_object, const v3s16 &camera_offset);
747 void handlePointingAtNothing(const ItemStack &playerItem);
748 void handlePointingAtNode(const PointedThing &pointed,
749 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
750 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
751 const v3f &player_position, bool show_debug);
752 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
753 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
754 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
755 const CameraOrientation &cam);
756 void updateShadows();
759 void showOverlayMessage(const char *msg, float dtime, int percent,
760 bool draw_clouds = true);
762 static void settingChangedCallback(const std::string &setting_name, void *data);
765 inline bool isKeyDown(GameKeyType k)
767 return input->isKeyDown(k);
769 inline bool wasKeyDown(GameKeyType k)
771 return input->wasKeyDown(k);
773 inline bool wasKeyPressed(GameKeyType k)
775 return input->wasKeyPressed(k);
777 inline bool wasKeyReleased(GameKeyType k)
779 return input->wasKeyReleased(k);
783 void handleAndroidChatInput();
788 bool force_fog_off = false;
789 bool disable_camera_update = false;
792 void showDeathFormspec();
793 void showPauseMenu();
795 void pauseAnimation();
796 void resumeAnimation();
798 // ClientEvent handlers
799 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
800 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
801 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
802 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
803 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
804 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
805 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
806 CameraOrientation *cam);
807 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
808 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
809 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
810 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
811 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
812 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
813 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
814 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
815 CameraOrientation *cam);
816 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
818 void updateChat(f32 dtime);
820 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
821 const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
822 const NodeMetadata *meta);
823 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
825 f32 getSensitivityScaleFactor() const;
827 InputHandler *input = nullptr;
829 Client *client = nullptr;
830 Server *server = nullptr;
832 IWritableTextureSource *texture_src = nullptr;
833 IWritableShaderSource *shader_src = nullptr;
835 // When created, these will be filled with data received from the server
836 IWritableItemDefManager *itemdef_manager = nullptr;
837 NodeDefManager *nodedef_manager = nullptr;
839 GameOnDemandSoundFetcher soundfetcher; // useful when testing
840 ISoundManager *sound = nullptr;
841 bool sound_is_dummy = false;
842 SoundMaker *soundmaker = nullptr;
844 ChatBackend *chat_backend = nullptr;
845 LogOutputBuffer m_chat_log_buf;
847 EventManager *eventmgr = nullptr;
848 QuicktuneShortcutter *quicktune = nullptr;
850 std::unique_ptr<GameUI> m_game_ui;
851 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
852 MapDrawControl *draw_control = nullptr;
853 Camera *camera = nullptr;
854 Clouds *clouds = nullptr; // Free using ->Drop()
855 Sky *sky = nullptr; // Free using ->Drop()
857 Minimap *mapper = nullptr;
859 // Map server hud ids to client hud ids
860 std::unordered_map<u32, u32> m_hud_server_to_client;
866 This class does take ownership/responsibily for cleaning up etc of any of
867 these items (e.g. device)
869 IrrlichtDevice *device;
870 RenderingEngine *m_rendering_engine;
871 video::IVideoDriver *driver;
872 scene::ISceneManager *smgr;
874 std::string *error_message;
875 bool *reconnect_requested;
876 scene::ISceneNode *skybox;
877 PausedNodesList paused_animated_nodes;
879 bool simple_singleplayer_mode;
882 /* Pre-calculated values
884 int crack_animation_length;
886 IntervalLimiter profiler_interval;
889 * TODO: Local caching of settings is not optimal and should at some stage
890 * be updated to use a global settings object for getting thse values
891 * (as opposed to the this local caching). This can be addressed in
894 bool m_cache_doubletap_jump;
895 bool m_cache_enable_clouds;
896 bool m_cache_enable_joysticks;
897 bool m_cache_enable_particles;
898 bool m_cache_enable_fog;
899 bool m_cache_enable_noclip;
900 bool m_cache_enable_free_move;
901 f32 m_cache_mouse_sensitivity;
902 f32 m_cache_joystick_frustum_sensitivity;
903 f32 m_repeat_place_time;
904 f32 m_cache_cam_smoothing;
905 f32 m_cache_fog_start;
907 bool m_invert_mouse = false;
908 bool m_first_loop_after_window_activation = false;
909 bool m_camera_offset_changed = false;
911 bool m_does_lost_focus_pause_game = false;
913 #if IRRLICHT_VERSION_MT_REVISION < 5
914 int m_reset_HW_buffer_counter = 0;
917 #ifdef HAVE_TOUCHSCREENGUI
918 bool m_cache_hold_aux1;
921 bool m_android_chat_open;
926 m_chat_log_buf(g_logger),
927 m_game_ui(new GameUI())
929 g_settings->registerChangedCallback("doubletap_jump",
930 &settingChangedCallback, this);
931 g_settings->registerChangedCallback("enable_clouds",
932 &settingChangedCallback, this);
933 g_settings->registerChangedCallback("doubletap_joysticks",
934 &settingChangedCallback, this);
935 g_settings->registerChangedCallback("enable_particles",
936 &settingChangedCallback, this);
937 g_settings->registerChangedCallback("enable_fog",
938 &settingChangedCallback, this);
939 g_settings->registerChangedCallback("mouse_sensitivity",
940 &settingChangedCallback, this);
941 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
942 &settingChangedCallback, this);
943 g_settings->registerChangedCallback("repeat_place_time",
944 &settingChangedCallback, this);
945 g_settings->registerChangedCallback("noclip",
946 &settingChangedCallback, this);
947 g_settings->registerChangedCallback("free_move",
948 &settingChangedCallback, this);
949 g_settings->registerChangedCallback("cinematic",
950 &settingChangedCallback, this);
951 g_settings->registerChangedCallback("cinematic_camera_smoothing",
952 &settingChangedCallback, this);
953 g_settings->registerChangedCallback("camera_smoothing",
954 &settingChangedCallback, this);
958 #ifdef HAVE_TOUCHSCREENGUI
959 m_cache_hold_aux1 = false; // This is initialised properly later
966 /****************************************************************************
968 ****************************************************************************/
977 delete server; // deleted first to stop all server threads
985 delete nodedef_manager;
986 delete itemdef_manager;
989 clearTextureNameCache();
991 g_settings->deregisterChangedCallback("doubletap_jump",
992 &settingChangedCallback, this);
993 g_settings->deregisterChangedCallback("enable_clouds",
994 &settingChangedCallback, this);
995 g_settings->deregisterChangedCallback("enable_particles",
996 &settingChangedCallback, this);
997 g_settings->deregisterChangedCallback("enable_fog",
998 &settingChangedCallback, this);
999 g_settings->deregisterChangedCallback("mouse_sensitivity",
1000 &settingChangedCallback, this);
1001 g_settings->deregisterChangedCallback("repeat_place_time",
1002 &settingChangedCallback, this);
1003 g_settings->deregisterChangedCallback("noclip",
1004 &settingChangedCallback, this);
1005 g_settings->deregisterChangedCallback("free_move",
1006 &settingChangedCallback, this);
1007 g_settings->deregisterChangedCallback("cinematic",
1008 &settingChangedCallback, this);
1009 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
1010 &settingChangedCallback, this);
1011 g_settings->deregisterChangedCallback("camera_smoothing",
1012 &settingChangedCallback, this);
1015 bool Game::startup(bool *kill,
1016 InputHandler *input,
1017 RenderingEngine *rendering_engine,
1018 const GameStartData &start_data,
1019 std::string &error_message,
1021 ChatBackend *chat_backend)
1025 m_rendering_engine = rendering_engine;
1026 device = m_rendering_engine->get_raw_device();
1028 this->error_message = &error_message;
1029 reconnect_requested = reconnect;
1030 this->input = input;
1031 this->chat_backend = chat_backend;
1032 simple_singleplayer_mode = start_data.isSinglePlayer();
1034 input->keycache.populate();
1036 driver = device->getVideoDriver();
1037 smgr = m_rendering_engine->get_scene_manager();
1039 smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1042 runData = GameRunData();
1043 runData.time_from_last_punch = 10.0;
1045 m_game_ui->initFlags();
1047 m_invert_mouse = g_settings->getBool("invert_mouse");
1048 m_first_loop_after_window_activation = true;
1050 g_client_translations->clear();
1052 // address can change if simple_singleplayer_mode
1053 if (!init(start_data.world_spec.path, start_data.address,
1054 start_data.socket_port, start_data.game_spec))
1057 if (!createClient(start_data))
1060 m_rendering_engine->initialize(client, hud);
1068 ProfilerGraph graph;
1069 RunStats stats = {};
1070 CameraOrientation cam_view_target = {};
1071 CameraOrientation cam_view = {};
1072 FpsControl draw_times;
1073 f32 dtime; // in seconds
1075 /* Clear the profiler */
1076 Profiler::GraphValues dummyvalues;
1077 g_profiler->graphGet(dummyvalues);
1081 set_light_table(g_settings->getFloat("display_gamma"));
1083 #ifdef HAVE_TOUCHSCREENGUI
1084 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1085 && client->checkPrivilege("fast");
1088 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1089 g_settings->getU16("screen_h"));
1091 while (m_rendering_engine->run()
1092 && !(*kill || g_gamecallback->shutdown_requested
1093 || (server && server->isShutdownRequested()))) {
1095 const irr::core::dimension2d<u32> ¤t_screen_size =
1096 m_rendering_engine->get_video_driver()->getScreenSize();
1097 // Verify if window size has changed and save it if it's the case
1098 // Ensure evaluating settings->getBool after verifying screensize
1099 // First condition is cheaper
1100 if (previous_screen_size != current_screen_size &&
1101 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1102 g_settings->getBool("autosave_screensize")) {
1103 g_settings->setU16("screen_w", current_screen_size.Width);
1104 g_settings->setU16("screen_h", current_screen_size.Height);
1105 previous_screen_size = current_screen_size;
1108 // Calculate dtime =
1109 // m_rendering_engine->run() from this iteration
1110 // + Sleep time until the wanted FPS are reached
1111 draw_times.limit(device, &dtime);
1113 // Prepare render data for next iteration
1115 updateStats(&stats, draw_times, dtime);
1116 updateInteractTimers(dtime);
1118 if (!checkConnection())
1120 if (!handleCallbacks())
1125 m_game_ui->clearInfoText();
1126 hud->resizeHotbar();
1129 updateProfilers(stats, draw_times, dtime);
1130 processUserInput(dtime);
1131 // Update camera before player movement to avoid camera lag of one frame
1132 updateCameraDirection(&cam_view_target, dtime);
1133 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1134 cam_view.camera_yaw) * m_cache_cam_smoothing;
1135 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1136 cam_view.camera_pitch) * m_cache_cam_smoothing;
1137 updatePlayerControl(cam_view);
1139 processClientEvents(&cam_view_target);
1141 updateCamera(dtime);
1143 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
1144 updateFrame(&graph, &stats, dtime, cam_view);
1145 updateProfilerGraphs(&graph);
1147 // Update if minimap has been disabled by the server
1148 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1150 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1157 void Game::shutdown()
1159 m_rendering_engine->finalize();
1160 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1161 if (g_settings->get("3d_mode") == "pageflip") {
1162 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1165 auto formspec = m_game_ui->getFormspecGUI();
1167 formspec->quitMenu();
1169 #ifdef HAVE_TOUCHSCREENGUI
1170 g_touchscreengui->hide();
1173 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1178 if (gui_chat_console)
1179 gui_chat_console->drop();
1185 while (g_menumgr.menuCount() > 0) {
1186 g_menumgr.m_stack.front()->setVisible(false);
1187 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1190 m_game_ui->deleteFormspec();
1192 chat_backend->addMessage(L"", L"# Disconnected.");
1193 chat_backend->addMessage(L"", L"");
1194 m_chat_log_buf.clear();
1198 while (!client->isShutdown()) {
1199 assert(texture_src != NULL);
1200 assert(shader_src != NULL);
1201 texture_src->processQueue();
1202 shader_src->processQueue();
1209 /****************************************************************************/
1210 /****************************************************************************
1212 ****************************************************************************/
1213 /****************************************************************************/
1216 const std::string &map_dir,
1217 const std::string &address,
1219 const SubgameSpec &gamespec)
1221 texture_src = createTextureSource();
1223 showOverlayMessage(N_("Loading..."), 0, 0);
1225 shader_src = createShaderSource();
1227 itemdef_manager = createItemDefManager();
1228 nodedef_manager = createNodeDefManager();
1230 eventmgr = new EventManager();
1231 quicktune = new QuicktuneShortcutter();
1233 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1234 && eventmgr && quicktune))
1240 // Create a server if not connecting to an existing one
1241 if (address.empty()) {
1242 if (!createSingleplayerServer(map_dir, gamespec, port))
1249 bool Game::initSound()
1252 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1253 infostream << "Attempting to use OpenAL audio" << std::endl;
1254 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1256 infostream << "Failed to initialize OpenAL audio" << std::endl;
1258 infostream << "Sound disabled." << std::endl;
1262 infostream << "Using dummy audio." << std::endl;
1263 sound = &dummySoundManager;
1264 sound_is_dummy = true;
1267 soundmaker = new SoundMaker(sound, nodedef_manager);
1271 soundmaker->registerReceiver(eventmgr);
1276 bool Game::createSingleplayerServer(const std::string &map_dir,
1277 const SubgameSpec &gamespec, u16 port)
1279 showOverlayMessage(N_("Creating server..."), 0, 5);
1281 std::string bind_str = g_settings->get("bind_address");
1282 Address bind_addr(0, 0, 0, 0, port);
1284 if (g_settings->getBool("ipv6_server")) {
1285 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1289 bind_addr.Resolve(bind_str.c_str());
1290 } catch (ResolveError &e) {
1291 infostream << "Resolving bind address \"" << bind_str
1292 << "\" failed: " << e.what()
1293 << " -- Listening on all addresses." << std::endl;
1296 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1297 *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
1298 bind_addr.serializeString().c_str());
1299 errorstream << *error_message << std::endl;
1303 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1304 false, nullptr, error_message);
1310 bool Game::createClient(const GameStartData &start_data)
1312 showOverlayMessage(N_("Creating client..."), 0, 10);
1314 draw_control = new MapDrawControl();
1318 bool could_connect, connect_aborted;
1319 #ifdef HAVE_TOUCHSCREENGUI
1320 if (g_touchscreengui) {
1321 g_touchscreengui->init(texture_src);
1322 g_touchscreengui->hide();
1325 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1328 if (!could_connect) {
1329 if (error_message->empty() && !connect_aborted) {
1330 // Should not happen if error messages are set properly
1331 *error_message = gettext("Connection failed for unknown reason");
1332 errorstream << *error_message << std::endl;
1337 if (!getServerContent(&connect_aborted)) {
1338 if (error_message->empty() && !connect_aborted) {
1339 // Should not happen if error messages are set properly
1340 *error_message = gettext("Connection failed for unknown reason");
1341 errorstream << *error_message << std::endl;
1346 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1347 &m_flags.force_fog_off, &runData.fog_range, client);
1348 shader_src->addShaderConstantSetterFactory(scsf);
1350 // Update cached textures, meshes and materials
1351 client->afterContentReceived();
1355 camera = new Camera(*draw_control, client, m_rendering_engine);
1356 if (client->modsLoaded())
1357 client->getScript()->on_camera_ready(camera);
1358 client->setCamera(camera);
1362 if (m_cache_enable_clouds)
1363 clouds = new Clouds(smgr, -1, time(0));
1367 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
1369 skybox = NULL; // This is used/set later on in the main run loop
1371 /* Pre-calculated values
1373 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1375 v2u32 size = t->getOriginalSize();
1376 crack_animation_length = size.Y / size.X;
1378 crack_animation_length = 5;
1384 /* Set window caption
1386 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1388 str += utf8_to_wide(g_version_hash);
1390 const wchar_t *text = nullptr;
1391 if (simple_singleplayer_mode)
1392 text = wgettext("Singleplayer");
1394 text = wgettext("Multiplayer");
1401 str += driver->getName();
1404 device->setWindowCaption(str.c_str());
1406 LocalPlayer *player = client->getEnv().getLocalPlayer();
1407 player->hurt_tilt_timer = 0;
1408 player->hurt_tilt_strength = 0;
1410 hud = new Hud(client, player, &player->inventory);
1412 mapper = client->getMinimap();
1414 if (mapper && client->modsLoaded())
1415 client->getScript()->on_minimap_ready(mapper);
1420 bool Game::initGui()
1424 // Remove stale "recent" chat messages from previous connections
1425 chat_backend->clearRecentChat();
1427 // Make sure the size of the recent messages buffer is right
1428 chat_backend->applySettings();
1430 // Chat backend and console
1431 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1432 -1, chat_backend, client, &g_menumgr);
1434 #ifdef HAVE_TOUCHSCREENGUI
1436 if (g_touchscreengui)
1437 g_touchscreengui->show();
1444 bool Game::connectToServer(const GameStartData &start_data,
1445 bool *connect_ok, bool *connection_aborted)
1447 *connect_ok = false; // Let's not be overly optimistic
1448 *connection_aborted = false;
1449 bool local_server_mode = false;
1451 showOverlayMessage(N_("Resolving address..."), 0, 15);
1453 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1456 connect_address.Resolve(start_data.address.c_str());
1458 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1459 if (connect_address.isIPv6()) {
1460 IPv6AddressBytes addr_bytes;
1461 addr_bytes.bytes[15] = 1;
1462 connect_address.setAddress(&addr_bytes);
1464 connect_address.setAddress(127, 0, 0, 1);
1466 local_server_mode = true;
1468 } catch (ResolveError &e) {
1469 *error_message = fmtgettext("Couldn't resolve address: %s", e.what());
1471 errorstream << *error_message << std::endl;
1475 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1476 *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
1477 errorstream << *error_message << std::endl;
1482 client = new Client(start_data.name.c_str(),
1483 start_data.password, start_data.address,
1484 *draw_control, texture_src, shader_src,
1485 itemdef_manager, nodedef_manager, sound, eventmgr,
1486 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get(),
1487 start_data.allow_login_or_register);
1488 client->migrateModStorage();
1489 } catch (const BaseException &e) {
1490 *error_message = fmtgettext("Error creating client: %s", e.what());
1491 errorstream << *error_message << std::endl;
1495 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1497 infostream << "Connecting to server at ";
1498 connect_address.print(infostream);
1499 infostream << std::endl;
1501 client->connect(connect_address,
1502 simple_singleplayer_mode || local_server_mode);
1505 Wait for server to accept connection
1511 FpsControl fps_control;
1513 f32 wait_time = 0; // in seconds
1515 fps_control.reset();
1517 while (m_rendering_engine->run()) {
1519 fps_control.limit(device, &dtime);
1521 // Update client and server
1522 client->step(dtime);
1525 server->step(dtime);
1528 if (client->getState() == LC_Init) {
1534 if (*connection_aborted)
1537 if (client->accessDenied()) {
1538 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1539 *reconnect_requested = client->reconnectRequested();
1540 errorstream << *error_message << std::endl;
1544 if (input->cancelPressed()) {
1545 *connection_aborted = true;
1546 infostream << "Connect aborted [Escape]" << std::endl;
1551 // Only time out if we aren't waiting for the server we started
1552 if (!start_data.address.empty() && wait_time > 10) {
1553 *error_message = gettext("Connection timed out.");
1554 errorstream << *error_message << std::endl;
1559 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1561 } catch (con::PeerNotFoundException &e) {
1562 // TODO: Should something be done here? At least an info/error
1570 bool Game::getServerContent(bool *aborted)
1574 FpsControl fps_control;
1575 f32 dtime; // in seconds
1577 fps_control.reset();
1579 while (m_rendering_engine->run()) {
1581 fps_control.limit(device, &dtime);
1583 // Update client and server
1584 client->step(dtime);
1587 server->step(dtime);
1590 if (client->mediaReceived() && client->itemdefReceived() &&
1591 client->nodedefReceived()) {
1596 if (!checkConnection())
1599 if (client->getState() < LC_Init) {
1600 *error_message = gettext("Client disconnected");
1601 errorstream << *error_message << std::endl;
1605 if (input->cancelPressed()) {
1607 infostream << "Connect aborted [Escape]" << std::endl;
1614 if (!client->itemdefReceived()) {
1615 const wchar_t *text = wgettext("Item definitions...");
1617 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1620 } else if (!client->nodedefReceived()) {
1621 const wchar_t *text = wgettext("Node definitions...");
1623 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1627 std::ostringstream message;
1628 std::fixed(message);
1629 message.precision(0);
1630 float receive = client->mediaReceiveProgress() * 100;
1631 message << gettext("Media...");
1633 message << " " << receive << "%";
1634 message.precision(2);
1636 if ((USE_CURL == 0) ||
1637 (!g_settings->getBool("enable_remote_media_server"))) {
1638 float cur = client->getCurRate();
1639 std::string cur_unit = gettext("KiB/s");
1643 cur_unit = gettext("MiB/s");
1646 message << " (" << cur << ' ' << cur_unit << ")";
1649 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1650 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
1651 texture_src, dtime, progress);
1659 /****************************************************************************/
1660 /****************************************************************************
1662 ****************************************************************************/
1663 /****************************************************************************/
1665 inline void Game::updateInteractTimers(f32 dtime)
1667 if (runData.nodig_delay_timer >= 0)
1668 runData.nodig_delay_timer -= dtime;
1670 if (runData.object_hit_delay_timer >= 0)
1671 runData.object_hit_delay_timer -= dtime;
1673 runData.time_from_last_punch += dtime;
1677 /* returns false if game should exit, otherwise true
1679 inline bool Game::checkConnection()
1681 if (client->accessDenied()) {
1682 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1683 *reconnect_requested = client->reconnectRequested();
1684 errorstream << *error_message << std::endl;
1692 /* returns false if game should exit, otherwise true
1694 inline bool Game::handleCallbacks()
1696 if (g_gamecallback->disconnect_requested) {
1697 g_gamecallback->disconnect_requested = false;
1701 if (g_gamecallback->changepassword_requested) {
1702 (new GUIPasswordChange(guienv, guiroot, -1,
1703 &g_menumgr, client, texture_src))->drop();
1704 g_gamecallback->changepassword_requested = false;
1707 if (g_gamecallback->changevolume_requested) {
1708 (new GUIVolumeChange(guienv, guiroot, -1,
1709 &g_menumgr, texture_src))->drop();
1710 g_gamecallback->changevolume_requested = false;
1713 if (g_gamecallback->keyconfig_requested) {
1714 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1715 &g_menumgr, texture_src))->drop();
1716 g_gamecallback->keyconfig_requested = false;
1719 if (g_gamecallback->keyconfig_changed) {
1720 input->keycache.populate(); // update the cache with new settings
1721 g_gamecallback->keyconfig_changed = false;
1728 void Game::processQueues()
1730 texture_src->processQueue();
1731 itemdef_manager->processQueue(client);
1732 shader_src->processQueue();
1735 void Game::updateDebugState()
1737 LocalPlayer *player = client->getEnv().getLocalPlayer();
1739 // debug UI and wireframe
1740 bool has_debug = client->checkPrivilege("debug");
1741 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
1743 if (m_game_ui->m_flags.show_basic_debug) {
1744 if (!has_basic_debug)
1745 m_game_ui->m_flags.show_basic_debug = false;
1746 } else if (m_game_ui->m_flags.show_minimal_debug) {
1747 if (has_basic_debug)
1748 m_game_ui->m_flags.show_basic_debug = true;
1750 if (!has_basic_debug)
1751 hud->disableBlockBounds();
1753 draw_control->show_wireframe = false;
1756 draw_control->allow_noclip = m_cache_enable_noclip && client->checkPrivilege("noclip");
1759 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1762 float profiler_print_interval =
1763 g_settings->getFloat("profiler_print_interval");
1764 bool print_to_log = true;
1766 if (profiler_print_interval == 0) {
1767 print_to_log = false;
1768 profiler_print_interval = 3;
1771 if (profiler_interval.step(dtime, profiler_print_interval)) {
1773 infostream << "Profiler:" << std::endl;
1774 g_profiler->print(infostream);
1777 m_game_ui->updateProfiler();
1778 g_profiler->clear();
1781 // Update update graphs
1782 g_profiler->graphAdd("Time non-rendering [us]",
1783 draw_times.busy_time - stats.drawtime);
1785 g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
1786 g_profiler->graphAdd("FPS", 1.0f / dtime);
1789 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1796 /* Time average and jitter calculation
1798 jp = &stats->dtime_jitter;
1799 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1801 jitter = dtime - jp->avg;
1803 if (jitter > jp->max)
1806 jp->counter += dtime;
1808 if (jp->counter > 0.0) {
1810 jp->max_sample = jp->max;
1811 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1815 /* Busytime average and jitter calculation
1817 jp = &stats->busy_time_jitter;
1818 jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
1820 jitter = draw_times.getBusyMs() - jp->avg;
1822 if (jitter > jp->max)
1824 if (jitter < jp->min)
1827 jp->counter += dtime;
1829 if (jp->counter > 0.0) {
1831 jp->max_sample = jp->max;
1832 jp->min_sample = jp->min;
1840 /****************************************************************************
1842 ****************************************************************************/
1844 void Game::processUserInput(f32 dtime)
1846 // Reset input if window not active or some menu is active
1847 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1849 #ifdef HAVE_TOUCHSCREENGUI
1850 g_touchscreengui->hide();
1853 #ifdef HAVE_TOUCHSCREENGUI
1854 else if (g_touchscreengui) {
1855 /* on touchscreengui step may generate own input events which ain't
1856 * what we want in case we just did clear them */
1857 g_touchscreengui->show();
1858 g_touchscreengui->step(dtime);
1862 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1863 gui_chat_console->closeConsoleAtOnce();
1866 // Input handler step() (used by the random input generator)
1870 auto formspec = m_game_ui->getFormspecGUI();
1872 formspec->getAndroidUIInput();
1874 handleAndroidChatInput();
1877 // Increase timer for double tap of "keymap_jump"
1878 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1879 runData.jump_timer += dtime;
1882 processItemSelection(&runData.new_playeritem);
1886 void Game::processKeyInput()
1888 if (wasKeyDown(KeyType::DROP)) {
1889 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1890 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1891 toggleAutoforward();
1892 } else if (wasKeyDown(KeyType::BACKWARD)) {
1893 if (g_settings->getBool("continuous_forward"))
1894 toggleAutoforward();
1895 } else if (wasKeyDown(KeyType::INVENTORY)) {
1897 } else if (input->cancelPressed()) {
1899 m_android_chat_open = false;
1901 if (!gui_chat_console->isOpenInhibited()) {
1904 } else if (wasKeyDown(KeyType::CHAT)) {
1905 openConsole(0.2, L"");
1906 } else if (wasKeyDown(KeyType::CMD)) {
1907 openConsole(0.2, L"/");
1908 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1909 if (client->modsLoaded())
1910 openConsole(0.2, L".");
1912 m_game_ui->showTranslatedStatusText("Client side scripting is disabled");
1913 } else if (wasKeyDown(KeyType::CONSOLE)) {
1914 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1915 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1917 } else if (wasKeyDown(KeyType::JUMP)) {
1918 toggleFreeMoveAlt();
1919 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1921 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1923 } else if (wasKeyDown(KeyType::NOCLIP)) {
1926 } else if (wasKeyDown(KeyType::MUTE)) {
1927 if (g_settings->getBool("enable_sound")) {
1928 bool new_mute_sound = !g_settings->getBool("mute_sound");
1929 g_settings->setBool("mute_sound", new_mute_sound);
1931 m_game_ui->showTranslatedStatusText("Sound muted");
1933 m_game_ui->showTranslatedStatusText("Sound unmuted");
1935 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1937 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1938 if (g_settings->getBool("enable_sound")) {
1939 float new_volume = g_settings->getFloat("sound_volume", 0.0f, 0.9f) + 0.1f;
1940 g_settings->setFloat("sound_volume", new_volume);
1941 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1942 m_game_ui->showStatusText(msg);
1944 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1946 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1947 if (g_settings->getBool("enable_sound")) {
1948 float new_volume = g_settings->getFloat("sound_volume", 0.1f, 1.0f) - 0.1f;
1949 g_settings->setFloat("sound_volume", new_volume);
1950 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1951 m_game_ui->showStatusText(msg);
1953 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1956 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1957 || wasKeyDown(KeyType::DEC_VOLUME)) {
1958 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1960 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1962 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1963 client->makeScreenshot();
1964 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
1965 toggleBlockBounds();
1966 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1967 m_game_ui->toggleHud();
1968 } else if (wasKeyDown(KeyType::MINIMAP)) {
1969 toggleMinimap(isKeyDown(KeyType::SNEAK));
1970 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1971 m_game_ui->toggleChat();
1972 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1974 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1975 toggleUpdateCamera();
1976 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1978 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1979 m_game_ui->toggleProfiler();
1980 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1981 increaseViewRange();
1982 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1983 decreaseViewRange();
1984 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1985 toggleFullViewRange();
1986 } else if (wasKeyDown(KeyType::ZOOM)) {
1988 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1990 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1992 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1994 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1998 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1999 runData.reset_jump_timer = false;
2000 runData.jump_timer = 0.0f;
2003 if (quicktune->hasMessage()) {
2004 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2008 void Game::processItemSelection(u16 *new_playeritem)
2010 LocalPlayer *player = client->getEnv().getLocalPlayer();
2012 /* Item selection using mouse wheel
2014 *new_playeritem = player->getWieldIndex();
2016 s32 wheel = input->getMouseWheel();
2017 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2018 player->hud_hotbar_itemcount - 1);
2022 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2025 if (wasKeyDown(KeyType::HOTBAR_PREV))
2029 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2031 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2034 /* Item selection using hotbar slot keys
2036 for (u16 i = 0; i <= max_item; i++) {
2037 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2038 *new_playeritem = i;
2045 void Game::dropSelectedItem(bool single_item)
2047 IDropAction *a = new IDropAction();
2048 a->count = single_item ? 1 : 0;
2049 a->from_inv.setCurrentPlayer();
2050 a->from_list = "main";
2051 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2052 client->inventoryAction(a);
2056 void Game::openInventory()
2059 * Don't permit to open inventory is CAO or player doesn't exists.
2060 * This prevent showing an empty inventory at player load
2063 LocalPlayer *player = client->getEnv().getLocalPlayer();
2064 if (!player || !player->getCAO())
2067 infostream << "Game: Launching inventory" << std::endl;
2069 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2071 InventoryLocation inventoryloc;
2072 inventoryloc.setCurrentPlayer();
2074 if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2079 if (fs_src->getForm().empty()) {
2084 TextDest *txt_dst = new TextDestPlayerInventory(client);
2085 auto *&formspec = m_game_ui->updateFormspec("");
2086 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2087 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2089 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2093 void Game::openConsole(float scale, const wchar_t *line)
2095 assert(scale > 0.0f && scale <= 1.0f);
2098 porting::showInputDialog(gettext("ok"), "", "", 2);
2099 m_android_chat_open = true;
2101 if (gui_chat_console->isOpenInhibited())
2103 gui_chat_console->openConsole(scale);
2105 gui_chat_console->setCloseOnEnter(true);
2106 gui_chat_console->replaceAndAddToHistory(line);
2112 void Game::handleAndroidChatInput()
2114 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2115 std::string text = porting::getInputDialogValue();
2116 client->typeChatMessage(utf8_to_wide(text));
2117 m_android_chat_open = false;
2123 void Game::toggleFreeMove()
2125 bool free_move = !g_settings->getBool("free_move");
2126 g_settings->set("free_move", bool_to_cstr(free_move));
2129 if (client->checkPrivilege("fly")) {
2130 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2132 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2135 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2139 void Game::toggleFreeMoveAlt()
2141 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2144 runData.reset_jump_timer = true;
2148 void Game::togglePitchMove()
2150 bool pitch_move = !g_settings->getBool("pitch_move");
2151 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2154 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2156 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2161 void Game::toggleFast()
2163 bool fast_move = !g_settings->getBool("fast_move");
2164 bool has_fast_privs = client->checkPrivilege("fast");
2165 g_settings->set("fast_move", bool_to_cstr(fast_move));
2168 if (has_fast_privs) {
2169 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2171 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2174 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2177 #ifdef HAVE_TOUCHSCREENGUI
2178 m_cache_hold_aux1 = fast_move && has_fast_privs;
2183 void Game::toggleNoClip()
2185 bool noclip = !g_settings->getBool("noclip");
2186 g_settings->set("noclip", bool_to_cstr(noclip));
2189 if (client->checkPrivilege("noclip")) {
2190 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2192 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2195 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2199 void Game::toggleCinematic()
2201 bool cinematic = !g_settings->getBool("cinematic");
2202 g_settings->set("cinematic", bool_to_cstr(cinematic));
2205 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2207 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2210 void Game::toggleBlockBounds()
2212 LocalPlayer *player = client->getEnv().getLocalPlayer();
2213 if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
2214 m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
2217 enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
2219 case Hud::BLOCK_BOUNDS_OFF:
2220 m_game_ui->showTranslatedStatusText("Block bounds hidden");
2222 case Hud::BLOCK_BOUNDS_CURRENT:
2223 m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
2225 case Hud::BLOCK_BOUNDS_NEAR:
2226 m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
2228 case Hud::BLOCK_BOUNDS_MAX:
2229 m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
2236 // Autoforward by toggling continuous forward.
2237 void Game::toggleAutoforward()
2239 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2240 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2242 if (autorun_enabled)
2243 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2245 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2248 void Game::toggleMinimap(bool shift_pressed)
2250 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2254 mapper->toggleMinimapShape();
2258 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2260 // Not so satisying code to keep compatibility with old fixed mode system
2262 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2264 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2265 m_game_ui->m_flags.show_minimap = false;
2268 // If radar is disabled, try to find a non radar mode or fall back to 0
2269 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2270 while (mapper->getModeIndex() &&
2271 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2274 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2278 // End of 'not so satifying code'
2279 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2280 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2281 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2283 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2286 void Game::toggleFog()
2288 bool fog_enabled = g_settings->getBool("enable_fog");
2289 g_settings->setBool("enable_fog", !fog_enabled);
2291 m_game_ui->showTranslatedStatusText("Fog disabled");
2293 m_game_ui->showTranslatedStatusText("Fog enabled");
2297 void Game::toggleDebug()
2299 LocalPlayer *player = client->getEnv().getLocalPlayer();
2300 bool has_debug = client->checkPrivilege("debug");
2301 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
2302 // Initial: No debug info
2303 // 1x toggle: Debug text
2304 // 2x toggle: Debug text with profiler graph
2305 // 3x toggle: Debug text and wireframe (needs "debug" priv)
2306 // Next toggle: Back to initial
2308 // The debug text can be in 2 modes: minimal and basic.
2309 // * Minimal: Only technical client info that not gameplay-relevant
2310 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
2311 // Basic mode is used when player has the debug HUD flag set,
2312 // otherwise the Minimal mode is used.
2313 if (!m_game_ui->m_flags.show_minimal_debug) {
2314 m_game_ui->m_flags.show_minimal_debug = true;
2315 if (has_basic_debug)
2316 m_game_ui->m_flags.show_basic_debug = true;
2317 m_game_ui->m_flags.show_profiler_graph = false;
2318 draw_control->show_wireframe = false;
2319 m_game_ui->showTranslatedStatusText("Debug info shown");
2320 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2321 if (has_basic_debug)
2322 m_game_ui->m_flags.show_basic_debug = true;
2323 m_game_ui->m_flags.show_profiler_graph = true;
2324 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2325 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2326 if (has_basic_debug)
2327 m_game_ui->m_flags.show_basic_debug = true;
2328 m_game_ui->m_flags.show_profiler_graph = false;
2329 draw_control->show_wireframe = true;
2330 m_game_ui->showTranslatedStatusText("Wireframe shown");
2332 m_game_ui->m_flags.show_minimal_debug = false;
2333 m_game_ui->m_flags.show_basic_debug = false;
2334 m_game_ui->m_flags.show_profiler_graph = false;
2335 draw_control->show_wireframe = false;
2337 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2339 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2345 void Game::toggleUpdateCamera()
2347 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2348 if (m_flags.disable_camera_update)
2349 m_game_ui->showTranslatedStatusText("Camera update disabled");
2351 m_game_ui->showTranslatedStatusText("Camera update enabled");
2355 void Game::increaseViewRange()
2357 s16 range = g_settings->getS16("viewing_range");
2358 s16 range_new = range + 10;
2360 if (range_new > 4000) {
2362 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
2363 m_game_ui->showStatusText(msg);
2365 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2366 m_game_ui->showStatusText(msg);
2368 g_settings->set("viewing_range", itos(range_new));
2372 void Game::decreaseViewRange()
2374 s16 range = g_settings->getS16("viewing_range");
2375 s16 range_new = range - 10;
2377 if (range_new < 20) {
2379 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
2380 m_game_ui->showStatusText(msg);
2382 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2383 m_game_ui->showStatusText(msg);
2385 g_settings->set("viewing_range", itos(range_new));
2389 void Game::toggleFullViewRange()
2391 draw_control->range_all = !draw_control->range_all;
2392 if (draw_control->range_all)
2393 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2395 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2399 void Game::checkZoomEnabled()
2401 LocalPlayer *player = client->getEnv().getLocalPlayer();
2402 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2403 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2406 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2408 if ((device->isWindowActive() && device->isWindowFocused()
2409 && !isMenuActive()) || input->isRandom()) {
2412 if (!input->isRandom()) {
2413 // Mac OSX gets upset if this is set every frame
2414 if (device->getCursorControl()->isVisible())
2415 device->getCursorControl()->setVisible(false);
2419 if (m_first_loop_after_window_activation) {
2420 m_first_loop_after_window_activation = false;
2422 input->setMousePos(driver->getScreenSize().Width / 2,
2423 driver->getScreenSize().Height / 2);
2425 updateCameraOrientation(cam, dtime);
2431 // Mac OSX gets upset if this is set every frame
2432 if (!device->getCursorControl()->isVisible())
2433 device->getCursorControl()->setVisible(true);
2436 m_first_loop_after_window_activation = true;
2441 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2442 // responsiveness independently of FOV.
2443 f32 Game::getSensitivityScaleFactor() const
2445 f32 fov_y = client->getCamera()->getFovY();
2447 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2448 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2450 return tan(fov_y / 2.0f) * 1.3763818698f;
2453 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2455 #ifdef HAVE_TOUCHSCREENGUI
2456 if (g_touchscreengui) {
2457 cam->camera_yaw += g_touchscreengui->getYawChange();
2458 cam->camera_pitch = g_touchscreengui->getPitch();
2461 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2462 v2s32 dist = input->getMousePos() - center;
2464 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2468 f32 sens_scale = getSensitivityScaleFactor();
2469 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2470 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2472 if (dist.X != 0 || dist.Y != 0)
2473 input->setMousePos(center.X, center.Y);
2474 #ifdef HAVE_TOUCHSCREENGUI
2478 if (m_cache_enable_joysticks) {
2479 f32 sens_scale = getSensitivityScaleFactor();
2480 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
2481 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2482 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2485 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2489 void Game::updatePlayerControl(const CameraOrientation &cam)
2491 LocalPlayer *player = client->getEnv().getLocalPlayer();
2493 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2495 PlayerControl control(
2496 isKeyDown(KeyType::FORWARD),
2497 isKeyDown(KeyType::BACKWARD),
2498 isKeyDown(KeyType::LEFT),
2499 isKeyDown(KeyType::RIGHT),
2500 isKeyDown(KeyType::JUMP) || player->getAutojump(),
2501 isKeyDown(KeyType::AUX1),
2502 isKeyDown(KeyType::SNEAK),
2503 isKeyDown(KeyType::ZOOM),
2504 isKeyDown(KeyType::DIG),
2505 isKeyDown(KeyType::PLACE),
2508 input->getMovementSpeed(),
2509 input->getMovementDirection()
2512 // autoforward if set: move at maximum speed
2513 if (player->getPlayerSettings().continuous_forward &&
2514 client->activeObjectsReceived() && !player->isDead()) {
2515 control.movement_speed = 1.0f;
2516 // sideways movement only
2517 float dx = sin(control.movement_direction);
2518 control.movement_direction = atan2(dx, 1.0f);
2521 #ifdef HAVE_TOUCHSCREENGUI
2522 /* For touch, simulate holding down AUX1 (fast move) if the user has
2523 * the fast_move setting toggled on. If there is an aux1 key defined for
2524 * touch then its meaning is inverted (i.e. holding aux1 means walk and
2527 if (m_cache_hold_aux1) {
2528 control.aux1 = control.aux1 ^ true;
2532 client->setPlayerControl(control);
2538 inline void Game::step(f32 *dtime)
2540 bool can_be_and_is_paused =
2541 (simple_singleplayer_mode && g_menumgr.pausesGame());
2543 if (can_be_and_is_paused) { // This is for a singleplayer server
2544 *dtime = 0; // No time passes
2546 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
2550 server->step(*dtime);
2552 client->step(*dtime);
2556 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2559 for (auto &&child: node->getChildren())
2560 pauseNodeAnimation(paused, child);
2561 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2563 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2564 float speed = animated_node->getAnimationSpeed();
2567 paused.push_back({grab(animated_node), speed});
2568 animated_node->setAnimationSpeed(0.0f);
2571 void Game::pauseAnimation()
2573 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2576 void Game::resumeAnimation()
2578 for (auto &&pair: paused_animated_nodes)
2579 pair.first->setAnimationSpeed(pair.second);
2580 paused_animated_nodes.clear();
2583 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2584 {&Game::handleClientEvent_None},
2585 {&Game::handleClientEvent_PlayerDamage},
2586 {&Game::handleClientEvent_PlayerForceMove},
2587 {&Game::handleClientEvent_Deathscreen},
2588 {&Game::handleClientEvent_ShowFormSpec},
2589 {&Game::handleClientEvent_ShowLocalFormSpec},
2590 {&Game::handleClientEvent_HandleParticleEvent},
2591 {&Game::handleClientEvent_HandleParticleEvent},
2592 {&Game::handleClientEvent_HandleParticleEvent},
2593 {&Game::handleClientEvent_HudAdd},
2594 {&Game::handleClientEvent_HudRemove},
2595 {&Game::handleClientEvent_HudChange},
2596 {&Game::handleClientEvent_SetSky},
2597 {&Game::handleClientEvent_SetSun},
2598 {&Game::handleClientEvent_SetMoon},
2599 {&Game::handleClientEvent_SetStars},
2600 {&Game::handleClientEvent_OverrideDayNigthRatio},
2601 {&Game::handleClientEvent_CloudParams},
2604 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2606 FATAL_ERROR("ClientEvent type None received");
2609 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2611 if (client->modsLoaded())
2612 client->getScript()->on_damage_taken(event->player_damage.amount);
2614 if (!event->player_damage.effect)
2617 // Damage flash and hurt tilt are not used at death
2618 if (client->getHP() > 0) {
2619 LocalPlayer *player = client->getEnv().getLocalPlayer();
2621 f32 hp_max = player->getCAO() ?
2622 player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2623 f32 damage_ratio = event->player_damage.amount / hp_max;
2625 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2626 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2628 player->hurt_tilt_timer = 1.5f;
2629 player->hurt_tilt_strength =
2630 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2633 // Play damage sound
2634 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2637 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2639 cam->camera_yaw = event->player_force_move.yaw;
2640 cam->camera_pitch = event->player_force_move.pitch;
2643 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2645 // If client scripting is enabled, deathscreen is handled by CSM code in
2646 // builtin/client/init.lua
2647 if (client->modsLoaded())
2648 client->getScript()->on_death();
2650 showDeathFormspec();
2652 /* Handle visualization */
2653 LocalPlayer *player = client->getEnv().getLocalPlayer();
2654 runData.damage_flash = 0;
2655 player->hurt_tilt_timer = 0;
2656 player->hurt_tilt_strength = 0;
2659 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2661 if (event->show_formspec.formspec->empty()) {
2662 auto formspec = m_game_ui->getFormspecGUI();
2663 if (formspec && (event->show_formspec.formname->empty()
2664 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2665 formspec->quitMenu();
2668 FormspecFormSource *fs_src =
2669 new FormspecFormSource(*(event->show_formspec.formspec));
2670 TextDestPlayerInventory *txt_dst =
2671 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2673 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2674 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2675 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2678 delete event->show_formspec.formspec;
2679 delete event->show_formspec.formname;
2682 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2684 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2685 LocalFormspecHandler *txt_dst =
2686 new LocalFormspecHandler(*event->show_formspec.formname, client);
2687 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2688 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2690 delete event->show_formspec.formspec;
2691 delete event->show_formspec.formname;
2694 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2695 CameraOrientation *cam)
2697 LocalPlayer *player = client->getEnv().getLocalPlayer();
2698 client->getParticleManager()->handleParticleEvent(event, client, player);
2701 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2703 LocalPlayer *player = client->getEnv().getLocalPlayer();
2705 u32 server_id = event->hudadd->server_id;
2706 // ignore if we already have a HUD with that ID
2707 auto i = m_hud_server_to_client.find(server_id);
2708 if (i != m_hud_server_to_client.end()) {
2709 delete event->hudadd;
2713 HudElement *e = new HudElement;
2714 e->type = static_cast<HudElementType>(event->hudadd->type);
2715 e->pos = event->hudadd->pos;
2716 e->name = event->hudadd->name;
2717 e->scale = event->hudadd->scale;
2718 e->text = event->hudadd->text;
2719 e->number = event->hudadd->number;
2720 e->item = event->hudadd->item;
2721 e->dir = event->hudadd->dir;
2722 e->align = event->hudadd->align;
2723 e->offset = event->hudadd->offset;
2724 e->world_pos = event->hudadd->world_pos;
2725 e->size = event->hudadd->size;
2726 e->z_index = event->hudadd->z_index;
2727 e->text2 = event->hudadd->text2;
2728 e->style = event->hudadd->style;
2729 m_hud_server_to_client[server_id] = player->addHud(e);
2731 delete event->hudadd;
2734 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2736 LocalPlayer *player = client->getEnv().getLocalPlayer();
2738 auto i = m_hud_server_to_client.find(event->hudrm.id);
2739 if (i != m_hud_server_to_client.end()) {
2740 HudElement *e = player->removeHud(i->second);
2742 m_hud_server_to_client.erase(i);
2747 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2749 LocalPlayer *player = client->getEnv().getLocalPlayer();
2751 HudElement *e = nullptr;
2753 auto i = m_hud_server_to_client.find(event->hudchange->id);
2754 if (i != m_hud_server_to_client.end()) {
2755 e = player->getHud(i->second);
2759 delete event->hudchange;
2763 #define CASE_SET(statval, prop, dataprop) \
2765 e->prop = event->hudchange->dataprop; \
2768 switch (event->hudchange->stat) {
2769 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2771 CASE_SET(HUD_STAT_NAME, name, sdata);
2773 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2775 CASE_SET(HUD_STAT_TEXT, text, sdata);
2777 CASE_SET(HUD_STAT_NUMBER, number, data);
2779 CASE_SET(HUD_STAT_ITEM, item, data);
2781 CASE_SET(HUD_STAT_DIR, dir, data);
2783 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2785 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2787 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2789 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2791 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2793 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2795 CASE_SET(HUD_STAT_STYLE, style, data);
2800 delete event->hudchange;
2803 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2805 sky->setVisible(false);
2806 // Whether clouds are visible in front of a custom skybox.
2807 sky->setCloudsEnabled(event->set_sky->clouds);
2813 // Clear the old textures out in case we switch rendering type.
2814 sky->clearSkyboxTextures();
2815 // Handle according to type
2816 if (event->set_sky->type == "regular") {
2817 // Shows the mesh skybox
2818 sky->setVisible(true);
2819 // Update mesh based skybox colours if applicable.
2820 sky->setSkyColors(event->set_sky->sky_color);
2821 sky->setHorizonTint(
2822 event->set_sky->fog_sun_tint,
2823 event->set_sky->fog_moon_tint,
2824 event->set_sky->fog_tint_type
2826 } else if (event->set_sky->type == "skybox" &&
2827 event->set_sky->textures.size() == 6) {
2828 // Disable the dyanmic mesh skybox:
2829 sky->setVisible(false);
2831 sky->setFallbackBgColor(event->set_sky->bgcolor);
2832 // Set sunrise and sunset fog tinting:
2833 sky->setHorizonTint(
2834 event->set_sky->fog_sun_tint,
2835 event->set_sky->fog_moon_tint,
2836 event->set_sky->fog_tint_type
2838 // Add textures to skybox.
2839 for (int i = 0; i < 6; i++)
2840 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2842 // Handle everything else as plain color.
2843 if (event->set_sky->type != "plain")
2844 infostream << "Unknown sky type: "
2845 << (event->set_sky->type) << std::endl;
2846 sky->setVisible(false);
2847 sky->setFallbackBgColor(event->set_sky->bgcolor);
2848 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2849 sky->setHorizonTint(
2850 event->set_sky->bgcolor,
2851 event->set_sky->bgcolor,
2856 delete event->set_sky;
2859 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2861 sky->setSunVisible(event->sun_params->visible);
2862 sky->setSunTexture(event->sun_params->texture,
2863 event->sun_params->tonemap, texture_src);
2864 sky->setSunScale(event->sun_params->scale);
2865 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2866 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2867 delete event->sun_params;
2870 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2872 sky->setMoonVisible(event->moon_params->visible);
2873 sky->setMoonTexture(event->moon_params->texture,
2874 event->moon_params->tonemap, texture_src);
2875 sky->setMoonScale(event->moon_params->scale);
2876 delete event->moon_params;
2879 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2881 sky->setStarsVisible(event->star_params->visible);
2882 sky->setStarCount(event->star_params->count);
2883 sky->setStarColor(event->star_params->starcolor);
2884 sky->setStarScale(event->star_params->scale);
2885 sky->setStarDayOpacity(event->star_params->day_opacity);
2886 delete event->star_params;
2889 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2890 CameraOrientation *cam)
2892 client->getEnv().setDayNightRatioOverride(
2893 event->override_day_night_ratio.do_override,
2894 event->override_day_night_ratio.ratio_f * 1000.0f);
2897 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2902 clouds->setDensity(event->cloud_params.density);
2903 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2904 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2905 clouds->setHeight(event->cloud_params.height);
2906 clouds->setThickness(event->cloud_params.thickness);
2907 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2910 void Game::processClientEvents(CameraOrientation *cam)
2912 while (client->hasClientEvents()) {
2913 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2914 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2915 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2916 (this->*evHandler.handler)(event.get(), cam);
2920 void Game::updateChat(f32 dtime)
2922 // Get new messages from error log buffer
2923 while (!m_chat_log_buf.empty())
2924 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2926 // Get new messages from client
2927 std::wstring message;
2928 while (client->getChatMessage(message)) {
2929 chat_backend->addUnparsedMessage(message);
2932 // Remove old messages
2933 chat_backend->step(dtime);
2935 // Display all messages in a static text element
2936 auto &buf = chat_backend->getRecentBuffer();
2937 if (buf.getLinesModified()) {
2938 buf.resetLinesModified();
2939 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
2942 // Make sure that the size is still correct
2943 m_game_ui->updateChatSize();
2946 void Game::updateCamera(f32 dtime)
2948 LocalPlayer *player = client->getEnv().getLocalPlayer();
2951 For interaction purposes, get info about the held item
2953 - Is it a usable item?
2954 - Can it point to liquids?
2956 ItemStack playeritem;
2958 ItemStack selected, hand;
2959 playeritem = player->getWieldedItem(&selected, &hand);
2962 ToolCapabilities playeritem_toolcap =
2963 playeritem.getToolCapabilities(itemdef_manager);
2965 v3s16 old_camera_offset = camera->getOffset();
2967 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2968 GenericCAO *playercao = player->getCAO();
2970 // If playercao not loaded, don't change camera
2974 camera->toggleCameraMode();
2976 // Make the player visible depending on camera mode.
2977 playercao->updateMeshCulling();
2978 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2981 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2982 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2984 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2985 camera->update(player, dtime, tool_reload_ratio);
2986 camera->step(dtime);
2988 f32 camera_fov = camera->getFovMax();
2989 v3s16 camera_offset = camera->getOffset();
2991 m_camera_offset_changed = (camera_offset != old_camera_offset);
2993 if (!m_flags.disable_camera_update) {
2994 v3f camera_position = camera->getPosition();
2995 v3f camera_direction = camera->getDirection();
2997 client->getEnv().getClientMap().updateCamera(camera_position,
2998 camera_direction, camera_fov, camera_offset);
3000 if (m_camera_offset_changed) {
3001 client->updateCameraOffset(camera_offset);
3002 client->getEnv().updateCameraOffset(camera_offset);
3005 clouds->updateCameraOffset(camera_offset);
3011 void Game::updateSound(f32 dtime)
3013 // Update sound listener
3014 v3s16 camera_offset = camera->getOffset();
3015 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3016 v3f(0, 0, 0), // velocity
3017 camera->getDirection(),
3018 camera->getCameraNode()->getUpVector());
3020 bool mute_sound = g_settings->getBool("mute_sound");
3022 sound->setListenerGain(0.0f);
3024 // Check if volume is in the proper range, else fix it.
3025 float old_volume = g_settings->getFloat("sound_volume");
3026 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3027 sound->setListenerGain(new_volume);
3029 if (old_volume != new_volume) {
3030 g_settings->setFloat("sound_volume", new_volume);
3034 LocalPlayer *player = client->getEnv().getLocalPlayer();
3036 // Tell the sound maker whether to make footstep sounds
3037 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3039 // Update sound maker
3040 if (player->makes_footstep_sound)
3041 soundmaker->step(dtime);
3043 ClientMap &map = client->getEnv().getClientMap();
3044 MapNode n = map.getNode(player->getFootstepNodePos());
3045 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3049 void Game::processPlayerInteraction(f32 dtime, bool show_hud)
3051 LocalPlayer *player = client->getEnv().getLocalPlayer();
3053 const v3f camera_direction = camera->getDirection();
3054 const v3s16 camera_offset = camera->getOffset();
3057 Calculate what block is the crosshair pointing to
3060 ItemStack selected_item, hand_item;
3061 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3063 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3064 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3066 core::line3d<f32> shootline;
3068 switch (camera->getCameraMode()) {
3069 case CAMERA_MODE_FIRST:
3070 // Shoot from camera position, with bobbing
3071 shootline.start = camera->getPosition();
3073 case CAMERA_MODE_THIRD:
3074 // Shoot from player head, no bobbing
3075 shootline.start = camera->getHeadPosition();
3077 case CAMERA_MODE_THIRD_FRONT:
3078 shootline.start = camera->getHeadPosition();
3079 // prevent player pointing anything in front-view
3083 shootline.end = shootline.start + camera_direction * BS * d;
3085 #ifdef HAVE_TOUCHSCREENGUI
3087 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3088 shootline = g_touchscreengui->getShootline();
3089 // Scale shootline to the acual distance the player can reach
3090 shootline.end = shootline.start
3091 + shootline.getVector().normalize() * BS * d;
3092 shootline.start += intToFloat(camera_offset, BS);
3093 shootline.end += intToFloat(camera_offset, BS);
3098 PointedThing pointed = updatePointedThing(shootline,
3099 selected_def.liquids_pointable,
3100 !runData.btn_down_for_dig,
3103 if (pointed != runData.pointed_old)
3104 infostream << "Pointing at " << pointed.dump() << std::endl;
3106 // Note that updating the selection mesh every frame is not particularly efficient,
3107 // but the halo rendering code is already inefficient so there's no point in optimizing it here
3108 hud->updateSelectionMesh(camera_offset);
3110 // Allow digging again if button is not pressed
3111 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3112 runData.digging_blocked = false;
3116 - releasing dig button
3117 - pointing away from node
3119 if (runData.digging) {
3120 if (wasKeyReleased(KeyType::DIG)) {
3121 infostream << "Dig button released (stopped digging)" << std::endl;
3122 runData.digging = false;
3123 } else if (pointed != runData.pointed_old) {
3124 if (pointed.type == POINTEDTHING_NODE
3125 && runData.pointed_old.type == POINTEDTHING_NODE
3126 && pointed.node_undersurface
3127 == runData.pointed_old.node_undersurface) {
3128 // Still pointing to the same node, but a different face.
3131 infostream << "Pointing away from node (stopped digging)" << std::endl;
3132 runData.digging = false;
3133 hud->updateSelectionMesh(camera_offset);
3137 if (!runData.digging) {
3138 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3139 client->setCrack(-1, v3s16(0, 0, 0));
3140 runData.dig_time = 0.0;
3142 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3143 // Remove e.g. torches faster when clicking instead of holding dig button
3144 runData.nodig_delay_timer = 0;
3145 runData.dig_instantly = false;
3148 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3149 runData.btn_down_for_dig = false;
3151 runData.punching = false;
3153 soundmaker->m_player_leftpunch_sound.name.clear();
3155 // Prepare for repeating, unless we're not supposed to
3156 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3157 runData.repeat_place_timer += dtime;
3159 runData.repeat_place_timer = 0;
3161 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3162 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3163 !client->getScript()->on_item_use(selected_item, pointed)))
3164 client->interact(INTERACT_USE, pointed);
3165 } else if (pointed.type == POINTEDTHING_NODE) {
3166 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3167 } else if (pointed.type == POINTEDTHING_OBJECT) {
3168 v3f player_position = player->getPosition();
3169 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
3170 handlePointingAtObject(pointed, tool_item, player_position,
3171 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
3172 } else if (isKeyDown(KeyType::DIG)) {
3173 // When button is held down in air, show continuous animation
3174 runData.punching = true;
3175 // Run callback even though item is not usable
3176 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3177 client->getScript()->on_item_use(selected_item, pointed);
3178 } else if (wasKeyPressed(KeyType::PLACE)) {
3179 handlePointingAtNothing(selected_item);
3182 runData.pointed_old = pointed;
3184 if (runData.punching || wasKeyPressed(KeyType::DIG))
3185 camera->setDigging(0); // dig animation
3187 input->clearWasKeyPressed();
3188 input->clearWasKeyReleased();
3189 // Ensure DIG & PLACE are marked as handled
3190 wasKeyDown(KeyType::DIG);
3191 wasKeyDown(KeyType::PLACE);
3193 input->joystick.clearWasKeyPressed(KeyType::DIG);
3194 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3196 input->joystick.clearWasKeyReleased(KeyType::DIG);
3197 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3201 PointedThing Game::updatePointedThing(
3202 const core::line3d<f32> &shootline,
3203 bool liquids_pointable,
3204 bool look_for_object,
3205 const v3s16 &camera_offset)
3207 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3208 selectionboxes->clear();
3209 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3210 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3211 "show_entity_selectionbox");
3213 ClientEnvironment &env = client->getEnv();
3214 ClientMap &map = env.getClientMap();
3215 const NodeDefManager *nodedef = map.getNodeDefManager();
3217 runData.selected_object = NULL;
3218 hud->pointing_at_object = false;
3220 RaycastState s(shootline, look_for_object, liquids_pointable);
3221 PointedThing result;
3222 env.continueRaycast(&s, &result);
3223 if (result.type == POINTEDTHING_OBJECT) {
3224 hud->pointing_at_object = true;
3226 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3227 aabb3f selection_box;
3228 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3229 runData.selected_object->getSelectionBox(&selection_box)) {
3230 v3f pos = runData.selected_object->getPosition();
3231 selectionboxes->push_back(aabb3f(selection_box));
3232 hud->setSelectionPos(pos, camera_offset);
3234 } else if (result.type == POINTEDTHING_NODE) {
3235 // Update selection boxes
3236 MapNode n = map.getNode(result.node_undersurface);
3237 std::vector<aabb3f> boxes;
3238 n.getSelectionBoxes(nodedef, &boxes,
3239 n.getNeighbors(result.node_undersurface, &map));
3242 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3243 i != boxes.end(); ++i) {
3245 box.MinEdge -= v3f(d, d, d);
3246 box.MaxEdge += v3f(d, d, d);
3247 selectionboxes->push_back(box);
3249 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3251 hud->setSelectedFaceNormal(v3f(
3252 result.intersection_normal.X,
3253 result.intersection_normal.Y,
3254 result.intersection_normal.Z));
3257 // Update selection mesh light level and vertex colors
3258 if (!selectionboxes->empty()) {
3259 v3f pf = hud->getSelectionPos();
3260 v3s16 p = floatToInt(pf, BS);
3262 // Get selection mesh light level
3263 MapNode n = map.getNode(p);
3264 u16 node_light = getInteriorLight(n, -1, nodedef);
3265 u16 light_level = node_light;
3267 for (const v3s16 &dir : g_6dirs) {
3268 n = map.getNode(p + dir);
3269 node_light = getInteriorLight(n, -1, nodedef);
3270 if (node_light > light_level)
3271 light_level = node_light;
3274 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3276 final_color_blend(&c, light_level, daynight_ratio);
3278 // Modify final color a bit with time
3279 u32 timer = client->getEnv().getFrameTime() % 5000;
3280 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3281 float sin_r = 0.08f * std::sin(timerf);
3282 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3283 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3284 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3285 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3286 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3288 // Set mesh final color
3289 hud->setSelectionMeshColor(c);
3295 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3297 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3298 PointedThing fauxPointed;
3299 fauxPointed.type = POINTEDTHING_NOTHING;
3300 client->interact(INTERACT_ACTIVATE, fauxPointed);
3304 void Game::handlePointingAtNode(const PointedThing &pointed,
3305 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3307 v3s16 nodepos = pointed.node_undersurface;
3308 v3s16 neighbourpos = pointed.node_abovesurface;
3311 Check information text of node
3314 ClientMap &map = client->getEnv().getClientMap();
3316 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3317 && !runData.digging_blocked
3318 && client->checkPrivilege("interact")) {
3319 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3322 // This should be done after digging handling
3323 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3326 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3327 meta->getString("infotext"))));
3329 MapNode n = map.getNode(nodepos);
3331 if (nodedef_manager->get(n).name == "unknown") {
3332 m_game_ui->setInfoText(L"Unknown node");
3336 if ((wasKeyPressed(KeyType::PLACE) ||
3337 runData.repeat_place_timer >= m_repeat_place_time) &&
3338 client->checkPrivilege("interact")) {
3339 runData.repeat_place_timer = 0;
3340 infostream << "Place button pressed while looking at ground" << std::endl;
3342 // Placing animation (always shown for feedback)
3343 camera->setDigging(1);
3345 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3347 // If the wielded item has node placement prediction,
3349 // And also set the sound and send the interact
3350 // But first check for meta formspec and rightclickable
3351 auto &def = selected_item.getDefinition(itemdef_manager);
3352 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3355 if (placed && client->modsLoaded())
3356 client->getScript()->on_placenode(pointed, def);
3360 bool Game::nodePlacement(const ItemDefinition &selected_def,
3361 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3362 const PointedThing &pointed, const NodeMetadata *meta)
3364 const auto &prediction = selected_def.node_placement_prediction;
3366 const NodeDefManager *nodedef = client->ndef();
3367 ClientMap &map = client->getEnv().getClientMap();
3369 bool is_valid_position;
3371 node = map.getNode(nodepos, &is_valid_position);
3372 if (!is_valid_position) {
3373 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3378 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3379 && !isKeyDown(KeyType::SNEAK)) {
3380 // on_rightclick callbacks are called anyway
3381 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3382 client->interact(INTERACT_PLACE, pointed);
3384 infostream << "Launching custom inventory view" << std::endl;
3386 InventoryLocation inventoryloc;
3387 inventoryloc.setNodeMeta(nodepos);
3389 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3390 &client->getEnv().getClientMap(), nodepos);
3391 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3393 auto *&formspec = m_game_ui->updateFormspec("");
3394 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3395 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3397 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3401 // on_rightclick callback
3402 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3403 !isKeyDown(KeyType::SNEAK))) {
3405 client->interact(INTERACT_PLACE, pointed);
3409 verbosestream << "Node placement prediction for "
3410 << selected_def.name << " is " << prediction << std::endl;
3411 v3s16 p = neighbourpos;
3413 // Place inside node itself if buildable_to
3414 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3415 if (is_valid_position) {
3416 if (nodedef->get(n_under).buildable_to) {
3419 node = map.getNode(p, &is_valid_position);
3420 if (is_valid_position && !nodedef->get(node).buildable_to) {
3421 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3423 client->interact(INTERACT_PLACE, pointed);
3429 // Find id of predicted node
3431 bool found = nodedef->getId(prediction, id);
3434 errorstream << "Node placement prediction failed for "
3435 << selected_def.name << " (places " << prediction
3436 << ") - Name not known" << std::endl;
3437 // Handle this as if prediction was empty
3439 client->interact(INTERACT_PLACE, pointed);
3443 const ContentFeatures &predicted_f = nodedef->get(id);
3445 // Predict param2 for facedir and wallmounted nodes
3446 // Compare core.item_place_node() for what the server does
3449 const u8 place_param2 = selected_def.place_param2;
3452 param2 = place_param2;
3453 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3454 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3455 v3s16 dir = nodepos - neighbourpos;
3457 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3458 param2 = dir.Y < 0 ? 1 : 0;
3459 } else if (abs(dir.X) > abs(dir.Z)) {
3460 param2 = dir.X < 0 ? 3 : 2;
3462 param2 = dir.Z < 0 ? 5 : 4;
3464 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3465 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3466 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3468 if (abs(dir.X) > abs(dir.Z)) {
3469 param2 = dir.X < 0 ? 3 : 1;
3471 param2 = dir.Z < 0 ? 2 : 0;
3475 // Check attachment if node is in group attached_node
3476 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3477 const static v3s16 wallmounted_dirs[8] = {
3487 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3488 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3489 pp = p + wallmounted_dirs[param2];
3491 pp = p + v3s16(0, -1, 0);
3493 if (!nodedef->get(map.getNode(pp)).walkable) {
3494 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3496 client->interact(INTERACT_PLACE, pointed);
3502 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3503 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3504 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3505 const auto &indexstr = selected_item.metadata.
3506 getString("palette_index", 0);
3507 if (!indexstr.empty()) {
3508 s32 index = mystoi(indexstr);
3509 if (predicted_f.param_type_2 == CPT2_COLOR) {
3511 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3512 // param2 = pure palette index + other
3513 param2 = (index & 0xf8) | (param2 & 0x07);
3514 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3515 // param2 = pure palette index + other
3516 param2 = (index & 0xe0) | (param2 & 0x1f);
3521 // Add node to client map
3522 MapNode n(id, 0, param2);
3525 LocalPlayer *player = client->getEnv().getLocalPlayer();
3527 // Dont place node when player would be inside new node
3528 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3529 if (!nodedef->get(n).walkable ||
3530 g_settings->getBool("enable_build_where_you_stand") ||
3531 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3532 (nodedef->get(n).walkable &&
3533 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3534 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3535 // This triggers the required mesh update too
3536 client->addNode(p, n);
3538 client->interact(INTERACT_PLACE, pointed);
3539 // A node is predicted, also play a sound
3540 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3543 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3546 } catch (const InvalidPositionException &e) {
3547 errorstream << "Node placement prediction failed for "
3548 << selected_def.name << " (places "
3549 << prediction << ") - Position not loaded" << std::endl;
3550 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3555 void Game::handlePointingAtObject(const PointedThing &pointed,
3556 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3558 std::wstring infotext = unescape_translate(
3559 utf8_to_wide(runData.selected_object->infoText()));
3562 if (!infotext.empty()) {
3565 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3568 m_game_ui->setInfoText(infotext);
3570 if (isKeyDown(KeyType::DIG)) {
3571 bool do_punch = false;
3572 bool do_punch_damage = false;
3574 if (runData.object_hit_delay_timer <= 0.0) {
3576 do_punch_damage = true;
3577 runData.object_hit_delay_timer = object_hit_delay;
3580 if (wasKeyPressed(KeyType::DIG))
3584 infostream << "Punched object" << std::endl;
3585 runData.punching = true;
3588 if (do_punch_damage) {
3589 // Report direct punch
3590 v3f objpos = runData.selected_object->getPosition();
3591 v3f dir = (objpos - player_position).normalize();
3593 bool disable_send = runData.selected_object->directReportPunch(
3594 dir, &tool_item, runData.time_from_last_punch);
3595 runData.time_from_last_punch = 0;
3598 client->interact(INTERACT_START_DIGGING, pointed);
3600 } else if (wasKeyDown(KeyType::PLACE)) {
3601 infostream << "Pressed place button while pointing at object" << std::endl;
3602 client->interact(INTERACT_PLACE, pointed); // place
3607 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3608 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3610 // See also: serverpackethandle.cpp, action == 2
3611 LocalPlayer *player = client->getEnv().getLocalPlayer();
3612 ClientMap &map = client->getEnv().getClientMap();
3613 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3615 // NOTE: Similar piece of code exists on the server side for
3617 // Get digging parameters
3618 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3619 &selected_item.getToolCapabilities(itemdef_manager),
3620 selected_item.wear);
3622 // If can't dig, try hand
3623 if (!params.diggable) {
3624 params = getDigParams(nodedef_manager->get(n).groups,
3625 &hand_item.getToolCapabilities(itemdef_manager));
3628 if (!params.diggable) {
3629 // I guess nobody will wait for this long
3630 runData.dig_time_complete = 10000000.0;
3632 runData.dig_time_complete = params.time;
3634 if (m_cache_enable_particles) {
3635 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3636 client->getParticleManager()->addNodeParticle(client,
3637 player, nodepos, n, features);
3641 if (!runData.digging) {
3642 infostream << "Started digging" << std::endl;
3643 runData.dig_instantly = runData.dig_time_complete == 0;
3644 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3646 client->interact(INTERACT_START_DIGGING, pointed);
3647 runData.digging = true;
3648 runData.btn_down_for_dig = true;
3651 if (!runData.dig_instantly) {
3652 runData.dig_index = (float)crack_animation_length
3654 / runData.dig_time_complete;
3656 // This is for e.g. torches
3657 runData.dig_index = crack_animation_length;
3660 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3662 if (sound_dig.exists() && params.diggable) {
3663 if (sound_dig.name == "__group") {
3664 if (!params.main_group.empty()) {
3665 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3666 soundmaker->m_player_leftpunch_sound.name =
3667 std::string("default_dig_") +
3671 soundmaker->m_player_leftpunch_sound = sound_dig;
3675 // Don't show cracks if not diggable
3676 if (runData.dig_time_complete >= 100000.0) {
3677 } else if (runData.dig_index < crack_animation_length) {
3678 //TimeTaker timer("client.setTempMod");
3679 //infostream<<"dig_index="<<dig_index<<std::endl;
3680 client->setCrack(runData.dig_index, nodepos);
3682 infostream << "Digging completed" << std::endl;
3683 client->setCrack(-1, v3s16(0, 0, 0));
3685 runData.dig_time = 0;
3686 runData.digging = false;
3687 // we successfully dug, now block it from repeating if we want to be safe
3688 if (g_settings->getBool("safe_dig_and_place"))
3689 runData.digging_blocked = true;
3691 runData.nodig_delay_timer =
3692 runData.dig_time_complete / (float)crack_animation_length;
3694 // We don't want a corresponding delay to very time consuming nodes
3695 // and nodes without digging time (e.g. torches) get a fixed delay.
3696 if (runData.nodig_delay_timer > 0.3)
3697 runData.nodig_delay_timer = 0.3;
3698 else if (runData.dig_instantly)
3699 runData.nodig_delay_timer = 0.15;
3701 bool is_valid_position;
3702 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3703 if (is_valid_position) {
3704 if (client->modsLoaded() &&
3705 client->getScript()->on_dignode(nodepos, wasnode)) {
3709 const ContentFeatures &f = client->ndef()->get(wasnode);
3710 if (f.node_dig_prediction == "air") {
3711 client->removeNode(nodepos);
3712 } else if (!f.node_dig_prediction.empty()) {
3714 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3716 client->addNode(nodepos, id, true);
3718 // implicit else: no prediction
3721 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3723 if (m_cache_enable_particles) {
3724 const ContentFeatures &features =
3725 client->getNodeDefManager()->get(wasnode);
3726 client->getParticleManager()->addDiggingParticles(client,
3727 player, nodepos, wasnode, features);
3731 // Send event to trigger sound
3732 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3735 if (runData.dig_time_complete < 100000.0) {
3736 runData.dig_time += dtime;
3738 runData.dig_time = 0;
3739 client->setCrack(-1, nodepos);
3742 camera->setDigging(0); // Dig animation
3745 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3746 const CameraOrientation &cam)
3748 TimeTaker tt_update("Game::updateFrame()");
3749 LocalPlayer *player = client->getEnv().getLocalPlayer();
3755 client->getEnv().updateFrameTime();
3761 if (draw_control->range_all) {
3762 runData.fog_range = 100000 * BS;
3764 runData.fog_range = draw_control->wanted_range * BS;
3768 Calculate general brightness
3770 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3771 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3772 float direct_brightness;
3775 // When in noclip mode force same sky brightness as above ground so you
3777 if (draw_control->allow_noclip && m_cache_enable_free_move &&
3778 client->checkPrivilege("fly")) {
3779 direct_brightness = time_brightness;
3780 sunlight_seen = true;
3782 float old_brightness = sky->getBrightness();
3783 direct_brightness = client->getEnv().getClientMap()
3784 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3785 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3789 float time_of_day_smooth = runData.time_of_day_smooth;
3790 float time_of_day = client->getEnv().getTimeOfDayF();
3792 static const float maxsm = 0.05f;
3793 static const float todsm = 0.05f;
3795 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3796 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3797 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3798 time_of_day_smooth = time_of_day;
3800 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3801 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3802 + (time_of_day + 1.0) * todsm;
3804 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3805 + time_of_day * todsm;
3807 runData.time_of_day_smooth = time_of_day_smooth;
3809 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3810 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3811 player->getPitch());
3817 if (sky->getCloudsVisible()) {
3818 clouds->setVisible(true);
3819 clouds->step(dtime);
3820 // camera->getPosition is not enough for 3rd person views
3821 v3f camera_node_position = camera->getCameraNode()->getPosition();
3822 v3s16 camera_offset = camera->getOffset();
3823 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3824 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3825 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3826 clouds->update(camera_node_position,
3827 sky->getCloudColor());
3828 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3829 // if inside clouds, and fog enabled, use that as sky
3831 video::SColor clouds_dark = clouds->getColor()
3832 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3833 sky->overrideColors(clouds_dark, clouds->getColor());
3834 sky->setInClouds(true);
3835 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3836 // do not draw clouds after all
3837 clouds->setVisible(false);
3840 clouds->setVisible(false);
3847 client->getParticleManager()->step(dtime);
3853 if (m_cache_enable_fog) {
3856 video::EFT_FOG_LINEAR,
3857 runData.fog_range * m_cache_fog_start,
3858 runData.fog_range * 1.0,
3866 video::EFT_FOG_LINEAR,
3878 if (player->hurt_tilt_timer > 0.0f) {
3879 player->hurt_tilt_timer -= dtime * 6.0f;
3881 if (player->hurt_tilt_timer < 0.0f)
3882 player->hurt_tilt_strength = 0.0f;
3886 Update minimap pos and rotation
3888 if (mapper && m_game_ui->m_flags.show_hud) {
3889 mapper->setPos(floatToInt(player->getPosition(), BS));
3890 mapper->setAngle(player->getYaw());
3894 Get chat messages from client
3903 if (player->getWieldIndex() != runData.new_playeritem)
3904 client->setPlayerItem(runData.new_playeritem);
3906 if (client->updateWieldedItem()) {
3907 // Update wielded tool
3908 ItemStack selected_item, hand_item;
3909 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3910 camera->wield(tool_item);
3914 Update block draw list every 200ms or when camera direction has
3917 runData.update_draw_list_timer += dtime;
3919 float update_draw_list_delta = 0.2f;
3921 v3f camera_direction = camera->getDirection();
3922 if (runData.update_draw_list_timer >= update_draw_list_delta
3923 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3924 || m_camera_offset_changed
3925 || client->getEnv().getClientMap().needsUpdateDrawList()) {
3926 runData.update_draw_list_timer = 0;
3927 client->getEnv().getClientMap().updateDrawList();
3928 runData.update_draw_list_last_cam_dir = camera_direction;
3931 if (RenderingEngine::get_shadow_renderer()) {
3935 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3938 make sure menu is on top
3939 1. Delete formspec menu reference if menu was removed
3940 2. Else, make sure formspec menu is on top
3942 auto formspec = m_game_ui->getFormspecGUI();
3943 do { // breakable. only runs for one iteration
3947 if (formspec->getReferenceCount() == 1) {
3948 m_game_ui->deleteFormspec();
3952 auto &loc = formspec->getFormspecLocation();
3953 if (loc.type == InventoryLocation::NODEMETA) {
3954 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3955 if (!meta || meta->getString("formspec").empty()) {
3956 formspec->quitMenu();
3962 guiroot->bringToFront(formspec);
3966 ==================== Drawing begins ====================
3968 const video::SColor skycolor = sky->getSkyColor();
3970 TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
3971 driver->beginScene(true, true, skycolor);
3973 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3974 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3975 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3976 bool draw_crosshair = (
3977 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3978 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3979 #ifdef HAVE_TOUCHSCREENGUI
3981 draw_crosshair = !g_settings->getBool("touchtarget");
3982 } catch (SettingNotFoundException) {
3985 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3986 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3991 v2u32 screensize = driver->getScreenSize();
3993 if (m_game_ui->m_flags.show_profiler_graph)
3994 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3999 if (runData.damage_flash > 0.0f) {
4000 video::SColor color(runData.damage_flash, 180, 0, 0);
4001 driver->draw2DRectangle(color,
4002 core::rect<s32>(0, 0, screensize.X, screensize.Y),
4005 runData.damage_flash -= 384.0f * dtime;
4009 ==================== End scene ====================
4011 #if IRRLICHT_VERSION_MT_REVISION < 5
4012 if (++m_reset_HW_buffer_counter > 500) {
4014 Periodically remove all mesh HW buffers.
4016 Work around for a quirk in Irrlicht where a HW buffer is only
4017 released after 20000 iterations (triggered from endScene()).
4019 Without this, all loaded but unused meshes will retain their HW
4020 buffers for at least 5 minutes, at which point looking up the HW buffers
4021 becomes a bottleneck and the framerate drops (as much as 30%).
4023 Tests showed that numbers between 50 and 1000 are good, so picked 500.
4024 There are no other public Irrlicht APIs that allow interacting with the
4025 HW buffers without tracking the status of every individual mesh.
4027 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4029 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4030 driver->removeAllHardwareBuffers();
4031 m_reset_HW_buffer_counter = 0;
4037 stats->drawtime = tt_draw.stop(true);
4038 g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
4039 g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
4042 /* Log times and stuff for visualization */
4043 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4045 Profiler::GraphValues values;
4046 g_profiler->graphGet(values);
4050 /****************************************************************************
4052 *****************************************************************************/
4053 void Game::updateShadows()
4055 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4059 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4061 float timeoftheday = getWickedTimeOfDay(in_timeofday);
4062 bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
4063 bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
4064 shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
4066 timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
4067 const float offset_constant = 10000.0f;
4069 v3f light = is_day ? sky->getSunDirection() : sky->getMoonDirection();
4071 v3f sun_pos = light * offset_constant;
4073 if (shadow->getDirectionalLightCount() == 0)
4074 shadow->addDirectionalLight();
4075 shadow->getDirectionalLight().setDirection(sun_pos);
4076 shadow->setTimeOfDay(in_timeofday);
4078 shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4081 /****************************************************************************
4083 ****************************************************************************/
4085 void FpsControl::reset()
4087 last_time = porting::getTimeUs();
4091 * On some computers framerate doesn't seem to be automatically limited
4093 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
4095 const float fps_limit = (device->isWindowFocused() && !g_menumgr.pausesGame())
4096 ? g_settings->getFloat("fps_max")
4097 : g_settings->getFloat("fps_max_unfocused");
4098 const u64 frametime_min = 1000000.0f / std::max(fps_limit, 1.0f);
4100 u64 time = porting::getTimeUs();
4102 if (time > last_time) // Make sure time hasn't overflowed
4103 busy_time = time - last_time;
4107 if (busy_time < frametime_min) {
4108 sleep_time = frametime_min - busy_time;
4109 if (sleep_time > 1000)
4110 sleep_ms(sleep_time / 1000);
4115 // Read the timer again to accurately determine how long we actually slept,
4116 // rather than calculating it by adding sleep_time to time.
4117 time = porting::getTimeUs();
4119 if (time > last_time) // Make sure last_time hasn't overflowed
4120 *dtime = (time - last_time) / 1000000.0f;
4127 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4129 const wchar_t *wmsg = wgettext(msg);
4130 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4135 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4137 ((Game *)data)->readSettings();
4140 void Game::readSettings()
4142 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4143 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4144 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4145 m_cache_enable_particles = g_settings->getBool("enable_particles");
4146 m_cache_enable_fog = g_settings->getBool("enable_fog");
4147 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f);
4148 m_cache_joystick_frustum_sensitivity = std::max(g_settings->getFloat("joystick_frustum_sensitivity"), 0.001f);
4149 m_repeat_place_time = g_settings->getFloat("repeat_place_time", 0.25f, 2.0);
4151 m_cache_enable_noclip = g_settings->getBool("noclip");
4152 m_cache_enable_free_move = g_settings->getBool("free_move");
4154 m_cache_fog_start = g_settings->getFloat("fog_start");
4156 m_cache_cam_smoothing = 0;
4157 if (g_settings->getBool("cinematic"))
4158 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4160 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4162 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4163 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4164 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4166 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4169 /****************************************************************************/
4170 /****************************************************************************
4172 ****************************************************************************/
4173 /****************************************************************************/
4175 void Game::showDeathFormspec()
4177 static std::string formspec_str =
4178 std::string("formspec_version[1]") +
4180 "bgcolor[#320000b4;true]"
4181 "label[4.85,1.35;" + gettext("You died") + "]"
4182 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4186 /* Note: FormspecFormSource and LocalFormspecHandler *
4187 * are deleted by guiFormSpecMenu */
4188 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4189 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4191 auto *&formspec = m_game_ui->getFormspecGUI();
4192 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4193 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4194 formspec->setFocus("btn_respawn");
4197 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4198 void Game::showPauseMenu()
4200 #ifdef HAVE_TOUCHSCREENGUI
4201 static const std::string control_text = strgettext("Default Controls:\n"
4202 "No menu visible:\n"
4203 "- single tap: button activate\n"
4204 "- double tap: place/use\n"
4205 "- slide finger: look around\n"
4206 "Menu/Inventory visible:\n"
4207 "- double tap (outside):\n"
4209 "- touch stack, touch slot:\n"
4211 "- touch&drag, tap 2nd finger\n"
4212 " --> place single item to slot\n"
4215 static const std::string control_text_template = strgettext("Controls:\n"
4216 "- %s: move forwards\n"
4217 "- %s: move backwards\n"
4219 "- %s: move right\n"
4220 "- %s: jump/climb up\n"
4223 "- %s: sneak/climb down\n"
4226 "- Mouse: turn/look\n"
4227 "- Mouse wheel: select item\n"
4231 char control_text_buf[600];
4233 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4234 GET_KEY_NAME(keymap_forward),
4235 GET_KEY_NAME(keymap_backward),
4236 GET_KEY_NAME(keymap_left),
4237 GET_KEY_NAME(keymap_right),
4238 GET_KEY_NAME(keymap_jump),
4239 GET_KEY_NAME(keymap_dig),
4240 GET_KEY_NAME(keymap_place),
4241 GET_KEY_NAME(keymap_sneak),
4242 GET_KEY_NAME(keymap_drop),
4243 GET_KEY_NAME(keymap_inventory),
4244 GET_KEY_NAME(keymap_chat)
4247 std::string control_text = std::string(control_text_buf);
4248 str_formspec_escape(control_text);
4251 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4252 std::ostringstream os;
4254 os << "formspec_version[1]" << SIZE_TAG
4255 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4256 << strgettext("Continue") << "]";
4258 if (!simple_singleplayer_mode) {
4259 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4260 << strgettext("Change Password") << "]";
4262 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4267 if (g_settings->getBool("enable_sound")) {
4268 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4269 << strgettext("Sound Volume") << "]";
4272 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4273 << strgettext("Change Keys") << "]";
4275 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4276 << strgettext("Exit to Menu") << "]";
4277 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4278 << strgettext("Exit to OS") << "]"
4279 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4280 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4282 << strgettext("Game info:") << "\n";
4283 const std::string &address = client->getAddressName();
4284 static const std::string mode = strgettext("- Mode: ");
4285 if (!simple_singleplayer_mode) {
4286 Address serverAddress = client->getServerAddress();
4287 if (!address.empty()) {
4288 os << mode << strgettext("Remote server") << "\n"
4289 << strgettext("- Address: ") << address;
4291 os << mode << strgettext("Hosting server");
4293 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4295 os << mode << strgettext("Singleplayer") << "\n";
4297 if (simple_singleplayer_mode || address.empty()) {
4298 static const std::string on = strgettext("On");
4299 static const std::string off = strgettext("Off");
4300 // Note: Status of enable_damage and creative_mode settings is intentionally
4301 // NOT shown here because the game might roll its own damage system and/or do
4302 // a per-player Creative Mode, in which case writing it here would mislead.
4303 bool damage = g_settings->getBool("enable_damage");
4304 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4305 if (!simple_singleplayer_mode) {
4307 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4308 //~ PvP = Player versus Player
4309 os << strgettext("- PvP: ") << pvp << "\n";
4311 os << strgettext("- Public: ") << announced << "\n";
4312 std::string server_name = g_settings->get("server_name");
4313 str_formspec_escape(server_name);
4314 if (announced == on && !server_name.empty())
4315 os << strgettext("- Server Name: ") << server_name;
4322 /* Note: FormspecFormSource and LocalFormspecHandler *
4323 * are deleted by guiFormSpecMenu */
4324 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4325 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4327 auto *&formspec = m_game_ui->getFormspecGUI();
4328 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4329 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4330 formspec->setFocus("btn_continue");
4331 formspec->doPause = true;
4333 if (simple_singleplayer_mode)
4337 /****************************************************************************/
4338 /****************************************************************************
4339 extern function for launching the game
4340 ****************************************************************************/
4341 /****************************************************************************/
4343 void the_game(bool *kill,
4344 InputHandler *input,
4345 RenderingEngine *rendering_engine,
4346 const GameStartData &start_data,
4347 std::string &error_message,
4348 ChatBackend &chat_backend,
4349 bool *reconnect_requested) // Used for local game
4353 /* Make a copy of the server address because if a local singleplayer server
4354 * is created then this is updated and we don't want to change the value
4355 * passed to us by the calling function
4360 if (game.startup(kill, input, rendering_engine, start_data,
4361 error_message, reconnect_requested, &chat_backend)) {
4365 } catch (SerializationError &e) {
4366 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
4367 error_message = strgettext("A serialization error occurred:") +"\n"
4368 + e.what() + "\n\n" + ver_err;
4369 errorstream << error_message << std::endl;
4370 } catch (ServerError &e) {
4371 error_message = e.what();
4372 errorstream << "ServerError: " << error_message << std::endl;
4373 } catch (ModError &e) {
4374 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
4375 error_message = std::string("ModError: ") + e.what() +
4376 strgettext("\nCheck debug.txt for details.");
4377 errorstream << error_message << std::endl;