3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "client/renderingengine.h"
27 #include "client/clientevent.h"
28 #include "client/gameui.h"
29 #include "client/inputhandler.h"
30 #include "client/tile.h" // For TextureSource
31 #include "client/keys.h"
32 #include "client/joystick_controller.h"
33 #include "clientmap.h"
36 #include "content_cao.h"
37 #include "content/subgames.h"
38 #include "client/event_manager.h"
39 #include "fontengine.h"
43 #include "gameparams.h"
45 #include "gui/guiChatConsole.h"
46 #include "gui/guiConfirmRegistration.h"
47 #include "gui/guiFormSpecMenu.h"
48 #include "gui/guiKeyChangeMenu.h"
49 #include "gui/guiPasswordChange.h"
50 #include "gui/guiVolumeChange.h"
51 #include "gui/mainmenumanager.h"
52 #include "gui/profilergraph.h"
55 #include "nodedef.h" // Needed for determining pointing to nodes
56 #include "nodemetadata.h"
57 #include "particles.h"
65 #include "translation.h"
66 #include "util/basic_macros.h"
67 #include "util/directiontables.h"
68 #include "util/pointedthing.h"
69 #include "util/quicktune_shortcutter.h"
70 #include "irrlicht_changes/static_text.h"
73 #include "script/scripting_client.h"
77 #include "client/sound_openal.h"
79 #include "client/sound.h"
85 struct TextDestNodeMetadata : public TextDest
87 TextDestNodeMetadata(v3s16 p, Client *client)
92 // This is deprecated I guess? -celeron55
93 void gotText(const std::wstring &text)
95 std::string ntext = wide_to_utf8(text);
96 infostream << "Submitting 'text' field of node at (" << m_p.X << ","
97 << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
99 fields["text"] = ntext;
100 m_client->sendNodemetaFields(m_p, "", fields);
102 void gotText(const StringMap &fields)
104 m_client->sendNodemetaFields(m_p, "", fields);
111 struct TextDestPlayerInventory : public TextDest
113 TextDestPlayerInventory(Client *client)
118 TextDestPlayerInventory(Client *client, const std::string &formname)
121 m_formname = formname;
123 void gotText(const StringMap &fields)
125 m_client->sendInventoryFields(m_formname, fields);
131 struct LocalFormspecHandler : public TextDest
133 LocalFormspecHandler(const std::string &formname)
135 m_formname = formname;
138 LocalFormspecHandler(const std::string &formname, Client *client):
141 m_formname = formname;
144 void gotText(const StringMap &fields)
146 if (m_formname == "MT_PAUSE_MENU") {
147 if (fields.find("btn_sound") != fields.end()) {
148 g_gamecallback->changeVolume();
152 if (fields.find("btn_key_config") != fields.end()) {
153 g_gamecallback->keyConfig();
157 if (fields.find("btn_exit_menu") != fields.end()) {
158 g_gamecallback->disconnect();
162 if (fields.find("btn_exit_os") != fields.end()) {
163 g_gamecallback->exitToOS();
165 RenderingEngine::get_raw_device()->closeDevice();
170 if (fields.find("btn_change_password") != fields.end()) {
171 g_gamecallback->changePassword();
178 if (m_formname == "MT_DEATH_SCREEN") {
179 assert(m_client != 0);
180 m_client->sendRespawn();
184 if (m_client->modsLoaded())
185 m_client->getScript()->on_formspec_input(m_formname, fields);
188 Client *m_client = nullptr;
191 /* Form update callback */
193 class NodeMetadataFormSource: public IFormSource
196 NodeMetadataFormSource(ClientMap *map, v3s16 p):
201 const std::string &getForm() const
203 static const std::string empty_string = "";
204 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
209 return meta->getString("formspec");
212 virtual std::string resolveText(const std::string &str)
214 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
219 return meta->resolveString(str);
226 class PlayerInventoryFormSource: public IFormSource
229 PlayerInventoryFormSource(Client *client):
234 const std::string &getForm() const
236 LocalPlayer *player = m_client->getEnv().getLocalPlayer();
237 return player->inventory_formspec;
243 class NodeDugEvent: public MtEvent
249 NodeDugEvent(v3s16 p, MapNode n):
253 MtEvent::Type getType() const
255 return MtEvent::NODE_DUG;
261 ISoundManager *m_sound;
262 const NodeDefManager *m_ndef;
264 bool makes_footstep_sound;
265 float m_player_step_timer;
266 float m_player_jump_timer;
268 SimpleSoundSpec m_player_step_sound;
269 SimpleSoundSpec m_player_leftpunch_sound;
270 SimpleSoundSpec m_player_rightpunch_sound;
272 SoundMaker(ISoundManager *sound, const NodeDefManager *ndef):
275 makes_footstep_sound(true),
276 m_player_step_timer(0.0f),
277 m_player_jump_timer(0.0f)
281 void playPlayerStep()
283 if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
284 m_player_step_timer = 0.03;
285 if (makes_footstep_sound)
286 m_sound->playSound(m_player_step_sound, false);
290 void playPlayerJump()
292 if (m_player_jump_timer <= 0.0f) {
293 m_player_jump_timer = 0.2f;
294 m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f), false);
298 static void viewBobbingStep(MtEvent *e, void *data)
300 SoundMaker *sm = (SoundMaker *)data;
301 sm->playPlayerStep();
304 static void playerRegainGround(MtEvent *e, void *data)
306 SoundMaker *sm = (SoundMaker *)data;
307 sm->playPlayerStep();
310 static void playerJump(MtEvent *e, void *data)
312 SoundMaker *sm = (SoundMaker *)data;
313 sm->playPlayerJump();
316 static void cameraPunchLeft(MtEvent *e, void *data)
318 SoundMaker *sm = (SoundMaker *)data;
319 sm->m_sound->playSound(sm->m_player_leftpunch_sound, false);
322 static void cameraPunchRight(MtEvent *e, void *data)
324 SoundMaker *sm = (SoundMaker *)data;
325 sm->m_sound->playSound(sm->m_player_rightpunch_sound, false);
328 static void nodeDug(MtEvent *e, void *data)
330 SoundMaker *sm = (SoundMaker *)data;
331 NodeDugEvent *nde = (NodeDugEvent *)e;
332 sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug, false);
335 static void playerDamage(MtEvent *e, void *data)
337 SoundMaker *sm = (SoundMaker *)data;
338 sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false);
341 static void playerFallingDamage(MtEvent *e, void *data)
343 SoundMaker *sm = (SoundMaker *)data;
344 sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false);
347 void registerReceiver(MtEventManager *mgr)
349 mgr->reg(MtEvent::VIEW_BOBBING_STEP, SoundMaker::viewBobbingStep, this);
350 mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, this);
351 mgr->reg(MtEvent::PLAYER_JUMP, SoundMaker::playerJump, this);
352 mgr->reg(MtEvent::CAMERA_PUNCH_LEFT, SoundMaker::cameraPunchLeft, this);
353 mgr->reg(MtEvent::CAMERA_PUNCH_RIGHT, SoundMaker::cameraPunchRight, this);
354 mgr->reg(MtEvent::NODE_DUG, SoundMaker::nodeDug, this);
355 mgr->reg(MtEvent::PLAYER_DAMAGE, SoundMaker::playerDamage, this);
356 mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, this);
359 void step(float dtime)
361 m_player_step_timer -= dtime;
362 m_player_jump_timer -= dtime;
366 // Locally stored sounds don't need to be preloaded because of this
367 class GameOnDemandSoundFetcher: public OnDemandSoundFetcher
369 std::set<std::string> m_fetched;
371 void paths_insert(std::set<std::string> &dst_paths,
372 const std::string &base,
373 const std::string &name)
375 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg");
376 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg");
377 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg");
378 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg");
379 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg");
380 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg");
381 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg");
382 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg");
383 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg");
384 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg");
385 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg");
388 void fetchSounds(const std::string &name,
389 std::set<std::string> &dst_paths,
390 std::set<std::string> &dst_datas)
392 if (m_fetched.count(name))
395 m_fetched.insert(name);
397 paths_insert(dst_paths, porting::path_share, name);
398 paths_insert(dst_paths, porting::path_user, name);
403 typedef s32 SamplerLayer_t;
406 class GameGlobalShaderConstantSetter : public IShaderConstantSetter
409 bool *m_force_fog_off;
412 CachedPixelShaderSetting<float, 4> m_sky_bg_color;
413 CachedPixelShaderSetting<float> m_fog_distance;
414 CachedVertexShaderSetting<float> m_animation_timer_vertex;
415 CachedPixelShaderSetting<float> m_animation_timer_pixel;
416 CachedPixelShaderSetting<float, 3> m_day_light;
417 CachedPixelShaderSetting<float, 4> m_star_color;
418 CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
419 CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
420 CachedPixelShaderSetting<float, 3> m_minimap_yaw;
421 CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
422 CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
423 CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
424 CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture;
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"),
461 g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
462 m_fog_enabled = g_settings->getBool("enable_fog");
465 ~GameGlobalShaderConstantSetter()
467 g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
470 void onSetConstants(video::IMaterialRendererServices *services) override
473 video::SColor bgcolor = m_sky->getBgColor();
474 video::SColorf bgcolorf(bgcolor);
475 float bgcolorfa[4] = {
481 m_sky_bg_color.set(bgcolorfa, services);
484 float fog_distance = 10000 * BS;
486 if (m_fog_enabled && !*m_force_fog_off)
487 fog_distance = *m_fog_range;
489 m_fog_distance.set(&fog_distance, services);
491 u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
492 video::SColorf sunlight;
493 get_sunlight_color(&sunlight, daynight_ratio);
498 m_day_light.set(dnc, services);
500 video::SColorf star_color = m_sky->getCurrentStarColor();
501 float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a};
502 m_star_color.set(clr, services);
504 u32 animation_timer = porting::getTimeMs() % 1000000;
505 float animation_timer_f = (float)animation_timer / 100000.f;
506 m_animation_timer_vertex.set(&animation_timer_f, services);
507 m_animation_timer_pixel.set(&animation_timer_f, services);
509 float eye_position_array[3];
510 v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
511 epos.getAs3Values(eye_position_array);
512 m_eye_position_pixel.set(eye_position_array, services);
513 m_eye_position_vertex.set(eye_position_array, services);
515 if (m_client->getMinimap()) {
516 float minimap_yaw_array[3];
517 v3f minimap_yaw = m_client->getMinimap()->getYawVec();
518 minimap_yaw.getAs3Values(minimap_yaw_array);
519 m_minimap_yaw.set(minimap_yaw_array, services);
522 float camera_offset_array[3];
523 v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
524 offset.getAs3Values(camera_offset_array);
525 m_camera_offset_pixel.set(camera_offset_array, services);
526 m_camera_offset_vertex.set(camera_offset_array, services);
528 SamplerLayer_t base_tex = 0, normal_tex = 1;
529 m_base_texture.set(&base_tex, services);
530 m_normal_texture.set(&normal_tex, services);
535 class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
538 bool *m_force_fog_off;
541 std::vector<GameGlobalShaderConstantSetter *> created_nosky;
543 GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
544 f32 *fog_range, Client *client) :
546 m_force_fog_off(force_fog_off),
547 m_fog_range(fog_range),
551 void setSky(Sky *sky) {
553 for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
554 ggscs->setSky(m_sky);
556 created_nosky.clear();
559 virtual IShaderConstantSetter* create()
561 auto *scs = new GameGlobalShaderConstantSetter(
562 m_sky, m_force_fog_off, m_fog_range, m_client);
564 created_nosky.push_back(scs);
569 #ifdef HAVE_TOUCHSCREENGUI
570 #define SIZE_TAG "size[11,5.5]"
572 #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
575 /****************************************************************************
576 ****************************************************************************/
578 const float object_hit_delay = 0.2;
581 u32 last_time, busy_time, sleep_time;
585 /* The reason the following structs are not anonymous structs within the
586 * class is that they are not used by the majority of member functions and
587 * many functions that do require objects of thse types do not modify them
588 * (so they can be passed as a const qualified parameter)
594 PointedThing pointed_old;
597 bool btn_down_for_dig;
599 bool digging_blocked;
600 bool reset_jump_timer;
601 float nodig_delay_timer;
603 float dig_time_complete;
604 float repeat_place_timer;
605 float object_hit_delay_timer;
606 float time_from_last_punch;
607 ClientActiveObject *selected_object;
611 float update_draw_list_timer;
615 v3f update_draw_list_last_cam_dir;
617 float time_of_day_smooth;
622 struct ClientEventHandler
624 void (Game::*handler)(ClientEvent *, CameraOrientation *);
627 /****************************************************************************
629 ****************************************************************************/
631 using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
633 /* This is not intended to be a public class. If a public class becomes
634 * desirable then it may be better to create another 'wrapper' class that
635 * hides most of the stuff in this class (nothing in this class is required
636 * by any other file) but exposes the public methods/data only.
643 bool startup(bool *kill,
645 RenderingEngine *rendering_engine,
646 const GameStartData &game_params,
647 std::string &error_message,
649 ChatBackend *chat_backend);
656 // Basic initialisation
657 bool init(const std::string &map_dir, const std::string &address,
658 u16 port, const SubgameSpec &gamespec);
660 bool createSingleplayerServer(const std::string &map_dir,
661 const SubgameSpec &gamespec, u16 port);
664 bool createClient(const GameStartData &start_data);
668 bool connectToServer(const GameStartData &start_data,
669 bool *connect_ok, bool *aborted);
670 bool getServerContent(bool *aborted);
674 void updateInteractTimers(f32 dtime);
675 bool checkConnection();
676 bool handleCallbacks();
677 void processQueues();
678 void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
679 void updateDebugState();
680 void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
681 void updateProfilerGraphs(ProfilerGraph *graph);
684 void processUserInput(f32 dtime);
685 void processKeyInput();
686 void processItemSelection(u16 *new_playeritem);
688 void dropSelectedItem(bool single_item = false);
689 void openInventory();
690 void openConsole(float scale, const wchar_t *line=NULL);
691 void toggleFreeMove();
692 void toggleFreeMoveAlt();
693 void togglePitchMove();
696 void toggleCinematic();
697 void toggleBlockBounds();
698 void toggleAutoforward();
700 void toggleMinimap(bool shift_pressed);
703 void toggleUpdateCamera();
705 void increaseViewRange();
706 void decreaseViewRange();
707 void toggleFullViewRange();
708 void checkZoomEnabled();
710 void updateCameraDirection(CameraOrientation *cam, float dtime);
711 void updateCameraOrientation(CameraOrientation *cam, float dtime);
712 void updatePlayerControl(const CameraOrientation &cam);
713 void step(f32 *dtime);
714 void processClientEvents(CameraOrientation *cam);
715 void updateCamera(u32 busy_time, f32 dtime);
716 void updateSound(f32 dtime);
717 void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug);
719 * Returns the object or node the player is pointing at.
720 * Also updates the selected thing in the Hud.
722 * @param[in] shootline the shootline, starting from
723 * the camera position. This also gives the maximal distance
725 * @param[in] liquids_pointable if false, liquids are ignored
726 * @param[in] look_for_object if false, objects are ignored
727 * @param[in] camera_offset offset of the camera
728 * @param[out] selected_object the selected object or
731 PointedThing updatePointedThing(
732 const core::line3d<f32> &shootline, bool liquids_pointable,
733 bool look_for_object, const v3s16 &camera_offset);
734 void handlePointingAtNothing(const ItemStack &playerItem);
735 void handlePointingAtNode(const PointedThing &pointed,
736 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
737 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
738 const v3f &player_position, bool show_debug);
739 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
740 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
741 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
742 const CameraOrientation &cam);
743 void updateShadows();
746 void limitFps(FpsControl *fps_timings, f32 *dtime);
748 void showOverlayMessage(const char *msg, float dtime, int percent,
749 bool draw_clouds = true);
751 static void settingChangedCallback(const std::string &setting_name, void *data);
754 inline bool isKeyDown(GameKeyType k)
756 return input->isKeyDown(k);
758 inline bool wasKeyDown(GameKeyType k)
760 return input->wasKeyDown(k);
762 inline bool wasKeyPressed(GameKeyType k)
764 return input->wasKeyPressed(k);
766 inline bool wasKeyReleased(GameKeyType k)
768 return input->wasKeyReleased(k);
772 void handleAndroidChatInput();
777 bool force_fog_off = false;
778 bool disable_camera_update = false;
781 void showDeathFormspec();
782 void showPauseMenu();
784 void pauseAnimation();
785 void resumeAnimation();
787 // ClientEvent handlers
788 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
789 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
790 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
791 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
792 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
793 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
794 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
795 CameraOrientation *cam);
796 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
797 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
798 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
799 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
800 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
801 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
802 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
803 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
804 CameraOrientation *cam);
805 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
807 void updateChat(f32 dtime);
809 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
810 const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
811 const NodeMetadata *meta);
812 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
814 f32 getSensitivityScaleFactor() const;
816 InputHandler *input = nullptr;
818 Client *client = nullptr;
819 Server *server = nullptr;
821 IWritableTextureSource *texture_src = nullptr;
822 IWritableShaderSource *shader_src = nullptr;
824 // When created, these will be filled with data received from the server
825 IWritableItemDefManager *itemdef_manager = nullptr;
826 NodeDefManager *nodedef_manager = nullptr;
828 GameOnDemandSoundFetcher soundfetcher; // useful when testing
829 ISoundManager *sound = nullptr;
830 bool sound_is_dummy = false;
831 SoundMaker *soundmaker = nullptr;
833 ChatBackend *chat_backend = nullptr;
834 LogOutputBuffer m_chat_log_buf;
836 EventManager *eventmgr = nullptr;
837 QuicktuneShortcutter *quicktune = nullptr;
838 bool registration_confirmation_shown = false;
840 std::unique_ptr<GameUI> m_game_ui;
841 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
842 MapDrawControl *draw_control = nullptr;
843 Camera *camera = nullptr;
844 Clouds *clouds = nullptr; // Free using ->Drop()
845 Sky *sky = nullptr; // Free using ->Drop()
847 Minimap *mapper = nullptr;
849 // Map server hud ids to client hud ids
850 std::unordered_map<u32, u32> m_hud_server_to_client;
856 This class does take ownership/responsibily for cleaning up etc of any of
857 these items (e.g. device)
859 IrrlichtDevice *device;
860 RenderingEngine *m_rendering_engine;
861 video::IVideoDriver *driver;
862 scene::ISceneManager *smgr;
864 std::string *error_message;
865 bool *reconnect_requested;
866 scene::ISceneNode *skybox;
867 PausedNodesList paused_animated_nodes;
869 bool simple_singleplayer_mode;
872 /* Pre-calculated values
874 int crack_animation_length;
876 IntervalLimiter profiler_interval;
879 * TODO: Local caching of settings is not optimal and should at some stage
880 * be updated to use a global settings object for getting thse values
881 * (as opposed to the this local caching). This can be addressed in
884 bool m_cache_doubletap_jump;
885 bool m_cache_enable_clouds;
886 bool m_cache_enable_joysticks;
887 bool m_cache_enable_particles;
888 bool m_cache_enable_fog;
889 bool m_cache_enable_noclip;
890 bool m_cache_enable_free_move;
891 f32 m_cache_mouse_sensitivity;
892 f32 m_cache_joystick_frustum_sensitivity;
893 f32 m_repeat_place_time;
894 f32 m_cache_cam_smoothing;
895 f32 m_cache_fog_start;
897 bool m_invert_mouse = false;
898 bool m_first_loop_after_window_activation = false;
899 bool m_camera_offset_changed = false;
901 bool m_does_lost_focus_pause_game = false;
903 int m_reset_HW_buffer_counter = 0;
904 #ifdef HAVE_TOUCHSCREENGUI
905 bool m_cache_hold_aux1;
908 bool m_android_chat_open;
913 m_chat_log_buf(g_logger),
914 m_game_ui(new GameUI())
916 g_settings->registerChangedCallback("doubletap_jump",
917 &settingChangedCallback, this);
918 g_settings->registerChangedCallback("enable_clouds",
919 &settingChangedCallback, this);
920 g_settings->registerChangedCallback("doubletap_joysticks",
921 &settingChangedCallback, this);
922 g_settings->registerChangedCallback("enable_particles",
923 &settingChangedCallback, this);
924 g_settings->registerChangedCallback("enable_fog",
925 &settingChangedCallback, this);
926 g_settings->registerChangedCallback("mouse_sensitivity",
927 &settingChangedCallback, this);
928 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
929 &settingChangedCallback, this);
930 g_settings->registerChangedCallback("repeat_place_time",
931 &settingChangedCallback, this);
932 g_settings->registerChangedCallback("noclip",
933 &settingChangedCallback, this);
934 g_settings->registerChangedCallback("free_move",
935 &settingChangedCallback, this);
936 g_settings->registerChangedCallback("cinematic",
937 &settingChangedCallback, this);
938 g_settings->registerChangedCallback("cinematic_camera_smoothing",
939 &settingChangedCallback, this);
940 g_settings->registerChangedCallback("camera_smoothing",
941 &settingChangedCallback, this);
945 #ifdef HAVE_TOUCHSCREENGUI
946 m_cache_hold_aux1 = false; // This is initialised properly later
953 /****************************************************************************
955 ****************************************************************************/
964 delete server; // deleted first to stop all server threads
972 delete nodedef_manager;
973 delete itemdef_manager;
976 clearTextureNameCache();
978 g_settings->deregisterChangedCallback("doubletap_jump",
979 &settingChangedCallback, this);
980 g_settings->deregisterChangedCallback("enable_clouds",
981 &settingChangedCallback, this);
982 g_settings->deregisterChangedCallback("enable_particles",
983 &settingChangedCallback, this);
984 g_settings->deregisterChangedCallback("enable_fog",
985 &settingChangedCallback, this);
986 g_settings->deregisterChangedCallback("mouse_sensitivity",
987 &settingChangedCallback, this);
988 g_settings->deregisterChangedCallback("repeat_place_time",
989 &settingChangedCallback, this);
990 g_settings->deregisterChangedCallback("noclip",
991 &settingChangedCallback, this);
992 g_settings->deregisterChangedCallback("free_move",
993 &settingChangedCallback, this);
994 g_settings->deregisterChangedCallback("cinematic",
995 &settingChangedCallback, this);
996 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
997 &settingChangedCallback, this);
998 g_settings->deregisterChangedCallback("camera_smoothing",
999 &settingChangedCallback, this);
1002 bool Game::startup(bool *kill,
1003 InputHandler *input,
1004 RenderingEngine *rendering_engine,
1005 const GameStartData &start_data,
1006 std::string &error_message,
1008 ChatBackend *chat_backend)
1012 m_rendering_engine = rendering_engine;
1013 device = m_rendering_engine->get_raw_device();
1015 this->error_message = &error_message;
1016 reconnect_requested = reconnect;
1017 this->input = input;
1018 this->chat_backend = chat_backend;
1019 simple_singleplayer_mode = start_data.isSinglePlayer();
1021 input->keycache.populate();
1023 driver = device->getVideoDriver();
1024 smgr = m_rendering_engine->get_scene_manager();
1026 smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1029 runData = GameRunData();
1030 runData.time_from_last_punch = 10.0;
1032 m_game_ui->initFlags();
1034 m_invert_mouse = g_settings->getBool("invert_mouse");
1035 m_first_loop_after_window_activation = true;
1037 g_client_translations->clear();
1039 // address can change if simple_singleplayer_mode
1040 if (!init(start_data.world_spec.path, start_data.address,
1041 start_data.socket_port, start_data.game_spec))
1044 if (!createClient(start_data))
1047 m_rendering_engine->initialize(client, hud);
1055 ProfilerGraph graph;
1056 RunStats stats = { 0 };
1057 CameraOrientation cam_view_target = { 0 };
1058 CameraOrientation cam_view = { 0 };
1059 FpsControl draw_times = { 0 };
1060 f32 dtime; // in seconds
1062 /* Clear the profiler */
1063 Profiler::GraphValues dummyvalues;
1064 g_profiler->graphGet(dummyvalues);
1066 draw_times.last_time = m_rendering_engine->get_timer_time();
1068 set_light_table(g_settings->getFloat("display_gamma"));
1070 #ifdef HAVE_TOUCHSCREENGUI
1071 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1072 && client->checkPrivilege("fast");
1075 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1076 g_settings->getU16("screen_h"));
1078 while (m_rendering_engine->run()
1079 && !(*kill || g_gamecallback->shutdown_requested
1080 || (server && server->isShutdownRequested()))) {
1082 const irr::core::dimension2d<u32> ¤t_screen_size =
1083 m_rendering_engine->get_video_driver()->getScreenSize();
1084 // Verify if window size has changed and save it if it's the case
1085 // Ensure evaluating settings->getBool after verifying screensize
1086 // First condition is cheaper
1087 if (previous_screen_size != current_screen_size &&
1088 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1089 g_settings->getBool("autosave_screensize")) {
1090 g_settings->setU16("screen_w", current_screen_size.Width);
1091 g_settings->setU16("screen_h", current_screen_size.Height);
1092 previous_screen_size = current_screen_size;
1095 // Calculate dtime =
1096 // m_rendering_engine->run() from this iteration
1097 // + Sleep time until the wanted FPS are reached
1098 limitFps(&draw_times, &dtime);
1100 // Prepare render data for next iteration
1102 updateStats(&stats, draw_times, dtime);
1103 updateInteractTimers(dtime);
1105 if (!checkConnection())
1107 if (!handleCallbacks())
1112 m_game_ui->clearInfoText();
1113 hud->resizeHotbar();
1116 updateProfilers(stats, draw_times, dtime);
1117 processUserInput(dtime);
1118 // Update camera before player movement to avoid camera lag of one frame
1119 updateCameraDirection(&cam_view_target, dtime);
1120 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1121 cam_view.camera_yaw) * m_cache_cam_smoothing;
1122 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1123 cam_view.camera_pitch) * m_cache_cam_smoothing;
1124 updatePlayerControl(cam_view);
1126 processClientEvents(&cam_view_target);
1128 updateCamera(draw_times.busy_time, dtime);
1130 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
1131 m_game_ui->m_flags.show_basic_debug);
1132 updateFrame(&graph, &stats, dtime, cam_view);
1133 updateProfilerGraphs(&graph);
1135 // Update if minimap has been disabled by the server
1136 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1138 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1145 void Game::shutdown()
1147 m_rendering_engine->finalize();
1148 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1149 if (g_settings->get("3d_mode") == "pageflip") {
1150 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1153 auto formspec = m_game_ui->getFormspecGUI();
1155 formspec->quitMenu();
1157 #ifdef HAVE_TOUCHSCREENGUI
1158 g_touchscreengui->hide();
1161 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1166 if (gui_chat_console)
1167 gui_chat_console->drop();
1173 while (g_menumgr.menuCount() > 0) {
1174 g_menumgr.m_stack.front()->setVisible(false);
1175 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1178 m_game_ui->deleteFormspec();
1180 chat_backend->addMessage(L"", L"# Disconnected.");
1181 chat_backend->addMessage(L"", L"");
1182 m_chat_log_buf.clear();
1186 while (!client->isShutdown()) {
1187 assert(texture_src != NULL);
1188 assert(shader_src != NULL);
1189 texture_src->processQueue();
1190 shader_src->processQueue();
1197 /****************************************************************************/
1198 /****************************************************************************
1200 ****************************************************************************/
1201 /****************************************************************************/
1204 const std::string &map_dir,
1205 const std::string &address,
1207 const SubgameSpec &gamespec)
1209 texture_src = createTextureSource();
1211 showOverlayMessage(N_("Loading..."), 0, 0);
1213 shader_src = createShaderSource();
1215 itemdef_manager = createItemDefManager();
1216 nodedef_manager = createNodeDefManager();
1218 eventmgr = new EventManager();
1219 quicktune = new QuicktuneShortcutter();
1221 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1222 && eventmgr && quicktune))
1228 // Create a server if not connecting to an existing one
1229 if (address.empty()) {
1230 if (!createSingleplayerServer(map_dir, gamespec, port))
1237 bool Game::initSound()
1240 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1241 infostream << "Attempting to use OpenAL audio" << std::endl;
1242 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1244 infostream << "Failed to initialize OpenAL audio" << std::endl;
1246 infostream << "Sound disabled." << std::endl;
1250 infostream << "Using dummy audio." << std::endl;
1251 sound = &dummySoundManager;
1252 sound_is_dummy = true;
1255 soundmaker = new SoundMaker(sound, nodedef_manager);
1259 soundmaker->registerReceiver(eventmgr);
1264 bool Game::createSingleplayerServer(const std::string &map_dir,
1265 const SubgameSpec &gamespec, u16 port)
1267 showOverlayMessage(N_("Creating server..."), 0, 5);
1269 std::string bind_str = g_settings->get("bind_address");
1270 Address bind_addr(0, 0, 0, 0, port);
1272 if (g_settings->getBool("ipv6_server")) {
1273 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1277 bind_addr.Resolve(bind_str.c_str());
1278 } catch (ResolveError &e) {
1279 infostream << "Resolving bind address \"" << bind_str
1280 << "\" failed: " << e.what()
1281 << " -- Listening on all addresses." << std::endl;
1284 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1285 *error_message = "Unable to listen on " +
1286 bind_addr.serializeString() +
1287 " because IPv6 is disabled";
1288 errorstream << *error_message << std::endl;
1292 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1293 false, nullptr, error_message);
1299 bool Game::createClient(const GameStartData &start_data)
1301 showOverlayMessage(N_("Creating client..."), 0, 10);
1303 draw_control = new MapDrawControl();
1307 bool could_connect, connect_aborted;
1308 #ifdef HAVE_TOUCHSCREENGUI
1309 if (g_touchscreengui) {
1310 g_touchscreengui->init(texture_src);
1311 g_touchscreengui->hide();
1314 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1317 if (!could_connect) {
1318 if (error_message->empty() && !connect_aborted) {
1319 // Should not happen if error messages are set properly
1320 *error_message = "Connection failed for unknown reason";
1321 errorstream << *error_message << std::endl;
1326 if (!getServerContent(&connect_aborted)) {
1327 if (error_message->empty() && !connect_aborted) {
1328 // Should not happen if error messages are set properly
1329 *error_message = "Connection failed for unknown reason";
1330 errorstream << *error_message << std::endl;
1335 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1336 &m_flags.force_fog_off, &runData.fog_range, client);
1337 shader_src->addShaderConstantSetterFactory(scsf);
1339 // Update cached textures, meshes and materials
1340 client->afterContentReceived();
1344 camera = new Camera(*draw_control, client, m_rendering_engine);
1345 if (!camera->successfullyCreated(*error_message))
1347 client->setCamera(camera);
1351 if (m_cache_enable_clouds)
1352 clouds = new Clouds(smgr, -1, time(0));
1356 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
1358 skybox = NULL; // This is used/set later on in the main run loop
1360 /* Pre-calculated values
1362 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1364 v2u32 size = t->getOriginalSize();
1365 crack_animation_length = size.Y / size.X;
1367 crack_animation_length = 5;
1373 /* Set window caption
1375 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1377 str += utf8_to_wide(g_version_hash);
1379 const wchar_t *text = nullptr;
1380 if (simple_singleplayer_mode)
1381 text = wgettext("Singleplayer");
1383 text = wgettext("Multiplayer");
1390 str += driver->getName();
1393 device->setWindowCaption(str.c_str());
1395 LocalPlayer *player = client->getEnv().getLocalPlayer();
1396 player->hurt_tilt_timer = 0;
1397 player->hurt_tilt_strength = 0;
1399 hud = new Hud(client, player, &player->inventory);
1401 mapper = client->getMinimap();
1403 if (mapper && client->modsLoaded())
1404 client->getScript()->on_minimap_ready(mapper);
1409 bool Game::initGui()
1413 // Remove stale "recent" chat messages from previous connections
1414 chat_backend->clearRecentChat();
1416 // Make sure the size of the recent messages buffer is right
1417 chat_backend->applySettings();
1419 // Chat backend and console
1420 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1421 -1, chat_backend, client, &g_menumgr);
1423 #ifdef HAVE_TOUCHSCREENGUI
1425 if (g_touchscreengui)
1426 g_touchscreengui->show();
1433 bool Game::connectToServer(const GameStartData &start_data,
1434 bool *connect_ok, bool *connection_aborted)
1436 *connect_ok = false; // Let's not be overly optimistic
1437 *connection_aborted = false;
1438 bool local_server_mode = false;
1440 showOverlayMessage(N_("Resolving address..."), 0, 15);
1442 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1445 connect_address.Resolve(start_data.address.c_str());
1447 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1448 //connect_address.Resolve("localhost");
1449 if (connect_address.isIPv6()) {
1450 IPv6AddressBytes addr_bytes;
1451 addr_bytes.bytes[15] = 1;
1452 connect_address.setAddress(&addr_bytes);
1454 connect_address.setAddress(127, 0, 0, 1);
1456 local_server_mode = true;
1458 } catch (ResolveError &e) {
1459 *error_message = std::string("Couldn't resolve address: ") + e.what();
1460 errorstream << *error_message << std::endl;
1464 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1465 *error_message = "Unable to connect to " +
1466 connect_address.serializeString() +
1467 " because IPv6 is disabled";
1468 errorstream << *error_message << std::endl;
1472 client = new Client(start_data.name.c_str(),
1473 start_data.password, start_data.address,
1474 *draw_control, texture_src, shader_src,
1475 itemdef_manager, nodedef_manager, sound, eventmgr,
1476 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get());
1478 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1480 infostream << "Connecting to server at ";
1481 connect_address.print(&infostream);
1482 infostream << std::endl;
1484 client->connect(connect_address,
1485 simple_singleplayer_mode || local_server_mode);
1488 Wait for server to accept connection
1494 FpsControl fps_control = { 0 };
1496 f32 wait_time = 0; // in seconds
1498 fps_control.last_time = m_rendering_engine->get_timer_time();
1500 while (m_rendering_engine->run()) {
1502 limitFps(&fps_control, &dtime);
1504 // Update client and server
1505 client->step(dtime);
1508 server->step(dtime);
1511 if (client->getState() == LC_Init) {
1517 if (*connection_aborted)
1520 if (client->accessDenied()) {
1521 *error_message = "Access denied. Reason: "
1522 + client->accessDeniedReason();
1523 *reconnect_requested = client->reconnectRequested();
1524 errorstream << *error_message << std::endl;
1528 if (input->cancelPressed()) {
1529 *connection_aborted = true;
1530 infostream << "Connect aborted [Escape]" << std::endl;
1534 if (client->m_is_registration_confirmation_state) {
1535 if (registration_confirmation_shown) {
1536 // Keep drawing the GUI
1537 m_rendering_engine->draw_menu_scene(guienv, dtime, true);
1539 registration_confirmation_shown = true;
1540 (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
1541 &g_menumgr, client, start_data.name, start_data.password,
1542 connection_aborted, texture_src))->drop();
1546 // Only time out if we aren't waiting for the server we started
1547 if (!start_data.address.empty() && wait_time > 10) {
1548 *error_message = "Connection timed out.";
1549 errorstream << *error_message << std::endl;
1554 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1557 } catch (con::PeerNotFoundException &e) {
1558 // TODO: Should something be done here? At least an info/error
1566 bool Game::getServerContent(bool *aborted)
1570 FpsControl fps_control = { 0 };
1571 f32 dtime; // in seconds
1573 fps_control.last_time = m_rendering_engine->get_timer_time();
1575 while (m_rendering_engine->run()) {
1577 limitFps(&fps_control, &dtime);
1579 // Update client and server
1580 client->step(dtime);
1583 server->step(dtime);
1586 if (client->mediaReceived() && client->itemdefReceived() &&
1587 client->nodedefReceived()) {
1592 if (!checkConnection())
1595 if (client->getState() < LC_Init) {
1596 *error_message = "Client disconnected";
1597 errorstream << *error_message << std::endl;
1601 if (input->cancelPressed()) {
1603 infostream << "Connect aborted [Escape]" << std::endl;
1610 if (!client->itemdefReceived()) {
1611 const wchar_t *text = wgettext("Item definitions...");
1613 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1616 } else if (!client->nodedefReceived()) {
1617 const wchar_t *text = wgettext("Node definitions...");
1619 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1623 std::ostringstream message;
1624 std::fixed(message);
1625 message.precision(0);
1626 float receive = client->mediaReceiveProgress() * 100;
1627 message << gettext("Media...");
1629 message << " " << receive << "%";
1630 message.precision(2);
1632 if ((USE_CURL == 0) ||
1633 (!g_settings->getBool("enable_remote_media_server"))) {
1634 float cur = client->getCurRate();
1635 std::string cur_unit = gettext("KiB/s");
1639 cur_unit = gettext("MiB/s");
1642 message << " (" << cur << ' ' << cur_unit << ")";
1645 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1646 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
1647 texture_src, dtime, progress);
1655 /****************************************************************************/
1656 /****************************************************************************
1658 ****************************************************************************/
1659 /****************************************************************************/
1661 inline void Game::updateInteractTimers(f32 dtime)
1663 if (runData.nodig_delay_timer >= 0)
1664 runData.nodig_delay_timer -= dtime;
1666 if (runData.object_hit_delay_timer >= 0)
1667 runData.object_hit_delay_timer -= dtime;
1669 runData.time_from_last_punch += dtime;
1673 /* returns false if game should exit, otherwise true
1675 inline bool Game::checkConnection()
1677 if (client->accessDenied()) {
1678 *error_message = "Access denied. Reason: "
1679 + client->accessDeniedReason();
1680 *reconnect_requested = client->reconnectRequested();
1681 errorstream << *error_message << std::endl;
1689 /* returns false if game should exit, otherwise true
1691 inline bool Game::handleCallbacks()
1693 if (g_gamecallback->disconnect_requested) {
1694 g_gamecallback->disconnect_requested = false;
1698 if (g_gamecallback->changepassword_requested) {
1699 (new GUIPasswordChange(guienv, guiroot, -1,
1700 &g_menumgr, client, texture_src))->drop();
1701 g_gamecallback->changepassword_requested = false;
1704 if (g_gamecallback->changevolume_requested) {
1705 (new GUIVolumeChange(guienv, guiroot, -1,
1706 &g_menumgr, texture_src))->drop();
1707 g_gamecallback->changevolume_requested = false;
1710 if (g_gamecallback->keyconfig_requested) {
1711 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1712 &g_menumgr, texture_src))->drop();
1713 g_gamecallback->keyconfig_requested = false;
1716 if (g_gamecallback->keyconfig_changed) {
1717 input->keycache.populate(); // update the cache with new settings
1718 g_gamecallback->keyconfig_changed = false;
1725 void Game::processQueues()
1727 texture_src->processQueue();
1728 itemdef_manager->processQueue(client);
1729 shader_src->processQueue();
1732 void Game::updateDebugState()
1734 bool has_basic_debug = client->checkPrivilege("basic_debug");
1735 bool has_debug = client->checkPrivilege("debug");
1737 if (m_game_ui->m_flags.show_basic_debug) {
1738 if (!has_basic_debug) {
1739 m_game_ui->m_flags.show_basic_debug = false;
1741 } else if (m_game_ui->m_flags.show_minimal_debug) {
1742 if (has_basic_debug) {
1743 m_game_ui->m_flags.show_basic_debug = true;
1746 if (!has_basic_debug)
1747 hud->disableBlockBounds();
1749 draw_control->show_wireframe = false;
1752 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1755 float profiler_print_interval =
1756 g_settings->getFloat("profiler_print_interval");
1757 bool print_to_log = true;
1759 if (profiler_print_interval == 0) {
1760 print_to_log = false;
1761 profiler_print_interval = 3;
1764 if (profiler_interval.step(dtime, profiler_print_interval)) {
1766 infostream << "Profiler:" << std::endl;
1767 g_profiler->print(infostream);
1770 m_game_ui->updateProfiler();
1771 g_profiler->clear();
1774 // Update update graphs
1775 g_profiler->graphAdd("Time non-rendering [ms]",
1776 draw_times.busy_time - stats.drawtime);
1778 g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time);
1779 g_profiler->graphAdd("FPS", 1.0f / dtime);
1782 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1789 /* Time average and jitter calculation
1791 jp = &stats->dtime_jitter;
1792 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1794 jitter = dtime - jp->avg;
1796 if (jitter > jp->max)
1799 jp->counter += dtime;
1801 if (jp->counter > 0.0) {
1803 jp->max_sample = jp->max;
1804 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1808 /* Busytime average and jitter calculation
1810 jp = &stats->busy_time_jitter;
1811 jp->avg = jp->avg + draw_times.busy_time * 0.02;
1813 jitter = draw_times.busy_time - jp->avg;
1815 if (jitter > jp->max)
1817 if (jitter < jp->min)
1820 jp->counter += dtime;
1822 if (jp->counter > 0.0) {
1824 jp->max_sample = jp->max;
1825 jp->min_sample = jp->min;
1833 /****************************************************************************
1835 ****************************************************************************/
1837 void Game::processUserInput(f32 dtime)
1839 // Reset input if window not active or some menu is active
1840 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1842 #ifdef HAVE_TOUCHSCREENGUI
1843 g_touchscreengui->hide();
1846 #ifdef HAVE_TOUCHSCREENGUI
1847 else if (g_touchscreengui) {
1848 /* on touchscreengui step may generate own input events which ain't
1849 * what we want in case we just did clear them */
1850 g_touchscreengui->show();
1851 g_touchscreengui->step(dtime);
1855 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1856 gui_chat_console->closeConsoleAtOnce();
1859 // Input handler step() (used by the random input generator)
1863 auto formspec = m_game_ui->getFormspecGUI();
1865 formspec->getAndroidUIInput();
1867 handleAndroidChatInput();
1870 // Increase timer for double tap of "keymap_jump"
1871 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1872 runData.jump_timer += dtime;
1875 processItemSelection(&runData.new_playeritem);
1879 void Game::processKeyInput()
1881 if (wasKeyDown(KeyType::DROP)) {
1882 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1883 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1884 toggleAutoforward();
1885 } else if (wasKeyDown(KeyType::BACKWARD)) {
1886 if (g_settings->getBool("continuous_forward"))
1887 toggleAutoforward();
1888 } else if (wasKeyDown(KeyType::INVENTORY)) {
1890 } else if (input->cancelPressed()) {
1892 m_android_chat_open = false;
1894 if (!gui_chat_console->isOpenInhibited()) {
1897 } else if (wasKeyDown(KeyType::CHAT)) {
1898 openConsole(0.2, L"");
1899 } else if (wasKeyDown(KeyType::CMD)) {
1900 openConsole(0.2, L"/");
1901 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1902 if (client->modsLoaded())
1903 openConsole(0.2, L".");
1905 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1906 } else if (wasKeyDown(KeyType::CONSOLE)) {
1907 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1908 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1910 } else if (wasKeyDown(KeyType::JUMP)) {
1911 toggleFreeMoveAlt();
1912 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1914 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1916 } else if (wasKeyDown(KeyType::NOCLIP)) {
1919 } else if (wasKeyDown(KeyType::MUTE)) {
1920 if (g_settings->getBool("enable_sound")) {
1921 bool new_mute_sound = !g_settings->getBool("mute_sound");
1922 g_settings->setBool("mute_sound", new_mute_sound);
1924 m_game_ui->showTranslatedStatusText("Sound muted");
1926 m_game_ui->showTranslatedStatusText("Sound unmuted");
1928 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1930 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1931 if (g_settings->getBool("enable_sound")) {
1932 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1933 g_settings->setFloat("sound_volume", new_volume);
1934 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1935 m_game_ui->showStatusText(msg);
1937 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1939 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1940 if (g_settings->getBool("enable_sound")) {
1941 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1942 g_settings->setFloat("sound_volume", new_volume);
1943 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1944 m_game_ui->showStatusText(msg);
1946 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1949 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1950 || wasKeyDown(KeyType::DEC_VOLUME)) {
1951 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1953 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1955 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1956 client->makeScreenshot();
1957 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
1958 toggleBlockBounds();
1959 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1960 m_game_ui->toggleHud();
1961 } else if (wasKeyDown(KeyType::MINIMAP)) {
1962 toggleMinimap(isKeyDown(KeyType::SNEAK));
1963 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1964 m_game_ui->toggleChat();
1965 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1967 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1968 toggleUpdateCamera();
1969 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1971 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1972 m_game_ui->toggleProfiler();
1973 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1974 increaseViewRange();
1975 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1976 decreaseViewRange();
1977 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1978 toggleFullViewRange();
1979 } else if (wasKeyDown(KeyType::ZOOM)) {
1981 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1983 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1985 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1987 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1991 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1992 runData.reset_jump_timer = false;
1993 runData.jump_timer = 0.0f;
1996 if (quicktune->hasMessage()) {
1997 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2001 void Game::processItemSelection(u16 *new_playeritem)
2003 LocalPlayer *player = client->getEnv().getLocalPlayer();
2005 /* Item selection using mouse wheel
2007 *new_playeritem = player->getWieldIndex();
2009 s32 wheel = input->getMouseWheel();
2010 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2011 player->hud_hotbar_itemcount - 1);
2015 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2018 if (wasKeyDown(KeyType::HOTBAR_PREV))
2022 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2024 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2027 /* Item selection using hotbar slot keys
2029 for (u16 i = 0; i <= max_item; i++) {
2030 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2031 *new_playeritem = i;
2038 void Game::dropSelectedItem(bool single_item)
2040 IDropAction *a = new IDropAction();
2041 a->count = single_item ? 1 : 0;
2042 a->from_inv.setCurrentPlayer();
2043 a->from_list = "main";
2044 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2045 client->inventoryAction(a);
2049 void Game::openInventory()
2052 * Don't permit to open inventory is CAO or player doesn't exists.
2053 * This prevent showing an empty inventory at player load
2056 LocalPlayer *player = client->getEnv().getLocalPlayer();
2057 if (!player || !player->getCAO())
2060 infostream << "Game: Launching inventory" << std::endl;
2062 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2064 InventoryLocation inventoryloc;
2065 inventoryloc.setCurrentPlayer();
2067 if (!client->modsLoaded()
2068 || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2069 TextDest *txt_dst = new TextDestPlayerInventory(client);
2070 auto *&formspec = m_game_ui->updateFormspec("");
2071 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2072 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2074 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2079 void Game::openConsole(float scale, const wchar_t *line)
2081 assert(scale > 0.0f && scale <= 1.0f);
2084 porting::showInputDialog(gettext("ok"), "", "", 2);
2085 m_android_chat_open = true;
2087 if (gui_chat_console->isOpenInhibited())
2089 gui_chat_console->openConsole(scale);
2091 gui_chat_console->setCloseOnEnter(true);
2092 gui_chat_console->replaceAndAddToHistory(line);
2098 void Game::handleAndroidChatInput()
2100 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2101 std::string text = porting::getInputDialogValue();
2102 client->typeChatMessage(utf8_to_wide(text));
2103 m_android_chat_open = false;
2109 void Game::toggleFreeMove()
2111 bool free_move = !g_settings->getBool("free_move");
2112 g_settings->set("free_move", bool_to_cstr(free_move));
2115 if (client->checkPrivilege("fly")) {
2116 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2118 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2121 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2125 void Game::toggleFreeMoveAlt()
2127 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2130 runData.reset_jump_timer = true;
2134 void Game::togglePitchMove()
2136 bool pitch_move = !g_settings->getBool("pitch_move");
2137 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2140 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2142 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2147 void Game::toggleFast()
2149 bool fast_move = !g_settings->getBool("fast_move");
2150 bool has_fast_privs = client->checkPrivilege("fast");
2151 g_settings->set("fast_move", bool_to_cstr(fast_move));
2154 if (has_fast_privs) {
2155 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2157 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2160 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2163 #ifdef HAVE_TOUCHSCREENGUI
2164 m_cache_hold_aux1 = fast_move && has_fast_privs;
2169 void Game::toggleNoClip()
2171 bool noclip = !g_settings->getBool("noclip");
2172 g_settings->set("noclip", bool_to_cstr(noclip));
2175 if (client->checkPrivilege("noclip")) {
2176 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2178 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2181 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2185 void Game::toggleCinematic()
2187 bool cinematic = !g_settings->getBool("cinematic");
2188 g_settings->set("cinematic", bool_to_cstr(cinematic));
2191 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2193 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2196 void Game::toggleBlockBounds()
2198 if (client->checkPrivilege("basic_debug")) {
2199 enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
2201 case Hud::BLOCK_BOUNDS_OFF:
2202 m_game_ui->showTranslatedStatusText("Block bounds hidden");
2204 case Hud::BLOCK_BOUNDS_CURRENT:
2205 m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
2207 case Hud::BLOCK_BOUNDS_NEAR:
2208 m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
2210 case Hud::BLOCK_BOUNDS_MAX:
2211 m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
2218 m_game_ui->showTranslatedStatusText("Can't show block bounds (need 'basic_debug' privilege)");
2222 // Autoforward by toggling continuous forward.
2223 void Game::toggleAutoforward()
2225 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2226 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2228 if (autorun_enabled)
2229 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2231 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2234 void Game::toggleMinimap(bool shift_pressed)
2236 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2240 mapper->toggleMinimapShape();
2244 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2246 // Not so satisying code to keep compatibility with old fixed mode system
2248 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2250 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2251 m_game_ui->m_flags.show_minimap = false;
2254 // If radar is disabled, try to find a non radar mode or fall back to 0
2255 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2256 while (mapper->getModeIndex() &&
2257 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2260 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2264 // End of 'not so satifying code'
2265 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2266 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2267 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2269 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2272 void Game::toggleFog()
2274 bool fog_enabled = g_settings->getBool("enable_fog");
2275 g_settings->setBool("enable_fog", !fog_enabled);
2277 m_game_ui->showTranslatedStatusText("Fog disabled");
2279 m_game_ui->showTranslatedStatusText("Fog enabled");
2283 void Game::toggleDebug()
2285 // Initial: No debug info
2286 // 1x toggle: Debug text
2287 // 2x toggle: Debug text with profiler graph
2288 // 3x toggle: Debug text and wireframe (needs "debug" priv)
2289 // Next toggle: Back to initial
2291 // The debug text can be in 2 modes: minimal and basic.
2292 // * Minimal: Only technical client info that not gameplay-relevant
2293 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
2294 // Basic mode is used when player has "basic_debug" priv,
2295 // otherwise the Minimal mode is used.
2296 if (!m_game_ui->m_flags.show_minimal_debug) {
2297 m_game_ui->m_flags.show_minimal_debug = true;
2298 if (client->checkPrivilege("basic_debug")) {
2299 m_game_ui->m_flags.show_basic_debug = true;
2301 m_game_ui->m_flags.show_profiler_graph = false;
2302 draw_control->show_wireframe = false;
2303 m_game_ui->showTranslatedStatusText("Debug info shown");
2304 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2305 if (client->checkPrivilege("basic_debug")) {
2306 m_game_ui->m_flags.show_basic_debug = true;
2308 m_game_ui->m_flags.show_profiler_graph = true;
2309 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2310 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2311 if (client->checkPrivilege("basic_debug")) {
2312 m_game_ui->m_flags.show_basic_debug = true;
2314 m_game_ui->m_flags.show_profiler_graph = false;
2315 draw_control->show_wireframe = true;
2316 m_game_ui->showTranslatedStatusText("Wireframe shown");
2318 m_game_ui->m_flags.show_minimal_debug = false;
2319 m_game_ui->m_flags.show_basic_debug = false;
2320 m_game_ui->m_flags.show_profiler_graph = false;
2321 draw_control->show_wireframe = false;
2322 if (client->checkPrivilege("debug")) {
2323 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2325 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2331 void Game::toggleUpdateCamera()
2333 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2334 if (m_flags.disable_camera_update)
2335 m_game_ui->showTranslatedStatusText("Camera update disabled");
2337 m_game_ui->showTranslatedStatusText("Camera update enabled");
2341 void Game::increaseViewRange()
2343 s16 range = g_settings->getS16("viewing_range");
2344 s16 range_new = range + 10;
2346 if (range_new > 4000) {
2348 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
2349 m_game_ui->showStatusText(msg);
2351 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2352 m_game_ui->showStatusText(msg);
2354 g_settings->set("viewing_range", itos(range_new));
2358 void Game::decreaseViewRange()
2360 s16 range = g_settings->getS16("viewing_range");
2361 s16 range_new = range - 10;
2363 if (range_new < 20) {
2365 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
2366 m_game_ui->showStatusText(msg);
2368 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2369 m_game_ui->showStatusText(msg);
2371 g_settings->set("viewing_range", itos(range_new));
2375 void Game::toggleFullViewRange()
2377 draw_control->range_all = !draw_control->range_all;
2378 if (draw_control->range_all)
2379 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2381 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2385 void Game::checkZoomEnabled()
2387 LocalPlayer *player = client->getEnv().getLocalPlayer();
2388 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2389 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2392 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2394 if ((device->isWindowActive() && device->isWindowFocused()
2395 && !isMenuActive()) || input->isRandom()) {
2398 if (!input->isRandom()) {
2399 // Mac OSX gets upset if this is set every frame
2400 if (device->getCursorControl()->isVisible())
2401 device->getCursorControl()->setVisible(false);
2405 if (m_first_loop_after_window_activation) {
2406 m_first_loop_after_window_activation = false;
2408 input->setMousePos(driver->getScreenSize().Width / 2,
2409 driver->getScreenSize().Height / 2);
2411 updateCameraOrientation(cam, dtime);
2417 // Mac OSX gets upset if this is set every frame
2418 if (!device->getCursorControl()->isVisible())
2419 device->getCursorControl()->setVisible(true);
2422 m_first_loop_after_window_activation = true;
2427 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2428 // responsiveness independently of FOV.
2429 f32 Game::getSensitivityScaleFactor() const
2431 f32 fov_y = client->getCamera()->getFovY();
2433 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2434 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2436 return tan(fov_y / 2.0f) * 1.3763818698f;
2439 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2441 #ifdef HAVE_TOUCHSCREENGUI
2442 if (g_touchscreengui) {
2443 cam->camera_yaw += g_touchscreengui->getYawChange();
2444 cam->camera_pitch = g_touchscreengui->getPitch();
2447 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2448 v2s32 dist = input->getMousePos() - center;
2450 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2454 f32 sens_scale = getSensitivityScaleFactor();
2455 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2456 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2458 if (dist.X != 0 || dist.Y != 0)
2459 input->setMousePos(center.X, center.Y);
2460 #ifdef HAVE_TOUCHSCREENGUI
2464 if (m_cache_enable_joysticks) {
2465 f32 sens_scale = getSensitivityScaleFactor();
2466 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
2467 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2468 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2471 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2475 void Game::updatePlayerControl(const CameraOrientation &cam)
2477 LocalPlayer *player = client->getEnv().getLocalPlayer();
2479 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2481 PlayerControl control(
2482 isKeyDown(KeyType::JUMP) || player->getAutojump(),
2483 isKeyDown(KeyType::AUX1),
2484 isKeyDown(KeyType::SNEAK),
2485 isKeyDown(KeyType::ZOOM),
2486 isKeyDown(KeyType::DIG),
2487 isKeyDown(KeyType::PLACE),
2490 input->getMovementSpeed(),
2491 input->getMovementDirection()
2494 // autoforward if set: move towards pointed position at maximum speed
2495 if (player->getPlayerSettings().continuous_forward &&
2496 client->activeObjectsReceived() && !player->isDead()) {
2497 control.movement_speed = 1.0f;
2498 control.movement_direction = 0.0f;
2501 #ifdef HAVE_TOUCHSCREENGUI
2502 /* For touch, simulate holding down AUX1 (fast move) if the user has
2503 * the fast_move setting toggled on. If there is an aux1 key defined for
2504 * touch then its meaning is inverted (i.e. holding aux1 means walk and
2507 if (m_cache_hold_aux1) {
2508 control.aux1 = control.aux1 ^ true;
2512 u32 keypress_bits = (
2513 ( (u32)(control.jump & 0x1) << 4) |
2514 ( (u32)(control.aux1 & 0x1) << 5) |
2515 ( (u32)(control.sneak & 0x1) << 6) |
2516 ( (u32)(control.dig & 0x1) << 7) |
2517 ( (u32)(control.place & 0x1) << 8) |
2518 ( (u32)(control.zoom & 0x1) << 9)
2521 // Set direction keys to ensure mod compatibility
2522 if (control.movement_speed > 0.001f) {
2523 float absolute_direction;
2525 // Check in original orientation (absolute value indicates forward / backward)
2526 absolute_direction = abs(control.movement_direction);
2527 if (absolute_direction < (3.0f / 8.0f * M_PI))
2528 keypress_bits |= (u32)(0x1 << 0); // Forward
2529 if (absolute_direction > (5.0f / 8.0f * M_PI))
2530 keypress_bits |= (u32)(0x1 << 1); // Backward
2532 // Rotate entire coordinate system by 90 degrees (absolute value indicates left / right)
2533 absolute_direction = control.movement_direction + M_PI_2;
2534 if (absolute_direction >= M_PI)
2535 absolute_direction -= 2 * M_PI;
2536 absolute_direction = abs(absolute_direction);
2537 if (absolute_direction < (3.0f / 8.0f * M_PI))
2538 keypress_bits |= (u32)(0x1 << 2); // Left
2539 if (absolute_direction > (5.0f / 8.0f * M_PI))
2540 keypress_bits |= (u32)(0x1 << 3); // Right
2543 client->setPlayerControl(control);
2544 player->keyPressed = keypress_bits;
2550 inline void Game::step(f32 *dtime)
2552 bool can_be_and_is_paused =
2553 (simple_singleplayer_mode && g_menumgr.pausesGame());
2555 if (can_be_and_is_paused) { // This is for a singleplayer server
2556 *dtime = 0; // No time passes
2558 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
2562 server->step(*dtime);
2564 client->step(*dtime);
2568 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2571 for (auto &&child: node->getChildren())
2572 pauseNodeAnimation(paused, child);
2573 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2575 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2576 float speed = animated_node->getAnimationSpeed();
2579 paused.push_back({grab(animated_node), speed});
2580 animated_node->setAnimationSpeed(0.0f);
2583 void Game::pauseAnimation()
2585 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2588 void Game::resumeAnimation()
2590 for (auto &&pair: paused_animated_nodes)
2591 pair.first->setAnimationSpeed(pair.second);
2592 paused_animated_nodes.clear();
2595 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2596 {&Game::handleClientEvent_None},
2597 {&Game::handleClientEvent_PlayerDamage},
2598 {&Game::handleClientEvent_PlayerForceMove},
2599 {&Game::handleClientEvent_Deathscreen},
2600 {&Game::handleClientEvent_ShowFormSpec},
2601 {&Game::handleClientEvent_ShowLocalFormSpec},
2602 {&Game::handleClientEvent_HandleParticleEvent},
2603 {&Game::handleClientEvent_HandleParticleEvent},
2604 {&Game::handleClientEvent_HandleParticleEvent},
2605 {&Game::handleClientEvent_HudAdd},
2606 {&Game::handleClientEvent_HudRemove},
2607 {&Game::handleClientEvent_HudChange},
2608 {&Game::handleClientEvent_SetSky},
2609 {&Game::handleClientEvent_SetSun},
2610 {&Game::handleClientEvent_SetMoon},
2611 {&Game::handleClientEvent_SetStars},
2612 {&Game::handleClientEvent_OverrideDayNigthRatio},
2613 {&Game::handleClientEvent_CloudParams},
2616 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2618 FATAL_ERROR("ClientEvent type None received");
2621 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2623 if (client->modsLoaded())
2624 client->getScript()->on_damage_taken(event->player_damage.amount);
2626 // Damage flash and hurt tilt are not used at death
2627 if (client->getHP() > 0) {
2628 LocalPlayer *player = client->getEnv().getLocalPlayer();
2630 f32 hp_max = player->getCAO() ?
2631 player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2632 f32 damage_ratio = event->player_damage.amount / hp_max;
2634 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2635 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2637 player->hurt_tilt_timer = 1.5f;
2638 player->hurt_tilt_strength =
2639 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2642 // Play damage sound
2643 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2646 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2648 cam->camera_yaw = event->player_force_move.yaw;
2649 cam->camera_pitch = event->player_force_move.pitch;
2652 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2654 // If client scripting is enabled, deathscreen is handled by CSM code in
2655 // builtin/client/init.lua
2656 if (client->modsLoaded())
2657 client->getScript()->on_death();
2659 showDeathFormspec();
2661 /* Handle visualization */
2662 LocalPlayer *player = client->getEnv().getLocalPlayer();
2663 runData.damage_flash = 0;
2664 player->hurt_tilt_timer = 0;
2665 player->hurt_tilt_strength = 0;
2668 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2670 if (event->show_formspec.formspec->empty()) {
2671 auto formspec = m_game_ui->getFormspecGUI();
2672 if (formspec && (event->show_formspec.formname->empty()
2673 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2674 formspec->quitMenu();
2677 FormspecFormSource *fs_src =
2678 new FormspecFormSource(*(event->show_formspec.formspec));
2679 TextDestPlayerInventory *txt_dst =
2680 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2682 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2683 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2684 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2687 delete event->show_formspec.formspec;
2688 delete event->show_formspec.formname;
2691 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2693 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2694 LocalFormspecHandler *txt_dst =
2695 new LocalFormspecHandler(*event->show_formspec.formname, client);
2696 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2697 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2699 delete event->show_formspec.formspec;
2700 delete event->show_formspec.formname;
2703 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2704 CameraOrientation *cam)
2706 LocalPlayer *player = client->getEnv().getLocalPlayer();
2707 client->getParticleManager()->handleParticleEvent(event, client, player);
2710 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2712 LocalPlayer *player = client->getEnv().getLocalPlayer();
2714 u32 server_id = event->hudadd->server_id;
2715 // ignore if we already have a HUD with that ID
2716 auto i = m_hud_server_to_client.find(server_id);
2717 if (i != m_hud_server_to_client.end()) {
2718 delete event->hudadd;
2722 HudElement *e = new HudElement;
2723 e->type = static_cast<HudElementType>(event->hudadd->type);
2724 e->pos = event->hudadd->pos;
2725 e->name = event->hudadd->name;
2726 e->scale = event->hudadd->scale;
2727 e->text = event->hudadd->text;
2728 e->number = event->hudadd->number;
2729 e->item = event->hudadd->item;
2730 e->dir = event->hudadd->dir;
2731 e->align = event->hudadd->align;
2732 e->offset = event->hudadd->offset;
2733 e->world_pos = event->hudadd->world_pos;
2734 e->size = event->hudadd->size;
2735 e->z_index = event->hudadd->z_index;
2736 e->text2 = event->hudadd->text2;
2737 e->style = event->hudadd->style;
2738 m_hud_server_to_client[server_id] = player->addHud(e);
2740 delete event->hudadd;
2743 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2745 LocalPlayer *player = client->getEnv().getLocalPlayer();
2747 auto i = m_hud_server_to_client.find(event->hudrm.id);
2748 if (i != m_hud_server_to_client.end()) {
2749 HudElement *e = player->removeHud(i->second);
2751 m_hud_server_to_client.erase(i);
2756 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2758 LocalPlayer *player = client->getEnv().getLocalPlayer();
2760 HudElement *e = nullptr;
2762 auto i = m_hud_server_to_client.find(event->hudchange->id);
2763 if (i != m_hud_server_to_client.end()) {
2764 e = player->getHud(i->second);
2768 delete event->hudchange;
2772 #define CASE_SET(statval, prop, dataprop) \
2774 e->prop = event->hudchange->dataprop; \
2777 switch (event->hudchange->stat) {
2778 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2780 CASE_SET(HUD_STAT_NAME, name, sdata);
2782 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2784 CASE_SET(HUD_STAT_TEXT, text, sdata);
2786 CASE_SET(HUD_STAT_NUMBER, number, data);
2788 CASE_SET(HUD_STAT_ITEM, item, data);
2790 CASE_SET(HUD_STAT_DIR, dir, data);
2792 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2794 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2796 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2798 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2800 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2802 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2804 CASE_SET(HUD_STAT_STYLE, style, data);
2809 delete event->hudchange;
2812 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2814 sky->setVisible(false);
2815 // Whether clouds are visible in front of a custom skybox.
2816 sky->setCloudsEnabled(event->set_sky->clouds);
2822 // Clear the old textures out in case we switch rendering type.
2823 sky->clearSkyboxTextures();
2824 // Handle according to type
2825 if (event->set_sky->type == "regular") {
2826 // Shows the mesh skybox
2827 sky->setVisible(true);
2828 // Update mesh based skybox colours if applicable.
2829 sky->setSkyColors(event->set_sky->sky_color);
2830 sky->setHorizonTint(
2831 event->set_sky->fog_sun_tint,
2832 event->set_sky->fog_moon_tint,
2833 event->set_sky->fog_tint_type
2835 } else if (event->set_sky->type == "skybox" &&
2836 event->set_sky->textures.size() == 6) {
2837 // Disable the dyanmic mesh skybox:
2838 sky->setVisible(false);
2840 sky->setFallbackBgColor(event->set_sky->bgcolor);
2841 // Set sunrise and sunset fog tinting:
2842 sky->setHorizonTint(
2843 event->set_sky->fog_sun_tint,
2844 event->set_sky->fog_moon_tint,
2845 event->set_sky->fog_tint_type
2847 // Add textures to skybox.
2848 for (int i = 0; i < 6; i++)
2849 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2851 // Handle everything else as plain color.
2852 if (event->set_sky->type != "plain")
2853 infostream << "Unknown sky type: "
2854 << (event->set_sky->type) << std::endl;
2855 sky->setVisible(false);
2856 sky->setFallbackBgColor(event->set_sky->bgcolor);
2857 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2858 sky->setHorizonTint(
2859 event->set_sky->bgcolor,
2860 event->set_sky->bgcolor,
2865 delete event->set_sky;
2868 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2870 sky->setSunVisible(event->sun_params->visible);
2871 sky->setSunTexture(event->sun_params->texture,
2872 event->sun_params->tonemap, texture_src);
2873 sky->setSunScale(event->sun_params->scale);
2874 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2875 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2876 delete event->sun_params;
2879 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2881 sky->setMoonVisible(event->moon_params->visible);
2882 sky->setMoonTexture(event->moon_params->texture,
2883 event->moon_params->tonemap, texture_src);
2884 sky->setMoonScale(event->moon_params->scale);
2885 delete event->moon_params;
2888 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2890 sky->setStarsVisible(event->star_params->visible);
2891 sky->setStarCount(event->star_params->count, false);
2892 sky->setStarColor(event->star_params->starcolor);
2893 sky->setStarScale(event->star_params->scale);
2894 delete event->star_params;
2897 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2898 CameraOrientation *cam)
2900 client->getEnv().setDayNightRatioOverride(
2901 event->override_day_night_ratio.do_override,
2902 event->override_day_night_ratio.ratio_f * 1000.0f);
2905 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2910 clouds->setDensity(event->cloud_params.density);
2911 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2912 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2913 clouds->setHeight(event->cloud_params.height);
2914 clouds->setThickness(event->cloud_params.thickness);
2915 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2918 void Game::processClientEvents(CameraOrientation *cam)
2920 while (client->hasClientEvents()) {
2921 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2922 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2923 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2924 (this->*evHandler.handler)(event.get(), cam);
2928 void Game::updateChat(f32 dtime)
2930 // Get new messages from error log buffer
2931 while (!m_chat_log_buf.empty())
2932 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2934 // Get new messages from client
2935 std::wstring message;
2936 while (client->getChatMessage(message)) {
2937 chat_backend->addUnparsedMessage(message);
2940 // Remove old messages
2941 chat_backend->step(dtime);
2943 // Display all messages in a static text element
2944 auto &buf = chat_backend->getRecentBuffer();
2945 if (buf.getLinesModified()) {
2946 buf.resetLinesModified();
2947 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
2950 // Make sure that the size is still correct
2951 m_game_ui->updateChatSize();
2954 void Game::updateCamera(u32 busy_time, f32 dtime)
2956 LocalPlayer *player = client->getEnv().getLocalPlayer();
2959 For interaction purposes, get info about the held item
2961 - Is it a usable item?
2962 - Can it point to liquids?
2964 ItemStack playeritem;
2966 ItemStack selected, hand;
2967 playeritem = player->getWieldedItem(&selected, &hand);
2970 ToolCapabilities playeritem_toolcap =
2971 playeritem.getToolCapabilities(itemdef_manager);
2973 v3s16 old_camera_offset = camera->getOffset();
2975 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2976 GenericCAO *playercao = player->getCAO();
2978 // If playercao not loaded, don't change camera
2982 camera->toggleCameraMode();
2984 // Make the player visible depending on camera mode.
2985 playercao->updateMeshCulling();
2986 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2989 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2990 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2992 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2993 camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
2994 camera->step(dtime);
2996 v3f camera_position = camera->getPosition();
2997 v3f camera_direction = camera->getDirection();
2998 f32 camera_fov = camera->getFovMax();
2999 v3s16 camera_offset = camera->getOffset();
3001 m_camera_offset_changed = (camera_offset != old_camera_offset);
3003 if (!m_flags.disable_camera_update) {
3004 client->getEnv().getClientMap().updateCamera(camera_position,
3005 camera_direction, camera_fov, camera_offset);
3007 if (m_camera_offset_changed) {
3008 client->updateCameraOffset(camera_offset);
3009 client->getEnv().updateCameraOffset(camera_offset);
3012 clouds->updateCameraOffset(camera_offset);
3018 void Game::updateSound(f32 dtime)
3020 // Update sound listener
3021 v3s16 camera_offset = camera->getOffset();
3022 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3023 v3f(0, 0, 0), // velocity
3024 camera->getDirection(),
3025 camera->getCameraNode()->getUpVector());
3027 bool mute_sound = g_settings->getBool("mute_sound");
3029 sound->setListenerGain(0.0f);
3031 // Check if volume is in the proper range, else fix it.
3032 float old_volume = g_settings->getFloat("sound_volume");
3033 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3034 sound->setListenerGain(new_volume);
3036 if (old_volume != new_volume) {
3037 g_settings->setFloat("sound_volume", new_volume);
3041 LocalPlayer *player = client->getEnv().getLocalPlayer();
3043 // Tell the sound maker whether to make footstep sounds
3044 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3046 // Update sound maker
3047 if (player->makes_footstep_sound)
3048 soundmaker->step(dtime);
3050 ClientMap &map = client->getEnv().getClientMap();
3051 MapNode n = map.getNode(player->getFootstepNodePos());
3052 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3056 void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
3058 LocalPlayer *player = client->getEnv().getLocalPlayer();
3060 const v3f camera_direction = camera->getDirection();
3061 const v3s16 camera_offset = camera->getOffset();
3064 Calculate what block is the crosshair pointing to
3067 ItemStack selected_item, hand_item;
3068 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3070 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3071 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3073 core::line3d<f32> shootline;
3075 switch (camera->getCameraMode()) {
3076 case CAMERA_MODE_FIRST:
3077 // Shoot from camera position, with bobbing
3078 shootline.start = camera->getPosition();
3080 case CAMERA_MODE_THIRD:
3081 // Shoot from player head, no bobbing
3082 shootline.start = camera->getHeadPosition();
3084 case CAMERA_MODE_THIRD_FRONT:
3085 shootline.start = camera->getHeadPosition();
3086 // prevent player pointing anything in front-view
3090 shootline.end = shootline.start + camera_direction * BS * d;
3092 #ifdef HAVE_TOUCHSCREENGUI
3094 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3095 shootline = g_touchscreengui->getShootline();
3096 // Scale shootline to the acual distance the player can reach
3097 shootline.end = shootline.start
3098 + shootline.getVector().normalize() * BS * d;
3099 shootline.start += intToFloat(camera_offset, BS);
3100 shootline.end += intToFloat(camera_offset, BS);
3105 PointedThing pointed = updatePointedThing(shootline,
3106 selected_def.liquids_pointable,
3107 !runData.btn_down_for_dig,
3110 if (pointed != runData.pointed_old) {
3111 infostream << "Pointing at " << pointed.dump() << std::endl;
3112 hud->updateSelectionMesh(camera_offset);
3115 // Allow digging again if button is not pressed
3116 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3117 runData.digging_blocked = false;
3121 - releasing dig button
3122 - pointing away from node
3124 if (runData.digging) {
3125 if (wasKeyReleased(KeyType::DIG)) {
3126 infostream << "Dig button released (stopped digging)" << std::endl;
3127 runData.digging = false;
3128 } else if (pointed != runData.pointed_old) {
3129 if (pointed.type == POINTEDTHING_NODE
3130 && runData.pointed_old.type == POINTEDTHING_NODE
3131 && pointed.node_undersurface
3132 == runData.pointed_old.node_undersurface) {
3133 // Still pointing to the same node, but a different face.
3136 infostream << "Pointing away from node (stopped digging)" << std::endl;
3137 runData.digging = false;
3138 hud->updateSelectionMesh(camera_offset);
3142 if (!runData.digging) {
3143 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3144 client->setCrack(-1, v3s16(0, 0, 0));
3145 runData.dig_time = 0.0;
3147 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3148 // Remove e.g. torches faster when clicking instead of holding dig button
3149 runData.nodig_delay_timer = 0;
3150 runData.dig_instantly = false;
3153 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3154 runData.btn_down_for_dig = false;
3156 runData.punching = false;
3158 soundmaker->m_player_leftpunch_sound.name = "";
3160 // Prepare for repeating, unless we're not supposed to
3161 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3162 runData.repeat_place_timer += dtime;
3164 runData.repeat_place_timer = 0;
3166 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3167 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3168 !client->getScript()->on_item_use(selected_item, pointed)))
3169 client->interact(INTERACT_USE, pointed);
3170 } else if (pointed.type == POINTEDTHING_NODE) {
3171 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3172 } else if (pointed.type == POINTEDTHING_OBJECT) {
3173 v3f player_position = player->getPosition();
3174 handlePointingAtObject(pointed, tool_item, player_position, show_debug);
3175 } else if (isKeyDown(KeyType::DIG)) {
3176 // When button is held down in air, show continuous animation
3177 runData.punching = true;
3178 // Run callback even though item is not usable
3179 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3180 client->getScript()->on_item_use(selected_item, pointed);
3181 } else if (wasKeyPressed(KeyType::PLACE)) {
3182 handlePointingAtNothing(selected_item);
3185 runData.pointed_old = pointed;
3187 if (runData.punching || wasKeyPressed(KeyType::DIG))
3188 camera->setDigging(0); // dig animation
3190 input->clearWasKeyPressed();
3191 input->clearWasKeyReleased();
3192 // Ensure DIG & PLACE are marked as handled
3193 wasKeyDown(KeyType::DIG);
3194 wasKeyDown(KeyType::PLACE);
3196 input->joystick.clearWasKeyPressed(KeyType::DIG);
3197 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3199 input->joystick.clearWasKeyReleased(KeyType::DIG);
3200 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3204 PointedThing Game::updatePointedThing(
3205 const core::line3d<f32> &shootline,
3206 bool liquids_pointable,
3207 bool look_for_object,
3208 const v3s16 &camera_offset)
3210 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3211 selectionboxes->clear();
3212 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3213 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3214 "show_entity_selectionbox");
3216 ClientEnvironment &env = client->getEnv();
3217 ClientMap &map = env.getClientMap();
3218 const NodeDefManager *nodedef = map.getNodeDefManager();
3220 runData.selected_object = NULL;
3221 hud->pointing_at_object = false;
3223 RaycastState s(shootline, look_for_object, liquids_pointable);
3224 PointedThing result;
3225 env.continueRaycast(&s, &result);
3226 if (result.type == POINTEDTHING_OBJECT) {
3227 hud->pointing_at_object = true;
3229 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3230 aabb3f selection_box;
3231 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3232 runData.selected_object->getSelectionBox(&selection_box)) {
3233 v3f pos = runData.selected_object->getPosition();
3234 selectionboxes->push_back(aabb3f(selection_box));
3235 hud->setSelectionPos(pos, camera_offset);
3237 } else if (result.type == POINTEDTHING_NODE) {
3238 // Update selection boxes
3239 MapNode n = map.getNode(result.node_undersurface);
3240 std::vector<aabb3f> boxes;
3241 n.getSelectionBoxes(nodedef, &boxes,
3242 n.getNeighbors(result.node_undersurface, &map));
3245 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3246 i != boxes.end(); ++i) {
3248 box.MinEdge -= v3f(d, d, d);
3249 box.MaxEdge += v3f(d, d, d);
3250 selectionboxes->push_back(box);
3252 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3254 hud->setSelectedFaceNormal(v3f(
3255 result.intersection_normal.X,
3256 result.intersection_normal.Y,
3257 result.intersection_normal.Z));
3260 // Update selection mesh light level and vertex colors
3261 if (!selectionboxes->empty()) {
3262 v3f pf = hud->getSelectionPos();
3263 v3s16 p = floatToInt(pf, BS);
3265 // Get selection mesh light level
3266 MapNode n = map.getNode(p);
3267 u16 node_light = getInteriorLight(n, -1, nodedef);
3268 u16 light_level = node_light;
3270 for (const v3s16 &dir : g_6dirs) {
3271 n = map.getNode(p + dir);
3272 node_light = getInteriorLight(n, -1, nodedef);
3273 if (node_light > light_level)
3274 light_level = node_light;
3277 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3279 final_color_blend(&c, light_level, daynight_ratio);
3281 // Modify final color a bit with time
3282 u32 timer = porting::getTimeMs() % 5000;
3283 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3284 float sin_r = 0.08f * std::sin(timerf);
3285 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3286 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3287 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3288 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3289 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3291 // Set mesh final color
3292 hud->setSelectionMeshColor(c);
3298 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3300 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3301 PointedThing fauxPointed;
3302 fauxPointed.type = POINTEDTHING_NOTHING;
3303 client->interact(INTERACT_ACTIVATE, fauxPointed);
3307 void Game::handlePointingAtNode(const PointedThing &pointed,
3308 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3310 v3s16 nodepos = pointed.node_undersurface;
3311 v3s16 neighbourpos = pointed.node_abovesurface;
3314 Check information text of node
3317 ClientMap &map = client->getEnv().getClientMap();
3319 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3320 && !runData.digging_blocked
3321 && client->checkPrivilege("interact")) {
3322 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3325 // This should be done after digging handling
3326 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3329 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3330 meta->getString("infotext"))));
3332 MapNode n = map.getNode(nodepos);
3334 if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
3335 m_game_ui->setInfoText(L"Unknown node: " +
3336 utf8_to_wide(nodedef_manager->get(n).name));
3340 if ((wasKeyPressed(KeyType::PLACE) ||
3341 runData.repeat_place_timer >= m_repeat_place_time) &&
3342 client->checkPrivilege("interact")) {
3343 runData.repeat_place_timer = 0;
3344 infostream << "Place button pressed while looking at ground" << std::endl;
3346 // Placing animation (always shown for feedback)
3347 camera->setDigging(1);
3349 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3351 // If the wielded item has node placement prediction,
3353 // And also set the sound and send the interact
3354 // But first check for meta formspec and rightclickable
3355 auto &def = selected_item.getDefinition(itemdef_manager);
3356 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3359 if (placed && client->modsLoaded())
3360 client->getScript()->on_placenode(pointed, def);
3364 bool Game::nodePlacement(const ItemDefinition &selected_def,
3365 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3366 const PointedThing &pointed, const NodeMetadata *meta)
3368 const auto &prediction = selected_def.node_placement_prediction;
3370 const NodeDefManager *nodedef = client->ndef();
3371 ClientMap &map = client->getEnv().getClientMap();
3373 bool is_valid_position;
3375 node = map.getNode(nodepos, &is_valid_position);
3376 if (!is_valid_position) {
3377 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3382 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3383 && !isKeyDown(KeyType::SNEAK)) {
3384 // on_rightclick callbacks are called anyway
3385 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3386 client->interact(INTERACT_PLACE, pointed);
3388 infostream << "Launching custom inventory view" << std::endl;
3390 InventoryLocation inventoryloc;
3391 inventoryloc.setNodeMeta(nodepos);
3393 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3394 &client->getEnv().getClientMap(), nodepos);
3395 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3397 auto *&formspec = m_game_ui->updateFormspec("");
3398 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3399 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3401 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3405 // on_rightclick callback
3406 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3407 !isKeyDown(KeyType::SNEAK))) {
3409 client->interact(INTERACT_PLACE, pointed);
3413 verbosestream << "Node placement prediction for "
3414 << selected_def.name << " is " << prediction << std::endl;
3415 v3s16 p = neighbourpos;
3417 // Place inside node itself if buildable_to
3418 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3419 if (is_valid_position) {
3420 if (nodedef->get(n_under).buildable_to) {
3423 node = map.getNode(p, &is_valid_position);
3424 if (is_valid_position && !nodedef->get(node).buildable_to) {
3425 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3427 client->interact(INTERACT_PLACE, pointed);
3433 // Find id of predicted node
3435 bool found = nodedef->getId(prediction, id);
3438 errorstream << "Node placement prediction failed for "
3439 << selected_def.name << " (places " << prediction
3440 << ") - Name not known" << std::endl;
3441 // Handle this as if prediction was empty
3443 client->interact(INTERACT_PLACE, pointed);
3447 const ContentFeatures &predicted_f = nodedef->get(id);
3449 // Predict param2 for facedir and wallmounted nodes
3450 // Compare core.item_place_node() for what the server does
3453 const u8 place_param2 = selected_def.place_param2;
3456 param2 = place_param2;
3457 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3458 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3459 v3s16 dir = nodepos - neighbourpos;
3461 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3462 param2 = dir.Y < 0 ? 1 : 0;
3463 } else if (abs(dir.X) > abs(dir.Z)) {
3464 param2 = dir.X < 0 ? 3 : 2;
3466 param2 = dir.Z < 0 ? 5 : 4;
3468 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3469 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3470 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3472 if (abs(dir.X) > abs(dir.Z)) {
3473 param2 = dir.X < 0 ? 3 : 1;
3475 param2 = dir.Z < 0 ? 2 : 0;
3479 // Check attachment if node is in group attached_node
3480 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3481 const static v3s16 wallmounted_dirs[8] = {
3491 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3492 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3493 pp = p + wallmounted_dirs[param2];
3495 pp = p + v3s16(0, -1, 0);
3497 if (!nodedef->get(map.getNode(pp)).walkable) {
3498 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3500 client->interact(INTERACT_PLACE, pointed);
3506 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3507 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3508 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3509 const auto &indexstr = selected_item.metadata.
3510 getString("palette_index", 0);
3511 if (!indexstr.empty()) {
3512 s32 index = mystoi(indexstr);
3513 if (predicted_f.param_type_2 == CPT2_COLOR) {
3515 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3516 // param2 = pure palette index + other
3517 param2 = (index & 0xf8) | (param2 & 0x07);
3518 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3519 // param2 = pure palette index + other
3520 param2 = (index & 0xe0) | (param2 & 0x1f);
3525 // Add node to client map
3526 MapNode n(id, 0, param2);
3529 LocalPlayer *player = client->getEnv().getLocalPlayer();
3531 // Dont place node when player would be inside new node
3532 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3533 if (!nodedef->get(n).walkable ||
3534 g_settings->getBool("enable_build_where_you_stand") ||
3535 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3536 (nodedef->get(n).walkable &&
3537 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3538 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3539 // This triggers the required mesh update too
3540 client->addNode(p, n);
3542 client->interact(INTERACT_PLACE, pointed);
3543 // A node is predicted, also play a sound
3544 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3547 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3550 } catch (const InvalidPositionException &e) {
3551 errorstream << "Node placement prediction failed for "
3552 << selected_def.name << " (places "
3553 << prediction << ") - Position not loaded" << std::endl;
3554 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3559 void Game::handlePointingAtObject(const PointedThing &pointed,
3560 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3562 std::wstring infotext = unescape_translate(
3563 utf8_to_wide(runData.selected_object->infoText()));
3566 if (!infotext.empty()) {
3569 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3572 m_game_ui->setInfoText(infotext);
3574 if (isKeyDown(KeyType::DIG)) {
3575 bool do_punch = false;
3576 bool do_punch_damage = false;
3578 if (runData.object_hit_delay_timer <= 0.0) {
3580 do_punch_damage = true;
3581 runData.object_hit_delay_timer = object_hit_delay;
3584 if (wasKeyPressed(KeyType::DIG))
3588 infostream << "Punched object" << std::endl;
3589 runData.punching = true;
3592 if (do_punch_damage) {
3593 // Report direct punch
3594 v3f objpos = runData.selected_object->getPosition();
3595 v3f dir = (objpos - player_position).normalize();
3597 bool disable_send = runData.selected_object->directReportPunch(
3598 dir, &tool_item, runData.time_from_last_punch);
3599 runData.time_from_last_punch = 0;
3602 client->interact(INTERACT_START_DIGGING, pointed);
3604 } else if (wasKeyDown(KeyType::PLACE)) {
3605 infostream << "Pressed place button while pointing at object" << std::endl;
3606 client->interact(INTERACT_PLACE, pointed); // place
3611 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3612 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3614 // See also: serverpackethandle.cpp, action == 2
3615 LocalPlayer *player = client->getEnv().getLocalPlayer();
3616 ClientMap &map = client->getEnv().getClientMap();
3617 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3619 // NOTE: Similar piece of code exists on the server side for
3621 // Get digging parameters
3622 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3623 &selected_item.getToolCapabilities(itemdef_manager));
3625 // If can't dig, try hand
3626 if (!params.diggable) {
3627 params = getDigParams(nodedef_manager->get(n).groups,
3628 &hand_item.getToolCapabilities(itemdef_manager));
3631 if (!params.diggable) {
3632 // I guess nobody will wait for this long
3633 runData.dig_time_complete = 10000000.0;
3635 runData.dig_time_complete = params.time;
3637 if (m_cache_enable_particles) {
3638 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3639 client->getParticleManager()->addNodeParticle(client,
3640 player, nodepos, n, features);
3644 if (!runData.digging) {
3645 infostream << "Started digging" << std::endl;
3646 runData.dig_instantly = runData.dig_time_complete == 0;
3647 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3649 client->interact(INTERACT_START_DIGGING, pointed);
3650 runData.digging = true;
3651 runData.btn_down_for_dig = true;
3654 if (!runData.dig_instantly) {
3655 runData.dig_index = (float)crack_animation_length
3657 / runData.dig_time_complete;
3659 // This is for e.g. torches
3660 runData.dig_index = crack_animation_length;
3663 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3665 if (sound_dig.exists() && params.diggable) {
3666 if (sound_dig.name == "__group") {
3667 if (!params.main_group.empty()) {
3668 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3669 soundmaker->m_player_leftpunch_sound.name =
3670 std::string("default_dig_") +
3674 soundmaker->m_player_leftpunch_sound = sound_dig;
3678 // Don't show cracks if not diggable
3679 if (runData.dig_time_complete >= 100000.0) {
3680 } else if (runData.dig_index < crack_animation_length) {
3681 //TimeTaker timer("client.setTempMod");
3682 //infostream<<"dig_index="<<dig_index<<std::endl;
3683 client->setCrack(runData.dig_index, nodepos);
3685 infostream << "Digging completed" << std::endl;
3686 client->setCrack(-1, v3s16(0, 0, 0));
3688 runData.dig_time = 0;
3689 runData.digging = false;
3690 // we successfully dug, now block it from repeating if we want to be safe
3691 if (g_settings->getBool("safe_dig_and_place"))
3692 runData.digging_blocked = true;
3694 runData.nodig_delay_timer =
3695 runData.dig_time_complete / (float)crack_animation_length;
3697 // We don't want a corresponding delay to very time consuming nodes
3698 // and nodes without digging time (e.g. torches) get a fixed delay.
3699 if (runData.nodig_delay_timer > 0.3)
3700 runData.nodig_delay_timer = 0.3;
3701 else if (runData.dig_instantly)
3702 runData.nodig_delay_timer = 0.15;
3704 bool is_valid_position;
3705 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3706 if (is_valid_position) {
3707 if (client->modsLoaded() &&
3708 client->getScript()->on_dignode(nodepos, wasnode)) {
3712 const ContentFeatures &f = client->ndef()->get(wasnode);
3713 if (f.node_dig_prediction == "air") {
3714 client->removeNode(nodepos);
3715 } else if (!f.node_dig_prediction.empty()) {
3717 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3719 client->addNode(nodepos, id, true);
3721 // implicit else: no prediction
3724 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3726 if (m_cache_enable_particles) {
3727 const ContentFeatures &features =
3728 client->getNodeDefManager()->get(wasnode);
3729 client->getParticleManager()->addDiggingParticles(client,
3730 player, nodepos, wasnode, features);
3734 // Send event to trigger sound
3735 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3738 if (runData.dig_time_complete < 100000.0) {
3739 runData.dig_time += dtime;
3741 runData.dig_time = 0;
3742 client->setCrack(-1, nodepos);
3745 camera->setDigging(0); // Dig animation
3748 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3749 const CameraOrientation &cam)
3751 TimeTaker tt_update("Game::updateFrame()");
3752 LocalPlayer *player = client->getEnv().getLocalPlayer();
3758 if (draw_control->range_all) {
3759 runData.fog_range = 100000 * BS;
3761 runData.fog_range = draw_control->wanted_range * BS;
3765 Calculate general brightness
3767 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3768 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3769 float direct_brightness;
3772 if (m_cache_enable_noclip && m_cache_enable_free_move) {
3773 direct_brightness = time_brightness;
3774 sunlight_seen = true;
3776 float old_brightness = sky->getBrightness();
3777 direct_brightness = client->getEnv().getClientMap()
3778 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3779 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3783 float time_of_day_smooth = runData.time_of_day_smooth;
3784 float time_of_day = client->getEnv().getTimeOfDayF();
3786 static const float maxsm = 0.05f;
3787 static const float todsm = 0.05f;
3789 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3790 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3791 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3792 time_of_day_smooth = time_of_day;
3794 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3795 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3796 + (time_of_day + 1.0) * todsm;
3798 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3799 + time_of_day * todsm;
3801 runData.time_of_day_smooth = time_of_day_smooth;
3803 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3804 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3805 player->getPitch());
3811 if (sky->getCloudsVisible()) {
3812 clouds->setVisible(true);
3813 clouds->step(dtime);
3814 // camera->getPosition is not enough for 3rd person views
3815 v3f camera_node_position = camera->getCameraNode()->getPosition();
3816 v3s16 camera_offset = camera->getOffset();
3817 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3818 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3819 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3820 clouds->update(camera_node_position,
3821 sky->getCloudColor());
3822 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3823 // if inside clouds, and fog enabled, use that as sky
3825 video::SColor clouds_dark = clouds->getColor()
3826 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3827 sky->overrideColors(clouds_dark, clouds->getColor());
3828 sky->setInClouds(true);
3829 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3830 // do not draw clouds after all
3831 clouds->setVisible(false);
3834 clouds->setVisible(false);
3841 client->getParticleManager()->step(dtime);
3847 if (m_cache_enable_fog) {
3850 video::EFT_FOG_LINEAR,
3851 runData.fog_range * m_cache_fog_start,
3852 runData.fog_range * 1.0,
3860 video::EFT_FOG_LINEAR,
3870 Get chat messages from client
3879 if (player->getWieldIndex() != runData.new_playeritem)
3880 client->setPlayerItem(runData.new_playeritem);
3882 if (client->updateWieldedItem()) {
3883 // Update wielded tool
3884 ItemStack selected_item, hand_item;
3885 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3886 camera->wield(tool_item);
3890 Update block draw list every 200ms or when camera direction has
3893 runData.update_draw_list_timer += dtime;
3895 float update_draw_list_delta = 0.2f;
3897 v3f camera_direction = camera->getDirection();
3898 if (runData.update_draw_list_timer >= update_draw_list_delta
3899 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3900 || m_camera_offset_changed
3901 || client->getEnv().getClientMap().needsUpdateDrawList()) {
3902 runData.update_draw_list_timer = 0;
3903 client->getEnv().getClientMap().updateDrawList();
3904 runData.update_draw_list_last_cam_dir = camera_direction;
3907 if (RenderingEngine::get_shadow_renderer()) {
3911 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3914 make sure menu is on top
3915 1. Delete formspec menu reference if menu was removed
3916 2. Else, make sure formspec menu is on top
3918 auto formspec = m_game_ui->getFormspecGUI();
3919 do { // breakable. only runs for one iteration
3923 if (formspec->getReferenceCount() == 1) {
3924 m_game_ui->deleteFormspec();
3928 auto &loc = formspec->getFormspecLocation();
3929 if (loc.type == InventoryLocation::NODEMETA) {
3930 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3931 if (!meta || meta->getString("formspec").empty()) {
3932 formspec->quitMenu();
3938 guiroot->bringToFront(formspec);
3944 const video::SColor &skycolor = sky->getSkyColor();
3946 TimeTaker tt_draw("Draw scene");
3947 driver->beginScene(true, true, skycolor);
3949 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3950 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3951 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3952 bool draw_crosshair = (
3953 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3954 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3955 #ifdef HAVE_TOUCHSCREENGUI
3957 draw_crosshair = !g_settings->getBool("touchtarget");
3958 } catch (SettingNotFoundException) {
3961 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3962 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3967 v2u32 screensize = driver->getScreenSize();
3969 if (m_game_ui->m_flags.show_profiler_graph)
3970 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3975 if (runData.damage_flash > 0.0f) {
3976 video::SColor color(runData.damage_flash, 180, 0, 0);
3977 driver->draw2DRectangle(color,
3978 core::rect<s32>(0, 0, screensize.X, screensize.Y),
3981 runData.damage_flash -= 384.0f * dtime;
3987 if (player->hurt_tilt_timer > 0.0f) {
3988 player->hurt_tilt_timer -= dtime * 6.0f;
3990 if (player->hurt_tilt_timer < 0.0f)
3991 player->hurt_tilt_strength = 0.0f;
3995 Update minimap pos and rotation
3997 if (mapper && m_game_ui->m_flags.show_hud) {
3998 mapper->setPos(floatToInt(player->getPosition(), BS));
3999 mapper->setAngle(player->getYaw());
4005 if (++m_reset_HW_buffer_counter > 500) {
4007 Periodically remove all mesh HW buffers.
4009 Work around for a quirk in Irrlicht where a HW buffer is only
4010 released after 20000 iterations (triggered from endScene()).
4012 Without this, all loaded but unused meshes will retain their HW
4013 buffers for at least 5 minutes, at which point looking up the HW buffers
4014 becomes a bottleneck and the framerate drops (as much as 30%).
4016 Tests showed that numbers between 50 and 1000 are good, so picked 500.
4017 There are no other public Irrlicht APIs that allow interacting with the
4018 HW buffers without tracking the status of every individual mesh.
4020 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4022 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4023 driver->removeAllHardwareBuffers();
4024 m_reset_HW_buffer_counter = 0;
4028 stats->drawtime = tt_draw.stop(true);
4029 g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
4030 g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
4033 /* Log times and stuff for visualization */
4034 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4036 Profiler::GraphValues values;
4037 g_profiler->graphGet(values);
4041 /****************************************************************************
4043 *****************************************************************************/
4044 void Game::updateShadows()
4046 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4050 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4052 float timeoftheday = fmod(getWickedTimeOfDay(in_timeofday) + 0.75f, 0.5f) + 0.25f;
4053 const float offset_constant = 10000.0f;
4055 v3f light(0.0f, 0.0f, -1.0f);
4056 light.rotateXZBy(90);
4057 light.rotateXYBy(timeoftheday * 360 - 90);
4058 light.rotateYZBy(sky->getSkyBodyOrbitTilt());
4060 v3f sun_pos = light * offset_constant;
4062 if (shadow->getDirectionalLightCount() == 0)
4063 shadow->addDirectionalLight();
4064 shadow->getDirectionalLight().setDirection(sun_pos);
4065 shadow->setTimeOfDay(in_timeofday);
4067 shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4070 /****************************************************************************
4072 ****************************************************************************/
4074 /* On some computers framerate doesn't seem to be automatically limited
4076 inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
4078 // not using getRealTime is necessary for wine
4079 device->getTimer()->tick(); // Maker sure device time is up-to-date
4080 u32 time = device->getTimer()->getTime();
4081 u32 last_time = fps_timings->last_time;
4083 if (time > last_time) // Make sure time hasn't overflowed
4084 fps_timings->busy_time = time - last_time;
4086 fps_timings->busy_time = 0;
4088 u32 frametime_min = 1000 / (
4089 device->isWindowFocused() && !g_menumgr.pausesGame()
4090 ? g_settings->getFloat("fps_max")
4091 : g_settings->getFloat("fps_max_unfocused"));
4093 if (fps_timings->busy_time < frametime_min) {
4094 fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
4095 device->sleep(fps_timings->sleep_time);
4097 fps_timings->sleep_time = 0;
4100 /* Get the new value of the device timer. Note that device->sleep() may
4101 * not sleep for the entire requested time as sleep may be interrupted and
4102 * therefore it is arguably more accurate to get the new time from the
4103 * device rather than calculating it by adding sleep_time to time.
4106 device->getTimer()->tick(); // Update device timer
4107 time = device->getTimer()->getTime();
4109 if (time > last_time) // Make sure last_time hasn't overflowed
4110 *dtime = (time - last_time) / 1000.0;
4114 fps_timings->last_time = time;
4117 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4119 const wchar_t *wmsg = wgettext(msg);
4120 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4125 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4127 ((Game *)data)->readSettings();
4130 void Game::readSettings()
4132 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4133 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4134 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4135 m_cache_enable_particles = g_settings->getBool("enable_particles");
4136 m_cache_enable_fog = g_settings->getBool("enable_fog");
4137 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
4138 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4139 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
4141 m_cache_enable_noclip = g_settings->getBool("noclip");
4142 m_cache_enable_free_move = g_settings->getBool("free_move");
4144 m_cache_fog_start = g_settings->getFloat("fog_start");
4146 m_cache_cam_smoothing = 0;
4147 if (g_settings->getBool("cinematic"))
4148 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4150 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4152 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4153 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4154 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4156 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4159 /****************************************************************************/
4160 /****************************************************************************
4162 ****************************************************************************/
4163 /****************************************************************************/
4165 void Game::showDeathFormspec()
4167 static std::string formspec_str =
4168 std::string("formspec_version[1]") +
4170 "bgcolor[#320000b4;true]"
4171 "label[4.85,1.35;" + gettext("You died") + "]"
4172 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4176 /* Note: FormspecFormSource and LocalFormspecHandler *
4177 * are deleted by guiFormSpecMenu */
4178 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4179 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4181 auto *&formspec = m_game_ui->getFormspecGUI();
4182 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4183 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4184 formspec->setFocus("btn_respawn");
4187 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4188 void Game::showPauseMenu()
4190 #ifdef HAVE_TOUCHSCREENGUI
4191 static const std::string control_text = strgettext("Default Controls:\n"
4192 "No menu visible:\n"
4193 "- single tap: button activate\n"
4194 "- double tap: place/use\n"
4195 "- slide finger: look around\n"
4196 "Menu/Inventory visible:\n"
4197 "- double tap (outside):\n"
4199 "- touch stack, touch slot:\n"
4201 "- touch&drag, tap 2nd finger\n"
4202 " --> place single item to slot\n"
4205 static const std::string control_text_template = strgettext("Controls:\n"
4206 "- %s: move forwards\n"
4207 "- %s: move backwards\n"
4209 "- %s: move right\n"
4210 "- %s: jump/climb up\n"
4213 "- %s: sneak/climb down\n"
4216 "- Mouse: turn/look\n"
4217 "- Mouse wheel: select item\n"
4221 char control_text_buf[600];
4223 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4224 GET_KEY_NAME(keymap_forward),
4225 GET_KEY_NAME(keymap_backward),
4226 GET_KEY_NAME(keymap_left),
4227 GET_KEY_NAME(keymap_right),
4228 GET_KEY_NAME(keymap_jump),
4229 GET_KEY_NAME(keymap_dig),
4230 GET_KEY_NAME(keymap_place),
4231 GET_KEY_NAME(keymap_sneak),
4232 GET_KEY_NAME(keymap_drop),
4233 GET_KEY_NAME(keymap_inventory),
4234 GET_KEY_NAME(keymap_chat)
4237 std::string control_text = std::string(control_text_buf);
4238 str_formspec_escape(control_text);
4241 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4242 std::ostringstream os;
4244 os << "formspec_version[1]" << SIZE_TAG
4245 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4246 << strgettext("Continue") << "]";
4248 if (!simple_singleplayer_mode) {
4249 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4250 << strgettext("Change Password") << "]";
4252 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4257 if (g_settings->getBool("enable_sound")) {
4258 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4259 << strgettext("Sound Volume") << "]";
4262 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4263 << strgettext("Change Keys") << "]";
4265 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4266 << strgettext("Exit to Menu") << "]";
4267 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4268 << strgettext("Exit to OS") << "]"
4269 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4270 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4272 << strgettext("Game info:") << "\n";
4273 const std::string &address = client->getAddressName();
4274 static const std::string mode = strgettext("- Mode: ");
4275 if (!simple_singleplayer_mode) {
4276 Address serverAddress = client->getServerAddress();
4277 if (!address.empty()) {
4278 os << mode << strgettext("Remote server") << "\n"
4279 << strgettext("- Address: ") << address;
4281 os << mode << strgettext("Hosting server");
4283 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4285 os << mode << strgettext("Singleplayer") << "\n";
4287 if (simple_singleplayer_mode || address.empty()) {
4288 static const std::string on = strgettext("On");
4289 static const std::string off = strgettext("Off");
4290 const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
4291 const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
4292 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4293 os << strgettext("- Damage: ") << damage << "\n"
4294 << strgettext("- Creative Mode: ") << creative << "\n";
4295 if (!simple_singleplayer_mode) {
4296 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4297 //~ PvP = Player versus Player
4298 os << strgettext("- PvP: ") << pvp << "\n"
4299 << strgettext("- Public: ") << announced << "\n";
4300 std::string server_name = g_settings->get("server_name");
4301 str_formspec_escape(server_name);
4302 if (announced == on && !server_name.empty())
4303 os << strgettext("- Server Name: ") << server_name;
4310 /* Note: FormspecFormSource and LocalFormspecHandler *
4311 * are deleted by guiFormSpecMenu */
4312 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4313 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4315 auto *&formspec = m_game_ui->getFormspecGUI();
4316 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4317 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4318 formspec->setFocus("btn_continue");
4319 formspec->doPause = true;
4321 if (simple_singleplayer_mode)
4325 /****************************************************************************/
4326 /****************************************************************************
4327 extern function for launching the game
4328 ****************************************************************************/
4329 /****************************************************************************/
4331 void the_game(bool *kill,
4332 InputHandler *input,
4333 RenderingEngine *rendering_engine,
4334 const GameStartData &start_data,
4335 std::string &error_message,
4336 ChatBackend &chat_backend,
4337 bool *reconnect_requested) // Used for local game
4341 /* Make a copy of the server address because if a local singleplayer server
4342 * is created then this is updated and we don't want to change the value
4343 * passed to us by the calling function
4348 if (game.startup(kill, input, rendering_engine, start_data,
4349 error_message, reconnect_requested, &chat_backend)) {
4353 } catch (SerializationError &e) {
4354 error_message = std::string("A serialization error occurred:\n")
4355 + e.what() + "\n\nThe server is probably "
4356 " running a different version of " PROJECT_NAME_C ".";
4357 errorstream << error_message << std::endl;
4358 } catch (ServerError &e) {
4359 error_message = e.what();
4360 errorstream << "ServerError: " << error_message << std::endl;
4361 } catch (ModError &e) {
4362 error_message = std::string("ModError: ") + e.what() +
4363 strgettext("\nCheck debug.txt for details.");
4364 errorstream << error_message << std::endl;