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);
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, const v2u32 &screensize);
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;
905 bool m_cache_hold_aux1;
906 bool m_android_chat_open;
911 m_chat_log_buf(g_logger),
912 m_game_ui(new GameUI())
914 g_settings->registerChangedCallback("doubletap_jump",
915 &settingChangedCallback, this);
916 g_settings->registerChangedCallback("enable_clouds",
917 &settingChangedCallback, this);
918 g_settings->registerChangedCallback("doubletap_joysticks",
919 &settingChangedCallback, this);
920 g_settings->registerChangedCallback("enable_particles",
921 &settingChangedCallback, this);
922 g_settings->registerChangedCallback("enable_fog",
923 &settingChangedCallback, this);
924 g_settings->registerChangedCallback("mouse_sensitivity",
925 &settingChangedCallback, this);
926 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
927 &settingChangedCallback, this);
928 g_settings->registerChangedCallback("repeat_place_time",
929 &settingChangedCallback, this);
930 g_settings->registerChangedCallback("noclip",
931 &settingChangedCallback, this);
932 g_settings->registerChangedCallback("free_move",
933 &settingChangedCallback, this);
934 g_settings->registerChangedCallback("cinematic",
935 &settingChangedCallback, this);
936 g_settings->registerChangedCallback("cinematic_camera_smoothing",
937 &settingChangedCallback, this);
938 g_settings->registerChangedCallback("camera_smoothing",
939 &settingChangedCallback, this);
944 m_cache_hold_aux1 = false; // This is initialised properly later
951 /****************************************************************************
953 ****************************************************************************/
962 delete server; // deleted first to stop all server threads
970 delete nodedef_manager;
971 delete itemdef_manager;
974 clearTextureNameCache();
976 g_settings->deregisterChangedCallback("doubletap_jump",
977 &settingChangedCallback, this);
978 g_settings->deregisterChangedCallback("enable_clouds",
979 &settingChangedCallback, this);
980 g_settings->deregisterChangedCallback("enable_particles",
981 &settingChangedCallback, this);
982 g_settings->deregisterChangedCallback("enable_fog",
983 &settingChangedCallback, this);
984 g_settings->deregisterChangedCallback("mouse_sensitivity",
985 &settingChangedCallback, this);
986 g_settings->deregisterChangedCallback("repeat_place_time",
987 &settingChangedCallback, this);
988 g_settings->deregisterChangedCallback("noclip",
989 &settingChangedCallback, this);
990 g_settings->deregisterChangedCallback("free_move",
991 &settingChangedCallback, this);
992 g_settings->deregisterChangedCallback("cinematic",
993 &settingChangedCallback, this);
994 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
995 &settingChangedCallback, this);
996 g_settings->deregisterChangedCallback("camera_smoothing",
997 &settingChangedCallback, this);
1000 bool Game::startup(bool *kill,
1001 InputHandler *input,
1002 RenderingEngine *rendering_engine,
1003 const GameStartData &start_data,
1004 std::string &error_message,
1006 ChatBackend *chat_backend)
1010 m_rendering_engine = rendering_engine;
1011 device = m_rendering_engine->get_raw_device();
1013 this->error_message = &error_message;
1014 reconnect_requested = reconnect;
1015 this->input = input;
1016 this->chat_backend = chat_backend;
1017 simple_singleplayer_mode = start_data.isSinglePlayer();
1019 input->keycache.populate();
1021 driver = device->getVideoDriver();
1022 smgr = m_rendering_engine->get_scene_manager();
1024 smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1027 runData = GameRunData();
1028 runData.time_from_last_punch = 10.0;
1030 m_game_ui->initFlags();
1032 m_invert_mouse = g_settings->getBool("invert_mouse");
1033 m_first_loop_after_window_activation = true;
1035 g_client_translations->clear();
1037 // address can change if simple_singleplayer_mode
1038 if (!init(start_data.world_spec.path, start_data.address,
1039 start_data.socket_port, start_data.game_spec))
1042 if (!createClient(start_data))
1045 m_rendering_engine->initialize(client, hud);
1053 ProfilerGraph graph;
1054 RunStats stats = { 0 };
1055 CameraOrientation cam_view_target = { 0 };
1056 CameraOrientation cam_view = { 0 };
1057 FpsControl draw_times = { 0 };
1058 f32 dtime; // in seconds
1060 /* Clear the profiler */
1061 Profiler::GraphValues dummyvalues;
1062 g_profiler->graphGet(dummyvalues);
1064 draw_times.last_time = m_rendering_engine->get_timer_time();
1066 set_light_table(g_settings->getFloat("display_gamma"));
1069 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1070 && client->checkPrivilege("fast");
1073 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1074 g_settings->getU16("screen_h"));
1076 while (m_rendering_engine->run()
1077 && !(*kill || g_gamecallback->shutdown_requested
1078 || (server && server->isShutdownRequested()))) {
1080 const irr::core::dimension2d<u32> ¤t_screen_size =
1081 m_rendering_engine->get_video_driver()->getScreenSize();
1082 // Verify if window size has changed and save it if it's the case
1083 // Ensure evaluating settings->getBool after verifying screensize
1084 // First condition is cheaper
1085 if (previous_screen_size != current_screen_size &&
1086 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1087 g_settings->getBool("autosave_screensize")) {
1088 g_settings->setU16("screen_w", current_screen_size.Width);
1089 g_settings->setU16("screen_h", current_screen_size.Height);
1090 previous_screen_size = current_screen_size;
1093 // Calculate dtime =
1094 // m_rendering_engine->run() from this iteration
1095 // + Sleep time until the wanted FPS are reached
1096 limitFps(&draw_times, &dtime);
1098 // Prepare render data for next iteration
1100 updateStats(&stats, draw_times, dtime);
1101 updateInteractTimers(dtime);
1103 if (!checkConnection())
1105 if (!handleCallbacks())
1110 m_game_ui->clearInfoText();
1111 hud->resizeHotbar();
1114 updateProfilers(stats, draw_times, dtime);
1115 processUserInput(dtime);
1116 // Update camera before player movement to avoid camera lag of one frame
1117 updateCameraDirection(&cam_view_target, dtime);
1118 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1119 cam_view.camera_yaw) * m_cache_cam_smoothing;
1120 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1121 cam_view.camera_pitch) * m_cache_cam_smoothing;
1122 updatePlayerControl(cam_view);
1124 processClientEvents(&cam_view_target);
1126 updateCamera(draw_times.busy_time, dtime);
1128 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
1129 m_game_ui->m_flags.show_basic_debug);
1130 updateFrame(&graph, &stats, dtime, cam_view);
1131 updateProfilerGraphs(&graph);
1133 // Update if minimap has been disabled by the server
1134 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1136 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1143 void Game::shutdown()
1145 m_rendering_engine->finalize();
1146 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1147 if (g_settings->get("3d_mode") == "pageflip") {
1148 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1151 auto formspec = m_game_ui->getFormspecGUI();
1153 formspec->quitMenu();
1155 #ifdef HAVE_TOUCHSCREENGUI
1156 g_touchscreengui->hide();
1159 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1164 if (gui_chat_console)
1165 gui_chat_console->drop();
1171 while (g_menumgr.menuCount() > 0) {
1172 g_menumgr.m_stack.front()->setVisible(false);
1173 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1176 m_game_ui->deleteFormspec();
1178 chat_backend->addMessage(L"", L"# Disconnected.");
1179 chat_backend->addMessage(L"", L"");
1180 m_chat_log_buf.clear();
1184 while (!client->isShutdown()) {
1185 assert(texture_src != NULL);
1186 assert(shader_src != NULL);
1187 texture_src->processQueue();
1188 shader_src->processQueue();
1195 /****************************************************************************/
1196 /****************************************************************************
1198 ****************************************************************************/
1199 /****************************************************************************/
1202 const std::string &map_dir,
1203 const std::string &address,
1205 const SubgameSpec &gamespec)
1207 texture_src = createTextureSource();
1209 showOverlayMessage(N_("Loading..."), 0, 0);
1211 shader_src = createShaderSource();
1213 itemdef_manager = createItemDefManager();
1214 nodedef_manager = createNodeDefManager();
1216 eventmgr = new EventManager();
1217 quicktune = new QuicktuneShortcutter();
1219 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1220 && eventmgr && quicktune))
1226 // Create a server if not connecting to an existing one
1227 if (address.empty()) {
1228 if (!createSingleplayerServer(map_dir, gamespec, port))
1235 bool Game::initSound()
1238 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1239 infostream << "Attempting to use OpenAL audio" << std::endl;
1240 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1242 infostream << "Failed to initialize OpenAL audio" << std::endl;
1244 infostream << "Sound disabled." << std::endl;
1248 infostream << "Using dummy audio." << std::endl;
1249 sound = &dummySoundManager;
1250 sound_is_dummy = true;
1253 soundmaker = new SoundMaker(sound, nodedef_manager);
1257 soundmaker->registerReceiver(eventmgr);
1262 bool Game::createSingleplayerServer(const std::string &map_dir,
1263 const SubgameSpec &gamespec, u16 port)
1265 showOverlayMessage(N_("Creating server..."), 0, 5);
1267 std::string bind_str = g_settings->get("bind_address");
1268 Address bind_addr(0, 0, 0, 0, port);
1270 if (g_settings->getBool("ipv6_server")) {
1271 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1275 bind_addr.Resolve(bind_str.c_str());
1276 } catch (ResolveError &e) {
1277 infostream << "Resolving bind address \"" << bind_str
1278 << "\" failed: " << e.what()
1279 << " -- Listening on all addresses." << std::endl;
1282 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1283 *error_message = "Unable to listen on " +
1284 bind_addr.serializeString() +
1285 " because IPv6 is disabled";
1286 errorstream << *error_message << std::endl;
1290 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1291 false, nullptr, error_message);
1297 bool Game::createClient(const GameStartData &start_data)
1299 showOverlayMessage(N_("Creating client..."), 0, 10);
1301 draw_control = new MapDrawControl();
1305 bool could_connect, connect_aborted;
1306 #ifdef HAVE_TOUCHSCREENGUI
1307 if (g_touchscreengui) {
1308 g_touchscreengui->init(texture_src);
1309 g_touchscreengui->hide();
1312 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1315 if (!could_connect) {
1316 if (error_message->empty() && !connect_aborted) {
1317 // Should not happen if error messages are set properly
1318 *error_message = "Connection failed for unknown reason";
1319 errorstream << *error_message << std::endl;
1324 if (!getServerContent(&connect_aborted)) {
1325 if (error_message->empty() && !connect_aborted) {
1326 // Should not happen if error messages are set properly
1327 *error_message = "Connection failed for unknown reason";
1328 errorstream << *error_message << std::endl;
1333 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1334 &m_flags.force_fog_off, &runData.fog_range, client);
1335 shader_src->addShaderConstantSetterFactory(scsf);
1337 // Update cached textures, meshes and materials
1338 client->afterContentReceived();
1342 camera = new Camera(*draw_control, client, m_rendering_engine);
1343 if (!camera->successfullyCreated(*error_message))
1345 client->setCamera(camera);
1349 if (m_cache_enable_clouds)
1350 clouds = new Clouds(smgr, -1, time(0));
1354 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
1356 skybox = NULL; // This is used/set later on in the main run loop
1358 /* Pre-calculated values
1360 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1362 v2u32 size = t->getOriginalSize();
1363 crack_animation_length = size.Y / size.X;
1365 crack_animation_length = 5;
1371 /* Set window caption
1373 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1375 str += utf8_to_wide(g_version_hash);
1377 const wchar_t *text = nullptr;
1378 if (simple_singleplayer_mode)
1379 text = wgettext("Singleplayer");
1381 text = wgettext("Multiplayer");
1388 str += driver->getName();
1391 device->setWindowCaption(str.c_str());
1393 LocalPlayer *player = client->getEnv().getLocalPlayer();
1394 player->hurt_tilt_timer = 0;
1395 player->hurt_tilt_strength = 0;
1397 hud = new Hud(client, player, &player->inventory);
1399 mapper = client->getMinimap();
1401 if (mapper && client->modsLoaded())
1402 client->getScript()->on_minimap_ready(mapper);
1407 bool Game::initGui()
1411 // Remove stale "recent" chat messages from previous connections
1412 chat_backend->clearRecentChat();
1414 // Make sure the size of the recent messages buffer is right
1415 chat_backend->applySettings();
1417 // Chat backend and console
1418 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1419 -1, chat_backend, client, &g_menumgr);
1421 #ifdef HAVE_TOUCHSCREENGUI
1423 if (g_touchscreengui)
1424 g_touchscreengui->show();
1431 bool Game::connectToServer(const GameStartData &start_data,
1432 bool *connect_ok, bool *connection_aborted)
1434 *connect_ok = false; // Let's not be overly optimistic
1435 *connection_aborted = false;
1436 bool local_server_mode = false;
1438 showOverlayMessage(N_("Resolving address..."), 0, 15);
1440 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1443 connect_address.Resolve(start_data.address.c_str());
1445 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1446 //connect_address.Resolve("localhost");
1447 if (connect_address.isIPv6()) {
1448 IPv6AddressBytes addr_bytes;
1449 addr_bytes.bytes[15] = 1;
1450 connect_address.setAddress(&addr_bytes);
1452 connect_address.setAddress(127, 0, 0, 1);
1454 local_server_mode = true;
1456 } catch (ResolveError &e) {
1457 *error_message = std::string("Couldn't resolve address: ") + e.what();
1458 errorstream << *error_message << std::endl;
1462 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1463 *error_message = "Unable to connect to " +
1464 connect_address.serializeString() +
1465 " because IPv6 is disabled";
1466 errorstream << *error_message << std::endl;
1470 client = new Client(start_data.name.c_str(),
1471 start_data.password, start_data.address,
1472 *draw_control, texture_src, shader_src,
1473 itemdef_manager, nodedef_manager, sound, eventmgr,
1474 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get());
1476 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1478 infostream << "Connecting to server at ";
1479 connect_address.print(&infostream);
1480 infostream << std::endl;
1482 client->connect(connect_address,
1483 simple_singleplayer_mode || local_server_mode);
1486 Wait for server to accept connection
1492 FpsControl fps_control = { 0 };
1494 f32 wait_time = 0; // in seconds
1496 fps_control.last_time = m_rendering_engine->get_timer_time();
1498 while (m_rendering_engine->run()) {
1500 limitFps(&fps_control, &dtime);
1502 // Update client and server
1503 client->step(dtime);
1506 server->step(dtime);
1509 if (client->getState() == LC_Init) {
1515 if (*connection_aborted)
1518 if (client->accessDenied()) {
1519 *error_message = "Access denied. Reason: "
1520 + client->accessDeniedReason();
1521 *reconnect_requested = client->reconnectRequested();
1522 errorstream << *error_message << std::endl;
1526 if (input->cancelPressed()) {
1527 *connection_aborted = true;
1528 infostream << "Connect aborted [Escape]" << std::endl;
1532 if (client->m_is_registration_confirmation_state) {
1533 if (registration_confirmation_shown) {
1534 // Keep drawing the GUI
1535 m_rendering_engine->draw_menu_scene(guienv, dtime, true);
1537 registration_confirmation_shown = true;
1538 (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
1539 &g_menumgr, client, start_data.name, start_data.password,
1540 connection_aborted, texture_src))->drop();
1544 // Only time out if we aren't waiting for the server we started
1545 if (!start_data.address.empty() && wait_time > 10) {
1546 *error_message = "Connection timed out.";
1547 errorstream << *error_message << std::endl;
1552 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1555 } catch (con::PeerNotFoundException &e) {
1556 // TODO: Should something be done here? At least an info/error
1564 bool Game::getServerContent(bool *aborted)
1568 FpsControl fps_control = { 0 };
1569 f32 dtime; // in seconds
1571 fps_control.last_time = m_rendering_engine->get_timer_time();
1573 while (m_rendering_engine->run()) {
1575 limitFps(&fps_control, &dtime);
1577 // Update client and server
1578 client->step(dtime);
1581 server->step(dtime);
1584 if (client->mediaReceived() && client->itemdefReceived() &&
1585 client->nodedefReceived()) {
1590 if (!checkConnection())
1593 if (client->getState() < LC_Init) {
1594 *error_message = "Client disconnected";
1595 errorstream << *error_message << std::endl;
1599 if (input->cancelPressed()) {
1601 infostream << "Connect aborted [Escape]" << std::endl;
1608 if (!client->itemdefReceived()) {
1609 const wchar_t *text = wgettext("Item definitions...");
1611 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1614 } else if (!client->nodedefReceived()) {
1615 const wchar_t *text = wgettext("Node definitions...");
1617 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1621 std::ostringstream message;
1622 std::fixed(message);
1623 message.precision(0);
1624 float receive = client->mediaReceiveProgress() * 100;
1625 message << gettext("Media...");
1627 message << " " << receive << "%";
1628 message.precision(2);
1630 if ((USE_CURL == 0) ||
1631 (!g_settings->getBool("enable_remote_media_server"))) {
1632 float cur = client->getCurRate();
1633 std::string cur_unit = gettext("KiB/s");
1637 cur_unit = gettext("MiB/s");
1640 message << " (" << cur << ' ' << cur_unit << ")";
1643 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1644 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
1645 texture_src, dtime, progress);
1653 /****************************************************************************/
1654 /****************************************************************************
1656 ****************************************************************************/
1657 /****************************************************************************/
1659 inline void Game::updateInteractTimers(f32 dtime)
1661 if (runData.nodig_delay_timer >= 0)
1662 runData.nodig_delay_timer -= dtime;
1664 if (runData.object_hit_delay_timer >= 0)
1665 runData.object_hit_delay_timer -= dtime;
1667 runData.time_from_last_punch += dtime;
1671 /* returns false if game should exit, otherwise true
1673 inline bool Game::checkConnection()
1675 if (client->accessDenied()) {
1676 *error_message = "Access denied. Reason: "
1677 + client->accessDeniedReason();
1678 *reconnect_requested = client->reconnectRequested();
1679 errorstream << *error_message << std::endl;
1687 /* returns false if game should exit, otherwise true
1689 inline bool Game::handleCallbacks()
1691 if (g_gamecallback->disconnect_requested) {
1692 g_gamecallback->disconnect_requested = false;
1696 if (g_gamecallback->changepassword_requested) {
1697 (new GUIPasswordChange(guienv, guiroot, -1,
1698 &g_menumgr, client, texture_src))->drop();
1699 g_gamecallback->changepassword_requested = false;
1702 if (g_gamecallback->changevolume_requested) {
1703 (new GUIVolumeChange(guienv, guiroot, -1,
1704 &g_menumgr, texture_src))->drop();
1705 g_gamecallback->changevolume_requested = false;
1708 if (g_gamecallback->keyconfig_requested) {
1709 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1710 &g_menumgr, texture_src))->drop();
1711 g_gamecallback->keyconfig_requested = false;
1714 if (g_gamecallback->keyconfig_changed) {
1715 input->keycache.populate(); // update the cache with new settings
1716 g_gamecallback->keyconfig_changed = false;
1723 void Game::processQueues()
1725 texture_src->processQueue();
1726 itemdef_manager->processQueue(client);
1727 shader_src->processQueue();
1730 void Game::updateDebugState()
1732 bool has_basic_debug = client->checkPrivilege("basic_debug");
1733 bool has_debug = client->checkPrivilege("debug");
1735 if (m_game_ui->m_flags.show_basic_debug) {
1736 if (!has_basic_debug) {
1737 m_game_ui->m_flags.show_basic_debug = false;
1739 } else if (m_game_ui->m_flags.show_minimal_debug) {
1740 if (has_basic_debug) {
1741 m_game_ui->m_flags.show_basic_debug = true;
1744 if (!has_basic_debug)
1745 hud->disableBlockBounds();
1747 draw_control->show_wireframe = false;
1750 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1753 float profiler_print_interval =
1754 g_settings->getFloat("profiler_print_interval");
1755 bool print_to_log = true;
1757 if (profiler_print_interval == 0) {
1758 print_to_log = false;
1759 profiler_print_interval = 3;
1762 if (profiler_interval.step(dtime, profiler_print_interval)) {
1764 infostream << "Profiler:" << std::endl;
1765 g_profiler->print(infostream);
1768 m_game_ui->updateProfiler();
1769 g_profiler->clear();
1772 // Update update graphs
1773 g_profiler->graphAdd("Time non-rendering [ms]",
1774 draw_times.busy_time - stats.drawtime);
1776 g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time);
1777 g_profiler->graphAdd("FPS", 1.0f / dtime);
1780 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1787 /* Time average and jitter calculation
1789 jp = &stats->dtime_jitter;
1790 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1792 jitter = dtime - jp->avg;
1794 if (jitter > jp->max)
1797 jp->counter += dtime;
1799 if (jp->counter > 0.0) {
1801 jp->max_sample = jp->max;
1802 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1806 /* Busytime average and jitter calculation
1808 jp = &stats->busy_time_jitter;
1809 jp->avg = jp->avg + draw_times.busy_time * 0.02;
1811 jitter = draw_times.busy_time - jp->avg;
1813 if (jitter > jp->max)
1815 if (jitter < jp->min)
1818 jp->counter += dtime;
1820 if (jp->counter > 0.0) {
1822 jp->max_sample = jp->max;
1823 jp->min_sample = jp->min;
1831 /****************************************************************************
1833 ****************************************************************************/
1835 void Game::processUserInput(f32 dtime)
1837 // Reset input if window not active or some menu is active
1838 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1840 #ifdef HAVE_TOUCHSCREENGUI
1841 g_touchscreengui->hide();
1844 #ifdef HAVE_TOUCHSCREENGUI
1845 else if (g_touchscreengui) {
1846 /* on touchscreengui step may generate own input events which ain't
1847 * what we want in case we just did clear them */
1848 g_touchscreengui->step(dtime);
1852 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1853 gui_chat_console->closeConsoleAtOnce();
1856 // Input handler step() (used by the random input generator)
1860 auto formspec = m_game_ui->getFormspecGUI();
1862 formspec->getAndroidUIInput();
1864 handleAndroidChatInput();
1867 // Increase timer for double tap of "keymap_jump"
1868 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1869 runData.jump_timer += dtime;
1872 processItemSelection(&runData.new_playeritem);
1876 void Game::processKeyInput()
1878 if (wasKeyDown(KeyType::DROP)) {
1879 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1880 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1881 toggleAutoforward();
1882 } else if (wasKeyDown(KeyType::BACKWARD)) {
1883 if (g_settings->getBool("continuous_forward"))
1884 toggleAutoforward();
1885 } else if (wasKeyDown(KeyType::INVENTORY)) {
1887 } else if (input->cancelPressed()) {
1889 m_android_chat_open = false;
1891 if (!gui_chat_console->isOpenInhibited()) {
1894 } else if (wasKeyDown(KeyType::CHAT)) {
1895 openConsole(0.2, L"");
1896 } else if (wasKeyDown(KeyType::CMD)) {
1897 openConsole(0.2, L"/");
1898 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1899 if (client->modsLoaded())
1900 openConsole(0.2, L".");
1902 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1903 } else if (wasKeyDown(KeyType::CONSOLE)) {
1904 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1905 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1907 } else if (wasKeyDown(KeyType::JUMP)) {
1908 toggleFreeMoveAlt();
1909 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1911 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1913 } else if (wasKeyDown(KeyType::NOCLIP)) {
1916 } else if (wasKeyDown(KeyType::MUTE)) {
1917 if (g_settings->getBool("enable_sound")) {
1918 bool new_mute_sound = !g_settings->getBool("mute_sound");
1919 g_settings->setBool("mute_sound", new_mute_sound);
1921 m_game_ui->showTranslatedStatusText("Sound muted");
1923 m_game_ui->showTranslatedStatusText("Sound unmuted");
1925 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1927 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1928 if (g_settings->getBool("enable_sound")) {
1929 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1930 g_settings->setFloat("sound_volume", new_volume);
1931 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1932 m_game_ui->showStatusText(msg);
1934 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1936 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1937 if (g_settings->getBool("enable_sound")) {
1938 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1939 g_settings->setFloat("sound_volume", new_volume);
1940 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1941 m_game_ui->showStatusText(msg);
1943 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1946 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1947 || wasKeyDown(KeyType::DEC_VOLUME)) {
1948 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1950 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1952 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1953 client->makeScreenshot();
1954 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
1955 toggleBlockBounds();
1956 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1957 m_game_ui->toggleHud();
1958 } else if (wasKeyDown(KeyType::MINIMAP)) {
1959 toggleMinimap(isKeyDown(KeyType::SNEAK));
1960 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1961 m_game_ui->toggleChat();
1962 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1964 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1965 toggleUpdateCamera();
1966 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1968 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1969 m_game_ui->toggleProfiler();
1970 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1971 increaseViewRange();
1972 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1973 decreaseViewRange();
1974 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1975 toggleFullViewRange();
1976 } else if (wasKeyDown(KeyType::ZOOM)) {
1978 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1980 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1982 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1984 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1988 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1989 runData.reset_jump_timer = false;
1990 runData.jump_timer = 0.0f;
1993 if (quicktune->hasMessage()) {
1994 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
1998 void Game::processItemSelection(u16 *new_playeritem)
2000 LocalPlayer *player = client->getEnv().getLocalPlayer();
2002 /* Item selection using mouse wheel
2004 *new_playeritem = player->getWieldIndex();
2006 s32 wheel = input->getMouseWheel();
2007 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2008 player->hud_hotbar_itemcount - 1);
2012 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2015 if (wasKeyDown(KeyType::HOTBAR_PREV))
2019 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2021 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2024 /* Item selection using hotbar slot keys
2026 for (u16 i = 0; i <= max_item; i++) {
2027 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2028 *new_playeritem = i;
2035 void Game::dropSelectedItem(bool single_item)
2037 IDropAction *a = new IDropAction();
2038 a->count = single_item ? 1 : 0;
2039 a->from_inv.setCurrentPlayer();
2040 a->from_list = "main";
2041 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2042 client->inventoryAction(a);
2046 void Game::openInventory()
2049 * Don't permit to open inventory is CAO or player doesn't exists.
2050 * This prevent showing an empty inventory at player load
2053 LocalPlayer *player = client->getEnv().getLocalPlayer();
2054 if (!player || !player->getCAO())
2057 infostream << "Game: Launching inventory" << std::endl;
2059 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2061 InventoryLocation inventoryloc;
2062 inventoryloc.setCurrentPlayer();
2064 if (!client->modsLoaded()
2065 || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2066 TextDest *txt_dst = new TextDestPlayerInventory(client);
2067 auto *&formspec = m_game_ui->updateFormspec("");
2068 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2069 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2071 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2076 void Game::openConsole(float scale, const wchar_t *line)
2078 assert(scale > 0.0f && scale <= 1.0f);
2081 porting::showInputDialog(gettext("ok"), "", "", 2);
2082 m_android_chat_open = true;
2084 if (gui_chat_console->isOpenInhibited())
2086 gui_chat_console->openConsole(scale);
2088 gui_chat_console->setCloseOnEnter(true);
2089 gui_chat_console->replaceAndAddToHistory(line);
2095 void Game::handleAndroidChatInput()
2097 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2098 std::string text = porting::getInputDialogValue();
2099 client->typeChatMessage(utf8_to_wide(text));
2100 m_android_chat_open = false;
2106 void Game::toggleFreeMove()
2108 bool free_move = !g_settings->getBool("free_move");
2109 g_settings->set("free_move", bool_to_cstr(free_move));
2112 if (client->checkPrivilege("fly")) {
2113 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2115 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2118 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2122 void Game::toggleFreeMoveAlt()
2124 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2127 runData.reset_jump_timer = true;
2131 void Game::togglePitchMove()
2133 bool pitch_move = !g_settings->getBool("pitch_move");
2134 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2137 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2139 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2144 void Game::toggleFast()
2146 bool fast_move = !g_settings->getBool("fast_move");
2147 bool has_fast_privs = client->checkPrivilege("fast");
2148 g_settings->set("fast_move", bool_to_cstr(fast_move));
2151 if (has_fast_privs) {
2152 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2154 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2157 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2161 m_cache_hold_aux1 = fast_move && has_fast_privs;
2166 void Game::toggleNoClip()
2168 bool noclip = !g_settings->getBool("noclip");
2169 g_settings->set("noclip", bool_to_cstr(noclip));
2172 if (client->checkPrivilege("noclip")) {
2173 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2175 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2178 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2182 void Game::toggleCinematic()
2184 bool cinematic = !g_settings->getBool("cinematic");
2185 g_settings->set("cinematic", bool_to_cstr(cinematic));
2188 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2190 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2193 void Game::toggleBlockBounds()
2195 if (client->checkPrivilege("basic_debug")) {
2196 enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
2198 case Hud::BLOCK_BOUNDS_OFF:
2199 m_game_ui->showTranslatedStatusText("Block bounds hidden");
2201 case Hud::BLOCK_BOUNDS_CURRENT:
2202 m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
2204 case Hud::BLOCK_BOUNDS_NEAR:
2205 m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
2207 case Hud::BLOCK_BOUNDS_MAX:
2208 m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
2215 m_game_ui->showTranslatedStatusText("Can't show block bounds (need 'basic_debug' privilege)");
2219 // Autoforward by toggling continuous forward.
2220 void Game::toggleAutoforward()
2222 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2223 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2225 if (autorun_enabled)
2226 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2228 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2231 void Game::toggleMinimap(bool shift_pressed)
2233 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2237 mapper->toggleMinimapShape();
2241 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2243 // Not so satisying code to keep compatibility with old fixed mode system
2245 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2247 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2248 m_game_ui->m_flags.show_minimap = false;
2251 // If radar is disabled, try to find a non radar mode or fall back to 0
2252 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2253 while (mapper->getModeIndex() &&
2254 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2257 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2261 // End of 'not so satifying code'
2262 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2263 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2264 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2266 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2269 void Game::toggleFog()
2271 bool fog_enabled = g_settings->getBool("enable_fog");
2272 g_settings->setBool("enable_fog", !fog_enabled);
2274 m_game_ui->showTranslatedStatusText("Fog disabled");
2276 m_game_ui->showTranslatedStatusText("Fog enabled");
2280 void Game::toggleDebug()
2282 // Initial: No debug info
2283 // 1x toggle: Debug text
2284 // 2x toggle: Debug text with profiler graph
2285 // 3x toggle: Debug text and wireframe (needs "debug" priv)
2286 // Next toggle: Back to initial
2288 // The debug text can be in 2 modes: minimal and basic.
2289 // * Minimal: Only technical client info that not gameplay-relevant
2290 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
2291 // Basic mode is used when player has "basic_debug" priv,
2292 // otherwise the Minimal mode is used.
2293 if (!m_game_ui->m_flags.show_minimal_debug) {
2294 m_game_ui->m_flags.show_minimal_debug = true;
2295 if (client->checkPrivilege("basic_debug")) {
2296 m_game_ui->m_flags.show_basic_debug = true;
2298 m_game_ui->m_flags.show_profiler_graph = false;
2299 draw_control->show_wireframe = false;
2300 m_game_ui->showTranslatedStatusText("Debug info shown");
2301 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2302 if (client->checkPrivilege("basic_debug")) {
2303 m_game_ui->m_flags.show_basic_debug = true;
2305 m_game_ui->m_flags.show_profiler_graph = true;
2306 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2307 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2308 if (client->checkPrivilege("basic_debug")) {
2309 m_game_ui->m_flags.show_basic_debug = true;
2311 m_game_ui->m_flags.show_profiler_graph = false;
2312 draw_control->show_wireframe = true;
2313 m_game_ui->showTranslatedStatusText("Wireframe shown");
2315 m_game_ui->m_flags.show_minimal_debug = false;
2316 m_game_ui->m_flags.show_basic_debug = false;
2317 m_game_ui->m_flags.show_profiler_graph = false;
2318 draw_control->show_wireframe = false;
2319 if (client->checkPrivilege("debug")) {
2320 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2322 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2328 void Game::toggleUpdateCamera()
2330 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2331 if (m_flags.disable_camera_update)
2332 m_game_ui->showTranslatedStatusText("Camera update disabled");
2334 m_game_ui->showTranslatedStatusText("Camera update enabled");
2338 void Game::increaseViewRange()
2340 s16 range = g_settings->getS16("viewing_range");
2341 s16 range_new = range + 10;
2343 if (range_new > 4000) {
2345 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
2346 m_game_ui->showStatusText(msg);
2348 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2349 m_game_ui->showStatusText(msg);
2351 g_settings->set("viewing_range", itos(range_new));
2355 void Game::decreaseViewRange()
2357 s16 range = g_settings->getS16("viewing_range");
2358 s16 range_new = range - 10;
2360 if (range_new < 20) {
2362 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
2363 m_game_ui->showStatusText(msg);
2365 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2366 m_game_ui->showStatusText(msg);
2368 g_settings->set("viewing_range", itos(range_new));
2372 void Game::toggleFullViewRange()
2374 draw_control->range_all = !draw_control->range_all;
2375 if (draw_control->range_all)
2376 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2378 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2382 void Game::checkZoomEnabled()
2384 LocalPlayer *player = client->getEnv().getLocalPlayer();
2385 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2386 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2389 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2391 if ((device->isWindowActive() && device->isWindowFocused()
2392 && !isMenuActive()) || input->isRandom()) {
2395 if (!input->isRandom()) {
2396 // Mac OSX gets upset if this is set every frame
2397 if (device->getCursorControl()->isVisible())
2398 device->getCursorControl()->setVisible(false);
2402 if (m_first_loop_after_window_activation) {
2403 m_first_loop_after_window_activation = false;
2405 input->setMousePos(driver->getScreenSize().Width / 2,
2406 driver->getScreenSize().Height / 2);
2408 updateCameraOrientation(cam, dtime);
2414 // Mac OSX gets upset if this is set every frame
2415 if (!device->getCursorControl()->isVisible())
2416 device->getCursorControl()->setVisible(true);
2419 m_first_loop_after_window_activation = true;
2424 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2425 // responsiveness independently of FOV.
2426 f32 Game::getSensitivityScaleFactor() const
2428 f32 fov_y = client->getCamera()->getFovY();
2430 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2431 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2433 return tan(fov_y / 2.0f) * 1.3763818698f;
2436 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2438 #ifdef HAVE_TOUCHSCREENGUI
2439 if (g_touchscreengui) {
2440 cam->camera_yaw += g_touchscreengui->getYawChange();
2441 cam->camera_pitch = g_touchscreengui->getPitch();
2444 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2445 v2s32 dist = input->getMousePos() - center;
2447 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2451 f32 sens_scale = getSensitivityScaleFactor();
2452 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2453 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2455 if (dist.X != 0 || dist.Y != 0)
2456 input->setMousePos(center.X, center.Y);
2457 #ifdef HAVE_TOUCHSCREENGUI
2461 if (m_cache_enable_joysticks) {
2462 f32 sens_scale = getSensitivityScaleFactor();
2463 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
2464 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2465 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2468 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2472 void Game::updatePlayerControl(const CameraOrientation &cam)
2474 LocalPlayer *player = client->getEnv().getLocalPlayer();
2476 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2478 PlayerControl control(
2479 isKeyDown(KeyType::JUMP) || player->getAutojump(),
2480 isKeyDown(KeyType::AUX1),
2481 isKeyDown(KeyType::SNEAK),
2482 isKeyDown(KeyType::ZOOM),
2483 isKeyDown(KeyType::DIG),
2484 isKeyDown(KeyType::PLACE),
2487 input->getMovementSpeed(),
2488 input->getMovementDirection()
2491 // autoforward if set: move towards pointed position at maximum speed
2492 if (player->getPlayerSettings().continuous_forward &&
2493 client->activeObjectsReceived() && !player->isDead()) {
2494 control.movement_speed = 1.0f;
2495 control.movement_direction = 0.0f;
2499 /* For Android, simulate holding down AUX1 (fast move) if the user has
2500 * the fast_move setting toggled on. If there is an aux1 key defined for
2501 * Android then its meaning is inverted (i.e. holding aux1 means walk and
2504 if (m_cache_hold_aux1) {
2505 control.aux1 = control.aux1 ^ true;
2509 u32 keypress_bits = (
2510 ( (u32)(control.jump & 0x1) << 4) |
2511 ( (u32)(control.aux1 & 0x1) << 5) |
2512 ( (u32)(control.sneak & 0x1) << 6) |
2513 ( (u32)(control.dig & 0x1) << 7) |
2514 ( (u32)(control.place & 0x1) << 8) |
2515 ( (u32)(control.zoom & 0x1) << 9)
2518 // Set direction keys to ensure mod compatibility
2519 if (control.movement_speed > 0.001f) {
2520 float absolute_direction;
2522 // Check in original orientation (absolute value indicates forward / backward)
2523 absolute_direction = abs(control.movement_direction);
2524 if (absolute_direction < (3.0f / 8.0f * M_PI))
2525 keypress_bits |= (u32)(0x1 << 0); // Forward
2526 if (absolute_direction > (5.0f / 8.0f * M_PI))
2527 keypress_bits |= (u32)(0x1 << 1); // Backward
2529 // Rotate entire coordinate system by 90 degrees (absolute value indicates left / right)
2530 absolute_direction = control.movement_direction + M_PI_2;
2531 if (absolute_direction >= M_PI)
2532 absolute_direction -= 2 * M_PI;
2533 absolute_direction = abs(absolute_direction);
2534 if (absolute_direction < (3.0f / 8.0f * M_PI))
2535 keypress_bits |= (u32)(0x1 << 2); // Left
2536 if (absolute_direction > (5.0f / 8.0f * M_PI))
2537 keypress_bits |= (u32)(0x1 << 3); // Right
2540 client->setPlayerControl(control);
2541 player->keyPressed = keypress_bits;
2547 inline void Game::step(f32 *dtime)
2549 bool can_be_and_is_paused =
2550 (simple_singleplayer_mode && g_menumgr.pausesGame());
2552 if (can_be_and_is_paused) { // This is for a singleplayer server
2553 *dtime = 0; // No time passes
2555 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
2559 server->step(*dtime);
2561 client->step(*dtime);
2565 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2568 for (auto &&child: node->getChildren())
2569 pauseNodeAnimation(paused, child);
2570 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2572 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2573 float speed = animated_node->getAnimationSpeed();
2576 paused.push_back({grab(animated_node), speed});
2577 animated_node->setAnimationSpeed(0.0f);
2580 void Game::pauseAnimation()
2582 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2585 void Game::resumeAnimation()
2587 for (auto &&pair: paused_animated_nodes)
2588 pair.first->setAnimationSpeed(pair.second);
2589 paused_animated_nodes.clear();
2592 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2593 {&Game::handleClientEvent_None},
2594 {&Game::handleClientEvent_PlayerDamage},
2595 {&Game::handleClientEvent_PlayerForceMove},
2596 {&Game::handleClientEvent_Deathscreen},
2597 {&Game::handleClientEvent_ShowFormSpec},
2598 {&Game::handleClientEvent_ShowLocalFormSpec},
2599 {&Game::handleClientEvent_HandleParticleEvent},
2600 {&Game::handleClientEvent_HandleParticleEvent},
2601 {&Game::handleClientEvent_HandleParticleEvent},
2602 {&Game::handleClientEvent_HudAdd},
2603 {&Game::handleClientEvent_HudRemove},
2604 {&Game::handleClientEvent_HudChange},
2605 {&Game::handleClientEvent_SetSky},
2606 {&Game::handleClientEvent_SetSun},
2607 {&Game::handleClientEvent_SetMoon},
2608 {&Game::handleClientEvent_SetStars},
2609 {&Game::handleClientEvent_OverrideDayNigthRatio},
2610 {&Game::handleClientEvent_CloudParams},
2613 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2615 FATAL_ERROR("ClientEvent type None received");
2618 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2620 if (client->modsLoaded())
2621 client->getScript()->on_damage_taken(event->player_damage.amount);
2623 // Damage flash and hurt tilt are not used at death
2624 if (client->getHP() > 0) {
2625 LocalPlayer *player = client->getEnv().getLocalPlayer();
2627 f32 hp_max = player->getCAO() ?
2628 player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2629 f32 damage_ratio = event->player_damage.amount / hp_max;
2631 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2632 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2634 player->hurt_tilt_timer = 1.5f;
2635 player->hurt_tilt_strength =
2636 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2639 // Play damage sound
2640 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2643 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2645 cam->camera_yaw = event->player_force_move.yaw;
2646 cam->camera_pitch = event->player_force_move.pitch;
2649 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2651 // If client scripting is enabled, deathscreen is handled by CSM code in
2652 // builtin/client/init.lua
2653 if (client->modsLoaded())
2654 client->getScript()->on_death();
2656 showDeathFormspec();
2658 /* Handle visualization */
2659 LocalPlayer *player = client->getEnv().getLocalPlayer();
2660 runData.damage_flash = 0;
2661 player->hurt_tilt_timer = 0;
2662 player->hurt_tilt_strength = 0;
2665 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2667 if (event->show_formspec.formspec->empty()) {
2668 auto formspec = m_game_ui->getFormspecGUI();
2669 if (formspec && (event->show_formspec.formname->empty()
2670 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2671 formspec->quitMenu();
2674 FormspecFormSource *fs_src =
2675 new FormspecFormSource(*(event->show_formspec.formspec));
2676 TextDestPlayerInventory *txt_dst =
2677 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2679 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2680 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2681 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2684 delete event->show_formspec.formspec;
2685 delete event->show_formspec.formname;
2688 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2690 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2691 LocalFormspecHandler *txt_dst =
2692 new LocalFormspecHandler(*event->show_formspec.formname, client);
2693 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2694 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2696 delete event->show_formspec.formspec;
2697 delete event->show_formspec.formname;
2700 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2701 CameraOrientation *cam)
2703 LocalPlayer *player = client->getEnv().getLocalPlayer();
2704 client->getParticleManager()->handleParticleEvent(event, client, player);
2707 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2709 LocalPlayer *player = client->getEnv().getLocalPlayer();
2711 u32 server_id = event->hudadd->server_id;
2712 // ignore if we already have a HUD with that ID
2713 auto i = m_hud_server_to_client.find(server_id);
2714 if (i != m_hud_server_to_client.end()) {
2715 delete event->hudadd;
2719 HudElement *e = new HudElement;
2720 e->type = static_cast<HudElementType>(event->hudadd->type);
2721 e->pos = event->hudadd->pos;
2722 e->name = event->hudadd->name;
2723 e->scale = event->hudadd->scale;
2724 e->text = event->hudadd->text;
2725 e->number = event->hudadd->number;
2726 e->item = event->hudadd->item;
2727 e->dir = event->hudadd->dir;
2728 e->align = event->hudadd->align;
2729 e->offset = event->hudadd->offset;
2730 e->world_pos = event->hudadd->world_pos;
2731 e->size = event->hudadd->size;
2732 e->z_index = event->hudadd->z_index;
2733 e->text2 = event->hudadd->text2;
2734 e->style = event->hudadd->style;
2735 m_hud_server_to_client[server_id] = player->addHud(e);
2737 delete event->hudadd;
2740 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2742 LocalPlayer *player = client->getEnv().getLocalPlayer();
2744 auto i = m_hud_server_to_client.find(event->hudrm.id);
2745 if (i != m_hud_server_to_client.end()) {
2746 HudElement *e = player->removeHud(i->second);
2748 m_hud_server_to_client.erase(i);
2753 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2755 LocalPlayer *player = client->getEnv().getLocalPlayer();
2757 HudElement *e = nullptr;
2759 auto i = m_hud_server_to_client.find(event->hudchange->id);
2760 if (i != m_hud_server_to_client.end()) {
2761 e = player->getHud(i->second);
2765 delete event->hudchange;
2769 #define CASE_SET(statval, prop, dataprop) \
2771 e->prop = event->hudchange->dataprop; \
2774 switch (event->hudchange->stat) {
2775 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2777 CASE_SET(HUD_STAT_NAME, name, sdata);
2779 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2781 CASE_SET(HUD_STAT_TEXT, text, sdata);
2783 CASE_SET(HUD_STAT_NUMBER, number, data);
2785 CASE_SET(HUD_STAT_ITEM, item, data);
2787 CASE_SET(HUD_STAT_DIR, dir, data);
2789 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2791 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2793 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2795 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2797 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2799 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2801 CASE_SET(HUD_STAT_STYLE, style, data);
2806 delete event->hudchange;
2809 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2811 sky->setVisible(false);
2812 // Whether clouds are visible in front of a custom skybox.
2813 sky->setCloudsEnabled(event->set_sky->clouds);
2819 // Clear the old textures out in case we switch rendering type.
2820 sky->clearSkyboxTextures();
2821 // Handle according to type
2822 if (event->set_sky->type == "regular") {
2823 // Shows the mesh skybox
2824 sky->setVisible(true);
2825 // Update mesh based skybox colours if applicable.
2826 sky->setSkyColors(event->set_sky->sky_color);
2827 sky->setHorizonTint(
2828 event->set_sky->fog_sun_tint,
2829 event->set_sky->fog_moon_tint,
2830 event->set_sky->fog_tint_type
2832 } else if (event->set_sky->type == "skybox" &&
2833 event->set_sky->textures.size() == 6) {
2834 // Disable the dyanmic mesh skybox:
2835 sky->setVisible(false);
2837 sky->setFallbackBgColor(event->set_sky->bgcolor);
2838 // Set sunrise and sunset fog tinting:
2839 sky->setHorizonTint(
2840 event->set_sky->fog_sun_tint,
2841 event->set_sky->fog_moon_tint,
2842 event->set_sky->fog_tint_type
2844 // Add textures to skybox.
2845 for (int i = 0; i < 6; i++)
2846 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2848 // Handle everything else as plain color.
2849 if (event->set_sky->type != "plain")
2850 infostream << "Unknown sky type: "
2851 << (event->set_sky->type) << std::endl;
2852 sky->setVisible(false);
2853 sky->setFallbackBgColor(event->set_sky->bgcolor);
2854 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2855 sky->setHorizonTint(
2856 event->set_sky->bgcolor,
2857 event->set_sky->bgcolor,
2862 delete event->set_sky;
2865 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2867 sky->setSunVisible(event->sun_params->visible);
2868 sky->setSunTexture(event->sun_params->texture,
2869 event->sun_params->tonemap, texture_src);
2870 sky->setSunScale(event->sun_params->scale);
2871 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2872 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2873 delete event->sun_params;
2876 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2878 sky->setMoonVisible(event->moon_params->visible);
2879 sky->setMoonTexture(event->moon_params->texture,
2880 event->moon_params->tonemap, texture_src);
2881 sky->setMoonScale(event->moon_params->scale);
2882 delete event->moon_params;
2885 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2887 sky->setStarsVisible(event->star_params->visible);
2888 sky->setStarCount(event->star_params->count, false);
2889 sky->setStarColor(event->star_params->starcolor);
2890 sky->setStarScale(event->star_params->scale);
2891 delete event->star_params;
2894 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2895 CameraOrientation *cam)
2897 client->getEnv().setDayNightRatioOverride(
2898 event->override_day_night_ratio.do_override,
2899 event->override_day_night_ratio.ratio_f * 1000.0f);
2902 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2907 clouds->setDensity(event->cloud_params.density);
2908 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2909 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2910 clouds->setHeight(event->cloud_params.height);
2911 clouds->setThickness(event->cloud_params.thickness);
2912 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2915 void Game::processClientEvents(CameraOrientation *cam)
2917 while (client->hasClientEvents()) {
2918 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2919 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2920 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2921 (this->*evHandler.handler)(event.get(), cam);
2925 void Game::updateChat(f32 dtime, const v2u32 &screensize)
2927 // Get new messages from error log buffer
2928 while (!m_chat_log_buf.empty())
2929 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2931 // Get new messages from client
2932 std::wstring message;
2933 while (client->getChatMessage(message)) {
2934 chat_backend->addUnparsedMessage(message);
2937 // Remove old messages
2938 chat_backend->step(dtime);
2940 // Display all messages in a static text element
2941 m_game_ui->setChatText(chat_backend->getRecentChat(),
2942 chat_backend->getRecentBuffer().getLineCount());
2945 void Game::updateCamera(u32 busy_time, f32 dtime)
2947 LocalPlayer *player = client->getEnv().getLocalPlayer();
2950 For interaction purposes, get info about the held item
2952 - Is it a usable item?
2953 - Can it point to liquids?
2955 ItemStack playeritem;
2957 ItemStack selected, hand;
2958 playeritem = player->getWieldedItem(&selected, &hand);
2961 ToolCapabilities playeritem_toolcap =
2962 playeritem.getToolCapabilities(itemdef_manager);
2964 v3s16 old_camera_offset = camera->getOffset();
2966 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2967 GenericCAO *playercao = player->getCAO();
2969 // If playercao not loaded, don't change camera
2973 camera->toggleCameraMode();
2975 // Make the player visible depending on camera mode.
2976 playercao->updateMeshCulling();
2977 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2980 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2981 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2983 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2984 camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
2985 camera->step(dtime);
2987 v3f camera_position = camera->getPosition();
2988 v3f camera_direction = camera->getDirection();
2989 f32 camera_fov = camera->getFovMax();
2990 v3s16 camera_offset = camera->getOffset();
2992 m_camera_offset_changed = (camera_offset != old_camera_offset);
2994 if (!m_flags.disable_camera_update) {
2995 client->getEnv().getClientMap().updateCamera(camera_position,
2996 camera_direction, camera_fov, camera_offset);
2998 if (m_camera_offset_changed) {
2999 client->updateCameraOffset(camera_offset);
3000 client->getEnv().updateCameraOffset(camera_offset);
3003 clouds->updateCameraOffset(camera_offset);
3009 void Game::updateSound(f32 dtime)
3011 // Update sound listener
3012 v3s16 camera_offset = camera->getOffset();
3013 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3014 v3f(0, 0, 0), // velocity
3015 camera->getDirection(),
3016 camera->getCameraNode()->getUpVector());
3018 bool mute_sound = g_settings->getBool("mute_sound");
3020 sound->setListenerGain(0.0f);
3022 // Check if volume is in the proper range, else fix it.
3023 float old_volume = g_settings->getFloat("sound_volume");
3024 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3025 sound->setListenerGain(new_volume);
3027 if (old_volume != new_volume) {
3028 g_settings->setFloat("sound_volume", new_volume);
3032 LocalPlayer *player = client->getEnv().getLocalPlayer();
3034 // Tell the sound maker whether to make footstep sounds
3035 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3037 // Update sound maker
3038 if (player->makes_footstep_sound)
3039 soundmaker->step(dtime);
3041 ClientMap &map = client->getEnv().getClientMap();
3042 MapNode n = map.getNode(player->getFootstepNodePos());
3043 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3047 void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
3049 LocalPlayer *player = client->getEnv().getLocalPlayer();
3051 const v3f camera_direction = camera->getDirection();
3052 const v3s16 camera_offset = camera->getOffset();
3055 Calculate what block is the crosshair pointing to
3058 ItemStack selected_item, hand_item;
3059 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3061 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3062 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3064 core::line3d<f32> shootline;
3066 switch (camera->getCameraMode()) {
3067 case CAMERA_MODE_FIRST:
3068 // Shoot from camera position, with bobbing
3069 shootline.start = camera->getPosition();
3071 case CAMERA_MODE_THIRD:
3072 // Shoot from player head, no bobbing
3073 shootline.start = camera->getHeadPosition();
3075 case CAMERA_MODE_THIRD_FRONT:
3076 shootline.start = camera->getHeadPosition();
3077 // prevent player pointing anything in front-view
3081 shootline.end = shootline.start + camera_direction * BS * d;
3083 #ifdef HAVE_TOUCHSCREENGUI
3085 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3086 shootline = g_touchscreengui->getShootline();
3087 // Scale shootline to the acual distance the player can reach
3088 shootline.end = shootline.start
3089 + shootline.getVector().normalize() * BS * d;
3090 shootline.start += intToFloat(camera_offset, BS);
3091 shootline.end += intToFloat(camera_offset, BS);
3096 PointedThing pointed = updatePointedThing(shootline,
3097 selected_def.liquids_pointable,
3098 !runData.btn_down_for_dig,
3101 if (pointed != runData.pointed_old) {
3102 infostream << "Pointing at " << pointed.dump() << std::endl;
3103 hud->updateSelectionMesh(camera_offset);
3106 // Allow digging again if button is not pressed
3107 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3108 runData.digging_blocked = false;
3112 - releasing dig button
3113 - pointing away from node
3115 if (runData.digging) {
3116 if (wasKeyReleased(KeyType::DIG)) {
3117 infostream << "Dig button released (stopped digging)" << std::endl;
3118 runData.digging = false;
3119 } else if (pointed != runData.pointed_old) {
3120 if (pointed.type == POINTEDTHING_NODE
3121 && runData.pointed_old.type == POINTEDTHING_NODE
3122 && pointed.node_undersurface
3123 == runData.pointed_old.node_undersurface) {
3124 // Still pointing to the same node, but a different face.
3127 infostream << "Pointing away from node (stopped digging)" << std::endl;
3128 runData.digging = false;
3129 hud->updateSelectionMesh(camera_offset);
3133 if (!runData.digging) {
3134 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3135 client->setCrack(-1, v3s16(0, 0, 0));
3136 runData.dig_time = 0.0;
3138 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3139 // Remove e.g. torches faster when clicking instead of holding dig button
3140 runData.nodig_delay_timer = 0;
3141 runData.dig_instantly = false;
3144 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3145 runData.btn_down_for_dig = false;
3147 runData.punching = false;
3149 soundmaker->m_player_leftpunch_sound.name = "";
3151 // Prepare for repeating, unless we're not supposed to
3152 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3153 runData.repeat_place_timer += dtime;
3155 runData.repeat_place_timer = 0;
3157 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3158 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3159 !client->getScript()->on_item_use(selected_item, pointed)))
3160 client->interact(INTERACT_USE, pointed);
3161 } else if (pointed.type == POINTEDTHING_NODE) {
3162 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3163 } else if (pointed.type == POINTEDTHING_OBJECT) {
3164 v3f player_position = player->getPosition();
3165 handlePointingAtObject(pointed, tool_item, player_position, show_debug);
3166 } else if (isKeyDown(KeyType::DIG)) {
3167 // When button is held down in air, show continuous animation
3168 runData.punching = true;
3169 // Run callback even though item is not usable
3170 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3171 client->getScript()->on_item_use(selected_item, pointed);
3172 } else if (wasKeyPressed(KeyType::PLACE)) {
3173 handlePointingAtNothing(selected_item);
3176 runData.pointed_old = pointed;
3178 if (runData.punching || wasKeyPressed(KeyType::DIG))
3179 camera->setDigging(0); // dig animation
3181 input->clearWasKeyPressed();
3182 input->clearWasKeyReleased();
3183 // Ensure DIG & PLACE are marked as handled
3184 wasKeyDown(KeyType::DIG);
3185 wasKeyDown(KeyType::PLACE);
3187 input->joystick.clearWasKeyPressed(KeyType::DIG);
3188 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3190 input->joystick.clearWasKeyReleased(KeyType::DIG);
3191 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3195 PointedThing Game::updatePointedThing(
3196 const core::line3d<f32> &shootline,
3197 bool liquids_pointable,
3198 bool look_for_object,
3199 const v3s16 &camera_offset)
3201 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3202 selectionboxes->clear();
3203 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3204 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3205 "show_entity_selectionbox");
3207 ClientEnvironment &env = client->getEnv();
3208 ClientMap &map = env.getClientMap();
3209 const NodeDefManager *nodedef = map.getNodeDefManager();
3211 runData.selected_object = NULL;
3212 hud->pointing_at_object = false;
3214 RaycastState s(shootline, look_for_object, liquids_pointable);
3215 PointedThing result;
3216 env.continueRaycast(&s, &result);
3217 if (result.type == POINTEDTHING_OBJECT) {
3218 hud->pointing_at_object = true;
3220 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3221 aabb3f selection_box;
3222 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3223 runData.selected_object->getSelectionBox(&selection_box)) {
3224 v3f pos = runData.selected_object->getPosition();
3225 selectionboxes->push_back(aabb3f(selection_box));
3226 hud->setSelectionPos(pos, camera_offset);
3228 } else if (result.type == POINTEDTHING_NODE) {
3229 // Update selection boxes
3230 MapNode n = map.getNode(result.node_undersurface);
3231 std::vector<aabb3f> boxes;
3232 n.getSelectionBoxes(nodedef, &boxes,
3233 n.getNeighbors(result.node_undersurface, &map));
3236 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3237 i != boxes.end(); ++i) {
3239 box.MinEdge -= v3f(d, d, d);
3240 box.MaxEdge += v3f(d, d, d);
3241 selectionboxes->push_back(box);
3243 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3245 hud->setSelectedFaceNormal(v3f(
3246 result.intersection_normal.X,
3247 result.intersection_normal.Y,
3248 result.intersection_normal.Z));
3251 // Update selection mesh light level and vertex colors
3252 if (!selectionboxes->empty()) {
3253 v3f pf = hud->getSelectionPos();
3254 v3s16 p = floatToInt(pf, BS);
3256 // Get selection mesh light level
3257 MapNode n = map.getNode(p);
3258 u16 node_light = getInteriorLight(n, -1, nodedef);
3259 u16 light_level = node_light;
3261 for (const v3s16 &dir : g_6dirs) {
3262 n = map.getNode(p + dir);
3263 node_light = getInteriorLight(n, -1, nodedef);
3264 if (node_light > light_level)
3265 light_level = node_light;
3268 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3270 final_color_blend(&c, light_level, daynight_ratio);
3272 // Modify final color a bit with time
3273 u32 timer = porting::getTimeMs() % 5000;
3274 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3275 float sin_r = 0.08f * std::sin(timerf);
3276 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3277 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3278 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3279 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3280 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3282 // Set mesh final color
3283 hud->setSelectionMeshColor(c);
3289 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3291 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3292 PointedThing fauxPointed;
3293 fauxPointed.type = POINTEDTHING_NOTHING;
3294 client->interact(INTERACT_ACTIVATE, fauxPointed);
3298 void Game::handlePointingAtNode(const PointedThing &pointed,
3299 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3301 v3s16 nodepos = pointed.node_undersurface;
3302 v3s16 neighbourpos = pointed.node_abovesurface;
3305 Check information text of node
3308 ClientMap &map = client->getEnv().getClientMap();
3310 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3311 && !runData.digging_blocked
3312 && client->checkPrivilege("interact")) {
3313 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3316 // This should be done after digging handling
3317 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3320 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3321 meta->getString("infotext"))));
3323 MapNode n = map.getNode(nodepos);
3325 if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
3326 m_game_ui->setInfoText(L"Unknown node: " +
3327 utf8_to_wide(nodedef_manager->get(n).name));
3331 if ((wasKeyPressed(KeyType::PLACE) ||
3332 runData.repeat_place_timer >= m_repeat_place_time) &&
3333 client->checkPrivilege("interact")) {
3334 runData.repeat_place_timer = 0;
3335 infostream << "Place button pressed while looking at ground" << std::endl;
3337 // Placing animation (always shown for feedback)
3338 camera->setDigging(1);
3340 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3342 // If the wielded item has node placement prediction,
3344 // And also set the sound and send the interact
3345 // But first check for meta formspec and rightclickable
3346 auto &def = selected_item.getDefinition(itemdef_manager);
3347 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3350 if (placed && client->modsLoaded())
3351 client->getScript()->on_placenode(pointed, def);
3355 bool Game::nodePlacement(const ItemDefinition &selected_def,
3356 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3357 const PointedThing &pointed, const NodeMetadata *meta)
3359 const auto &prediction = selected_def.node_placement_prediction;
3361 const NodeDefManager *nodedef = client->ndef();
3362 ClientMap &map = client->getEnv().getClientMap();
3364 bool is_valid_position;
3366 node = map.getNode(nodepos, &is_valid_position);
3367 if (!is_valid_position) {
3368 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3373 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3374 && !isKeyDown(KeyType::SNEAK)) {
3375 // on_rightclick callbacks are called anyway
3376 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3377 client->interact(INTERACT_PLACE, pointed);
3379 infostream << "Launching custom inventory view" << std::endl;
3381 InventoryLocation inventoryloc;
3382 inventoryloc.setNodeMeta(nodepos);
3384 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3385 &client->getEnv().getClientMap(), nodepos);
3386 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3388 auto *&formspec = m_game_ui->updateFormspec("");
3389 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3390 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3392 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3396 // on_rightclick callback
3397 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3398 !isKeyDown(KeyType::SNEAK))) {
3400 client->interact(INTERACT_PLACE, pointed);
3404 verbosestream << "Node placement prediction for "
3405 << selected_def.name << " is " << prediction << std::endl;
3406 v3s16 p = neighbourpos;
3408 // Place inside node itself if buildable_to
3409 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3410 if (is_valid_position) {
3411 if (nodedef->get(n_under).buildable_to) {
3414 node = map.getNode(p, &is_valid_position);
3415 if (is_valid_position && !nodedef->get(node).buildable_to) {
3416 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3418 client->interact(INTERACT_PLACE, pointed);
3424 // Find id of predicted node
3426 bool found = nodedef->getId(prediction, id);
3429 errorstream << "Node placement prediction failed for "
3430 << selected_def.name << " (places " << prediction
3431 << ") - Name not known" << std::endl;
3432 // Handle this as if prediction was empty
3434 client->interact(INTERACT_PLACE, pointed);
3438 const ContentFeatures &predicted_f = nodedef->get(id);
3440 // Predict param2 for facedir and wallmounted nodes
3441 // Compare core.item_place_node() for what the server does
3444 const u8 place_param2 = selected_def.place_param2;
3447 param2 = place_param2;
3448 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3449 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3450 v3s16 dir = nodepos - neighbourpos;
3452 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3453 param2 = dir.Y < 0 ? 1 : 0;
3454 } else if (abs(dir.X) > abs(dir.Z)) {
3455 param2 = dir.X < 0 ? 3 : 2;
3457 param2 = dir.Z < 0 ? 5 : 4;
3459 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3460 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3461 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3463 if (abs(dir.X) > abs(dir.Z)) {
3464 param2 = dir.X < 0 ? 3 : 1;
3466 param2 = dir.Z < 0 ? 2 : 0;
3470 // Check attachment if node is in group attached_node
3471 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3472 const static v3s16 wallmounted_dirs[8] = {
3482 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3483 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3484 pp = p + wallmounted_dirs[param2];
3486 pp = p + v3s16(0, -1, 0);
3488 if (!nodedef->get(map.getNode(pp)).walkable) {
3489 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3491 client->interact(INTERACT_PLACE, pointed);
3497 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3498 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3499 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3500 const auto &indexstr = selected_item.metadata.
3501 getString("palette_index", 0);
3502 if (!indexstr.empty()) {
3503 s32 index = mystoi(indexstr);
3504 if (predicted_f.param_type_2 == CPT2_COLOR) {
3506 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3507 // param2 = pure palette index + other
3508 param2 = (index & 0xf8) | (param2 & 0x07);
3509 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3510 // param2 = pure palette index + other
3511 param2 = (index & 0xe0) | (param2 & 0x1f);
3516 // Add node to client map
3517 MapNode n(id, 0, param2);
3520 LocalPlayer *player = client->getEnv().getLocalPlayer();
3522 // Dont place node when player would be inside new node
3523 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3524 if (!nodedef->get(n).walkable ||
3525 g_settings->getBool("enable_build_where_you_stand") ||
3526 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3527 (nodedef->get(n).walkable &&
3528 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3529 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3530 // This triggers the required mesh update too
3531 client->addNode(p, n);
3533 client->interact(INTERACT_PLACE, pointed);
3534 // A node is predicted, also play a sound
3535 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3538 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3541 } catch (const InvalidPositionException &e) {
3542 errorstream << "Node placement prediction failed for "
3543 << selected_def.name << " (places "
3544 << prediction << ") - Position not loaded" << std::endl;
3545 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3550 void Game::handlePointingAtObject(const PointedThing &pointed,
3551 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3553 std::wstring infotext = unescape_translate(
3554 utf8_to_wide(runData.selected_object->infoText()));
3557 if (!infotext.empty()) {
3560 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3563 m_game_ui->setInfoText(infotext);
3565 if (isKeyDown(KeyType::DIG)) {
3566 bool do_punch = false;
3567 bool do_punch_damage = false;
3569 if (runData.object_hit_delay_timer <= 0.0) {
3571 do_punch_damage = true;
3572 runData.object_hit_delay_timer = object_hit_delay;
3575 if (wasKeyPressed(KeyType::DIG))
3579 infostream << "Punched object" << std::endl;
3580 runData.punching = true;
3583 if (do_punch_damage) {
3584 // Report direct punch
3585 v3f objpos = runData.selected_object->getPosition();
3586 v3f dir = (objpos - player_position).normalize();
3588 bool disable_send = runData.selected_object->directReportPunch(
3589 dir, &tool_item, runData.time_from_last_punch);
3590 runData.time_from_last_punch = 0;
3593 client->interact(INTERACT_START_DIGGING, pointed);
3595 } else if (wasKeyDown(KeyType::PLACE)) {
3596 infostream << "Pressed place button while pointing at object" << std::endl;
3597 client->interact(INTERACT_PLACE, pointed); // place
3602 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3603 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3605 // See also: serverpackethandle.cpp, action == 2
3606 LocalPlayer *player = client->getEnv().getLocalPlayer();
3607 ClientMap &map = client->getEnv().getClientMap();
3608 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3610 // NOTE: Similar piece of code exists on the server side for
3612 // Get digging parameters
3613 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3614 &selected_item.getToolCapabilities(itemdef_manager));
3616 // If can't dig, try hand
3617 if (!params.diggable) {
3618 params = getDigParams(nodedef_manager->get(n).groups,
3619 &hand_item.getToolCapabilities(itemdef_manager));
3622 if (!params.diggable) {
3623 // I guess nobody will wait for this long
3624 runData.dig_time_complete = 10000000.0;
3626 runData.dig_time_complete = params.time;
3628 if (m_cache_enable_particles) {
3629 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3630 client->getParticleManager()->addNodeParticle(client,
3631 player, nodepos, n, features);
3635 if (!runData.digging) {
3636 infostream << "Started digging" << std::endl;
3637 runData.dig_instantly = runData.dig_time_complete == 0;
3638 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3640 client->interact(INTERACT_START_DIGGING, pointed);
3641 runData.digging = true;
3642 runData.btn_down_for_dig = true;
3645 if (!runData.dig_instantly) {
3646 runData.dig_index = (float)crack_animation_length
3648 / runData.dig_time_complete;
3650 // This is for e.g. torches
3651 runData.dig_index = crack_animation_length;
3654 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3656 if (sound_dig.exists() && params.diggable) {
3657 if (sound_dig.name == "__group") {
3658 if (!params.main_group.empty()) {
3659 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3660 soundmaker->m_player_leftpunch_sound.name =
3661 std::string("default_dig_") +
3665 soundmaker->m_player_leftpunch_sound = sound_dig;
3669 // Don't show cracks if not diggable
3670 if (runData.dig_time_complete >= 100000.0) {
3671 } else if (runData.dig_index < crack_animation_length) {
3672 //TimeTaker timer("client.setTempMod");
3673 //infostream<<"dig_index="<<dig_index<<std::endl;
3674 client->setCrack(runData.dig_index, nodepos);
3676 infostream << "Digging completed" << std::endl;
3677 client->setCrack(-1, v3s16(0, 0, 0));
3679 runData.dig_time = 0;
3680 runData.digging = false;
3681 // we successfully dug, now block it from repeating if we want to be safe
3682 if (g_settings->getBool("safe_dig_and_place"))
3683 runData.digging_blocked = true;
3685 runData.nodig_delay_timer =
3686 runData.dig_time_complete / (float)crack_animation_length;
3688 // We don't want a corresponding delay to very time consuming nodes
3689 // and nodes without digging time (e.g. torches) get a fixed delay.
3690 if (runData.nodig_delay_timer > 0.3)
3691 runData.nodig_delay_timer = 0.3;
3692 else if (runData.dig_instantly)
3693 runData.nodig_delay_timer = 0.15;
3695 bool is_valid_position;
3696 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3697 if (is_valid_position) {
3698 if (client->modsLoaded() &&
3699 client->getScript()->on_dignode(nodepos, wasnode)) {
3703 const ContentFeatures &f = client->ndef()->get(wasnode);
3704 if (f.node_dig_prediction == "air") {
3705 client->removeNode(nodepos);
3706 } else if (!f.node_dig_prediction.empty()) {
3708 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3710 client->addNode(nodepos, id, true);
3712 // implicit else: no prediction
3715 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3717 if (m_cache_enable_particles) {
3718 const ContentFeatures &features =
3719 client->getNodeDefManager()->get(wasnode);
3720 client->getParticleManager()->addDiggingParticles(client,
3721 player, nodepos, wasnode, features);
3725 // Send event to trigger sound
3726 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3729 if (runData.dig_time_complete < 100000.0) {
3730 runData.dig_time += dtime;
3732 runData.dig_time = 0;
3733 client->setCrack(-1, nodepos);
3736 camera->setDigging(0); // Dig animation
3739 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3740 const CameraOrientation &cam)
3742 TimeTaker tt_update("Game::updateFrame()");
3743 LocalPlayer *player = client->getEnv().getLocalPlayer();
3749 if (draw_control->range_all) {
3750 runData.fog_range = 100000 * BS;
3752 runData.fog_range = draw_control->wanted_range * BS;
3756 Calculate general brightness
3758 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3759 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3760 float direct_brightness;
3763 if (m_cache_enable_noclip && m_cache_enable_free_move) {
3764 direct_brightness = time_brightness;
3765 sunlight_seen = true;
3767 float old_brightness = sky->getBrightness();
3768 direct_brightness = client->getEnv().getClientMap()
3769 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3770 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3774 float time_of_day_smooth = runData.time_of_day_smooth;
3775 float time_of_day = client->getEnv().getTimeOfDayF();
3777 static const float maxsm = 0.05f;
3778 static const float todsm = 0.05f;
3780 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3781 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3782 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3783 time_of_day_smooth = time_of_day;
3785 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3786 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3787 + (time_of_day + 1.0) * todsm;
3789 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3790 + time_of_day * todsm;
3792 runData.time_of_day_smooth = time_of_day_smooth;
3794 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3795 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3796 player->getPitch());
3802 if (sky->getCloudsVisible()) {
3803 clouds->setVisible(true);
3804 clouds->step(dtime);
3805 // camera->getPosition is not enough for 3rd person views
3806 v3f camera_node_position = camera->getCameraNode()->getPosition();
3807 v3s16 camera_offset = camera->getOffset();
3808 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3809 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3810 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3811 clouds->update(camera_node_position,
3812 sky->getCloudColor());
3813 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3814 // if inside clouds, and fog enabled, use that as sky
3816 video::SColor clouds_dark = clouds->getColor()
3817 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3818 sky->overrideColors(clouds_dark, clouds->getColor());
3819 sky->setInClouds(true);
3820 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3821 // do not draw clouds after all
3822 clouds->setVisible(false);
3825 clouds->setVisible(false);
3832 client->getParticleManager()->step(dtime);
3838 if (m_cache_enable_fog) {
3841 video::EFT_FOG_LINEAR,
3842 runData.fog_range * m_cache_fog_start,
3843 runData.fog_range * 1.0,
3851 video::EFT_FOG_LINEAR,
3861 Get chat messages from client
3864 v2u32 screensize = driver->getScreenSize();
3866 updateChat(dtime, screensize);
3872 if (player->getWieldIndex() != runData.new_playeritem)
3873 client->setPlayerItem(runData.new_playeritem);
3875 if (client->updateWieldedItem()) {
3876 // Update wielded tool
3877 ItemStack selected_item, hand_item;
3878 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3879 camera->wield(tool_item);
3883 Update block draw list every 200ms or when camera direction has
3886 runData.update_draw_list_timer += dtime;
3888 float update_draw_list_delta = 0.2f;
3890 v3f camera_direction = camera->getDirection();
3891 if (runData.update_draw_list_timer >= update_draw_list_delta
3892 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3893 || m_camera_offset_changed) {
3895 runData.update_draw_list_timer = 0;
3896 client->getEnv().getClientMap().updateDrawList();
3897 runData.update_draw_list_last_cam_dir = camera_direction;
3900 if (RenderingEngine::get_shadow_renderer()) {
3904 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3907 make sure menu is on top
3908 1. Delete formspec menu reference if menu was removed
3909 2. Else, make sure formspec menu is on top
3911 auto formspec = m_game_ui->getFormspecGUI();
3912 do { // breakable. only runs for one iteration
3916 if (formspec->getReferenceCount() == 1) {
3917 m_game_ui->deleteFormspec();
3921 auto &loc = formspec->getFormspecLocation();
3922 if (loc.type == InventoryLocation::NODEMETA) {
3923 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3924 if (!meta || meta->getString("formspec").empty()) {
3925 formspec->quitMenu();
3931 guiroot->bringToFront(formspec);
3937 const video::SColor &skycolor = sky->getSkyColor();
3939 TimeTaker tt_draw("Draw scene");
3940 driver->beginScene(true, true, skycolor);
3942 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3943 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3944 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3945 bool draw_crosshair = (
3946 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3947 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3948 #ifdef HAVE_TOUCHSCREENGUI
3950 draw_crosshair = !g_settings->getBool("touchtarget");
3951 } catch (SettingNotFoundException) {
3954 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3955 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3960 if (m_game_ui->m_flags.show_profiler_graph)
3961 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3966 if (runData.damage_flash > 0.0f) {
3967 video::SColor color(runData.damage_flash, 180, 0, 0);
3968 driver->draw2DRectangle(color,
3969 core::rect<s32>(0, 0, screensize.X, screensize.Y),
3972 runData.damage_flash -= 384.0f * dtime;
3978 if (player->hurt_tilt_timer > 0.0f) {
3979 player->hurt_tilt_timer -= dtime * 6.0f;
3981 if (player->hurt_tilt_timer < 0.0f)
3982 player->hurt_tilt_strength = 0.0f;
3986 Update minimap pos and rotation
3988 if (mapper && m_game_ui->m_flags.show_hud) {
3989 mapper->setPos(floatToInt(player->getPosition(), BS));
3990 mapper->setAngle(player->getYaw());
3996 if (++m_reset_HW_buffer_counter > 500) {
3998 Periodically remove all mesh HW buffers.
4000 Work around for a quirk in Irrlicht where a HW buffer is only
4001 released after 20000 iterations (triggered from endScene()).
4003 Without this, all loaded but unused meshes will retain their HW
4004 buffers for at least 5 minutes, at which point looking up the HW buffers
4005 becomes a bottleneck and the framerate drops (as much as 30%).
4007 Tests showed that numbers between 50 and 1000 are good, so picked 500.
4008 There are no other public Irrlicht APIs that allow interacting with the
4009 HW buffers without tracking the status of every individual mesh.
4011 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4013 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4014 driver->removeAllHardwareBuffers();
4015 m_reset_HW_buffer_counter = 0;
4019 stats->drawtime = tt_draw.stop(true);
4020 g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
4021 g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
4024 /* Log times and stuff for visualization */
4025 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4027 Profiler::GraphValues values;
4028 g_profiler->graphGet(values);
4032 /****************************************************************************
4034 *****************************************************************************/
4035 void Game::updateShadows()
4037 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4041 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4043 float timeoftheday = fmod(getWickedTimeOfDay(in_timeofday) + 0.75f, 0.5f) + 0.25f;
4044 const float offset_constant = 10000.0f;
4046 v3f light(0.0f, 0.0f, -1.0f);
4047 light.rotateXZBy(90);
4048 light.rotateXYBy(timeoftheday * 360 - 90);
4049 light.rotateYZBy(sky->getSkyBodyOrbitTilt());
4051 v3f sun_pos = light * offset_constant;
4053 if (shadow->getDirectionalLightCount() == 0)
4054 shadow->addDirectionalLight();
4055 shadow->getDirectionalLight().setDirection(sun_pos);
4056 shadow->setTimeOfDay(in_timeofday);
4058 shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4061 /****************************************************************************
4063 ****************************************************************************/
4065 /* On some computers framerate doesn't seem to be automatically limited
4067 inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
4069 // not using getRealTime is necessary for wine
4070 device->getTimer()->tick(); // Maker sure device time is up-to-date
4071 u32 time = device->getTimer()->getTime();
4072 u32 last_time = fps_timings->last_time;
4074 if (time > last_time) // Make sure time hasn't overflowed
4075 fps_timings->busy_time = time - last_time;
4077 fps_timings->busy_time = 0;
4079 u32 frametime_min = 1000 / (
4080 device->isWindowFocused() && !g_menumgr.pausesGame()
4081 ? g_settings->getFloat("fps_max")
4082 : g_settings->getFloat("fps_max_unfocused"));
4084 if (fps_timings->busy_time < frametime_min) {
4085 fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
4086 device->sleep(fps_timings->sleep_time);
4088 fps_timings->sleep_time = 0;
4091 /* Get the new value of the device timer. Note that device->sleep() may
4092 * not sleep for the entire requested time as sleep may be interrupted and
4093 * therefore it is arguably more accurate to get the new time from the
4094 * device rather than calculating it by adding sleep_time to time.
4097 device->getTimer()->tick(); // Update device timer
4098 time = device->getTimer()->getTime();
4100 if (time > last_time) // Make sure last_time hasn't overflowed
4101 *dtime = (time - last_time) / 1000.0;
4105 fps_timings->last_time = time;
4108 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4110 const wchar_t *wmsg = wgettext(msg);
4111 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4116 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4118 ((Game *)data)->readSettings();
4121 void Game::readSettings()
4123 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4124 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4125 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4126 m_cache_enable_particles = g_settings->getBool("enable_particles");
4127 m_cache_enable_fog = g_settings->getBool("enable_fog");
4128 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
4129 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4130 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
4132 m_cache_enable_noclip = g_settings->getBool("noclip");
4133 m_cache_enable_free_move = g_settings->getBool("free_move");
4135 m_cache_fog_start = g_settings->getFloat("fog_start");
4137 m_cache_cam_smoothing = 0;
4138 if (g_settings->getBool("cinematic"))
4139 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4141 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4143 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4144 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4145 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4147 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4150 /****************************************************************************/
4151 /****************************************************************************
4153 ****************************************************************************/
4154 /****************************************************************************/
4156 void Game::showDeathFormspec()
4158 static std::string formspec_str =
4159 std::string("formspec_version[1]") +
4161 "bgcolor[#320000b4;true]"
4162 "label[4.85,1.35;" + gettext("You died") + "]"
4163 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4167 /* Note: FormspecFormSource and LocalFormspecHandler *
4168 * are deleted by guiFormSpecMenu */
4169 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4170 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4172 auto *&formspec = m_game_ui->getFormspecGUI();
4173 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4174 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4175 formspec->setFocus("btn_respawn");
4178 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4179 void Game::showPauseMenu()
4182 static const std::string control_text = strgettext("Default Controls:\n"
4183 "No menu visible:\n"
4184 "- single tap: button activate\n"
4185 "- double tap: place/use\n"
4186 "- slide finger: look around\n"
4187 "Menu/Inventory visible:\n"
4188 "- double tap (outside):\n"
4190 "- touch stack, touch slot:\n"
4192 "- touch&drag, tap 2nd finger\n"
4193 " --> place single item to slot\n"
4196 static const std::string control_text_template = strgettext("Controls:\n"
4197 "- %s: move forwards\n"
4198 "- %s: move backwards\n"
4200 "- %s: move right\n"
4201 "- %s: jump/climb up\n"
4204 "- %s: sneak/climb down\n"
4207 "- Mouse: turn/look\n"
4208 "- Mouse wheel: select item\n"
4212 char control_text_buf[600];
4214 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4215 GET_KEY_NAME(keymap_forward),
4216 GET_KEY_NAME(keymap_backward),
4217 GET_KEY_NAME(keymap_left),
4218 GET_KEY_NAME(keymap_right),
4219 GET_KEY_NAME(keymap_jump),
4220 GET_KEY_NAME(keymap_dig),
4221 GET_KEY_NAME(keymap_place),
4222 GET_KEY_NAME(keymap_sneak),
4223 GET_KEY_NAME(keymap_drop),
4224 GET_KEY_NAME(keymap_inventory),
4225 GET_KEY_NAME(keymap_chat)
4228 std::string control_text = std::string(control_text_buf);
4229 str_formspec_escape(control_text);
4232 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4233 std::ostringstream os;
4235 os << "formspec_version[1]" << SIZE_TAG
4236 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4237 << strgettext("Continue") << "]";
4239 if (!simple_singleplayer_mode) {
4240 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4241 << strgettext("Change Password") << "]";
4243 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4248 if (g_settings->getBool("enable_sound")) {
4249 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4250 << strgettext("Sound Volume") << "]";
4253 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4254 << strgettext("Change Keys") << "]";
4256 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4257 << strgettext("Exit to Menu") << "]";
4258 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4259 << strgettext("Exit to OS") << "]"
4260 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4261 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4263 << strgettext("Game info:") << "\n";
4264 const std::string &address = client->getAddressName();
4265 static const std::string mode = strgettext("- Mode: ");
4266 if (!simple_singleplayer_mode) {
4267 Address serverAddress = client->getServerAddress();
4268 if (!address.empty()) {
4269 os << mode << strgettext("Remote server") << "\n"
4270 << strgettext("- Address: ") << address;
4272 os << mode << strgettext("Hosting server");
4274 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4276 os << mode << strgettext("Singleplayer") << "\n";
4278 if (simple_singleplayer_mode || address.empty()) {
4279 static const std::string on = strgettext("On");
4280 static const std::string off = strgettext("Off");
4281 const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
4282 const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
4283 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4284 os << strgettext("- Damage: ") << damage << "\n"
4285 << strgettext("- Creative Mode: ") << creative << "\n";
4286 if (!simple_singleplayer_mode) {
4287 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4288 //~ PvP = Player versus Player
4289 os << strgettext("- PvP: ") << pvp << "\n"
4290 << strgettext("- Public: ") << announced << "\n";
4291 std::string server_name = g_settings->get("server_name");
4292 str_formspec_escape(server_name);
4293 if (announced == on && !server_name.empty())
4294 os << strgettext("- Server Name: ") << server_name;
4301 /* Note: FormspecFormSource and LocalFormspecHandler *
4302 * are deleted by guiFormSpecMenu */
4303 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4304 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4306 auto *&formspec = m_game_ui->getFormspecGUI();
4307 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4308 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4309 formspec->setFocus("btn_continue");
4310 formspec->doPause = true;
4312 if (simple_singleplayer_mode)
4316 /****************************************************************************/
4317 /****************************************************************************
4318 extern function for launching the game
4319 ****************************************************************************/
4320 /****************************************************************************/
4322 void the_game(bool *kill,
4323 InputHandler *input,
4324 RenderingEngine *rendering_engine,
4325 const GameStartData &start_data,
4326 std::string &error_message,
4327 ChatBackend &chat_backend,
4328 bool *reconnect_requested) // Used for local game
4332 /* Make a copy of the server address because if a local singleplayer server
4333 * is created then this is updated and we don't want to change the value
4334 * passed to us by the calling function
4339 if (game.startup(kill, input, rendering_engine, start_data,
4340 error_message, reconnect_requested, &chat_backend)) {
4344 } catch (SerializationError &e) {
4345 error_message = std::string("A serialization error occurred:\n")
4346 + e.what() + "\n\nThe server is probably "
4347 " running a different version of " PROJECT_NAME_C ".";
4348 errorstream << error_message << std::endl;
4349 } catch (ServerError &e) {
4350 error_message = e.what();
4351 errorstream << "ServerError: " << error_message << std::endl;
4352 } catch (ModError &e) {
4353 error_message = std::string("ModError: ") + e.what() +
4354 strgettext("\nCheck debug.txt for details.");
4355 errorstream << error_message << std::endl;