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 const GameStartData &game_params,
646 std::string &error_message,
648 ChatBackend *chat_backend);
655 void extendedResourceCleanup();
657 // Basic initialisation
658 bool init(const std::string &map_dir, const std::string &address,
659 u16 port, const SubgameSpec &gamespec);
661 bool createSingleplayerServer(const std::string &map_dir,
662 const SubgameSpec &gamespec, u16 port);
665 bool createClient(const GameStartData &start_data);
669 bool connectToServer(const GameStartData &start_data,
670 bool *connect_ok, bool *aborted);
671 bool getServerContent(bool *aborted);
675 void updateInteractTimers(f32 dtime);
676 bool checkConnection();
677 bool handleCallbacks();
678 void processQueues();
679 void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
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 toggleAutoforward();
699 void toggleMinimap(bool shift_pressed);
702 void toggleUpdateCamera();
704 void increaseViewRange();
705 void decreaseViewRange();
706 void toggleFullViewRange();
707 void checkZoomEnabled();
709 void updateCameraDirection(CameraOrientation *cam, float dtime);
710 void updateCameraOrientation(CameraOrientation *cam, float dtime);
711 void updatePlayerControl(const CameraOrientation &cam);
712 void step(f32 *dtime);
713 void processClientEvents(CameraOrientation *cam);
714 void updateCamera(u32 busy_time, f32 dtime);
715 void updateSound(f32 dtime);
716 void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug);
718 * Returns the object or node the player is pointing at.
719 * Also updates the selected thing in the Hud.
721 * @param[in] shootline the shootline, starting from
722 * the camera position. This also gives the maximal distance
724 * @param[in] liquids_pointable if false, liquids are ignored
725 * @param[in] look_for_object if false, objects are ignored
726 * @param[in] camera_offset offset of the camera
727 * @param[out] selected_object the selected object or
730 PointedThing updatePointedThing(
731 const core::line3d<f32> &shootline, bool liquids_pointable,
732 bool look_for_object, const v3s16 &camera_offset);
733 void handlePointingAtNothing(const ItemStack &playerItem);
734 void handlePointingAtNode(const PointedThing &pointed,
735 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
736 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
737 const v3f &player_position, bool show_debug);
738 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
739 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
740 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
741 const CameraOrientation &cam);
744 void limitFps(FpsControl *fps_timings, f32 *dtime);
746 void showOverlayMessage(const char *msg, float dtime, int percent,
747 bool draw_clouds = true);
749 static void settingChangedCallback(const std::string &setting_name, void *data);
752 inline bool isKeyDown(GameKeyType k)
754 return input->isKeyDown(k);
756 inline bool wasKeyDown(GameKeyType k)
758 return input->wasKeyDown(k);
760 inline bool wasKeyPressed(GameKeyType k)
762 return input->wasKeyPressed(k);
764 inline bool wasKeyReleased(GameKeyType k)
766 return input->wasKeyReleased(k);
770 void handleAndroidChatInput();
775 bool force_fog_off = false;
776 bool disable_camera_update = false;
779 void showDeathFormspec();
780 void showPauseMenu();
782 void pauseAnimation();
783 void resumeAnimation();
785 // ClientEvent handlers
786 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
787 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
788 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
789 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
790 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
791 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
792 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
793 CameraOrientation *cam);
794 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
795 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
796 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
797 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
798 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
799 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
800 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
801 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
802 CameraOrientation *cam);
803 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
805 void updateChat(f32 dtime, const v2u32 &screensize);
807 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
808 const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
809 const NodeMetadata *meta);
810 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
812 f32 getSensitivityScaleFactor() const;
814 InputHandler *input = nullptr;
816 Client *client = nullptr;
817 Server *server = nullptr;
819 IWritableTextureSource *texture_src = nullptr;
820 IWritableShaderSource *shader_src = nullptr;
822 // When created, these will be filled with data received from the server
823 IWritableItemDefManager *itemdef_manager = nullptr;
824 NodeDefManager *nodedef_manager = nullptr;
826 GameOnDemandSoundFetcher soundfetcher; // useful when testing
827 ISoundManager *sound = nullptr;
828 bool sound_is_dummy = false;
829 SoundMaker *soundmaker = nullptr;
831 ChatBackend *chat_backend = nullptr;
832 LogOutputBuffer m_chat_log_buf;
834 EventManager *eventmgr = nullptr;
835 QuicktuneShortcutter *quicktune = nullptr;
836 bool registration_confirmation_shown = false;
838 std::unique_ptr<GameUI> m_game_ui;
839 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
840 MapDrawControl *draw_control = nullptr;
841 Camera *camera = nullptr;
842 Clouds *clouds = nullptr; // Free using ->Drop()
843 Sky *sky = nullptr; // Free using ->Drop()
845 Minimap *mapper = nullptr;
847 // Map server hud ids to client hud ids
848 std::unordered_map<u32, u32> m_hud_server_to_client;
854 This class does take ownership/responsibily for cleaning up etc of any of
855 these items (e.g. device)
857 IrrlichtDevice *device;
858 video::IVideoDriver *driver;
859 scene::ISceneManager *smgr;
861 std::string *error_message;
862 bool *reconnect_requested;
863 scene::ISceneNode *skybox;
864 PausedNodesList paused_animated_nodes;
866 bool simple_singleplayer_mode;
869 /* Pre-calculated values
871 int crack_animation_length;
873 IntervalLimiter profiler_interval;
876 * TODO: Local caching of settings is not optimal and should at some stage
877 * be updated to use a global settings object for getting thse values
878 * (as opposed to the this local caching). This can be addressed in
881 bool m_cache_doubletap_jump;
882 bool m_cache_enable_clouds;
883 bool m_cache_enable_joysticks;
884 bool m_cache_enable_particles;
885 bool m_cache_enable_fog;
886 bool m_cache_enable_noclip;
887 bool m_cache_enable_free_move;
888 f32 m_cache_mouse_sensitivity;
889 f32 m_cache_joystick_frustum_sensitivity;
890 f32 m_repeat_place_time;
891 f32 m_cache_cam_smoothing;
892 f32 m_cache_fog_start;
894 bool m_invert_mouse = false;
895 bool m_first_loop_after_window_activation = false;
896 bool m_camera_offset_changed = false;
898 bool m_does_lost_focus_pause_game = false;
900 int m_reset_HW_buffer_counter = 0;
902 bool m_cache_hold_aux1;
903 bool m_android_chat_open;
908 m_chat_log_buf(g_logger),
909 m_game_ui(new GameUI())
911 g_settings->registerChangedCallback("doubletap_jump",
912 &settingChangedCallback, this);
913 g_settings->registerChangedCallback("enable_clouds",
914 &settingChangedCallback, this);
915 g_settings->registerChangedCallback("doubletap_joysticks",
916 &settingChangedCallback, this);
917 g_settings->registerChangedCallback("enable_particles",
918 &settingChangedCallback, this);
919 g_settings->registerChangedCallback("enable_fog",
920 &settingChangedCallback, this);
921 g_settings->registerChangedCallback("mouse_sensitivity",
922 &settingChangedCallback, this);
923 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
924 &settingChangedCallback, this);
925 g_settings->registerChangedCallback("repeat_place_time",
926 &settingChangedCallback, this);
927 g_settings->registerChangedCallback("noclip",
928 &settingChangedCallback, this);
929 g_settings->registerChangedCallback("free_move",
930 &settingChangedCallback, this);
931 g_settings->registerChangedCallback("cinematic",
932 &settingChangedCallback, this);
933 g_settings->registerChangedCallback("cinematic_camera_smoothing",
934 &settingChangedCallback, this);
935 g_settings->registerChangedCallback("camera_smoothing",
936 &settingChangedCallback, this);
941 m_cache_hold_aux1 = false; // This is initialised properly later
948 /****************************************************************************
950 ****************************************************************************/
959 delete server; // deleted first to stop all server threads
967 delete nodedef_manager;
968 delete itemdef_manager;
971 extendedResourceCleanup();
973 g_settings->deregisterChangedCallback("doubletap_jump",
974 &settingChangedCallback, this);
975 g_settings->deregisterChangedCallback("enable_clouds",
976 &settingChangedCallback, this);
977 g_settings->deregisterChangedCallback("enable_particles",
978 &settingChangedCallback, this);
979 g_settings->deregisterChangedCallback("enable_fog",
980 &settingChangedCallback, this);
981 g_settings->deregisterChangedCallback("mouse_sensitivity",
982 &settingChangedCallback, this);
983 g_settings->deregisterChangedCallback("repeat_place_time",
984 &settingChangedCallback, this);
985 g_settings->deregisterChangedCallback("noclip",
986 &settingChangedCallback, this);
987 g_settings->deregisterChangedCallback("free_move",
988 &settingChangedCallback, this);
989 g_settings->deregisterChangedCallback("cinematic",
990 &settingChangedCallback, this);
991 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
992 &settingChangedCallback, this);
993 g_settings->deregisterChangedCallback("camera_smoothing",
994 &settingChangedCallback, this);
997 bool Game::startup(bool *kill,
999 const GameStartData &start_data,
1000 std::string &error_message,
1002 ChatBackend *chat_backend)
1006 this->device = RenderingEngine::get_raw_device();
1008 this->error_message = &error_message;
1009 this->reconnect_requested = reconnect;
1010 this->input = input;
1011 this->chat_backend = chat_backend;
1012 this->simple_singleplayer_mode = start_data.isSinglePlayer();
1014 input->keycache.populate();
1016 driver = device->getVideoDriver();
1017 smgr = RenderingEngine::get_scene_manager();
1019 RenderingEngine::get_scene_manager()->getParameters()->
1020 setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1023 runData = GameRunData();
1024 runData.time_from_last_punch = 10.0;
1026 m_game_ui->initFlags();
1028 m_invert_mouse = g_settings->getBool("invert_mouse");
1029 m_first_loop_after_window_activation = true;
1031 g_client_translations->clear();
1033 // address can change if simple_singleplayer_mode
1034 if (!init(start_data.world_spec.path, start_data.address,
1035 start_data.socket_port, start_data.game_spec))
1038 if (!createClient(start_data))
1041 RenderingEngine::initialize(client, hud);
1049 ProfilerGraph graph;
1050 RunStats stats = { 0 };
1051 CameraOrientation cam_view_target = { 0 };
1052 CameraOrientation cam_view = { 0 };
1053 FpsControl draw_times = { 0 };
1054 f32 dtime; // in seconds
1056 /* Clear the profiler */
1057 Profiler::GraphValues dummyvalues;
1058 g_profiler->graphGet(dummyvalues);
1060 draw_times.last_time = RenderingEngine::get_timer_time();
1062 set_light_table(g_settings->getFloat("display_gamma"));
1065 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1066 && client->checkPrivilege("fast");
1069 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1070 g_settings->getU16("screen_h"));
1072 while (RenderingEngine::run()
1073 && !(*kill || g_gamecallback->shutdown_requested
1074 || (server && server->isShutdownRequested()))) {
1076 const irr::core::dimension2d<u32> ¤t_screen_size =
1077 RenderingEngine::get_video_driver()->getScreenSize();
1078 // Verify if window size has changed and save it if it's the case
1079 // Ensure evaluating settings->getBool after verifying screensize
1080 // First condition is cheaper
1081 if (previous_screen_size != current_screen_size &&
1082 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1083 g_settings->getBool("autosave_screensize")) {
1084 g_settings->setU16("screen_w", current_screen_size.Width);
1085 g_settings->setU16("screen_h", current_screen_size.Height);
1086 previous_screen_size = current_screen_size;
1089 // Calculate dtime =
1090 // RenderingEngine::run() from this iteration
1091 // + Sleep time until the wanted FPS are reached
1092 limitFps(&draw_times, &dtime);
1094 // Prepare render data for next iteration
1096 updateStats(&stats, draw_times, dtime);
1097 updateInteractTimers(dtime);
1099 if (!checkConnection())
1101 if (!handleCallbacks())
1106 m_game_ui->clearInfoText();
1107 hud->resizeHotbar();
1109 updateProfilers(stats, draw_times, dtime);
1110 processUserInput(dtime);
1111 // Update camera before player movement to avoid camera lag of one frame
1112 updateCameraDirection(&cam_view_target, dtime);
1113 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1114 cam_view.camera_yaw) * m_cache_cam_smoothing;
1115 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1116 cam_view.camera_pitch) * m_cache_cam_smoothing;
1117 updatePlayerControl(cam_view);
1119 processClientEvents(&cam_view_target);
1120 updateCamera(draw_times.busy_time, dtime);
1122 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
1123 m_game_ui->m_flags.show_debug);
1124 updateFrame(&graph, &stats, dtime, cam_view);
1125 updateProfilerGraphs(&graph);
1127 // Update if minimap has been disabled by the server
1128 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1130 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1137 void Game::shutdown()
1139 RenderingEngine::finalize();
1140 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1141 if (g_settings->get("3d_mode") == "pageflip") {
1142 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1145 auto formspec = m_game_ui->getFormspecGUI();
1147 formspec->quitMenu();
1149 #ifdef HAVE_TOUCHSCREENGUI
1150 g_touchscreengui->hide();
1153 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1158 if (gui_chat_console)
1159 gui_chat_console->drop();
1165 while (g_menumgr.menuCount() > 0) {
1166 g_menumgr.m_stack.front()->setVisible(false);
1167 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1170 m_game_ui->deleteFormspec();
1172 chat_backend->addMessage(L"", L"# Disconnected.");
1173 chat_backend->addMessage(L"", L"");
1174 m_chat_log_buf.clear();
1178 while (!client->isShutdown()) {
1179 assert(texture_src != NULL);
1180 assert(shader_src != NULL);
1181 texture_src->processQueue();
1182 shader_src->processQueue();
1189 /****************************************************************************/
1190 /****************************************************************************
1192 ****************************************************************************/
1193 /****************************************************************************/
1196 const std::string &map_dir,
1197 const std::string &address,
1199 const SubgameSpec &gamespec)
1201 texture_src = createTextureSource();
1203 showOverlayMessage(N_("Loading..."), 0, 0);
1205 shader_src = createShaderSource();
1207 itemdef_manager = createItemDefManager();
1208 nodedef_manager = createNodeDefManager();
1210 eventmgr = new EventManager();
1211 quicktune = new QuicktuneShortcutter();
1213 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1214 && eventmgr && quicktune))
1220 // Create a server if not connecting to an existing one
1221 if (address.empty()) {
1222 if (!createSingleplayerServer(map_dir, gamespec, port))
1229 bool Game::initSound()
1232 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1233 infostream << "Attempting to use OpenAL audio" << std::endl;
1234 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1236 infostream << "Failed to initialize OpenAL audio" << std::endl;
1238 infostream << "Sound disabled." << std::endl;
1242 infostream << "Using dummy audio." << std::endl;
1243 sound = &dummySoundManager;
1244 sound_is_dummy = true;
1247 soundmaker = new SoundMaker(sound, nodedef_manager);
1251 soundmaker->registerReceiver(eventmgr);
1256 bool Game::createSingleplayerServer(const std::string &map_dir,
1257 const SubgameSpec &gamespec, u16 port)
1259 showOverlayMessage(N_("Creating server..."), 0, 5);
1261 std::string bind_str = g_settings->get("bind_address");
1262 Address bind_addr(0, 0, 0, 0, port);
1264 if (g_settings->getBool("ipv6_server")) {
1265 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1269 bind_addr.Resolve(bind_str.c_str());
1270 } catch (ResolveError &e) {
1271 infostream << "Resolving bind address \"" << bind_str
1272 << "\" failed: " << e.what()
1273 << " -- Listening on all addresses." << std::endl;
1276 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1277 *error_message = "Unable to listen on " +
1278 bind_addr.serializeString() +
1279 " because IPv6 is disabled";
1280 errorstream << *error_message << std::endl;
1284 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1285 false, nullptr, error_message);
1291 bool Game::createClient(const GameStartData &start_data)
1293 showOverlayMessage(N_("Creating client..."), 0, 10);
1295 draw_control = new MapDrawControl;
1299 bool could_connect, connect_aborted;
1300 #ifdef HAVE_TOUCHSCREENGUI
1301 if (g_touchscreengui) {
1302 g_touchscreengui->init(texture_src);
1303 g_touchscreengui->hide();
1306 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1309 if (!could_connect) {
1310 if (error_message->empty() && !connect_aborted) {
1311 // Should not happen if error messages are set properly
1312 *error_message = "Connection failed for unknown reason";
1313 errorstream << *error_message << std::endl;
1318 if (!getServerContent(&connect_aborted)) {
1319 if (error_message->empty() && !connect_aborted) {
1320 // Should not happen if error messages are set properly
1321 *error_message = "Connection failed for unknown reason";
1322 errorstream << *error_message << std::endl;
1327 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1328 &m_flags.force_fog_off, &runData.fog_range, client);
1329 shader_src->addShaderConstantSetterFactory(scsf);
1331 // Update cached textures, meshes and materials
1332 client->afterContentReceived();
1336 camera = new Camera(*draw_control, client);
1337 if (!camera->successfullyCreated(*error_message))
1339 client->setCamera(camera);
1343 if (m_cache_enable_clouds)
1344 clouds = new Clouds(smgr, -1, time(0));
1348 sky = new Sky(-1, texture_src, shader_src);
1350 skybox = NULL; // This is used/set later on in the main run loop
1352 /* Pre-calculated values
1354 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1356 v2u32 size = t->getOriginalSize();
1357 crack_animation_length = size.Y / size.X;
1359 crack_animation_length = 5;
1365 /* Set window caption
1367 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1369 str += utf8_to_wide(g_version_hash);
1371 const wchar_t *text = nullptr;
1372 if (simple_singleplayer_mode)
1373 text = wgettext("Singleplayer");
1375 text = wgettext("Multiplayer");
1382 str += driver->getName();
1385 device->setWindowCaption(str.c_str());
1387 LocalPlayer *player = client->getEnv().getLocalPlayer();
1388 player->hurt_tilt_timer = 0;
1389 player->hurt_tilt_strength = 0;
1391 hud = new Hud(guienv, client, player, &player->inventory);
1393 mapper = client->getMinimap();
1395 if (mapper && client->modsLoaded())
1396 client->getScript()->on_minimap_ready(mapper);
1401 bool Game::initGui()
1405 // Remove stale "recent" chat messages from previous connections
1406 chat_backend->clearRecentChat();
1408 // Make sure the size of the recent messages buffer is right
1409 chat_backend->applySettings();
1411 // Chat backend and console
1412 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1413 -1, chat_backend, client, &g_menumgr);
1415 #ifdef HAVE_TOUCHSCREENGUI
1417 if (g_touchscreengui)
1418 g_touchscreengui->show();
1425 bool Game::connectToServer(const GameStartData &start_data,
1426 bool *connect_ok, bool *connection_aborted)
1428 *connect_ok = false; // Let's not be overly optimistic
1429 *connection_aborted = false;
1430 bool local_server_mode = false;
1432 showOverlayMessage(N_("Resolving address..."), 0, 15);
1434 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1437 connect_address.Resolve(start_data.address.c_str());
1439 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1440 //connect_address.Resolve("localhost");
1441 if (connect_address.isIPv6()) {
1442 IPv6AddressBytes addr_bytes;
1443 addr_bytes.bytes[15] = 1;
1444 connect_address.setAddress(&addr_bytes);
1446 connect_address.setAddress(127, 0, 0, 1);
1448 local_server_mode = true;
1450 } catch (ResolveError &e) {
1451 *error_message = std::string("Couldn't resolve address: ") + e.what();
1452 errorstream << *error_message << std::endl;
1456 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1457 *error_message = "Unable to connect to " +
1458 connect_address.serializeString() +
1459 " because IPv6 is disabled";
1460 errorstream << *error_message << std::endl;
1464 client = new Client(start_data.name.c_str(),
1465 start_data.password, start_data.address,
1466 *draw_control, texture_src, shader_src,
1467 itemdef_manager, nodedef_manager, sound, eventmgr,
1468 connect_address.isIPv6(), m_game_ui.get());
1470 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1472 infostream << "Connecting to server at ";
1473 connect_address.print(&infostream);
1474 infostream << std::endl;
1476 client->connect(connect_address,
1477 simple_singleplayer_mode || local_server_mode);
1480 Wait for server to accept connection
1486 FpsControl fps_control = { 0 };
1488 f32 wait_time = 0; // in seconds
1490 fps_control.last_time = RenderingEngine::get_timer_time();
1492 while (RenderingEngine::run()) {
1494 limitFps(&fps_control, &dtime);
1496 // Update client and server
1497 client->step(dtime);
1500 server->step(dtime);
1503 if (client->getState() == LC_Init) {
1509 if (*connection_aborted)
1512 if (client->accessDenied()) {
1513 *error_message = "Access denied. Reason: "
1514 + client->accessDeniedReason();
1515 *reconnect_requested = client->reconnectRequested();
1516 errorstream << *error_message << std::endl;
1520 if (input->cancelPressed()) {
1521 *connection_aborted = true;
1522 infostream << "Connect aborted [Escape]" << std::endl;
1526 if (client->m_is_registration_confirmation_state) {
1527 if (registration_confirmation_shown) {
1528 // Keep drawing the GUI
1529 RenderingEngine::draw_menu_scene(guienv, dtime, true);
1531 registration_confirmation_shown = true;
1532 (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
1533 &g_menumgr, client, start_data.name, start_data.password,
1534 connection_aborted, texture_src))->drop();
1538 // Only time out if we aren't waiting for the server we started
1539 if (!start_data.address.empty() && wait_time > 10) {
1540 *error_message = "Connection timed out.";
1541 errorstream << *error_message << std::endl;
1546 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1549 } catch (con::PeerNotFoundException &e) {
1550 // TODO: Should something be done here? At least an info/error
1558 bool Game::getServerContent(bool *aborted)
1562 FpsControl fps_control = { 0 };
1563 f32 dtime; // in seconds
1565 fps_control.last_time = RenderingEngine::get_timer_time();
1567 while (RenderingEngine::run()) {
1569 limitFps(&fps_control, &dtime);
1571 // Update client and server
1572 client->step(dtime);
1575 server->step(dtime);
1578 if (client->mediaReceived() && client->itemdefReceived() &&
1579 client->nodedefReceived()) {
1584 if (!checkConnection())
1587 if (client->getState() < LC_Init) {
1588 *error_message = "Client disconnected";
1589 errorstream << *error_message << std::endl;
1593 if (input->cancelPressed()) {
1595 infostream << "Connect aborted [Escape]" << std::endl;
1602 if (!client->itemdefReceived()) {
1603 const wchar_t *text = wgettext("Item definitions...");
1605 RenderingEngine::draw_load_screen(text, guienv, texture_src,
1608 } else if (!client->nodedefReceived()) {
1609 const wchar_t *text = wgettext("Node definitions...");
1611 RenderingEngine::draw_load_screen(text, guienv, texture_src,
1615 std::stringstream message;
1616 std::fixed(message);
1617 message.precision(0);
1618 float receive = client->mediaReceiveProgress() * 100;
1619 message << gettext("Media...");
1621 message << " " << receive << "%";
1622 message.precision(2);
1624 if ((USE_CURL == 0) ||
1625 (!g_settings->getBool("enable_remote_media_server"))) {
1626 float cur = client->getCurRate();
1627 std::string cur_unit = gettext("KiB/s");
1631 cur_unit = gettext("MiB/s");
1634 message << " (" << cur << ' ' << cur_unit << ")";
1637 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1638 RenderingEngine::draw_load_screen(utf8_to_wide(message.str()), guienv,
1639 texture_src, dtime, progress);
1647 /****************************************************************************/
1648 /****************************************************************************
1650 ****************************************************************************/
1651 /****************************************************************************/
1653 inline void Game::updateInteractTimers(f32 dtime)
1655 if (runData.nodig_delay_timer >= 0)
1656 runData.nodig_delay_timer -= dtime;
1658 if (runData.object_hit_delay_timer >= 0)
1659 runData.object_hit_delay_timer -= dtime;
1661 runData.time_from_last_punch += dtime;
1665 /* returns false if game should exit, otherwise true
1667 inline bool Game::checkConnection()
1669 if (client->accessDenied()) {
1670 *error_message = "Access denied. Reason: "
1671 + client->accessDeniedReason();
1672 *reconnect_requested = client->reconnectRequested();
1673 errorstream << *error_message << std::endl;
1681 /* returns false if game should exit, otherwise true
1683 inline bool Game::handleCallbacks()
1685 if (g_gamecallback->disconnect_requested) {
1686 g_gamecallback->disconnect_requested = false;
1690 if (g_gamecallback->changepassword_requested) {
1691 (new GUIPasswordChange(guienv, guiroot, -1,
1692 &g_menumgr, client, texture_src))->drop();
1693 g_gamecallback->changepassword_requested = false;
1696 if (g_gamecallback->changevolume_requested) {
1697 (new GUIVolumeChange(guienv, guiroot, -1,
1698 &g_menumgr, texture_src))->drop();
1699 g_gamecallback->changevolume_requested = false;
1702 if (g_gamecallback->keyconfig_requested) {
1703 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1704 &g_menumgr, texture_src))->drop();
1705 g_gamecallback->keyconfig_requested = false;
1708 if (g_gamecallback->keyconfig_changed) {
1709 input->keycache.populate(); // update the cache with new settings
1710 g_gamecallback->keyconfig_changed = false;
1717 void Game::processQueues()
1719 texture_src->processQueue();
1720 itemdef_manager->processQueue(client);
1721 shader_src->processQueue();
1725 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1728 float profiler_print_interval =
1729 g_settings->getFloat("profiler_print_interval");
1730 bool print_to_log = true;
1732 if (profiler_print_interval == 0) {
1733 print_to_log = false;
1734 profiler_print_interval = 3;
1737 if (profiler_interval.step(dtime, profiler_print_interval)) {
1739 infostream << "Profiler:" << std::endl;
1740 g_profiler->print(infostream);
1743 m_game_ui->updateProfiler();
1744 g_profiler->clear();
1747 // Update update graphs
1748 g_profiler->graphAdd("Time non-rendering [ms]",
1749 draw_times.busy_time - stats.drawtime);
1751 g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time);
1752 g_profiler->graphAdd("FPS", 1.0f / dtime);
1755 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1762 /* Time average and jitter calculation
1764 jp = &stats->dtime_jitter;
1765 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1767 jitter = dtime - jp->avg;
1769 if (jitter > jp->max)
1772 jp->counter += dtime;
1774 if (jp->counter > 0.0) {
1776 jp->max_sample = jp->max;
1777 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1781 /* Busytime average and jitter calculation
1783 jp = &stats->busy_time_jitter;
1784 jp->avg = jp->avg + draw_times.busy_time * 0.02;
1786 jitter = draw_times.busy_time - jp->avg;
1788 if (jitter > jp->max)
1790 if (jitter < jp->min)
1793 jp->counter += dtime;
1795 if (jp->counter > 0.0) {
1797 jp->max_sample = jp->max;
1798 jp->min_sample = jp->min;
1806 /****************************************************************************
1808 ****************************************************************************/
1810 void Game::processUserInput(f32 dtime)
1812 // Reset input if window not active or some menu is active
1813 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1815 #ifdef HAVE_TOUCHSCREENGUI
1816 g_touchscreengui->hide();
1819 #ifdef HAVE_TOUCHSCREENGUI
1820 else if (g_touchscreengui) {
1821 /* on touchscreengui step may generate own input events which ain't
1822 * what we want in case we just did clear them */
1823 g_touchscreengui->step(dtime);
1827 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1828 gui_chat_console->closeConsoleAtOnce();
1831 // Input handler step() (used by the random input generator)
1835 auto formspec = m_game_ui->getFormspecGUI();
1837 formspec->getAndroidUIInput();
1839 handleAndroidChatInput();
1842 // Increase timer for double tap of "keymap_jump"
1843 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1844 runData.jump_timer += dtime;
1847 processItemSelection(&runData.new_playeritem);
1851 void Game::processKeyInput()
1853 if (wasKeyDown(KeyType::DROP)) {
1854 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1855 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1856 toggleAutoforward();
1857 } else if (wasKeyDown(KeyType::BACKWARD)) {
1858 if (g_settings->getBool("continuous_forward"))
1859 toggleAutoforward();
1860 } else if (wasKeyDown(KeyType::INVENTORY)) {
1862 } else if (input->cancelPressed()) {
1864 m_android_chat_open = false;
1866 if (!gui_chat_console->isOpenInhibited()) {
1869 } else if (wasKeyDown(KeyType::CHAT)) {
1870 openConsole(0.2, L"");
1871 } else if (wasKeyDown(KeyType::CMD)) {
1872 openConsole(0.2, L"/");
1873 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1874 if (client->modsLoaded())
1875 openConsole(0.2, L".");
1877 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1878 } else if (wasKeyDown(KeyType::CONSOLE)) {
1879 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1880 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1882 } else if (wasKeyDown(KeyType::JUMP)) {
1883 toggleFreeMoveAlt();
1884 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1886 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1888 } else if (wasKeyDown(KeyType::NOCLIP)) {
1891 } else if (wasKeyDown(KeyType::MUTE)) {
1892 if (g_settings->getBool("enable_sound")) {
1893 bool new_mute_sound = !g_settings->getBool("mute_sound");
1894 g_settings->setBool("mute_sound", new_mute_sound);
1896 m_game_ui->showTranslatedStatusText("Sound muted");
1898 m_game_ui->showTranslatedStatusText("Sound unmuted");
1900 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1902 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1903 if (g_settings->getBool("enable_sound")) {
1904 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1906 g_settings->setFloat("sound_volume", new_volume);
1907 const wchar_t *str = wgettext("Volume changed to %d%%");
1908 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1910 m_game_ui->showStatusText(buf);
1912 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1914 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1915 if (g_settings->getBool("enable_sound")) {
1916 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1918 g_settings->setFloat("sound_volume", new_volume);
1919 const wchar_t *str = wgettext("Volume changed to %d%%");
1920 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1922 m_game_ui->showStatusText(buf);
1924 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1927 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1928 || wasKeyDown(KeyType::DEC_VOLUME)) {
1929 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1931 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1933 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1934 client->makeScreenshot();
1935 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
1936 hud->toggleBlockBounds();
1937 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1938 m_game_ui->toggleHud();
1939 } else if (wasKeyDown(KeyType::MINIMAP)) {
1940 toggleMinimap(isKeyDown(KeyType::SNEAK));
1941 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1942 m_game_ui->toggleChat();
1943 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1945 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1946 toggleUpdateCamera();
1947 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1949 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1950 m_game_ui->toggleProfiler();
1951 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1952 increaseViewRange();
1953 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1954 decreaseViewRange();
1955 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1956 toggleFullViewRange();
1957 } else if (wasKeyDown(KeyType::ZOOM)) {
1959 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1961 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1963 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1965 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1969 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1970 runData.reset_jump_timer = false;
1971 runData.jump_timer = 0.0f;
1974 if (quicktune->hasMessage()) {
1975 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
1979 void Game::processItemSelection(u16 *new_playeritem)
1981 LocalPlayer *player = client->getEnv().getLocalPlayer();
1983 /* Item selection using mouse wheel
1985 *new_playeritem = player->getWieldIndex();
1987 s32 wheel = input->getMouseWheel();
1988 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
1989 player->hud_hotbar_itemcount - 1);
1993 if (wasKeyDown(KeyType::HOTBAR_NEXT))
1996 if (wasKeyDown(KeyType::HOTBAR_PREV))
2000 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2002 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2005 /* Item selection using hotbar slot keys
2007 for (u16 i = 0; i <= max_item; i++) {
2008 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2009 *new_playeritem = i;
2016 void Game::dropSelectedItem(bool single_item)
2018 IDropAction *a = new IDropAction();
2019 a->count = single_item ? 1 : 0;
2020 a->from_inv.setCurrentPlayer();
2021 a->from_list = "main";
2022 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2023 client->inventoryAction(a);
2027 void Game::openInventory()
2030 * Don't permit to open inventory is CAO or player doesn't exists.
2031 * This prevent showing an empty inventory at player load
2034 LocalPlayer *player = client->getEnv().getLocalPlayer();
2035 if (!player || !player->getCAO())
2038 infostream << "Game: Launching inventory" << std::endl;
2040 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2042 InventoryLocation inventoryloc;
2043 inventoryloc.setCurrentPlayer();
2045 if (!client->modsLoaded()
2046 || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2047 TextDest *txt_dst = new TextDestPlayerInventory(client);
2048 auto *&formspec = m_game_ui->updateFormspec("");
2049 GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
2050 txt_dst, client->getFormspecPrepend(), sound);
2052 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2057 void Game::openConsole(float scale, const wchar_t *line)
2059 assert(scale > 0.0f && scale <= 1.0f);
2062 porting::showInputDialog(gettext("ok"), "", "", 2);
2063 m_android_chat_open = true;
2065 if (gui_chat_console->isOpenInhibited())
2067 gui_chat_console->openConsole(scale);
2069 gui_chat_console->setCloseOnEnter(true);
2070 gui_chat_console->replaceAndAddToHistory(line);
2076 void Game::handleAndroidChatInput()
2078 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2079 std::string text = porting::getInputDialogValue();
2080 client->typeChatMessage(utf8_to_wide(text));
2081 m_android_chat_open = false;
2087 void Game::toggleFreeMove()
2089 bool free_move = !g_settings->getBool("free_move");
2090 g_settings->set("free_move", bool_to_cstr(free_move));
2093 if (client->checkPrivilege("fly")) {
2094 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2096 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2099 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2103 void Game::toggleFreeMoveAlt()
2105 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2108 runData.reset_jump_timer = true;
2112 void Game::togglePitchMove()
2114 bool pitch_move = !g_settings->getBool("pitch_move");
2115 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2118 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2120 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2125 void Game::toggleFast()
2127 bool fast_move = !g_settings->getBool("fast_move");
2128 bool has_fast_privs = client->checkPrivilege("fast");
2129 g_settings->set("fast_move", bool_to_cstr(fast_move));
2132 if (has_fast_privs) {
2133 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2135 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2138 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2142 m_cache_hold_aux1 = fast_move && has_fast_privs;
2147 void Game::toggleNoClip()
2149 bool noclip = !g_settings->getBool("noclip");
2150 g_settings->set("noclip", bool_to_cstr(noclip));
2153 if (client->checkPrivilege("noclip")) {
2154 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2156 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2159 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2163 void Game::toggleCinematic()
2165 bool cinematic = !g_settings->getBool("cinematic");
2166 g_settings->set("cinematic", bool_to_cstr(cinematic));
2169 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2171 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2174 // Autoforward by toggling continuous forward.
2175 void Game::toggleAutoforward()
2177 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2178 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2180 if (autorun_enabled)
2181 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2183 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2186 void Game::toggleMinimap(bool shift_pressed)
2188 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2192 mapper->toggleMinimapShape();
2196 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2198 // Not so satisying code to keep compatibility with old fixed mode system
2200 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2202 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2203 m_game_ui->m_flags.show_minimap = false;
2206 // If radar is disabled, try to find a non radar mode or fall back to 0
2207 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2208 while (mapper->getModeIndex() &&
2209 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2212 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2216 // End of 'not so satifying code'
2217 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2218 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2219 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2221 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2224 void Game::toggleFog()
2226 bool fog_enabled = g_settings->getBool("enable_fog");
2227 g_settings->setBool("enable_fog", !fog_enabled);
2229 m_game_ui->showTranslatedStatusText("Fog disabled");
2231 m_game_ui->showTranslatedStatusText("Fog enabled");
2235 void Game::toggleDebug()
2237 // Initial / 4x toggle: Chat only
2238 // 1x toggle: Debug text with chat
2239 // 2x toggle: Debug text with profiler graph
2240 // 3x toggle: Debug text and wireframe
2241 if (!m_game_ui->m_flags.show_debug) {
2242 m_game_ui->m_flags.show_debug = true;
2243 m_game_ui->m_flags.show_profiler_graph = false;
2244 draw_control->show_wireframe = false;
2245 m_game_ui->showTranslatedStatusText("Debug info shown");
2246 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2247 m_game_ui->m_flags.show_profiler_graph = true;
2248 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2249 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2250 m_game_ui->m_flags.show_profiler_graph = false;
2251 draw_control->show_wireframe = true;
2252 m_game_ui->showTranslatedStatusText("Wireframe shown");
2254 m_game_ui->m_flags.show_debug = false;
2255 m_game_ui->m_flags.show_profiler_graph = false;
2256 draw_control->show_wireframe = false;
2257 if (client->checkPrivilege("debug")) {
2258 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2260 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2266 void Game::toggleUpdateCamera()
2268 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2269 if (m_flags.disable_camera_update)
2270 m_game_ui->showTranslatedStatusText("Camera update disabled");
2272 m_game_ui->showTranslatedStatusText("Camera update enabled");
2276 void Game::increaseViewRange()
2278 s16 range = g_settings->getS16("viewing_range");
2279 s16 range_new = range + 10;
2283 if (range_new > 4000) {
2285 str = wgettext("Viewing range is at maximum: %d");
2286 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2288 m_game_ui->showStatusText(buf);
2291 str = wgettext("Viewing range changed to %d");
2292 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2294 m_game_ui->showStatusText(buf);
2296 g_settings->set("viewing_range", itos(range_new));
2300 void Game::decreaseViewRange()
2302 s16 range = g_settings->getS16("viewing_range");
2303 s16 range_new = range - 10;
2307 if (range_new < 20) {
2309 str = wgettext("Viewing range is at minimum: %d");
2310 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2312 m_game_ui->showStatusText(buf);
2314 str = wgettext("Viewing range changed to %d");
2315 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2317 m_game_ui->showStatusText(buf);
2319 g_settings->set("viewing_range", itos(range_new));
2323 void Game::toggleFullViewRange()
2325 draw_control->range_all = !draw_control->range_all;
2326 if (draw_control->range_all)
2327 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2329 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2333 void Game::checkZoomEnabled()
2335 LocalPlayer *player = client->getEnv().getLocalPlayer();
2336 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2337 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2340 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2342 if ((device->isWindowActive() && device->isWindowFocused()
2343 && !isMenuActive()) || input->isRandom()) {
2346 if (!input->isRandom()) {
2347 // Mac OSX gets upset if this is set every frame
2348 if (device->getCursorControl()->isVisible())
2349 device->getCursorControl()->setVisible(false);
2353 if (m_first_loop_after_window_activation) {
2354 m_first_loop_after_window_activation = false;
2356 input->setMousePos(driver->getScreenSize().Width / 2,
2357 driver->getScreenSize().Height / 2);
2359 updateCameraOrientation(cam, dtime);
2365 // Mac OSX gets upset if this is set every frame
2366 if (!device->getCursorControl()->isVisible())
2367 device->getCursorControl()->setVisible(true);
2370 m_first_loop_after_window_activation = true;
2375 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2376 // responsiveness independently of FOV.
2377 f32 Game::getSensitivityScaleFactor() const
2379 f32 fov_y = client->getCamera()->getFovY();
2381 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2382 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2384 return tan(fov_y / 2.0f) * 1.3763818698f;
2387 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2389 #ifdef HAVE_TOUCHSCREENGUI
2390 if (g_touchscreengui) {
2391 cam->camera_yaw += g_touchscreengui->getYawChange();
2392 cam->camera_pitch = g_touchscreengui->getPitch();
2395 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2396 v2s32 dist = input->getMousePos() - center;
2398 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2402 f32 sens_scale = getSensitivityScaleFactor();
2403 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2404 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2406 if (dist.X != 0 || dist.Y != 0)
2407 input->setMousePos(center.X, center.Y);
2408 #ifdef HAVE_TOUCHSCREENGUI
2412 if (m_cache_enable_joysticks) {
2413 f32 sens_scale = getSensitivityScaleFactor();
2414 f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime * sens_scale;
2415 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2416 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2419 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2423 void Game::updatePlayerControl(const CameraOrientation &cam)
2425 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2427 // DO NOT use the isKeyDown method for the forward, backward, left, right
2428 // buttons, as the code that uses the controls needs to be able to
2429 // distinguish between the two in order to know when to use joysticks.
2431 PlayerControl control(
2432 input->isKeyDown(KeyType::FORWARD),
2433 input->isKeyDown(KeyType::BACKWARD),
2434 input->isKeyDown(KeyType::LEFT),
2435 input->isKeyDown(KeyType::RIGHT),
2436 isKeyDown(KeyType::JUMP),
2437 isKeyDown(KeyType::AUX1),
2438 isKeyDown(KeyType::SNEAK),
2439 isKeyDown(KeyType::ZOOM),
2440 isKeyDown(KeyType::DIG),
2441 isKeyDown(KeyType::PLACE),
2444 input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE),
2445 input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE)
2448 u32 keypress_bits = (
2449 ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) |
2450 ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) |
2451 ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) |
2452 ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) |
2453 ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) |
2454 ( (u32)(isKeyDown(KeyType::AUX1) & 0x1) << 5) |
2455 ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) |
2456 ( (u32)(isKeyDown(KeyType::DIG) & 0x1) << 7) |
2457 ( (u32)(isKeyDown(KeyType::PLACE) & 0x1) << 8) |
2458 ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9)
2462 /* For Android, simulate holding down AUX1 (fast move) if the user has
2463 * the fast_move setting toggled on. If there is an aux1 key defined for
2464 * Android then its meaning is inverted (i.e. holding aux1 means walk and
2467 if (m_cache_hold_aux1) {
2468 control.aux1 = control.aux1 ^ true;
2469 keypress_bits ^= ((u32)(1U << 5));
2473 LocalPlayer *player = client->getEnv().getLocalPlayer();
2475 // autojump if set: simulate "jump" key
2476 if (player->getAutojump()) {
2477 control.jump = true;
2478 keypress_bits |= 1U << 4;
2481 // autoforward if set: simulate "up" key
2482 if (player->getPlayerSettings().continuous_forward &&
2483 client->activeObjectsReceived() && !player->isDead()) {
2485 keypress_bits |= 1U << 0;
2488 client->setPlayerControl(control);
2489 player->keyPressed = keypress_bits;
2495 inline void Game::step(f32 *dtime)
2497 bool can_be_and_is_paused =
2498 (simple_singleplayer_mode && g_menumgr.pausesGame());
2500 if (can_be_and_is_paused) { // This is for a singleplayer server
2501 *dtime = 0; // No time passes
2503 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
2507 server->step(*dtime);
2509 client->step(*dtime);
2513 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2516 for (auto &&child: node->getChildren())
2517 pauseNodeAnimation(paused, child);
2518 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2520 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2521 float speed = animated_node->getAnimationSpeed();
2524 paused.push_back({grab(animated_node), speed});
2525 animated_node->setAnimationSpeed(0.0f);
2528 void Game::pauseAnimation()
2530 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2533 void Game::resumeAnimation()
2535 for (auto &&pair: paused_animated_nodes)
2536 pair.first->setAnimationSpeed(pair.second);
2537 paused_animated_nodes.clear();
2540 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2541 {&Game::handleClientEvent_None},
2542 {&Game::handleClientEvent_PlayerDamage},
2543 {&Game::handleClientEvent_PlayerForceMove},
2544 {&Game::handleClientEvent_Deathscreen},
2545 {&Game::handleClientEvent_ShowFormSpec},
2546 {&Game::handleClientEvent_ShowLocalFormSpec},
2547 {&Game::handleClientEvent_HandleParticleEvent},
2548 {&Game::handleClientEvent_HandleParticleEvent},
2549 {&Game::handleClientEvent_HandleParticleEvent},
2550 {&Game::handleClientEvent_HudAdd},
2551 {&Game::handleClientEvent_HudRemove},
2552 {&Game::handleClientEvent_HudChange},
2553 {&Game::handleClientEvent_SetSky},
2554 {&Game::handleClientEvent_SetSun},
2555 {&Game::handleClientEvent_SetMoon},
2556 {&Game::handleClientEvent_SetStars},
2557 {&Game::handleClientEvent_OverrideDayNigthRatio},
2558 {&Game::handleClientEvent_CloudParams},
2561 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2563 FATAL_ERROR("ClientEvent type None received");
2566 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2568 if (client->modsLoaded())
2569 client->getScript()->on_damage_taken(event->player_damage.amount);
2571 // Damage flash and hurt tilt are not used at death
2572 if (client->getHP() > 0) {
2573 LocalPlayer *player = client->getEnv().getLocalPlayer();
2575 f32 hp_max = player->getCAO() ?
2576 player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2577 f32 damage_ratio = event->player_damage.amount / hp_max;
2579 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2580 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2582 player->hurt_tilt_timer = 1.5f;
2583 player->hurt_tilt_strength =
2584 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2587 // Play damage sound
2588 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2591 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2593 cam->camera_yaw = event->player_force_move.yaw;
2594 cam->camera_pitch = event->player_force_move.pitch;
2597 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2599 // If client scripting is enabled, deathscreen is handled by CSM code in
2600 // builtin/client/init.lua
2601 if (client->modsLoaded())
2602 client->getScript()->on_death();
2604 showDeathFormspec();
2606 /* Handle visualization */
2607 LocalPlayer *player = client->getEnv().getLocalPlayer();
2608 runData.damage_flash = 0;
2609 player->hurt_tilt_timer = 0;
2610 player->hurt_tilt_strength = 0;
2613 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2615 if (event->show_formspec.formspec->empty()) {
2616 auto formspec = m_game_ui->getFormspecGUI();
2617 if (formspec && (event->show_formspec.formname->empty()
2618 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2619 formspec->quitMenu();
2622 FormspecFormSource *fs_src =
2623 new FormspecFormSource(*(event->show_formspec.formspec));
2624 TextDestPlayerInventory *txt_dst =
2625 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2627 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2628 GUIFormSpecMenu::create(formspec, client, &input->joystick,
2629 fs_src, txt_dst, client->getFormspecPrepend(), sound);
2632 delete event->show_formspec.formspec;
2633 delete event->show_formspec.formname;
2636 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2638 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2639 LocalFormspecHandler *txt_dst =
2640 new LocalFormspecHandler(*event->show_formspec.formname, client);
2641 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, &input->joystick,
2642 fs_src, txt_dst, client->getFormspecPrepend(), sound);
2644 delete event->show_formspec.formspec;
2645 delete event->show_formspec.formname;
2648 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2649 CameraOrientation *cam)
2651 LocalPlayer *player = client->getEnv().getLocalPlayer();
2652 client->getParticleManager()->handleParticleEvent(event, client, player);
2655 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2657 LocalPlayer *player = client->getEnv().getLocalPlayer();
2659 u32 server_id = event->hudadd->server_id;
2660 // ignore if we already have a HUD with that ID
2661 auto i = m_hud_server_to_client.find(server_id);
2662 if (i != m_hud_server_to_client.end()) {
2663 delete event->hudadd;
2667 HudElement *e = new HudElement;
2668 e->type = static_cast<HudElementType>(event->hudadd->type);
2669 e->pos = event->hudadd->pos;
2670 e->name = event->hudadd->name;
2671 e->scale = event->hudadd->scale;
2672 e->text = event->hudadd->text;
2673 e->number = event->hudadd->number;
2674 e->item = event->hudadd->item;
2675 e->dir = event->hudadd->dir;
2676 e->align = event->hudadd->align;
2677 e->offset = event->hudadd->offset;
2678 e->world_pos = event->hudadd->world_pos;
2679 e->size = event->hudadd->size;
2680 e->z_index = event->hudadd->z_index;
2681 e->text2 = event->hudadd->text2;
2682 m_hud_server_to_client[server_id] = player->addHud(e);
2684 delete event->hudadd;
2687 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2689 LocalPlayer *player = client->getEnv().getLocalPlayer();
2691 auto i = m_hud_server_to_client.find(event->hudrm.id);
2692 if (i != m_hud_server_to_client.end()) {
2693 HudElement *e = player->removeHud(i->second);
2695 m_hud_server_to_client.erase(i);
2700 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2702 LocalPlayer *player = client->getEnv().getLocalPlayer();
2704 HudElement *e = nullptr;
2706 auto i = m_hud_server_to_client.find(event->hudchange->id);
2707 if (i != m_hud_server_to_client.end()) {
2708 e = player->getHud(i->second);
2712 delete event->hudchange;
2716 #define CASE_SET(statval, prop, dataprop) \
2718 e->prop = event->hudchange->dataprop; \
2721 switch (event->hudchange->stat) {
2722 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2724 CASE_SET(HUD_STAT_NAME, name, sdata);
2726 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2728 CASE_SET(HUD_STAT_TEXT, text, sdata);
2730 CASE_SET(HUD_STAT_NUMBER, number, data);
2732 CASE_SET(HUD_STAT_ITEM, item, data);
2734 CASE_SET(HUD_STAT_DIR, dir, data);
2736 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2738 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2740 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2742 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2744 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2746 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2751 delete event->hudchange;
2754 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2756 sky->setVisible(false);
2757 // Whether clouds are visible in front of a custom skybox.
2758 sky->setCloudsEnabled(event->set_sky->clouds);
2764 // Clear the old textures out in case we switch rendering type.
2765 sky->clearSkyboxTextures();
2766 // Handle according to type
2767 if (event->set_sky->type == "regular") {
2768 // Shows the mesh skybox
2769 sky->setVisible(true);
2770 // Update mesh based skybox colours if applicable.
2771 sky->setSkyColors(event->set_sky->sky_color);
2772 sky->setHorizonTint(
2773 event->set_sky->fog_sun_tint,
2774 event->set_sky->fog_moon_tint,
2775 event->set_sky->fog_tint_type
2777 } else if (event->set_sky->type == "skybox" &&
2778 event->set_sky->textures.size() == 6) {
2779 // Disable the dyanmic mesh skybox:
2780 sky->setVisible(false);
2782 sky->setFallbackBgColor(event->set_sky->bgcolor);
2783 // Set sunrise and sunset fog tinting:
2784 sky->setHorizonTint(
2785 event->set_sky->fog_sun_tint,
2786 event->set_sky->fog_moon_tint,
2787 event->set_sky->fog_tint_type
2789 // Add textures to skybox.
2790 for (int i = 0; i < 6; i++)
2791 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2793 // Handle everything else as plain color.
2794 if (event->set_sky->type != "plain")
2795 infostream << "Unknown sky type: "
2796 << (event->set_sky->type) << std::endl;
2797 sky->setVisible(false);
2798 sky->setFallbackBgColor(event->set_sky->bgcolor);
2799 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2800 sky->setHorizonTint(
2801 event->set_sky->bgcolor,
2802 event->set_sky->bgcolor,
2806 delete event->set_sky;
2809 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2811 sky->setSunVisible(event->sun_params->visible);
2812 sky->setSunTexture(event->sun_params->texture,
2813 event->sun_params->tonemap, texture_src);
2814 sky->setSunScale(event->sun_params->scale);
2815 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2816 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2817 delete event->sun_params;
2820 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2822 sky->setMoonVisible(event->moon_params->visible);
2823 sky->setMoonTexture(event->moon_params->texture,
2824 event->moon_params->tonemap, texture_src);
2825 sky->setMoonScale(event->moon_params->scale);
2826 delete event->moon_params;
2829 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2831 sky->setStarsVisible(event->star_params->visible);
2832 sky->setStarCount(event->star_params->count, false);
2833 sky->setStarColor(event->star_params->starcolor);
2834 sky->setStarScale(event->star_params->scale);
2835 delete event->star_params;
2838 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2839 CameraOrientation *cam)
2841 client->getEnv().setDayNightRatioOverride(
2842 event->override_day_night_ratio.do_override,
2843 event->override_day_night_ratio.ratio_f * 1000.0f);
2846 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2851 clouds->setDensity(event->cloud_params.density);
2852 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2853 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2854 clouds->setHeight(event->cloud_params.height);
2855 clouds->setThickness(event->cloud_params.thickness);
2856 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2859 void Game::processClientEvents(CameraOrientation *cam)
2861 while (client->hasClientEvents()) {
2862 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2863 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2864 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2865 (this->*evHandler.handler)(event.get(), cam);
2869 void Game::updateChat(f32 dtime, const v2u32 &screensize)
2871 // Get new messages from error log buffer
2872 while (!m_chat_log_buf.empty())
2873 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2875 // Get new messages from client
2876 std::wstring message;
2877 while (client->getChatMessage(message)) {
2878 chat_backend->addUnparsedMessage(message);
2881 // Remove old messages
2882 chat_backend->step(dtime);
2884 // Display all messages in a static text element
2885 m_game_ui->setChatText(chat_backend->getRecentChat(),
2886 chat_backend->getRecentBuffer().getLineCount());
2889 void Game::updateCamera(u32 busy_time, f32 dtime)
2891 LocalPlayer *player = client->getEnv().getLocalPlayer();
2894 For interaction purposes, get info about the held item
2896 - Is it a usable item?
2897 - Can it point to liquids?
2899 ItemStack playeritem;
2901 ItemStack selected, hand;
2902 playeritem = player->getWieldedItem(&selected, &hand);
2905 ToolCapabilities playeritem_toolcap =
2906 playeritem.getToolCapabilities(itemdef_manager);
2908 v3s16 old_camera_offset = camera->getOffset();
2910 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2911 GenericCAO *playercao = player->getCAO();
2913 // If playercao not loaded, don't change camera
2917 camera->toggleCameraMode();
2919 // Make the player visible depending on camera mode.
2920 playercao->updateMeshCulling();
2921 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2924 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2925 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2927 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2928 camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
2929 camera->step(dtime);
2931 v3f camera_position = camera->getPosition();
2932 v3f camera_direction = camera->getDirection();
2933 f32 camera_fov = camera->getFovMax();
2934 v3s16 camera_offset = camera->getOffset();
2936 m_camera_offset_changed = (camera_offset != old_camera_offset);
2938 if (!m_flags.disable_camera_update) {
2939 client->getEnv().getClientMap().updateCamera(camera_position,
2940 camera_direction, camera_fov, camera_offset);
2942 if (m_camera_offset_changed) {
2943 client->updateCameraOffset(camera_offset);
2944 client->getEnv().updateCameraOffset(camera_offset);
2947 clouds->updateCameraOffset(camera_offset);
2953 void Game::updateSound(f32 dtime)
2955 // Update sound listener
2956 v3s16 camera_offset = camera->getOffset();
2957 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
2958 v3f(0, 0, 0), // velocity
2959 camera->getDirection(),
2960 camera->getCameraNode()->getUpVector());
2962 bool mute_sound = g_settings->getBool("mute_sound");
2964 sound->setListenerGain(0.0f);
2966 // Check if volume is in the proper range, else fix it.
2967 float old_volume = g_settings->getFloat("sound_volume");
2968 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
2969 sound->setListenerGain(new_volume);
2971 if (old_volume != new_volume) {
2972 g_settings->setFloat("sound_volume", new_volume);
2976 LocalPlayer *player = client->getEnv().getLocalPlayer();
2978 // Tell the sound maker whether to make footstep sounds
2979 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
2981 // Update sound maker
2982 if (player->makes_footstep_sound)
2983 soundmaker->step(dtime);
2985 ClientMap &map = client->getEnv().getClientMap();
2986 MapNode n = map.getNode(player->getFootstepNodePos());
2987 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
2991 void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
2993 LocalPlayer *player = client->getEnv().getLocalPlayer();
2995 const v3f camera_direction = camera->getDirection();
2996 const v3s16 camera_offset = camera->getOffset();
2999 Calculate what block is the crosshair pointing to
3002 ItemStack selected_item, hand_item;
3003 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3005 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3006 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3008 core::line3d<f32> shootline;
3010 switch (camera->getCameraMode()) {
3011 case CAMERA_MODE_FIRST:
3012 // Shoot from camera position, with bobbing
3013 shootline.start = camera->getPosition();
3015 case CAMERA_MODE_THIRD:
3016 // Shoot from player head, no bobbing
3017 shootline.start = camera->getHeadPosition();
3019 case CAMERA_MODE_THIRD_FRONT:
3020 shootline.start = camera->getHeadPosition();
3021 // prevent player pointing anything in front-view
3025 shootline.end = shootline.start + camera_direction * BS * d;
3027 #ifdef HAVE_TOUCHSCREENGUI
3029 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3030 shootline = g_touchscreengui->getShootline();
3031 // Scale shootline to the acual distance the player can reach
3032 shootline.end = shootline.start
3033 + shootline.getVector().normalize() * BS * d;
3034 shootline.start += intToFloat(camera_offset, BS);
3035 shootline.end += intToFloat(camera_offset, BS);
3040 PointedThing pointed = updatePointedThing(shootline,
3041 selected_def.liquids_pointable,
3042 !runData.btn_down_for_dig,
3045 if (pointed != runData.pointed_old) {
3046 infostream << "Pointing at " << pointed.dump() << std::endl;
3047 hud->updateSelectionMesh(camera_offset);
3050 // Allow digging again if button is not pressed
3051 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3052 runData.digging_blocked = false;
3056 - releasing dig button
3057 - pointing away from node
3059 if (runData.digging) {
3060 if (wasKeyReleased(KeyType::DIG)) {
3061 infostream << "Dig button released (stopped digging)" << std::endl;
3062 runData.digging = false;
3063 } else if (pointed != runData.pointed_old) {
3064 if (pointed.type == POINTEDTHING_NODE
3065 && runData.pointed_old.type == POINTEDTHING_NODE
3066 && pointed.node_undersurface
3067 == runData.pointed_old.node_undersurface) {
3068 // Still pointing to the same node, but a different face.
3071 infostream << "Pointing away from node (stopped digging)" << std::endl;
3072 runData.digging = false;
3073 hud->updateSelectionMesh(camera_offset);
3077 if (!runData.digging) {
3078 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3079 client->setCrack(-1, v3s16(0, 0, 0));
3080 runData.dig_time = 0.0;
3082 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3083 // Remove e.g. torches faster when clicking instead of holding dig button
3084 runData.nodig_delay_timer = 0;
3085 runData.dig_instantly = false;
3088 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3089 runData.btn_down_for_dig = false;
3091 runData.punching = false;
3093 soundmaker->m_player_leftpunch_sound.name = "";
3095 // Prepare for repeating, unless we're not supposed to
3096 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3097 runData.repeat_place_timer += dtime;
3099 runData.repeat_place_timer = 0;
3101 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3102 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3103 !client->getScript()->on_item_use(selected_item, pointed)))
3104 client->interact(INTERACT_USE, pointed);
3105 } else if (pointed.type == POINTEDTHING_NODE) {
3106 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3107 } else if (pointed.type == POINTEDTHING_OBJECT) {
3108 v3f player_position = player->getPosition();
3109 handlePointingAtObject(pointed, tool_item, player_position, show_debug);
3110 } else if (isKeyDown(KeyType::DIG)) {
3111 // When button is held down in air, show continuous animation
3112 runData.punching = true;
3113 // Run callback even though item is not usable
3114 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3115 client->getScript()->on_item_use(selected_item, pointed);
3116 } else if (wasKeyPressed(KeyType::PLACE)) {
3117 handlePointingAtNothing(selected_item);
3120 runData.pointed_old = pointed;
3122 if (runData.punching || wasKeyPressed(KeyType::DIG))
3123 camera->setDigging(0); // dig animation
3125 input->clearWasKeyPressed();
3126 input->clearWasKeyReleased();
3127 // Ensure DIG & PLACE are marked as handled
3128 wasKeyDown(KeyType::DIG);
3129 wasKeyDown(KeyType::PLACE);
3131 input->joystick.clearWasKeyPressed(KeyType::DIG);
3132 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3134 input->joystick.clearWasKeyReleased(KeyType::DIG);
3135 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3139 PointedThing Game::updatePointedThing(
3140 const core::line3d<f32> &shootline,
3141 bool liquids_pointable,
3142 bool look_for_object,
3143 const v3s16 &camera_offset)
3145 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3146 selectionboxes->clear();
3147 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3148 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3149 "show_entity_selectionbox");
3151 ClientEnvironment &env = client->getEnv();
3152 ClientMap &map = env.getClientMap();
3153 const NodeDefManager *nodedef = map.getNodeDefManager();
3155 runData.selected_object = NULL;
3156 hud->pointing_at_object = false;
3158 RaycastState s(shootline, look_for_object, liquids_pointable);
3159 PointedThing result;
3160 env.continueRaycast(&s, &result);
3161 if (result.type == POINTEDTHING_OBJECT) {
3162 hud->pointing_at_object = true;
3164 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3165 aabb3f selection_box;
3166 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3167 runData.selected_object->getSelectionBox(&selection_box)) {
3168 v3f pos = runData.selected_object->getPosition();
3169 selectionboxes->push_back(aabb3f(selection_box));
3170 hud->setSelectionPos(pos, camera_offset);
3172 } else if (result.type == POINTEDTHING_NODE) {
3173 // Update selection boxes
3174 MapNode n = map.getNode(result.node_undersurface);
3175 std::vector<aabb3f> boxes;
3176 n.getSelectionBoxes(nodedef, &boxes,
3177 n.getNeighbors(result.node_undersurface, &map));
3180 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3181 i != boxes.end(); ++i) {
3183 box.MinEdge -= v3f(d, d, d);
3184 box.MaxEdge += v3f(d, d, d);
3185 selectionboxes->push_back(box);
3187 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3189 hud->setSelectedFaceNormal(v3f(
3190 result.intersection_normal.X,
3191 result.intersection_normal.Y,
3192 result.intersection_normal.Z));
3195 // Update selection mesh light level and vertex colors
3196 if (!selectionboxes->empty()) {
3197 v3f pf = hud->getSelectionPos();
3198 v3s16 p = floatToInt(pf, BS);
3200 // Get selection mesh light level
3201 MapNode n = map.getNode(p);
3202 u16 node_light = getInteriorLight(n, -1, nodedef);
3203 u16 light_level = node_light;
3205 for (const v3s16 &dir : g_6dirs) {
3206 n = map.getNode(p + dir);
3207 node_light = getInteriorLight(n, -1, nodedef);
3208 if (node_light > light_level)
3209 light_level = node_light;
3212 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3214 final_color_blend(&c, light_level, daynight_ratio);
3216 // Modify final color a bit with time
3217 u32 timer = porting::getTimeMs() % 5000;
3218 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3219 float sin_r = 0.08f * std::sin(timerf);
3220 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3221 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3222 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3223 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3224 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3226 // Set mesh final color
3227 hud->setSelectionMeshColor(c);
3233 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3235 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3236 PointedThing fauxPointed;
3237 fauxPointed.type = POINTEDTHING_NOTHING;
3238 client->interact(INTERACT_ACTIVATE, fauxPointed);
3242 void Game::handlePointingAtNode(const PointedThing &pointed,
3243 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3245 v3s16 nodepos = pointed.node_undersurface;
3246 v3s16 neighbourpos = pointed.node_abovesurface;
3249 Check information text of node
3252 ClientMap &map = client->getEnv().getClientMap();
3254 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3255 && !runData.digging_blocked
3256 && client->checkPrivilege("interact")) {
3257 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3260 // This should be done after digging handling
3261 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3264 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3265 meta->getString("infotext"))));
3267 MapNode n = map.getNode(nodepos);
3269 if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
3270 m_game_ui->setInfoText(L"Unknown node: " +
3271 utf8_to_wide(nodedef_manager->get(n).name));
3275 if ((wasKeyPressed(KeyType::PLACE) ||
3276 runData.repeat_place_timer >= m_repeat_place_time) &&
3277 client->checkPrivilege("interact")) {
3278 runData.repeat_place_timer = 0;
3279 infostream << "Place button pressed while looking at ground" << std::endl;
3281 // Placing animation (always shown for feedback)
3282 camera->setDigging(1);
3284 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3286 // If the wielded item has node placement prediction,
3288 // And also set the sound and send the interact
3289 // But first check for meta formspec and rightclickable
3290 auto &def = selected_item.getDefinition(itemdef_manager);
3291 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3294 if (placed && client->modsLoaded())
3295 client->getScript()->on_placenode(pointed, def);
3299 bool Game::nodePlacement(const ItemDefinition &selected_def,
3300 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3301 const PointedThing &pointed, const NodeMetadata *meta)
3303 const auto &prediction = selected_def.node_placement_prediction;
3305 const NodeDefManager *nodedef = client->ndef();
3306 ClientMap &map = client->getEnv().getClientMap();
3308 bool is_valid_position;
3310 node = map.getNode(nodepos, &is_valid_position);
3311 if (!is_valid_position) {
3312 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3317 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3318 && !isKeyDown(KeyType::SNEAK)) {
3319 // on_rightclick callbacks are called anyway
3320 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3321 client->interact(INTERACT_PLACE, pointed);
3323 infostream << "Launching custom inventory view" << std::endl;
3325 InventoryLocation inventoryloc;
3326 inventoryloc.setNodeMeta(nodepos);
3328 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3329 &client->getEnv().getClientMap(), nodepos);
3330 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3332 auto *&formspec = m_game_ui->updateFormspec("");
3333 GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
3334 txt_dst, client->getFormspecPrepend(), sound);
3336 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3340 // on_rightclick callback
3341 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3342 !isKeyDown(KeyType::SNEAK))) {
3344 client->interact(INTERACT_PLACE, pointed);
3348 verbosestream << "Node placement prediction for "
3349 << selected_def.name << " is " << prediction << std::endl;
3350 v3s16 p = neighbourpos;
3352 // Place inside node itself if buildable_to
3353 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3354 if (is_valid_position) {
3355 if (nodedef->get(n_under).buildable_to) {
3358 node = map.getNode(p, &is_valid_position);
3359 if (is_valid_position && !nodedef->get(node).buildable_to) {
3360 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3362 client->interact(INTERACT_PLACE, pointed);
3368 // Find id of predicted node
3370 bool found = nodedef->getId(prediction, id);
3373 errorstream << "Node placement prediction failed for "
3374 << selected_def.name << " (places " << prediction
3375 << ") - Name not known" << std::endl;
3376 // Handle this as if prediction was empty
3378 client->interact(INTERACT_PLACE, pointed);
3382 const ContentFeatures &predicted_f = nodedef->get(id);
3384 // Predict param2 for facedir and wallmounted nodes
3385 // Compare core.item_place_node() for what the server does
3388 const u8 place_param2 = selected_def.place_param2;
3391 param2 = place_param2;
3392 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3393 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3394 v3s16 dir = nodepos - neighbourpos;
3396 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3397 param2 = dir.Y < 0 ? 1 : 0;
3398 } else if (abs(dir.X) > abs(dir.Z)) {
3399 param2 = dir.X < 0 ? 3 : 2;
3401 param2 = dir.Z < 0 ? 5 : 4;
3403 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3404 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3405 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3407 if (abs(dir.X) > abs(dir.Z)) {
3408 param2 = dir.X < 0 ? 3 : 1;
3410 param2 = dir.Z < 0 ? 2 : 0;
3414 // Check attachment if node is in group attached_node
3415 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3416 const static v3s16 wallmounted_dirs[8] = {
3426 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3427 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3428 pp = p + wallmounted_dirs[param2];
3430 pp = p + v3s16(0, -1, 0);
3432 if (!nodedef->get(map.getNode(pp)).walkable) {
3433 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3435 client->interact(INTERACT_PLACE, pointed);
3441 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3442 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3443 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3444 const auto &indexstr = selected_item.metadata.
3445 getString("palette_index", 0);
3446 if (!indexstr.empty()) {
3447 s32 index = mystoi(indexstr);
3448 if (predicted_f.param_type_2 == CPT2_COLOR) {
3450 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3451 // param2 = pure palette index + other
3452 param2 = (index & 0xf8) | (param2 & 0x07);
3453 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3454 // param2 = pure palette index + other
3455 param2 = (index & 0xe0) | (param2 & 0x1f);
3460 // Add node to client map
3461 MapNode n(id, 0, param2);
3464 LocalPlayer *player = client->getEnv().getLocalPlayer();
3466 // Dont place node when player would be inside new node
3467 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3468 if (!nodedef->get(n).walkable ||
3469 g_settings->getBool("enable_build_where_you_stand") ||
3470 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3471 (nodedef->get(n).walkable &&
3472 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3473 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3474 // This triggers the required mesh update too
3475 client->addNode(p, n);
3477 client->interact(INTERACT_PLACE, pointed);
3478 // A node is predicted, also play a sound
3479 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3482 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3485 } catch (const InvalidPositionException &e) {
3486 errorstream << "Node placement prediction failed for "
3487 << selected_def.name << " (places "
3488 << prediction << ") - Position not loaded" << std::endl;
3489 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3494 void Game::handlePointingAtObject(const PointedThing &pointed,
3495 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3497 std::wstring infotext = unescape_translate(
3498 utf8_to_wide(runData.selected_object->infoText()));
3501 if (!infotext.empty()) {
3504 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3507 m_game_ui->setInfoText(infotext);
3509 if (isKeyDown(KeyType::DIG)) {
3510 bool do_punch = false;
3511 bool do_punch_damage = false;
3513 if (runData.object_hit_delay_timer <= 0.0) {
3515 do_punch_damage = true;
3516 runData.object_hit_delay_timer = object_hit_delay;
3519 if (wasKeyPressed(KeyType::DIG))
3523 infostream << "Punched object" << std::endl;
3524 runData.punching = true;
3527 if (do_punch_damage) {
3528 // Report direct punch
3529 v3f objpos = runData.selected_object->getPosition();
3530 v3f dir = (objpos - player_position).normalize();
3532 bool disable_send = runData.selected_object->directReportPunch(
3533 dir, &tool_item, runData.time_from_last_punch);
3534 runData.time_from_last_punch = 0;
3537 client->interact(INTERACT_START_DIGGING, pointed);
3539 } else if (wasKeyDown(KeyType::PLACE)) {
3540 infostream << "Pressed place button while pointing at object" << std::endl;
3541 client->interact(INTERACT_PLACE, pointed); // place
3546 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3547 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3549 // See also: serverpackethandle.cpp, action == 2
3550 LocalPlayer *player = client->getEnv().getLocalPlayer();
3551 ClientMap &map = client->getEnv().getClientMap();
3552 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3554 // NOTE: Similar piece of code exists on the server side for
3556 // Get digging parameters
3557 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3558 &selected_item.getToolCapabilities(itemdef_manager));
3560 // If can't dig, try hand
3561 if (!params.diggable) {
3562 params = getDigParams(nodedef_manager->get(n).groups,
3563 &hand_item.getToolCapabilities(itemdef_manager));
3566 if (!params.diggable) {
3567 // I guess nobody will wait for this long
3568 runData.dig_time_complete = 10000000.0;
3570 runData.dig_time_complete = params.time;
3572 if (m_cache_enable_particles) {
3573 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3574 client->getParticleManager()->addNodeParticle(client,
3575 player, nodepos, n, features);
3579 if (!runData.digging) {
3580 infostream << "Started digging" << std::endl;
3581 runData.dig_instantly = runData.dig_time_complete == 0;
3582 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3584 client->interact(INTERACT_START_DIGGING, pointed);
3585 runData.digging = true;
3586 runData.btn_down_for_dig = true;
3589 if (!runData.dig_instantly) {
3590 runData.dig_index = (float)crack_animation_length
3592 / runData.dig_time_complete;
3594 // This is for e.g. torches
3595 runData.dig_index = crack_animation_length;
3598 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3600 if (sound_dig.exists() && params.diggable) {
3601 if (sound_dig.name == "__group") {
3602 if (!params.main_group.empty()) {
3603 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3604 soundmaker->m_player_leftpunch_sound.name =
3605 std::string("default_dig_") +
3609 soundmaker->m_player_leftpunch_sound = sound_dig;
3613 // Don't show cracks if not diggable
3614 if (runData.dig_time_complete >= 100000.0) {
3615 } else if (runData.dig_index < crack_animation_length) {
3616 //TimeTaker timer("client.setTempMod");
3617 //infostream<<"dig_index="<<dig_index<<std::endl;
3618 client->setCrack(runData.dig_index, nodepos);
3620 infostream << "Digging completed" << std::endl;
3621 client->setCrack(-1, v3s16(0, 0, 0));
3623 runData.dig_time = 0;
3624 runData.digging = false;
3625 // we successfully dug, now block it from repeating if we want to be safe
3626 if (g_settings->getBool("safe_dig_and_place"))
3627 runData.digging_blocked = true;
3629 runData.nodig_delay_timer =
3630 runData.dig_time_complete / (float)crack_animation_length;
3632 // We don't want a corresponding delay to very time consuming nodes
3633 // and nodes without digging time (e.g. torches) get a fixed delay.
3634 if (runData.nodig_delay_timer > 0.3)
3635 runData.nodig_delay_timer = 0.3;
3636 else if (runData.dig_instantly)
3637 runData.nodig_delay_timer = 0.15;
3639 bool is_valid_position;
3640 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3641 if (is_valid_position) {
3642 if (client->modsLoaded() &&
3643 client->getScript()->on_dignode(nodepos, wasnode)) {
3647 const ContentFeatures &f = client->ndef()->get(wasnode);
3648 if (f.node_dig_prediction == "air") {
3649 client->removeNode(nodepos);
3650 } else if (!f.node_dig_prediction.empty()) {
3652 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3654 client->addNode(nodepos, id, true);
3656 // implicit else: no prediction
3659 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3661 if (m_cache_enable_particles) {
3662 const ContentFeatures &features =
3663 client->getNodeDefManager()->get(wasnode);
3664 client->getParticleManager()->addDiggingParticles(client,
3665 player, nodepos, wasnode, features);
3669 // Send event to trigger sound
3670 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3673 if (runData.dig_time_complete < 100000.0) {
3674 runData.dig_time += dtime;
3676 runData.dig_time = 0;
3677 client->setCrack(-1, nodepos);
3680 camera->setDigging(0); // Dig animation
3683 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3684 const CameraOrientation &cam)
3686 TimeTaker tt_update("Game::updateFrame()");
3687 LocalPlayer *player = client->getEnv().getLocalPlayer();
3693 if (draw_control->range_all) {
3694 runData.fog_range = 100000 * BS;
3696 runData.fog_range = draw_control->wanted_range * BS;
3700 Calculate general brightness
3702 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3703 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3704 float direct_brightness;
3707 if (m_cache_enable_noclip && m_cache_enable_free_move) {
3708 direct_brightness = time_brightness;
3709 sunlight_seen = true;
3711 float old_brightness = sky->getBrightness();
3712 direct_brightness = client->getEnv().getClientMap()
3713 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3714 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3718 float time_of_day_smooth = runData.time_of_day_smooth;
3719 float time_of_day = client->getEnv().getTimeOfDayF();
3721 static const float maxsm = 0.05f;
3722 static const float todsm = 0.05f;
3724 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3725 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3726 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3727 time_of_day_smooth = time_of_day;
3729 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3730 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3731 + (time_of_day + 1.0) * todsm;
3733 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3734 + time_of_day * todsm;
3736 runData.time_of_day_smooth = time_of_day_smooth;
3738 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3739 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3740 player->getPitch());
3746 if (sky->getCloudsVisible()) {
3747 clouds->setVisible(true);
3748 clouds->step(dtime);
3749 // camera->getPosition is not enough for 3rd person views
3750 v3f camera_node_position = camera->getCameraNode()->getPosition();
3751 v3s16 camera_offset = camera->getOffset();
3752 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3753 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3754 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3755 clouds->update(camera_node_position,
3756 sky->getCloudColor());
3757 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3758 // if inside clouds, and fog enabled, use that as sky
3760 video::SColor clouds_dark = clouds->getColor()
3761 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3762 sky->overrideColors(clouds_dark, clouds->getColor());
3763 sky->setInClouds(true);
3764 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3765 // do not draw clouds after all
3766 clouds->setVisible(false);
3769 clouds->setVisible(false);
3776 client->getParticleManager()->step(dtime);
3782 if (m_cache_enable_fog) {
3785 video::EFT_FOG_LINEAR,
3786 runData.fog_range * m_cache_fog_start,
3787 runData.fog_range * 1.0,
3795 video::EFT_FOG_LINEAR,
3805 Get chat messages from client
3808 v2u32 screensize = driver->getScreenSize();
3810 updateChat(dtime, screensize);
3816 if (player->getWieldIndex() != runData.new_playeritem)
3817 client->setPlayerItem(runData.new_playeritem);
3819 if (client->updateWieldedItem()) {
3820 // Update wielded tool
3821 ItemStack selected_item, hand_item;
3822 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3823 camera->wield(tool_item);
3827 Update block draw list every 200ms or when camera direction has
3830 runData.update_draw_list_timer += dtime;
3832 v3f camera_direction = camera->getDirection();
3833 if (runData.update_draw_list_timer >= 0.2
3834 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3835 || m_camera_offset_changed) {
3836 runData.update_draw_list_timer = 0;
3837 client->getEnv().getClientMap().updateDrawList();
3838 runData.update_draw_list_last_cam_dir = camera_direction;
3841 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3844 make sure menu is on top
3845 1. Delete formspec menu reference if menu was removed
3846 2. Else, make sure formspec menu is on top
3848 auto formspec = m_game_ui->getFormspecGUI();
3849 do { // breakable. only runs for one iteration
3853 if (formspec->getReferenceCount() == 1) {
3854 m_game_ui->deleteFormspec();
3858 auto &loc = formspec->getFormspecLocation();
3859 if (loc.type == InventoryLocation::NODEMETA) {
3860 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3861 if (!meta || meta->getString("formspec").empty()) {
3862 formspec->quitMenu();
3868 guiroot->bringToFront(formspec);
3874 const video::SColor &skycolor = sky->getSkyColor();
3876 TimeTaker tt_draw("Draw scene");
3877 driver->beginScene(true, true, skycolor);
3879 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3880 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3881 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3882 bool draw_crosshair = (
3883 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3884 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3885 #ifdef HAVE_TOUCHSCREENGUI
3887 draw_crosshair = !g_settings->getBool("touchtarget");
3888 } catch (SettingNotFoundException) {
3891 RenderingEngine::draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3892 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3897 if (m_game_ui->m_flags.show_profiler_graph)
3898 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3903 if (runData.damage_flash > 0.0f) {
3904 video::SColor color(runData.damage_flash, 180, 0, 0);
3905 driver->draw2DRectangle(color,
3906 core::rect<s32>(0, 0, screensize.X, screensize.Y),
3909 runData.damage_flash -= 384.0f * dtime;
3915 if (player->hurt_tilt_timer > 0.0f) {
3916 player->hurt_tilt_timer -= dtime * 6.0f;
3918 if (player->hurt_tilt_timer < 0.0f)
3919 player->hurt_tilt_strength = 0.0f;
3923 Update minimap pos and rotation
3925 if (mapper && m_game_ui->m_flags.show_hud) {
3926 mapper->setPos(floatToInt(player->getPosition(), BS));
3927 mapper->setAngle(player->getYaw());
3933 if (++m_reset_HW_buffer_counter > 500) {
3935 Periodically remove all mesh HW buffers.
3937 Work around for a quirk in Irrlicht where a HW buffer is only
3938 released after 20000 iterations (triggered from endScene()).
3940 Without this, all loaded but unused meshes will retain their HW
3941 buffers for at least 5 minutes, at which point looking up the HW buffers
3942 becomes a bottleneck and the framerate drops (as much as 30%).
3944 Tests showed that numbers between 50 and 1000 are good, so picked 500.
3945 There are no other public Irrlicht APIs that allow interacting with the
3946 HW buffers without tracking the status of every individual mesh.
3948 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
3950 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
3951 driver->removeAllHardwareBuffers();
3952 m_reset_HW_buffer_counter = 0;
3956 stats->drawtime = tt_draw.stop(true);
3957 g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
3958 g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
3961 /* Log times and stuff for visualization */
3962 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
3964 Profiler::GraphValues values;
3965 g_profiler->graphGet(values);
3971 /****************************************************************************
3973 ****************************************************************************/
3975 /* On some computers framerate doesn't seem to be automatically limited
3977 inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
3979 // not using getRealTime is necessary for wine
3980 device->getTimer()->tick(); // Maker sure device time is up-to-date
3981 u32 time = device->getTimer()->getTime();
3982 u32 last_time = fps_timings->last_time;
3984 if (time > last_time) // Make sure time hasn't overflowed
3985 fps_timings->busy_time = time - last_time;
3987 fps_timings->busy_time = 0;
3989 u32 frametime_min = 1000 / (
3990 device->isWindowFocused() && !g_menumgr.pausesGame()
3991 ? g_settings->getFloat("fps_max")
3992 : g_settings->getFloat("fps_max_unfocused"));
3994 if (fps_timings->busy_time < frametime_min) {
3995 fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
3996 device->sleep(fps_timings->sleep_time);
3998 fps_timings->sleep_time = 0;
4001 /* Get the new value of the device timer. Note that device->sleep() may
4002 * not sleep for the entire requested time as sleep may be interrupted and
4003 * therefore it is arguably more accurate to get the new time from the
4004 * device rather than calculating it by adding sleep_time to time.
4007 device->getTimer()->tick(); // Update device timer
4008 time = device->getTimer()->getTime();
4010 if (time > last_time) // Make sure last_time hasn't overflowed
4011 *dtime = (time - last_time) / 1000.0;
4015 fps_timings->last_time = time;
4018 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4020 const wchar_t *wmsg = wgettext(msg);
4021 RenderingEngine::draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4026 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4028 ((Game *)data)->readSettings();
4031 void Game::readSettings()
4033 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4034 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4035 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4036 m_cache_enable_particles = g_settings->getBool("enable_particles");
4037 m_cache_enable_fog = g_settings->getBool("enable_fog");
4038 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
4039 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4040 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
4042 m_cache_enable_noclip = g_settings->getBool("noclip");
4043 m_cache_enable_free_move = g_settings->getBool("free_move");
4045 m_cache_fog_start = g_settings->getFloat("fog_start");
4047 m_cache_cam_smoothing = 0;
4048 if (g_settings->getBool("cinematic"))
4049 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4051 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4053 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4054 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4055 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4057 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4060 /****************************************************************************/
4061 /****************************************************************************
4063 ****************************************************************************/
4064 /****************************************************************************/
4066 void Game::extendedResourceCleanup()
4068 // Extended resource accounting
4069 infostream << "Irrlicht resources after cleanup:" << std::endl;
4070 infostream << "\tRemaining meshes : "
4071 << RenderingEngine::get_mesh_cache()->getMeshCount() << std::endl;
4072 infostream << "\tRemaining textures : "
4073 << driver->getTextureCount() << std::endl;
4075 for (unsigned int i = 0; i < driver->getTextureCount(); i++) {
4076 irr::video::ITexture *texture = driver->getTextureByIndex(i);
4077 infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str()
4081 clearTextureNameCache();
4082 infostream << "\tRemaining materials: "
4083 << driver-> getMaterialRendererCount()
4084 << " (note: irrlicht doesn't support removing renderers)" << std::endl;
4087 void Game::showDeathFormspec()
4089 static std::string formspec_str =
4090 std::string("formspec_version[1]") +
4092 "bgcolor[#320000b4;true]"
4093 "label[4.85,1.35;" + gettext("You died") + "]"
4094 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4098 /* Note: FormspecFormSource and LocalFormspecHandler *
4099 * are deleted by guiFormSpecMenu */
4100 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4101 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4103 auto *&formspec = m_game_ui->getFormspecGUI();
4104 GUIFormSpecMenu::create(formspec, client, &input->joystick,
4105 fs_src, txt_dst, client->getFormspecPrepend(), sound);
4106 formspec->setFocus("btn_respawn");
4109 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4110 void Game::showPauseMenu()
4113 static const std::string control_text = strgettext("Default Controls:\n"
4114 "No menu visible:\n"
4115 "- single tap: button activate\n"
4116 "- double tap: place/use\n"
4117 "- slide finger: look around\n"
4118 "Menu/Inventory visible:\n"
4119 "- double tap (outside):\n"
4121 "- touch stack, touch slot:\n"
4123 "- touch&drag, tap 2nd finger\n"
4124 " --> place single item to slot\n"
4127 static const std::string control_text_template = strgettext("Controls:\n"
4128 "- %s: move forwards\n"
4129 "- %s: move backwards\n"
4131 "- %s: move right\n"
4132 "- %s: jump/climb up\n"
4135 "- %s: sneak/climb down\n"
4138 "- Mouse: turn/look\n"
4139 "- Mouse wheel: select item\n"
4143 char control_text_buf[600];
4145 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4146 GET_KEY_NAME(keymap_forward),
4147 GET_KEY_NAME(keymap_backward),
4148 GET_KEY_NAME(keymap_left),
4149 GET_KEY_NAME(keymap_right),
4150 GET_KEY_NAME(keymap_jump),
4151 GET_KEY_NAME(keymap_dig),
4152 GET_KEY_NAME(keymap_place),
4153 GET_KEY_NAME(keymap_sneak),
4154 GET_KEY_NAME(keymap_drop),
4155 GET_KEY_NAME(keymap_inventory),
4156 GET_KEY_NAME(keymap_chat)
4159 std::string control_text = std::string(control_text_buf);
4160 str_formspec_escape(control_text);
4163 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4164 std::ostringstream os;
4166 os << "formspec_version[1]" << SIZE_TAG
4167 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4168 << strgettext("Continue") << "]";
4170 if (!simple_singleplayer_mode) {
4171 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4172 << strgettext("Change Password") << "]";
4174 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4179 if (g_settings->getBool("enable_sound")) {
4180 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4181 << strgettext("Sound Volume") << "]";
4184 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4185 << strgettext("Change Keys") << "]";
4187 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4188 << strgettext("Exit to Menu") << "]";
4189 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4190 << strgettext("Exit to OS") << "]"
4191 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4192 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4194 << strgettext("Game info:") << "\n";
4195 const std::string &address = client->getAddressName();
4196 static const std::string mode = strgettext("- Mode: ");
4197 if (!simple_singleplayer_mode) {
4198 Address serverAddress = client->getServerAddress();
4199 if (!address.empty()) {
4200 os << mode << strgettext("Remote server") << "\n"
4201 << strgettext("- Address: ") << address;
4203 os << mode << strgettext("Hosting server");
4205 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4207 os << mode << strgettext("Singleplayer") << "\n";
4209 if (simple_singleplayer_mode || address.empty()) {
4210 static const std::string on = strgettext("On");
4211 static const std::string off = strgettext("Off");
4212 const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
4213 const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
4214 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4215 os << strgettext("- Damage: ") << damage << "\n"
4216 << strgettext("- Creative Mode: ") << creative << "\n";
4217 if (!simple_singleplayer_mode) {
4218 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4219 //~ PvP = Player versus Player
4220 os << strgettext("- PvP: ") << pvp << "\n"
4221 << strgettext("- Public: ") << announced << "\n";
4222 std::string server_name = g_settings->get("server_name");
4223 str_formspec_escape(server_name);
4224 if (announced == on && !server_name.empty())
4225 os << strgettext("- Server Name: ") << server_name;
4232 /* Note: FormspecFormSource and LocalFormspecHandler *
4233 * are deleted by guiFormSpecMenu */
4234 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4235 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4237 auto *&formspec = m_game_ui->getFormspecGUI();
4238 GUIFormSpecMenu::create(formspec, client, &input->joystick,
4239 fs_src, txt_dst, client->getFormspecPrepend(), sound);
4240 formspec->setFocus("btn_continue");
4241 formspec->doPause = true;
4243 if (simple_singleplayer_mode)
4247 /****************************************************************************/
4248 /****************************************************************************
4249 extern function for launching the game
4250 ****************************************************************************/
4251 /****************************************************************************/
4253 void the_game(bool *kill,
4254 InputHandler *input,
4255 const GameStartData &start_data,
4256 std::string &error_message,
4257 ChatBackend &chat_backend,
4258 bool *reconnect_requested) // Used for local game
4262 /* Make a copy of the server address because if a local singleplayer server
4263 * is created then this is updated and we don't want to change the value
4264 * passed to us by the calling function
4269 if (game.startup(kill, input, start_data, error_message,
4270 reconnect_requested, &chat_backend)) {
4274 } catch (SerializationError &e) {
4275 error_message = std::string("A serialization error occurred:\n")
4276 + e.what() + "\n\nThe server is probably "
4277 " running a different version of " PROJECT_NAME_C ".";
4278 errorstream << error_message << std::endl;
4279 } catch (ServerError &e) {
4280 error_message = e.what();
4281 errorstream << "ServerError: " << error_message << std::endl;
4282 } catch (ModError &e) {
4283 error_message = std::string("ModError: ") + e.what() +
4284 strgettext("\nCheck debug.txt for details.");
4285 errorstream << error_message << std::endl;