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 // before 1.8 there isn't a "integer interface", only float
404 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
405 typedef f32 SamplerLayer_t;
407 typedef s32 SamplerLayer_t;
411 class GameGlobalShaderConstantSetter : public IShaderConstantSetter
414 bool *m_force_fog_off;
417 CachedPixelShaderSetting<float, 4> m_sky_bg_color;
418 CachedPixelShaderSetting<float> m_fog_distance;
419 CachedVertexShaderSetting<float> m_animation_timer_vertex;
420 CachedPixelShaderSetting<float> m_animation_timer_pixel;
421 CachedPixelShaderSetting<float, 3> m_day_light;
422 CachedPixelShaderSetting<float, 4> m_star_color;
423 CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
424 CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
425 CachedPixelShaderSetting<float, 3> m_minimap_yaw;
426 CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
427 CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
428 CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
429 CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture;
433 void onSettingsChange(const std::string &name)
435 if (name == "enable_fog")
436 m_fog_enabled = g_settings->getBool("enable_fog");
439 static void settingsCallback(const std::string &name, void *userdata)
441 reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
444 void setSky(Sky *sky) { m_sky = sky; }
446 GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
447 f32 *fog_range, Client *client) :
449 m_force_fog_off(force_fog_off),
450 m_fog_range(fog_range),
451 m_sky_bg_color("skyBgColor"),
452 m_fog_distance("fogDistance"),
453 m_animation_timer_vertex("animationTimer"),
454 m_animation_timer_pixel("animationTimer"),
455 m_day_light("dayLight"),
456 m_star_color("starColor"),
457 m_eye_position_pixel("eyePosition"),
458 m_eye_position_vertex("eyePosition"),
459 m_minimap_yaw("yawVec"),
460 m_camera_offset_pixel("cameraOffset"),
461 m_camera_offset_vertex("cameraOffset"),
462 m_base_texture("baseTexture"),
463 m_normal_texture("normalTexture"),
466 g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
467 m_fog_enabled = g_settings->getBool("enable_fog");
470 ~GameGlobalShaderConstantSetter()
472 g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
475 void onSetConstants(video::IMaterialRendererServices *services) override
478 video::SColor bgcolor = m_sky->getBgColor();
479 video::SColorf bgcolorf(bgcolor);
480 float bgcolorfa[4] = {
486 m_sky_bg_color.set(bgcolorfa, services);
489 float fog_distance = 10000 * BS;
491 if (m_fog_enabled && !*m_force_fog_off)
492 fog_distance = *m_fog_range;
494 m_fog_distance.set(&fog_distance, services);
496 u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
497 video::SColorf sunlight;
498 get_sunlight_color(&sunlight, daynight_ratio);
503 m_day_light.set(dnc, services);
505 video::SColorf star_color = m_sky->getCurrentStarColor();
506 float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a};
507 m_star_color.set(clr, services);
509 u32 animation_timer = porting::getTimeMs() % 1000000;
510 float animation_timer_f = (float)animation_timer / 100000.f;
511 m_animation_timer_vertex.set(&animation_timer_f, services);
512 m_animation_timer_pixel.set(&animation_timer_f, services);
514 float eye_position_array[3];
515 v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
516 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
517 eye_position_array[0] = epos.X;
518 eye_position_array[1] = epos.Y;
519 eye_position_array[2] = epos.Z;
521 epos.getAs3Values(eye_position_array);
523 m_eye_position_pixel.set(eye_position_array, services);
524 m_eye_position_vertex.set(eye_position_array, services);
526 if (m_client->getMinimap()) {
527 float minimap_yaw_array[3];
528 v3f minimap_yaw = m_client->getMinimap()->getYawVec();
529 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
530 minimap_yaw_array[0] = minimap_yaw.X;
531 minimap_yaw_array[1] = minimap_yaw.Y;
532 minimap_yaw_array[2] = minimap_yaw.Z;
534 minimap_yaw.getAs3Values(minimap_yaw_array);
536 m_minimap_yaw.set(minimap_yaw_array, services);
539 float camera_offset_array[3];
540 v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
541 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
542 camera_offset_array[0] = offset.X;
543 camera_offset_array[1] = offset.Y;
544 camera_offset_array[2] = offset.Z;
546 offset.getAs3Values(camera_offset_array);
548 m_camera_offset_pixel.set(camera_offset_array, services);
549 m_camera_offset_vertex.set(camera_offset_array, services);
551 SamplerLayer_t base_tex = 0, normal_tex = 1;
552 m_base_texture.set(&base_tex, services);
553 m_normal_texture.set(&normal_tex, services);
558 class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
561 bool *m_force_fog_off;
564 std::vector<GameGlobalShaderConstantSetter *> created_nosky;
566 GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
567 f32 *fog_range, Client *client) :
569 m_force_fog_off(force_fog_off),
570 m_fog_range(fog_range),
574 void setSky(Sky *sky) {
576 for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
577 ggscs->setSky(m_sky);
579 created_nosky.clear();
582 virtual IShaderConstantSetter* create()
584 auto *scs = new GameGlobalShaderConstantSetter(
585 m_sky, m_force_fog_off, m_fog_range, m_client);
587 created_nosky.push_back(scs);
593 #define SIZE_TAG "size[11,5.5]"
595 #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
598 /****************************************************************************
599 ****************************************************************************/
601 const float object_hit_delay = 0.2;
604 u32 last_time, busy_time, sleep_time;
608 /* The reason the following structs are not anonymous structs within the
609 * class is that they are not used by the majority of member functions and
610 * many functions that do require objects of thse types do not modify them
611 * (so they can be passed as a const qualified parameter)
617 PointedThing pointed_old;
620 bool btn_down_for_dig;
622 bool digging_blocked;
623 bool reset_jump_timer;
624 float nodig_delay_timer;
626 float dig_time_complete;
627 float repeat_place_timer;
628 float object_hit_delay_timer;
629 float time_from_last_punch;
630 ClientActiveObject *selected_object;
634 float update_draw_list_timer;
638 v3f update_draw_list_last_cam_dir;
640 float time_of_day_smooth;
645 struct ClientEventHandler
647 void (Game::*handler)(ClientEvent *, CameraOrientation *);
650 /****************************************************************************
652 ****************************************************************************/
654 using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
656 /* This is not intended to be a public class. If a public class becomes
657 * desirable then it may be better to create another 'wrapper' class that
658 * hides most of the stuff in this class (nothing in this class is required
659 * by any other file) but exposes the public methods/data only.
666 bool startup(bool *kill,
668 const GameStartData &game_params,
669 std::string &error_message,
671 ChatBackend *chat_backend);
678 void extendedResourceCleanup();
680 // Basic initialisation
681 bool init(const std::string &map_dir, const std::string &address,
682 u16 port, const SubgameSpec &gamespec);
684 bool createSingleplayerServer(const std::string &map_dir,
685 const SubgameSpec &gamespec, u16 port);
688 bool createClient(const GameStartData &start_data);
692 bool connectToServer(const GameStartData &start_data,
693 bool *connect_ok, bool *aborted);
694 bool getServerContent(bool *aborted);
698 void updateInteractTimers(f32 dtime);
699 bool checkConnection();
700 bool handleCallbacks();
701 void processQueues();
702 void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
703 void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
704 void updateProfilerGraphs(ProfilerGraph *graph);
707 void processUserInput(f32 dtime);
708 void processKeyInput();
709 void processItemSelection(u16 *new_playeritem);
711 void dropSelectedItem(bool single_item = false);
712 void openInventory();
713 void openConsole(float scale, const wchar_t *line=NULL);
714 void toggleFreeMove();
715 void toggleFreeMoveAlt();
716 void togglePitchMove();
719 void toggleCinematic();
720 void toggleAutoforward();
722 void toggleMinimap(bool shift_pressed);
725 void toggleUpdateCamera();
727 void increaseViewRange();
728 void decreaseViewRange();
729 void toggleFullViewRange();
730 void checkZoomEnabled();
732 void updateCameraDirection(CameraOrientation *cam, float dtime);
733 void updateCameraOrientation(CameraOrientation *cam, float dtime);
734 void updatePlayerControl(const CameraOrientation &cam);
735 void step(f32 *dtime);
736 void processClientEvents(CameraOrientation *cam);
737 void updateCamera(u32 busy_time, f32 dtime);
738 void updateSound(f32 dtime);
739 void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug);
741 * Returns the object or node the player is pointing at.
742 * Also updates the selected thing in the Hud.
744 * @param[in] shootline the shootline, starting from
745 * the camera position. This also gives the maximal distance
747 * @param[in] liquids_pointable if false, liquids are ignored
748 * @param[in] look_for_object if false, objects are ignored
749 * @param[in] camera_offset offset of the camera
750 * @param[out] selected_object the selected object or
753 PointedThing updatePointedThing(
754 const core::line3d<f32> &shootline, bool liquids_pointable,
755 bool look_for_object, const v3s16 &camera_offset);
756 void handlePointingAtNothing(const ItemStack &playerItem);
757 void handlePointingAtNode(const PointedThing &pointed,
758 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
759 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
760 const v3f &player_position, bool show_debug);
761 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
762 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
763 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
764 const CameraOrientation &cam);
767 void limitFps(FpsControl *fps_timings, f32 *dtime);
769 void showOverlayMessage(const char *msg, float dtime, int percent,
770 bool draw_clouds = true);
772 static void settingChangedCallback(const std::string &setting_name, void *data);
775 inline bool isKeyDown(GameKeyType k)
777 return input->isKeyDown(k);
779 inline bool wasKeyDown(GameKeyType k)
781 return input->wasKeyDown(k);
783 inline bool wasKeyPressed(GameKeyType k)
785 return input->wasKeyPressed(k);
787 inline bool wasKeyReleased(GameKeyType k)
789 return input->wasKeyReleased(k);
793 void handleAndroidChatInput();
798 bool force_fog_off = false;
799 bool disable_camera_update = false;
802 void showDeathFormspec();
803 void showPauseMenu();
805 void pauseAnimation();
806 void resumeAnimation();
808 // ClientEvent handlers
809 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
810 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
811 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
812 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
813 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
814 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
815 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
816 CameraOrientation *cam);
817 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
818 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
819 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
820 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
821 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
822 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
823 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
824 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
825 CameraOrientation *cam);
826 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
828 void updateChat(f32 dtime, const v2u32 &screensize);
830 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
831 const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
832 const NodeMetadata *meta);
833 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
835 f32 getSensitivityScaleFactor() const;
837 InputHandler *input = nullptr;
839 Client *client = nullptr;
840 Server *server = nullptr;
842 IWritableTextureSource *texture_src = nullptr;
843 IWritableShaderSource *shader_src = nullptr;
845 // When created, these will be filled with data received from the server
846 IWritableItemDefManager *itemdef_manager = nullptr;
847 NodeDefManager *nodedef_manager = nullptr;
849 GameOnDemandSoundFetcher soundfetcher; // useful when testing
850 ISoundManager *sound = nullptr;
851 bool sound_is_dummy = false;
852 SoundMaker *soundmaker = nullptr;
854 ChatBackend *chat_backend = nullptr;
855 LogOutputBuffer m_chat_log_buf;
857 EventManager *eventmgr = nullptr;
858 QuicktuneShortcutter *quicktune = nullptr;
859 bool registration_confirmation_shown = false;
861 std::unique_ptr<GameUI> m_game_ui;
862 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
863 MapDrawControl *draw_control = nullptr;
864 Camera *camera = nullptr;
865 Clouds *clouds = nullptr; // Free using ->Drop()
866 Sky *sky = nullptr; // Free using ->Drop()
868 Minimap *mapper = nullptr;
870 // Map server hud ids to client hud ids
871 std::unordered_map<u32, u32> m_hud_server_to_client;
877 This class does take ownership/responsibily for cleaning up etc of any of
878 these items (e.g. device)
880 IrrlichtDevice *device;
881 video::IVideoDriver *driver;
882 scene::ISceneManager *smgr;
884 std::string *error_message;
885 bool *reconnect_requested;
886 scene::ISceneNode *skybox;
887 PausedNodesList paused_animated_nodes;
889 bool simple_singleplayer_mode;
892 /* Pre-calculated values
894 int crack_animation_length;
896 IntervalLimiter profiler_interval;
899 * TODO: Local caching of settings is not optimal and should at some stage
900 * be updated to use a global settings object for getting thse values
901 * (as opposed to the this local caching). This can be addressed in
904 bool m_cache_doubletap_jump;
905 bool m_cache_enable_clouds;
906 bool m_cache_enable_joysticks;
907 bool m_cache_enable_particles;
908 bool m_cache_enable_fog;
909 bool m_cache_enable_noclip;
910 bool m_cache_enable_free_move;
911 f32 m_cache_mouse_sensitivity;
912 f32 m_cache_joystick_frustum_sensitivity;
913 f32 m_repeat_place_time;
914 f32 m_cache_cam_smoothing;
915 f32 m_cache_fog_start;
917 bool m_invert_mouse = false;
918 bool m_first_loop_after_window_activation = false;
919 bool m_camera_offset_changed = false;
921 bool m_does_lost_focus_pause_game = false;
923 int m_reset_HW_buffer_counter = 0;
925 bool m_cache_hold_aux1;
926 bool m_android_chat_open;
931 m_chat_log_buf(g_logger),
932 m_game_ui(new GameUI())
934 g_settings->registerChangedCallback("doubletap_jump",
935 &settingChangedCallback, this);
936 g_settings->registerChangedCallback("enable_clouds",
937 &settingChangedCallback, this);
938 g_settings->registerChangedCallback("doubletap_joysticks",
939 &settingChangedCallback, this);
940 g_settings->registerChangedCallback("enable_particles",
941 &settingChangedCallback, this);
942 g_settings->registerChangedCallback("enable_fog",
943 &settingChangedCallback, this);
944 g_settings->registerChangedCallback("mouse_sensitivity",
945 &settingChangedCallback, this);
946 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
947 &settingChangedCallback, this);
948 g_settings->registerChangedCallback("repeat_place_time",
949 &settingChangedCallback, this);
950 g_settings->registerChangedCallback("noclip",
951 &settingChangedCallback, this);
952 g_settings->registerChangedCallback("free_move",
953 &settingChangedCallback, this);
954 g_settings->registerChangedCallback("cinematic",
955 &settingChangedCallback, this);
956 g_settings->registerChangedCallback("cinematic_camera_smoothing",
957 &settingChangedCallback, this);
958 g_settings->registerChangedCallback("camera_smoothing",
959 &settingChangedCallback, this);
964 m_cache_hold_aux1 = false; // This is initialised properly later
971 /****************************************************************************
973 ****************************************************************************/
982 delete server; // deleted first to stop all server threads
990 delete nodedef_manager;
991 delete itemdef_manager;
994 extendedResourceCleanup();
996 g_settings->deregisterChangedCallback("doubletap_jump",
997 &settingChangedCallback, this);
998 g_settings->deregisterChangedCallback("enable_clouds",
999 &settingChangedCallback, this);
1000 g_settings->deregisterChangedCallback("enable_particles",
1001 &settingChangedCallback, this);
1002 g_settings->deregisterChangedCallback("enable_fog",
1003 &settingChangedCallback, this);
1004 g_settings->deregisterChangedCallback("mouse_sensitivity",
1005 &settingChangedCallback, this);
1006 g_settings->deregisterChangedCallback("repeat_place_time",
1007 &settingChangedCallback, this);
1008 g_settings->deregisterChangedCallback("noclip",
1009 &settingChangedCallback, this);
1010 g_settings->deregisterChangedCallback("free_move",
1011 &settingChangedCallback, this);
1012 g_settings->deregisterChangedCallback("cinematic",
1013 &settingChangedCallback, this);
1014 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
1015 &settingChangedCallback, this);
1016 g_settings->deregisterChangedCallback("camera_smoothing",
1017 &settingChangedCallback, this);
1020 bool Game::startup(bool *kill,
1021 InputHandler *input,
1022 const GameStartData &start_data,
1023 std::string &error_message,
1025 ChatBackend *chat_backend)
1029 this->device = RenderingEngine::get_raw_device();
1031 this->error_message = &error_message;
1032 this->reconnect_requested = reconnect;
1033 this->input = input;
1034 this->chat_backend = chat_backend;
1035 this->simple_singleplayer_mode = start_data.isSinglePlayer();
1037 input->keycache.populate();
1039 driver = device->getVideoDriver();
1040 smgr = RenderingEngine::get_scene_manager();
1042 RenderingEngine::get_scene_manager()->getParameters()->
1043 setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1046 runData = GameRunData();
1047 runData.time_from_last_punch = 10.0;
1049 m_game_ui->initFlags();
1051 m_invert_mouse = g_settings->getBool("invert_mouse");
1052 m_first_loop_after_window_activation = true;
1054 g_client_translations->clear();
1056 // address can change if simple_singleplayer_mode
1057 if (!init(start_data.world_spec.path, start_data.address,
1058 start_data.socket_port, start_data.game_spec))
1061 if (!createClient(start_data))
1064 RenderingEngine::initialize(client, hud);
1072 ProfilerGraph graph;
1073 RunStats stats = { 0 };
1074 CameraOrientation cam_view_target = { 0 };
1075 CameraOrientation cam_view = { 0 };
1076 FpsControl draw_times = { 0 };
1077 f32 dtime; // in seconds
1079 /* Clear the profiler */
1080 Profiler::GraphValues dummyvalues;
1081 g_profiler->graphGet(dummyvalues);
1083 draw_times.last_time = RenderingEngine::get_timer_time();
1085 set_light_table(g_settings->getFloat("display_gamma"));
1088 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1089 && client->checkPrivilege("fast");
1092 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1093 g_settings->getU16("screen_h"));
1095 while (RenderingEngine::run()
1096 && !(*kill || g_gamecallback->shutdown_requested
1097 || (server && server->isShutdownRequested()))) {
1099 const irr::core::dimension2d<u32> ¤t_screen_size =
1100 RenderingEngine::get_video_driver()->getScreenSize();
1101 // Verify if window size has changed and save it if it's the case
1102 // Ensure evaluating settings->getBool after verifying screensize
1103 // First condition is cheaper
1104 if (previous_screen_size != current_screen_size &&
1105 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1106 g_settings->getBool("autosave_screensize")) {
1107 g_settings->setU16("screen_w", current_screen_size.Width);
1108 g_settings->setU16("screen_h", current_screen_size.Height);
1109 previous_screen_size = current_screen_size;
1112 // Calculate dtime =
1113 // RenderingEngine::run() from this iteration
1114 // + Sleep time until the wanted FPS are reached
1115 limitFps(&draw_times, &dtime);
1117 // Prepare render data for next iteration
1119 updateStats(&stats, draw_times, dtime);
1120 updateInteractTimers(dtime);
1122 if (!checkConnection())
1124 if (!handleCallbacks())
1129 m_game_ui->clearInfoText();
1130 hud->resizeHotbar();
1132 updateProfilers(stats, draw_times, dtime);
1133 processUserInput(dtime);
1134 // Update camera before player movement to avoid camera lag of one frame
1135 updateCameraDirection(&cam_view_target, dtime);
1136 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1137 cam_view.camera_yaw) * m_cache_cam_smoothing;
1138 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1139 cam_view.camera_pitch) * m_cache_cam_smoothing;
1140 updatePlayerControl(cam_view);
1142 processClientEvents(&cam_view_target);
1143 updateCamera(draw_times.busy_time, dtime);
1145 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
1146 m_game_ui->m_flags.show_debug);
1147 updateFrame(&graph, &stats, dtime, cam_view);
1148 updateProfilerGraphs(&graph);
1150 // Update if minimap has been disabled by the server
1151 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1153 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1160 void Game::shutdown()
1162 RenderingEngine::finalize();
1163 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1164 if (g_settings->get("3d_mode") == "pageflip") {
1165 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1168 auto formspec = m_game_ui->getFormspecGUI();
1170 formspec->quitMenu();
1172 #ifdef HAVE_TOUCHSCREENGUI
1173 g_touchscreengui->hide();
1176 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1181 if (gui_chat_console)
1182 gui_chat_console->drop();
1188 while (g_menumgr.menuCount() > 0) {
1189 g_menumgr.m_stack.front()->setVisible(false);
1190 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1193 m_game_ui->deleteFormspec();
1195 chat_backend->addMessage(L"", L"# Disconnected.");
1196 chat_backend->addMessage(L"", L"");
1197 m_chat_log_buf.clear();
1201 while (!client->isShutdown()) {
1202 assert(texture_src != NULL);
1203 assert(shader_src != NULL);
1204 texture_src->processQueue();
1205 shader_src->processQueue();
1212 /****************************************************************************/
1213 /****************************************************************************
1215 ****************************************************************************/
1216 /****************************************************************************/
1219 const std::string &map_dir,
1220 const std::string &address,
1222 const SubgameSpec &gamespec)
1224 texture_src = createTextureSource();
1226 showOverlayMessage(N_("Loading..."), 0, 0);
1228 shader_src = createShaderSource();
1230 itemdef_manager = createItemDefManager();
1231 nodedef_manager = createNodeDefManager();
1233 eventmgr = new EventManager();
1234 quicktune = new QuicktuneShortcutter();
1236 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1237 && eventmgr && quicktune))
1243 // Create a server if not connecting to an existing one
1244 if (address.empty()) {
1245 if (!createSingleplayerServer(map_dir, gamespec, port))
1252 bool Game::initSound()
1255 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1256 infostream << "Attempting to use OpenAL audio" << std::endl;
1257 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1259 infostream << "Failed to initialize OpenAL audio" << std::endl;
1261 infostream << "Sound disabled." << std::endl;
1265 infostream << "Using dummy audio." << std::endl;
1266 sound = &dummySoundManager;
1267 sound_is_dummy = true;
1270 soundmaker = new SoundMaker(sound, nodedef_manager);
1274 soundmaker->registerReceiver(eventmgr);
1279 bool Game::createSingleplayerServer(const std::string &map_dir,
1280 const SubgameSpec &gamespec, u16 port)
1282 showOverlayMessage(N_("Creating server..."), 0, 5);
1284 std::string bind_str = g_settings->get("bind_address");
1285 Address bind_addr(0, 0, 0, 0, port);
1287 if (g_settings->getBool("ipv6_server")) {
1288 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1292 bind_addr.Resolve(bind_str.c_str());
1293 } catch (ResolveError &e) {
1294 infostream << "Resolving bind address \"" << bind_str
1295 << "\" failed: " << e.what()
1296 << " -- Listening on all addresses." << std::endl;
1299 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1300 *error_message = "Unable to listen on " +
1301 bind_addr.serializeString() +
1302 " because IPv6 is disabled";
1303 errorstream << *error_message << std::endl;
1307 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1308 false, nullptr, error_message);
1314 bool Game::createClient(const GameStartData &start_data)
1316 showOverlayMessage(N_("Creating client..."), 0, 10);
1318 draw_control = new MapDrawControl;
1322 bool could_connect, connect_aborted;
1323 #ifdef HAVE_TOUCHSCREENGUI
1324 if (g_touchscreengui) {
1325 g_touchscreengui->init(texture_src);
1326 g_touchscreengui->hide();
1329 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1332 if (!could_connect) {
1333 if (error_message->empty() && !connect_aborted) {
1334 // Should not happen if error messages are set properly
1335 *error_message = "Connection failed for unknown reason";
1336 errorstream << *error_message << std::endl;
1341 if (!getServerContent(&connect_aborted)) {
1342 if (error_message->empty() && !connect_aborted) {
1343 // Should not happen if error messages are set properly
1344 *error_message = "Connection failed for unknown reason";
1345 errorstream << *error_message << std::endl;
1350 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1351 &m_flags.force_fog_off, &runData.fog_range, client);
1352 shader_src->addShaderConstantSetterFactory(scsf);
1354 // Update cached textures, meshes and materials
1355 client->afterContentReceived();
1359 camera = new Camera(*draw_control, client);
1360 if (!camera->successfullyCreated(*error_message))
1362 client->setCamera(camera);
1366 if (m_cache_enable_clouds)
1367 clouds = new Clouds(smgr, -1, time(0));
1371 sky = new Sky(-1, texture_src, shader_src);
1373 skybox = NULL; // This is used/set later on in the main run loop
1375 /* Pre-calculated values
1377 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1379 v2u32 size = t->getOriginalSize();
1380 crack_animation_length = size.Y / size.X;
1382 crack_animation_length = 5;
1388 /* Set window caption
1390 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1392 str += utf8_to_wide(g_version_hash);
1394 str += driver->getName();
1396 device->setWindowCaption(str.c_str());
1398 LocalPlayer *player = client->getEnv().getLocalPlayer();
1399 player->hurt_tilt_timer = 0;
1400 player->hurt_tilt_strength = 0;
1402 hud = new Hud(guienv, client, player, &player->inventory);
1404 mapper = client->getMinimap();
1406 if (mapper && client->modsLoaded())
1407 client->getScript()->on_minimap_ready(mapper);
1412 bool Game::initGui()
1416 // Remove stale "recent" chat messages from previous connections
1417 chat_backend->clearRecentChat();
1419 // Make sure the size of the recent messages buffer is right
1420 chat_backend->applySettings();
1422 // Chat backend and console
1423 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1424 -1, chat_backend, client, &g_menumgr);
1426 #ifdef HAVE_TOUCHSCREENGUI
1428 if (g_touchscreengui)
1429 g_touchscreengui->show();
1436 bool Game::connectToServer(const GameStartData &start_data,
1437 bool *connect_ok, bool *connection_aborted)
1439 *connect_ok = false; // Let's not be overly optimistic
1440 *connection_aborted = false;
1441 bool local_server_mode = false;
1443 showOverlayMessage(N_("Resolving address..."), 0, 15);
1445 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1448 connect_address.Resolve(start_data.address.c_str());
1450 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1451 //connect_address.Resolve("localhost");
1452 if (connect_address.isIPv6()) {
1453 IPv6AddressBytes addr_bytes;
1454 addr_bytes.bytes[15] = 1;
1455 connect_address.setAddress(&addr_bytes);
1457 connect_address.setAddress(127, 0, 0, 1);
1459 local_server_mode = true;
1461 } catch (ResolveError &e) {
1462 *error_message = std::string("Couldn't resolve address: ") + e.what();
1463 errorstream << *error_message << std::endl;
1467 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1468 *error_message = "Unable to connect to " +
1469 connect_address.serializeString() +
1470 " because IPv6 is disabled";
1471 errorstream << *error_message << std::endl;
1475 client = new Client(start_data.name.c_str(),
1476 start_data.password, start_data.address,
1477 *draw_control, texture_src, shader_src,
1478 itemdef_manager, nodedef_manager, sound, eventmgr,
1479 connect_address.isIPv6(), m_game_ui.get());
1481 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1483 infostream << "Connecting to server at ";
1484 connect_address.print(&infostream);
1485 infostream << std::endl;
1487 client->connect(connect_address,
1488 simple_singleplayer_mode || local_server_mode);
1491 Wait for server to accept connection
1497 FpsControl fps_control = { 0 };
1499 f32 wait_time = 0; // in seconds
1501 fps_control.last_time = RenderingEngine::get_timer_time();
1503 while (RenderingEngine::run()) {
1505 limitFps(&fps_control, &dtime);
1507 // Update client and server
1508 client->step(dtime);
1511 server->step(dtime);
1514 if (client->getState() == LC_Init) {
1520 if (*connection_aborted)
1523 if (client->accessDenied()) {
1524 *error_message = "Access denied. Reason: "
1525 + client->accessDeniedReason();
1526 *reconnect_requested = client->reconnectRequested();
1527 errorstream << *error_message << std::endl;
1531 if (input->cancelPressed()) {
1532 *connection_aborted = true;
1533 infostream << "Connect aborted [Escape]" << std::endl;
1537 if (client->m_is_registration_confirmation_state) {
1538 if (registration_confirmation_shown) {
1539 // Keep drawing the GUI
1540 RenderingEngine::draw_menu_scene(guienv, dtime, true);
1542 registration_confirmation_shown = true;
1543 (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
1544 &g_menumgr, client, start_data.name, start_data.password,
1545 connection_aborted, texture_src))->drop();
1549 // Only time out if we aren't waiting for the server we started
1550 if (!start_data.isSinglePlayer() && wait_time > 10) {
1551 *error_message = "Connection timed out.";
1552 errorstream << *error_message << std::endl;
1557 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1560 } catch (con::PeerNotFoundException &e) {
1561 // TODO: Should something be done here? At least an info/error
1569 bool Game::getServerContent(bool *aborted)
1573 FpsControl fps_control = { 0 };
1574 f32 dtime; // in seconds
1576 fps_control.last_time = RenderingEngine::get_timer_time();
1578 while (RenderingEngine::run()) {
1580 limitFps(&fps_control, &dtime);
1582 // Update client and server
1583 client->step(dtime);
1586 server->step(dtime);
1589 if (client->mediaReceived() && client->itemdefReceived() &&
1590 client->nodedefReceived()) {
1595 if (!checkConnection())
1598 if (client->getState() < LC_Init) {
1599 *error_message = "Client disconnected";
1600 errorstream << *error_message << std::endl;
1604 if (input->cancelPressed()) {
1606 infostream << "Connect aborted [Escape]" << std::endl;
1613 if (!client->itemdefReceived()) {
1614 const wchar_t *text = wgettext("Item definitions...");
1616 RenderingEngine::draw_load_screen(text, guienv, texture_src,
1619 } else if (!client->nodedefReceived()) {
1620 const wchar_t *text = wgettext("Node definitions...");
1622 RenderingEngine::draw_load_screen(text, guienv, texture_src,
1626 std::stringstream message;
1627 std::fixed(message);
1628 message.precision(0);
1629 float receive = client->mediaReceiveProgress() * 100;
1630 message << gettext("Media...");
1632 message << " " << receive << "%";
1633 message.precision(2);
1635 if ((USE_CURL == 0) ||
1636 (!g_settings->getBool("enable_remote_media_server"))) {
1637 float cur = client->getCurRate();
1638 std::string cur_unit = gettext("KiB/s");
1642 cur_unit = gettext("MiB/s");
1645 message << " (" << cur << ' ' << cur_unit << ")";
1648 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1649 RenderingEngine::draw_load_screen(utf8_to_wide(message.str()), guienv,
1650 texture_src, dtime, progress);
1658 /****************************************************************************/
1659 /****************************************************************************
1661 ****************************************************************************/
1662 /****************************************************************************/
1664 inline void Game::updateInteractTimers(f32 dtime)
1666 if (runData.nodig_delay_timer >= 0)
1667 runData.nodig_delay_timer -= dtime;
1669 if (runData.object_hit_delay_timer >= 0)
1670 runData.object_hit_delay_timer -= dtime;
1672 runData.time_from_last_punch += dtime;
1676 /* returns false if game should exit, otherwise true
1678 inline bool Game::checkConnection()
1680 if (client->accessDenied()) {
1681 *error_message = "Access denied. Reason: "
1682 + client->accessDeniedReason();
1683 *reconnect_requested = client->reconnectRequested();
1684 errorstream << *error_message << std::endl;
1692 /* returns false if game should exit, otherwise true
1694 inline bool Game::handleCallbacks()
1696 if (g_gamecallback->disconnect_requested) {
1697 g_gamecallback->disconnect_requested = false;
1701 if (g_gamecallback->changepassword_requested) {
1702 (new GUIPasswordChange(guienv, guiroot, -1,
1703 &g_menumgr, client, texture_src))->drop();
1704 g_gamecallback->changepassword_requested = false;
1707 if (g_gamecallback->changevolume_requested) {
1708 (new GUIVolumeChange(guienv, guiroot, -1,
1709 &g_menumgr, texture_src))->drop();
1710 g_gamecallback->changevolume_requested = false;
1713 if (g_gamecallback->keyconfig_requested) {
1714 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1715 &g_menumgr, texture_src))->drop();
1716 g_gamecallback->keyconfig_requested = false;
1719 if (g_gamecallback->keyconfig_changed) {
1720 input->keycache.populate(); // update the cache with new settings
1721 g_gamecallback->keyconfig_changed = false;
1728 void Game::processQueues()
1730 texture_src->processQueue();
1731 itemdef_manager->processQueue(client);
1732 shader_src->processQueue();
1736 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1739 float profiler_print_interval =
1740 g_settings->getFloat("profiler_print_interval");
1741 bool print_to_log = true;
1743 if (profiler_print_interval == 0) {
1744 print_to_log = false;
1745 profiler_print_interval = 3;
1748 if (profiler_interval.step(dtime, profiler_print_interval)) {
1750 infostream << "Profiler:" << std::endl;
1751 g_profiler->print(infostream);
1754 m_game_ui->updateProfiler();
1755 g_profiler->clear();
1758 // Update update graphs
1759 g_profiler->graphAdd("Time non-rendering [ms]",
1760 draw_times.busy_time - stats.drawtime);
1762 g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time);
1763 g_profiler->graphAdd("FPS", 1.0f / dtime);
1766 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1773 /* Time average and jitter calculation
1775 jp = &stats->dtime_jitter;
1776 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1778 jitter = dtime - jp->avg;
1780 if (jitter > jp->max)
1783 jp->counter += dtime;
1785 if (jp->counter > 0.0) {
1787 jp->max_sample = jp->max;
1788 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1792 /* Busytime average and jitter calculation
1794 jp = &stats->busy_time_jitter;
1795 jp->avg = jp->avg + draw_times.busy_time * 0.02;
1797 jitter = draw_times.busy_time - jp->avg;
1799 if (jitter > jp->max)
1801 if (jitter < jp->min)
1804 jp->counter += dtime;
1806 if (jp->counter > 0.0) {
1808 jp->max_sample = jp->max;
1809 jp->min_sample = jp->min;
1817 /****************************************************************************
1819 ****************************************************************************/
1821 void Game::processUserInput(f32 dtime)
1823 // Reset input if window not active or some menu is active
1824 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1826 #ifdef HAVE_TOUCHSCREENGUI
1827 g_touchscreengui->hide();
1830 #ifdef HAVE_TOUCHSCREENGUI
1831 else if (g_touchscreengui) {
1832 /* on touchscreengui step may generate own input events which ain't
1833 * what we want in case we just did clear them */
1834 g_touchscreengui->step(dtime);
1838 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1839 gui_chat_console->closeConsoleAtOnce();
1842 // Input handler step() (used by the random input generator)
1846 auto formspec = m_game_ui->getFormspecGUI();
1848 formspec->getAndroidUIInput();
1850 handleAndroidChatInput();
1853 // Increase timer for double tap of "keymap_jump"
1854 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1855 runData.jump_timer += dtime;
1858 processItemSelection(&runData.new_playeritem);
1862 void Game::processKeyInput()
1864 if (wasKeyDown(KeyType::DROP)) {
1865 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1866 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1867 toggleAutoforward();
1868 } else if (wasKeyDown(KeyType::BACKWARD)) {
1869 if (g_settings->getBool("continuous_forward"))
1870 toggleAutoforward();
1871 } else if (wasKeyDown(KeyType::INVENTORY)) {
1873 } else if (input->cancelPressed()) {
1875 m_android_chat_open = false;
1877 if (!gui_chat_console->isOpenInhibited()) {
1880 } else if (wasKeyDown(KeyType::CHAT)) {
1881 openConsole(0.2, L"");
1882 } else if (wasKeyDown(KeyType::CMD)) {
1883 openConsole(0.2, L"/");
1884 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1885 if (client->modsLoaded())
1886 openConsole(0.2, L".");
1888 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1889 } else if (wasKeyDown(KeyType::CONSOLE)) {
1890 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1891 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1893 } else if (wasKeyDown(KeyType::JUMP)) {
1894 toggleFreeMoveAlt();
1895 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1897 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1899 } else if (wasKeyDown(KeyType::NOCLIP)) {
1902 } else if (wasKeyDown(KeyType::MUTE)) {
1903 if (g_settings->getBool("enable_sound")) {
1904 bool new_mute_sound = !g_settings->getBool("mute_sound");
1905 g_settings->setBool("mute_sound", new_mute_sound);
1907 m_game_ui->showTranslatedStatusText("Sound muted");
1909 m_game_ui->showTranslatedStatusText("Sound unmuted");
1911 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1913 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1914 if (g_settings->getBool("enable_sound")) {
1915 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1917 g_settings->setFloat("sound_volume", new_volume);
1918 const wchar_t *str = wgettext("Volume changed to %d%%");
1919 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1921 m_game_ui->showStatusText(buf);
1923 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1925 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1926 if (g_settings->getBool("enable_sound")) {
1927 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1929 g_settings->setFloat("sound_volume", new_volume);
1930 const wchar_t *str = wgettext("Volume changed to %d%%");
1931 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1933 m_game_ui->showStatusText(buf);
1935 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1938 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1939 || wasKeyDown(KeyType::DEC_VOLUME)) {
1940 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1942 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1944 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1945 client->makeScreenshot();
1946 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1947 m_game_ui->toggleHud();
1948 } else if (wasKeyDown(KeyType::MINIMAP)) {
1949 toggleMinimap(isKeyDown(KeyType::SNEAK));
1950 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1951 m_game_ui->toggleChat();
1952 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1954 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1955 toggleUpdateCamera();
1956 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1958 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1959 m_game_ui->toggleProfiler();
1960 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1961 increaseViewRange();
1962 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1963 decreaseViewRange();
1964 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1965 toggleFullViewRange();
1966 } else if (wasKeyDown(KeyType::ZOOM)) {
1968 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1970 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1972 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1974 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1978 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1979 runData.reset_jump_timer = false;
1980 runData.jump_timer = 0.0f;
1983 if (quicktune->hasMessage()) {
1984 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
1988 void Game::processItemSelection(u16 *new_playeritem)
1990 LocalPlayer *player = client->getEnv().getLocalPlayer();
1992 /* Item selection using mouse wheel
1994 *new_playeritem = player->getWieldIndex();
1996 s32 wheel = input->getMouseWheel();
1997 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
1998 player->hud_hotbar_itemcount - 1);
2002 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2005 if (wasKeyDown(KeyType::HOTBAR_PREV))
2009 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2011 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2014 /* Item selection using hotbar slot keys
2016 for (u16 i = 0; i <= max_item; i++) {
2017 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2018 *new_playeritem = i;
2025 void Game::dropSelectedItem(bool single_item)
2027 IDropAction *a = new IDropAction();
2028 a->count = single_item ? 1 : 0;
2029 a->from_inv.setCurrentPlayer();
2030 a->from_list = "main";
2031 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2032 client->inventoryAction(a);
2036 void Game::openInventory()
2039 * Don't permit to open inventory is CAO or player doesn't exists.
2040 * This prevent showing an empty inventory at player load
2043 LocalPlayer *player = client->getEnv().getLocalPlayer();
2044 if (!player || !player->getCAO())
2047 infostream << "Game: Launching inventory" << std::endl;
2049 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2051 InventoryLocation inventoryloc;
2052 inventoryloc.setCurrentPlayer();
2054 if (!client->modsLoaded()
2055 || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2056 TextDest *txt_dst = new TextDestPlayerInventory(client);
2057 auto *&formspec = m_game_ui->updateFormspec("");
2058 GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
2059 txt_dst, client->getFormspecPrepend(), sound);
2061 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2066 void Game::openConsole(float scale, const wchar_t *line)
2068 assert(scale > 0.0f && scale <= 1.0f);
2071 porting::showInputDialog(gettext("ok"), "", "", 2);
2072 m_android_chat_open = true;
2074 if (gui_chat_console->isOpenInhibited())
2076 gui_chat_console->openConsole(scale);
2078 gui_chat_console->setCloseOnEnter(true);
2079 gui_chat_console->replaceAndAddToHistory(line);
2085 void Game::handleAndroidChatInput()
2087 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2088 std::string text = porting::getInputDialogValue();
2089 client->typeChatMessage(utf8_to_wide(text));
2090 m_android_chat_open = false;
2096 void Game::toggleFreeMove()
2098 bool free_move = !g_settings->getBool("free_move");
2099 g_settings->set("free_move", bool_to_cstr(free_move));
2102 if (client->checkPrivilege("fly")) {
2103 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2105 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2108 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2112 void Game::toggleFreeMoveAlt()
2114 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2117 runData.reset_jump_timer = true;
2121 void Game::togglePitchMove()
2123 bool pitch_move = !g_settings->getBool("pitch_move");
2124 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2127 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2129 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2134 void Game::toggleFast()
2136 bool fast_move = !g_settings->getBool("fast_move");
2137 bool has_fast_privs = client->checkPrivilege("fast");
2138 g_settings->set("fast_move", bool_to_cstr(fast_move));
2141 if (has_fast_privs) {
2142 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2144 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2147 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2151 m_cache_hold_aux1 = fast_move && has_fast_privs;
2156 void Game::toggleNoClip()
2158 bool noclip = !g_settings->getBool("noclip");
2159 g_settings->set("noclip", bool_to_cstr(noclip));
2162 if (client->checkPrivilege("noclip")) {
2163 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2165 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2168 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2172 void Game::toggleCinematic()
2174 bool cinematic = !g_settings->getBool("cinematic");
2175 g_settings->set("cinematic", bool_to_cstr(cinematic));
2178 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2180 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2183 // Autoforward by toggling continuous forward.
2184 void Game::toggleAutoforward()
2186 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2187 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2189 if (autorun_enabled)
2190 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2192 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2195 void Game::toggleMinimap(bool shift_pressed)
2197 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2201 mapper->toggleMinimapShape();
2205 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2207 // Not so satisying code to keep compatibility with old fixed mode system
2209 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2211 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2212 m_game_ui->m_flags.show_minimap = false;
2215 // If radar is disabled, try to find a non radar mode or fall back to 0
2216 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2217 while (mapper->getModeIndex() &&
2218 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2221 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2225 // End of 'not so satifying code'
2226 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2227 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2228 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2230 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2233 void Game::toggleFog()
2235 bool fog_enabled = g_settings->getBool("enable_fog");
2236 g_settings->setBool("enable_fog", !fog_enabled);
2238 m_game_ui->showTranslatedStatusText("Fog disabled");
2240 m_game_ui->showTranslatedStatusText("Fog enabled");
2244 void Game::toggleDebug()
2246 // Initial / 4x toggle: Chat only
2247 // 1x toggle: Debug text with chat
2248 // 2x toggle: Debug text with profiler graph
2249 // 3x toggle: Debug text and wireframe
2250 if (!m_game_ui->m_flags.show_debug) {
2251 m_game_ui->m_flags.show_debug = true;
2252 m_game_ui->m_flags.show_profiler_graph = false;
2253 draw_control->show_wireframe = false;
2254 m_game_ui->showTranslatedStatusText("Debug info shown");
2255 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2256 m_game_ui->m_flags.show_profiler_graph = true;
2257 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2258 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2259 m_game_ui->m_flags.show_profiler_graph = false;
2260 draw_control->show_wireframe = true;
2261 m_game_ui->showTranslatedStatusText("Wireframe shown");
2263 m_game_ui->m_flags.show_debug = false;
2264 m_game_ui->m_flags.show_profiler_graph = false;
2265 draw_control->show_wireframe = false;
2266 if (client->checkPrivilege("debug")) {
2267 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2269 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2275 void Game::toggleUpdateCamera()
2277 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2278 if (m_flags.disable_camera_update)
2279 m_game_ui->showTranslatedStatusText("Camera update disabled");
2281 m_game_ui->showTranslatedStatusText("Camera update enabled");
2285 void Game::increaseViewRange()
2287 s16 range = g_settings->getS16("viewing_range");
2288 s16 range_new = range + 10;
2292 if (range_new > 4000) {
2294 str = wgettext("Viewing range is at maximum: %d");
2295 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2297 m_game_ui->showStatusText(buf);
2300 str = wgettext("Viewing range changed to %d");
2301 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2303 m_game_ui->showStatusText(buf);
2305 g_settings->set("viewing_range", itos(range_new));
2309 void Game::decreaseViewRange()
2311 s16 range = g_settings->getS16("viewing_range");
2312 s16 range_new = range - 10;
2316 if (range_new < 20) {
2318 str = wgettext("Viewing range is at minimum: %d");
2319 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2321 m_game_ui->showStatusText(buf);
2323 str = wgettext("Viewing range changed to %d");
2324 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2326 m_game_ui->showStatusText(buf);
2328 g_settings->set("viewing_range", itos(range_new));
2332 void Game::toggleFullViewRange()
2334 draw_control->range_all = !draw_control->range_all;
2335 if (draw_control->range_all)
2336 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2338 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2342 void Game::checkZoomEnabled()
2344 LocalPlayer *player = client->getEnv().getLocalPlayer();
2345 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2346 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2349 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2351 if ((device->isWindowActive() && device->isWindowFocused()
2352 && !isMenuActive()) || input->isRandom()) {
2355 if (!input->isRandom()) {
2356 // Mac OSX gets upset if this is set every frame
2357 if (device->getCursorControl()->isVisible())
2358 device->getCursorControl()->setVisible(false);
2362 if (m_first_loop_after_window_activation) {
2363 m_first_loop_after_window_activation = false;
2365 input->setMousePos(driver->getScreenSize().Width / 2,
2366 driver->getScreenSize().Height / 2);
2368 updateCameraOrientation(cam, dtime);
2374 // Mac OSX gets upset if this is set every frame
2375 if (!device->getCursorControl()->isVisible())
2376 device->getCursorControl()->setVisible(true);
2379 m_first_loop_after_window_activation = true;
2384 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2385 // responsiveness independently of FOV.
2386 f32 Game::getSensitivityScaleFactor() const
2388 f32 fov_y = client->getCamera()->getFovY();
2390 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2391 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2393 return tan(fov_y / 2.0f) * 1.3763818698f;
2396 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2398 #ifdef HAVE_TOUCHSCREENGUI
2399 if (g_touchscreengui) {
2400 cam->camera_yaw += g_touchscreengui->getYawChange();
2401 cam->camera_pitch = g_touchscreengui->getPitch();
2404 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2405 v2s32 dist = input->getMousePos() - center;
2407 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2411 f32 sens_scale = getSensitivityScaleFactor();
2412 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2413 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2415 if (dist.X != 0 || dist.Y != 0)
2416 input->setMousePos(center.X, center.Y);
2417 #ifdef HAVE_TOUCHSCREENGUI
2421 if (m_cache_enable_joysticks) {
2422 f32 sens_scale = getSensitivityScaleFactor();
2423 f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime * sens_scale;
2424 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2425 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2428 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2432 void Game::updatePlayerControl(const CameraOrientation &cam)
2434 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2436 // DO NOT use the isKeyDown method for the forward, backward, left, right
2437 // buttons, as the code that uses the controls needs to be able to
2438 // distinguish between the two in order to know when to use joysticks.
2440 PlayerControl control(
2441 input->isKeyDown(KeyType::FORWARD),
2442 input->isKeyDown(KeyType::BACKWARD),
2443 input->isKeyDown(KeyType::LEFT),
2444 input->isKeyDown(KeyType::RIGHT),
2445 isKeyDown(KeyType::JUMP),
2446 isKeyDown(KeyType::AUX1),
2447 isKeyDown(KeyType::SNEAK),
2448 isKeyDown(KeyType::ZOOM),
2449 isKeyDown(KeyType::DIG),
2450 isKeyDown(KeyType::PLACE),
2453 input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE),
2454 input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE)
2457 u32 keypress_bits = (
2458 ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) |
2459 ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) |
2460 ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) |
2461 ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) |
2462 ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) |
2463 ( (u32)(isKeyDown(KeyType::AUX1) & 0x1) << 5) |
2464 ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) |
2465 ( (u32)(isKeyDown(KeyType::DIG) & 0x1) << 7) |
2466 ( (u32)(isKeyDown(KeyType::PLACE) & 0x1) << 8) |
2467 ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9)
2471 /* For Android, simulate holding down AUX1 (fast move) if the user has
2472 * the fast_move setting toggled on. If there is an aux1 key defined for
2473 * Android then its meaning is inverted (i.e. holding aux1 means walk and
2476 if (m_cache_hold_aux1) {
2477 control.aux1 = control.aux1 ^ true;
2478 keypress_bits ^= ((u32)(1U << 5));
2482 LocalPlayer *player = client->getEnv().getLocalPlayer();
2484 // autojump if set: simulate "jump" key
2485 if (player->getAutojump()) {
2486 control.jump = true;
2487 keypress_bits |= 1U << 4;
2490 // autoforward if set: simulate "up" key
2491 if (player->getPlayerSettings().continuous_forward &&
2492 client->activeObjectsReceived() && !player->isDead()) {
2494 keypress_bits |= 1U << 0;
2497 client->setPlayerControl(control);
2498 player->keyPressed = keypress_bits;
2504 inline void Game::step(f32 *dtime)
2506 bool can_be_and_is_paused =
2507 (simple_singleplayer_mode && g_menumgr.pausesGame());
2509 if (can_be_and_is_paused) { // This is for a singleplayer server
2510 *dtime = 0; // No time passes
2512 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
2516 server->step(*dtime);
2518 client->step(*dtime);
2522 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2525 for (auto &&child: node->getChildren())
2526 pauseNodeAnimation(paused, child);
2527 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2529 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2530 float speed = animated_node->getAnimationSpeed();
2533 paused.push_back({grab(animated_node), speed});
2534 animated_node->setAnimationSpeed(0.0f);
2537 void Game::pauseAnimation()
2539 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2542 void Game::resumeAnimation()
2544 for (auto &&pair: paused_animated_nodes)
2545 pair.first->setAnimationSpeed(pair.second);
2546 paused_animated_nodes.clear();
2549 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2550 {&Game::handleClientEvent_None},
2551 {&Game::handleClientEvent_PlayerDamage},
2552 {&Game::handleClientEvent_PlayerForceMove},
2553 {&Game::handleClientEvent_Deathscreen},
2554 {&Game::handleClientEvent_ShowFormSpec},
2555 {&Game::handleClientEvent_ShowLocalFormSpec},
2556 {&Game::handleClientEvent_HandleParticleEvent},
2557 {&Game::handleClientEvent_HandleParticleEvent},
2558 {&Game::handleClientEvent_HandleParticleEvent},
2559 {&Game::handleClientEvent_HudAdd},
2560 {&Game::handleClientEvent_HudRemove},
2561 {&Game::handleClientEvent_HudChange},
2562 {&Game::handleClientEvent_SetSky},
2563 {&Game::handleClientEvent_SetSun},
2564 {&Game::handleClientEvent_SetMoon},
2565 {&Game::handleClientEvent_SetStars},
2566 {&Game::handleClientEvent_OverrideDayNigthRatio},
2567 {&Game::handleClientEvent_CloudParams},
2570 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2572 FATAL_ERROR("ClientEvent type None received");
2575 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2577 if (client->modsLoaded())
2578 client->getScript()->on_damage_taken(event->player_damage.amount);
2580 // Damage flash and hurt tilt are not used at death
2581 if (client->getHP() > 0) {
2582 runData.damage_flash += 95.0f + 3.2f * event->player_damage.amount;
2583 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2585 LocalPlayer *player = client->getEnv().getLocalPlayer();
2587 player->hurt_tilt_timer = 1.5f;
2588 player->hurt_tilt_strength =
2589 rangelim(event->player_damage.amount / 4.0f, 1.0f, 4.0f);
2592 // Play damage sound
2593 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2596 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2598 cam->camera_yaw = event->player_force_move.yaw;
2599 cam->camera_pitch = event->player_force_move.pitch;
2602 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2604 // If client scripting is enabled, deathscreen is handled by CSM code in
2605 // builtin/client/init.lua
2606 if (client->modsLoaded())
2607 client->getScript()->on_death();
2609 showDeathFormspec();
2611 /* Handle visualization */
2612 LocalPlayer *player = client->getEnv().getLocalPlayer();
2613 runData.damage_flash = 0;
2614 player->hurt_tilt_timer = 0;
2615 player->hurt_tilt_strength = 0;
2618 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2620 if (event->show_formspec.formspec->empty()) {
2621 auto formspec = m_game_ui->getFormspecGUI();
2622 if (formspec && (event->show_formspec.formname->empty()
2623 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2624 formspec->quitMenu();
2627 FormspecFormSource *fs_src =
2628 new FormspecFormSource(*(event->show_formspec.formspec));
2629 TextDestPlayerInventory *txt_dst =
2630 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2632 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2633 GUIFormSpecMenu::create(formspec, client, &input->joystick,
2634 fs_src, txt_dst, client->getFormspecPrepend(), sound);
2637 delete event->show_formspec.formspec;
2638 delete event->show_formspec.formname;
2641 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2643 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2644 LocalFormspecHandler *txt_dst =
2645 new LocalFormspecHandler(*event->show_formspec.formname, client);
2646 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, &input->joystick,
2647 fs_src, txt_dst, client->getFormspecPrepend(), sound);
2649 delete event->show_formspec.formspec;
2650 delete event->show_formspec.formname;
2653 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2654 CameraOrientation *cam)
2656 LocalPlayer *player = client->getEnv().getLocalPlayer();
2657 client->getParticleManager()->handleParticleEvent(event, client, player);
2660 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2662 LocalPlayer *player = client->getEnv().getLocalPlayer();
2664 u32 server_id = event->hudadd->server_id;
2665 // ignore if we already have a HUD with that ID
2666 auto i = m_hud_server_to_client.find(server_id);
2667 if (i != m_hud_server_to_client.end()) {
2668 delete event->hudadd;
2672 HudElement *e = new HudElement;
2673 e->type = static_cast<HudElementType>(event->hudadd->type);
2674 e->pos = event->hudadd->pos;
2675 e->name = event->hudadd->name;
2676 e->scale = event->hudadd->scale;
2677 e->text = event->hudadd->text;
2678 e->number = event->hudadd->number;
2679 e->item = event->hudadd->item;
2680 e->dir = event->hudadd->dir;
2681 e->align = event->hudadd->align;
2682 e->offset = event->hudadd->offset;
2683 e->world_pos = event->hudadd->world_pos;
2684 e->size = event->hudadd->size;
2685 e->z_index = event->hudadd->z_index;
2686 e->text2 = event->hudadd->text2;
2687 m_hud_server_to_client[server_id] = player->addHud(e);
2689 delete event->hudadd;
2692 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2694 LocalPlayer *player = client->getEnv().getLocalPlayer();
2696 auto i = m_hud_server_to_client.find(event->hudrm.id);
2697 if (i != m_hud_server_to_client.end()) {
2698 HudElement *e = player->removeHud(i->second);
2700 m_hud_server_to_client.erase(i);
2705 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2707 LocalPlayer *player = client->getEnv().getLocalPlayer();
2709 HudElement *e = nullptr;
2711 auto i = m_hud_server_to_client.find(event->hudchange->id);
2712 if (i != m_hud_server_to_client.end()) {
2713 e = player->getHud(i->second);
2717 delete event->hudchange;
2721 #define CASE_SET(statval, prop, dataprop) \
2723 e->prop = event->hudchange->dataprop; \
2726 switch (event->hudchange->stat) {
2727 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2729 CASE_SET(HUD_STAT_NAME, name, sdata);
2731 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2733 CASE_SET(HUD_STAT_TEXT, text, sdata);
2735 CASE_SET(HUD_STAT_NUMBER, number, data);
2737 CASE_SET(HUD_STAT_ITEM, item, data);
2739 CASE_SET(HUD_STAT_DIR, dir, data);
2741 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2743 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2745 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2747 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2749 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2751 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2756 delete event->hudchange;
2759 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2761 sky->setVisible(false);
2762 // Whether clouds are visible in front of a custom skybox.
2763 sky->setCloudsEnabled(event->set_sky->clouds);
2769 // Clear the old textures out in case we switch rendering type.
2770 sky->clearSkyboxTextures();
2771 // Handle according to type
2772 if (event->set_sky->type == "regular") {
2773 // Shows the mesh skybox
2774 sky->setVisible(true);
2775 // Update mesh based skybox colours if applicable.
2776 sky->setSkyColors(event->set_sky->sky_color);
2777 sky->setHorizonTint(
2778 event->set_sky->fog_sun_tint,
2779 event->set_sky->fog_moon_tint,
2780 event->set_sky->fog_tint_type
2782 } else if (event->set_sky->type == "skybox" &&
2783 event->set_sky->textures.size() == 6) {
2784 // Disable the dyanmic mesh skybox:
2785 sky->setVisible(false);
2787 sky->setFallbackBgColor(event->set_sky->bgcolor);
2788 // Set sunrise and sunset fog tinting:
2789 sky->setHorizonTint(
2790 event->set_sky->fog_sun_tint,
2791 event->set_sky->fog_moon_tint,
2792 event->set_sky->fog_tint_type
2794 // Add textures to skybox.
2795 for (int i = 0; i < 6; i++)
2796 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2798 // Handle everything else as plain color.
2799 if (event->set_sky->type != "plain")
2800 infostream << "Unknown sky type: "
2801 << (event->set_sky->type) << std::endl;
2802 sky->setVisible(false);
2803 sky->setFallbackBgColor(event->set_sky->bgcolor);
2804 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2805 sky->setHorizonTint(
2806 event->set_sky->bgcolor,
2807 event->set_sky->bgcolor,
2811 delete event->set_sky;
2814 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2816 sky->setSunVisible(event->sun_params->visible);
2817 sky->setSunTexture(event->sun_params->texture,
2818 event->sun_params->tonemap, texture_src);
2819 sky->setSunScale(event->sun_params->scale);
2820 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2821 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2822 delete event->sun_params;
2825 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2827 sky->setMoonVisible(event->moon_params->visible);
2828 sky->setMoonTexture(event->moon_params->texture,
2829 event->moon_params->tonemap, texture_src);
2830 sky->setMoonScale(event->moon_params->scale);
2831 delete event->moon_params;
2834 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2836 sky->setStarsVisible(event->star_params->visible);
2837 sky->setStarCount(event->star_params->count, false);
2838 sky->setStarColor(event->star_params->starcolor);
2839 sky->setStarScale(event->star_params->scale);
2840 delete event->star_params;
2843 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2844 CameraOrientation *cam)
2846 client->getEnv().setDayNightRatioOverride(
2847 event->override_day_night_ratio.do_override,
2848 event->override_day_night_ratio.ratio_f * 1000.0f);
2851 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2856 clouds->setDensity(event->cloud_params.density);
2857 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2858 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2859 clouds->setHeight(event->cloud_params.height);
2860 clouds->setThickness(event->cloud_params.thickness);
2861 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2864 void Game::processClientEvents(CameraOrientation *cam)
2866 while (client->hasClientEvents()) {
2867 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2868 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2869 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2870 (this->*evHandler.handler)(event.get(), cam);
2874 void Game::updateChat(f32 dtime, const v2u32 &screensize)
2876 // Get new messages from error log buffer
2877 while (!m_chat_log_buf.empty())
2878 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2880 // Get new messages from client
2881 std::wstring message;
2882 while (client->getChatMessage(message)) {
2883 chat_backend->addUnparsedMessage(message);
2886 // Remove old messages
2887 chat_backend->step(dtime);
2889 // Display all messages in a static text element
2890 m_game_ui->setChatText(chat_backend->getRecentChat(),
2891 chat_backend->getRecentBuffer().getLineCount());
2894 void Game::updateCamera(u32 busy_time, f32 dtime)
2896 LocalPlayer *player = client->getEnv().getLocalPlayer();
2899 For interaction purposes, get info about the held item
2901 - Is it a usable item?
2902 - Can it point to liquids?
2904 ItemStack playeritem;
2906 ItemStack selected, hand;
2907 playeritem = player->getWieldedItem(&selected, &hand);
2910 ToolCapabilities playeritem_toolcap =
2911 playeritem.getToolCapabilities(itemdef_manager);
2913 v3s16 old_camera_offset = camera->getOffset();
2915 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2916 GenericCAO *playercao = player->getCAO();
2918 // If playercao not loaded, don't change camera
2922 camera->toggleCameraMode();
2924 // Make the player visible depending on camera mode.
2925 playercao->updateMeshCulling();
2926 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2929 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2930 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2932 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2933 camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
2934 camera->step(dtime);
2936 v3f camera_position = camera->getPosition();
2937 v3f camera_direction = camera->getDirection();
2938 f32 camera_fov = camera->getFovMax();
2939 v3s16 camera_offset = camera->getOffset();
2941 m_camera_offset_changed = (camera_offset != old_camera_offset);
2943 if (!m_flags.disable_camera_update) {
2944 client->getEnv().getClientMap().updateCamera(camera_position,
2945 camera_direction, camera_fov, camera_offset);
2947 if (m_camera_offset_changed) {
2948 client->updateCameraOffset(camera_offset);
2949 client->getEnv().updateCameraOffset(camera_offset);
2952 clouds->updateCameraOffset(camera_offset);
2958 void Game::updateSound(f32 dtime)
2960 // Update sound listener
2961 v3s16 camera_offset = camera->getOffset();
2962 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
2963 v3f(0, 0, 0), // velocity
2964 camera->getDirection(),
2965 camera->getCameraNode()->getUpVector());
2967 bool mute_sound = g_settings->getBool("mute_sound");
2969 sound->setListenerGain(0.0f);
2971 // Check if volume is in the proper range, else fix it.
2972 float old_volume = g_settings->getFloat("sound_volume");
2973 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
2974 sound->setListenerGain(new_volume);
2976 if (old_volume != new_volume) {
2977 g_settings->setFloat("sound_volume", new_volume);
2981 LocalPlayer *player = client->getEnv().getLocalPlayer();
2983 // Tell the sound maker whether to make footstep sounds
2984 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
2986 // Update sound maker
2987 if (player->makes_footstep_sound)
2988 soundmaker->step(dtime);
2990 ClientMap &map = client->getEnv().getClientMap();
2991 MapNode n = map.getNode(player->getFootstepNodePos());
2992 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
2996 void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
2998 LocalPlayer *player = client->getEnv().getLocalPlayer();
3000 const v3f camera_direction = camera->getDirection();
3001 const v3s16 camera_offset = camera->getOffset();
3004 Calculate what block is the crosshair pointing to
3007 ItemStack selected_item, hand_item;
3008 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3010 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3011 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3013 core::line3d<f32> shootline;
3015 switch (camera->getCameraMode()) {
3016 case CAMERA_MODE_FIRST:
3017 // Shoot from camera position, with bobbing
3018 shootline.start = camera->getPosition();
3020 case CAMERA_MODE_THIRD:
3021 // Shoot from player head, no bobbing
3022 shootline.start = camera->getHeadPosition();
3024 case CAMERA_MODE_THIRD_FRONT:
3025 shootline.start = camera->getHeadPosition();
3026 // prevent player pointing anything in front-view
3030 shootline.end = shootline.start + camera_direction * BS * d;
3032 #ifdef HAVE_TOUCHSCREENGUI
3034 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3035 shootline = g_touchscreengui->getShootline();
3036 // Scale shootline to the acual distance the player can reach
3037 shootline.end = shootline.start
3038 + shootline.getVector().normalize() * BS * d;
3039 shootline.start += intToFloat(camera_offset, BS);
3040 shootline.end += intToFloat(camera_offset, BS);
3045 PointedThing pointed = updatePointedThing(shootline,
3046 selected_def.liquids_pointable,
3047 !runData.btn_down_for_dig,
3050 if (pointed != runData.pointed_old) {
3051 infostream << "Pointing at " << pointed.dump() << std::endl;
3052 hud->updateSelectionMesh(camera_offset);
3055 // Allow digging again if button is not pressed
3056 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3057 runData.digging_blocked = false;
3061 - releasing dig button
3062 - pointing away from node
3064 if (runData.digging) {
3065 if (wasKeyReleased(KeyType::DIG)) {
3066 infostream << "Dig button released (stopped digging)" << std::endl;
3067 runData.digging = false;
3068 } else if (pointed != runData.pointed_old) {
3069 if (pointed.type == POINTEDTHING_NODE
3070 && runData.pointed_old.type == POINTEDTHING_NODE
3071 && pointed.node_undersurface
3072 == runData.pointed_old.node_undersurface) {
3073 // Still pointing to the same node, but a different face.
3076 infostream << "Pointing away from node (stopped digging)" << std::endl;
3077 runData.digging = false;
3078 hud->updateSelectionMesh(camera_offset);
3082 if (!runData.digging) {
3083 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3084 client->setCrack(-1, v3s16(0, 0, 0));
3085 runData.dig_time = 0.0;
3087 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3088 // Remove e.g. torches faster when clicking instead of holding dig button
3089 runData.nodig_delay_timer = 0;
3090 runData.dig_instantly = false;
3093 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3094 runData.btn_down_for_dig = false;
3096 runData.punching = false;
3098 soundmaker->m_player_leftpunch_sound.name = "";
3100 // Prepare for repeating, unless we're not supposed to
3101 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3102 runData.repeat_place_timer += dtime;
3104 runData.repeat_place_timer = 0;
3106 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3107 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3108 !client->getScript()->on_item_use(selected_item, pointed)))
3109 client->interact(INTERACT_USE, pointed);
3110 } else if (pointed.type == POINTEDTHING_NODE) {
3111 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3112 } else if (pointed.type == POINTEDTHING_OBJECT) {
3113 v3f player_position = player->getPosition();
3114 handlePointingAtObject(pointed, tool_item, player_position, show_debug);
3115 } else if (isKeyDown(KeyType::DIG)) {
3116 // When button is held down in air, show continuous animation
3117 runData.punching = true;
3118 // Run callback even though item is not usable
3119 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3120 client->getScript()->on_item_use(selected_item, pointed);
3121 } else if (wasKeyPressed(KeyType::PLACE)) {
3122 handlePointingAtNothing(selected_item);
3125 runData.pointed_old = pointed;
3127 if (runData.punching || wasKeyPressed(KeyType::DIG))
3128 camera->setDigging(0); // dig animation
3130 input->clearWasKeyPressed();
3131 input->clearWasKeyReleased();
3132 // Ensure DIG & PLACE are marked as handled
3133 wasKeyDown(KeyType::DIG);
3134 wasKeyDown(KeyType::PLACE);
3136 input->joystick.clearWasKeyPressed(KeyType::DIG);
3137 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3139 input->joystick.clearWasKeyReleased(KeyType::DIG);
3140 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3144 PointedThing Game::updatePointedThing(
3145 const core::line3d<f32> &shootline,
3146 bool liquids_pointable,
3147 bool look_for_object,
3148 const v3s16 &camera_offset)
3150 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3151 selectionboxes->clear();
3152 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3153 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3154 "show_entity_selectionbox");
3156 ClientEnvironment &env = client->getEnv();
3157 ClientMap &map = env.getClientMap();
3158 const NodeDefManager *nodedef = map.getNodeDefManager();
3160 runData.selected_object = NULL;
3161 hud->pointing_at_object = false;
3163 RaycastState s(shootline, look_for_object, liquids_pointable);
3164 PointedThing result;
3165 env.continueRaycast(&s, &result);
3166 if (result.type == POINTEDTHING_OBJECT) {
3167 hud->pointing_at_object = true;
3169 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3170 aabb3f selection_box;
3171 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3172 runData.selected_object->getSelectionBox(&selection_box)) {
3173 v3f pos = runData.selected_object->getPosition();
3174 selectionboxes->push_back(aabb3f(selection_box));
3175 hud->setSelectionPos(pos, camera_offset);
3177 } else if (result.type == POINTEDTHING_NODE) {
3178 // Update selection boxes
3179 MapNode n = map.getNode(result.node_undersurface);
3180 std::vector<aabb3f> boxes;
3181 n.getSelectionBoxes(nodedef, &boxes,
3182 n.getNeighbors(result.node_undersurface, &map));
3185 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3186 i != boxes.end(); ++i) {
3188 box.MinEdge -= v3f(d, d, d);
3189 box.MaxEdge += v3f(d, d, d);
3190 selectionboxes->push_back(box);
3192 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3194 hud->setSelectedFaceNormal(v3f(
3195 result.intersection_normal.X,
3196 result.intersection_normal.Y,
3197 result.intersection_normal.Z));
3200 // Update selection mesh light level and vertex colors
3201 if (!selectionboxes->empty()) {
3202 v3f pf = hud->getSelectionPos();
3203 v3s16 p = floatToInt(pf, BS);
3205 // Get selection mesh light level
3206 MapNode n = map.getNode(p);
3207 u16 node_light = getInteriorLight(n, -1, nodedef);
3208 u16 light_level = node_light;
3210 for (const v3s16 &dir : g_6dirs) {
3211 n = map.getNode(p + dir);
3212 node_light = getInteriorLight(n, -1, nodedef);
3213 if (node_light > light_level)
3214 light_level = node_light;
3217 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3219 final_color_blend(&c, light_level, daynight_ratio);
3221 // Modify final color a bit with time
3222 u32 timer = porting::getTimeMs() % 5000;
3223 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3224 float sin_r = 0.08f * std::sin(timerf);
3225 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3226 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3227 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3228 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3229 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3231 // Set mesh final color
3232 hud->setSelectionMeshColor(c);
3238 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3240 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3241 PointedThing fauxPointed;
3242 fauxPointed.type = POINTEDTHING_NOTHING;
3243 client->interact(INTERACT_ACTIVATE, fauxPointed);
3247 void Game::handlePointingAtNode(const PointedThing &pointed,
3248 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3250 v3s16 nodepos = pointed.node_undersurface;
3251 v3s16 neighbourpos = pointed.node_abovesurface;
3254 Check information text of node
3257 ClientMap &map = client->getEnv().getClientMap();
3259 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3260 && !runData.digging_blocked
3261 && client->checkPrivilege("interact")) {
3262 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3265 // This should be done after digging handling
3266 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3269 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3270 meta->getString("infotext"))));
3272 MapNode n = map.getNode(nodepos);
3274 if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
3275 m_game_ui->setInfoText(L"Unknown node: " +
3276 utf8_to_wide(nodedef_manager->get(n).name));
3280 if ((wasKeyPressed(KeyType::PLACE) ||
3281 runData.repeat_place_timer >= m_repeat_place_time) &&
3282 client->checkPrivilege("interact")) {
3283 runData.repeat_place_timer = 0;
3284 infostream << "Place button pressed while looking at ground" << std::endl;
3286 // Placing animation (always shown for feedback)
3287 camera->setDigging(1);
3289 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3291 // If the wielded item has node placement prediction,
3293 // And also set the sound and send the interact
3294 // But first check for meta formspec and rightclickable
3295 auto &def = selected_item.getDefinition(itemdef_manager);
3296 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3299 if (placed && client->modsLoaded())
3300 client->getScript()->on_placenode(pointed, def);
3304 bool Game::nodePlacement(const ItemDefinition &selected_def,
3305 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3306 const PointedThing &pointed, const NodeMetadata *meta)
3308 const auto &prediction = selected_def.node_placement_prediction;
3310 const NodeDefManager *nodedef = client->ndef();
3311 ClientMap &map = client->getEnv().getClientMap();
3313 bool is_valid_position;
3315 node = map.getNode(nodepos, &is_valid_position);
3316 if (!is_valid_position) {
3317 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3322 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3323 && !isKeyDown(KeyType::SNEAK)) {
3324 // on_rightclick callbacks are called anyway
3325 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3326 client->interact(INTERACT_PLACE, pointed);
3328 infostream << "Launching custom inventory view" << std::endl;
3330 InventoryLocation inventoryloc;
3331 inventoryloc.setNodeMeta(nodepos);
3333 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3334 &client->getEnv().getClientMap(), nodepos);
3335 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3337 auto *&formspec = m_game_ui->updateFormspec("");
3338 GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
3339 txt_dst, client->getFormspecPrepend(), sound);
3341 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3345 // on_rightclick callback
3346 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3347 !isKeyDown(KeyType::SNEAK))) {
3349 client->interact(INTERACT_PLACE, pointed);
3353 verbosestream << "Node placement prediction for "
3354 << selected_def.name << " is " << prediction << std::endl;
3355 v3s16 p = neighbourpos;
3357 // Place inside node itself if buildable_to
3358 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3359 if (is_valid_position) {
3360 if (nodedef->get(n_under).buildable_to) {
3363 node = map.getNode(p, &is_valid_position);
3364 if (is_valid_position && !nodedef->get(node).buildable_to) {
3365 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3367 client->interact(INTERACT_PLACE, pointed);
3373 // Find id of predicted node
3375 bool found = nodedef->getId(prediction, id);
3378 errorstream << "Node placement prediction failed for "
3379 << selected_def.name << " (places " << prediction
3380 << ") - Name not known" << std::endl;
3381 // Handle this as if prediction was empty
3383 client->interact(INTERACT_PLACE, pointed);
3387 const ContentFeatures &predicted_f = nodedef->get(id);
3389 // Predict param2 for facedir and wallmounted nodes
3390 // Compare core.item_place_node() for what the server does
3393 const u8 place_param2 = selected_def.place_param2;
3396 param2 = place_param2;
3397 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3398 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3399 v3s16 dir = nodepos - neighbourpos;
3401 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3402 param2 = dir.Y < 0 ? 1 : 0;
3403 } else if (abs(dir.X) > abs(dir.Z)) {
3404 param2 = dir.X < 0 ? 3 : 2;
3406 param2 = dir.Z < 0 ? 5 : 4;
3408 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3409 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3410 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3412 if (abs(dir.X) > abs(dir.Z)) {
3413 param2 = dir.X < 0 ? 3 : 1;
3415 param2 = dir.Z < 0 ? 2 : 0;
3419 // Check attachment if node is in group attached_node
3420 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3421 const static v3s16 wallmounted_dirs[8] = {
3431 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3432 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3433 pp = p + wallmounted_dirs[param2];
3435 pp = p + v3s16(0, -1, 0);
3437 if (!nodedef->get(map.getNode(pp)).walkable) {
3438 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3440 client->interact(INTERACT_PLACE, pointed);
3446 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3447 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3448 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3449 const auto &indexstr = selected_item.metadata.
3450 getString("palette_index", 0);
3451 if (!indexstr.empty()) {
3452 s32 index = mystoi(indexstr);
3453 if (predicted_f.param_type_2 == CPT2_COLOR) {
3455 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3456 // param2 = pure palette index + other
3457 param2 = (index & 0xf8) | (param2 & 0x07);
3458 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3459 // param2 = pure palette index + other
3460 param2 = (index & 0xe0) | (param2 & 0x1f);
3465 // Add node to client map
3466 MapNode n(id, 0, param2);
3469 LocalPlayer *player = client->getEnv().getLocalPlayer();
3471 // Dont place node when player would be inside new node
3472 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3473 if (!nodedef->get(n).walkable ||
3474 g_settings->getBool("enable_build_where_you_stand") ||
3475 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3476 (nodedef->get(n).walkable &&
3477 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3478 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3479 // This triggers the required mesh update too
3480 client->addNode(p, n);
3482 client->interact(INTERACT_PLACE, pointed);
3483 // A node is predicted, also play a sound
3484 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3487 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3490 } catch (const InvalidPositionException &e) {
3491 errorstream << "Node placement prediction failed for "
3492 << selected_def.name << " (places "
3493 << prediction << ") - Position not loaded" << std::endl;
3494 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3499 void Game::handlePointingAtObject(const PointedThing &pointed,
3500 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3502 std::wstring infotext = unescape_translate(
3503 utf8_to_wide(runData.selected_object->infoText()));
3506 if (!infotext.empty()) {
3509 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3512 m_game_ui->setInfoText(infotext);
3514 if (isKeyDown(KeyType::DIG)) {
3515 bool do_punch = false;
3516 bool do_punch_damage = false;
3518 if (runData.object_hit_delay_timer <= 0.0) {
3520 do_punch_damage = true;
3521 runData.object_hit_delay_timer = object_hit_delay;
3524 if (wasKeyPressed(KeyType::DIG))
3528 infostream << "Punched object" << std::endl;
3529 runData.punching = true;
3532 if (do_punch_damage) {
3533 // Report direct punch
3534 v3f objpos = runData.selected_object->getPosition();
3535 v3f dir = (objpos - player_position).normalize();
3537 bool disable_send = runData.selected_object->directReportPunch(
3538 dir, &tool_item, runData.time_from_last_punch);
3539 runData.time_from_last_punch = 0;
3542 client->interact(INTERACT_START_DIGGING, pointed);
3544 } else if (wasKeyDown(KeyType::PLACE)) {
3545 infostream << "Pressed place button while pointing at object" << std::endl;
3546 client->interact(INTERACT_PLACE, pointed); // place
3551 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3552 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3554 // See also: serverpackethandle.cpp, action == 2
3555 LocalPlayer *player = client->getEnv().getLocalPlayer();
3556 ClientMap &map = client->getEnv().getClientMap();
3557 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3559 // NOTE: Similar piece of code exists on the server side for
3561 // Get digging parameters
3562 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3563 &selected_item.getToolCapabilities(itemdef_manager));
3565 // If can't dig, try hand
3566 if (!params.diggable) {
3567 params = getDigParams(nodedef_manager->get(n).groups,
3568 &hand_item.getToolCapabilities(itemdef_manager));
3571 if (!params.diggable) {
3572 // I guess nobody will wait for this long
3573 runData.dig_time_complete = 10000000.0;
3575 runData.dig_time_complete = params.time;
3577 if (m_cache_enable_particles) {
3578 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3579 client->getParticleManager()->addNodeParticle(client,
3580 player, nodepos, n, features);
3584 if (!runData.digging) {
3585 infostream << "Started digging" << std::endl;
3586 runData.dig_instantly = runData.dig_time_complete == 0;
3587 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3589 client->interact(INTERACT_START_DIGGING, pointed);
3590 runData.digging = true;
3591 runData.btn_down_for_dig = true;
3594 if (!runData.dig_instantly) {
3595 runData.dig_index = (float)crack_animation_length
3597 / runData.dig_time_complete;
3599 // This is for e.g. torches
3600 runData.dig_index = crack_animation_length;
3603 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3605 if (sound_dig.exists() && params.diggable) {
3606 if (sound_dig.name == "__group") {
3607 if (!params.main_group.empty()) {
3608 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3609 soundmaker->m_player_leftpunch_sound.name =
3610 std::string("default_dig_") +
3614 soundmaker->m_player_leftpunch_sound = sound_dig;
3618 // Don't show cracks if not diggable
3619 if (runData.dig_time_complete >= 100000.0) {
3620 } else if (runData.dig_index < crack_animation_length) {
3621 //TimeTaker timer("client.setTempMod");
3622 //infostream<<"dig_index="<<dig_index<<std::endl;
3623 client->setCrack(runData.dig_index, nodepos);
3625 infostream << "Digging completed" << std::endl;
3626 client->setCrack(-1, v3s16(0, 0, 0));
3628 runData.dig_time = 0;
3629 runData.digging = false;
3630 // we successfully dug, now block it from repeating if we want to be safe
3631 if (g_settings->getBool("safe_dig_and_place"))
3632 runData.digging_blocked = true;
3634 runData.nodig_delay_timer =
3635 runData.dig_time_complete / (float)crack_animation_length;
3637 // We don't want a corresponding delay to very time consuming nodes
3638 // and nodes without digging time (e.g. torches) get a fixed delay.
3639 if (runData.nodig_delay_timer > 0.3)
3640 runData.nodig_delay_timer = 0.3;
3641 else if (runData.dig_instantly)
3642 runData.nodig_delay_timer = 0.15;
3644 bool is_valid_position;
3645 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3646 if (is_valid_position) {
3647 if (client->modsLoaded() &&
3648 client->getScript()->on_dignode(nodepos, wasnode)) {
3652 const ContentFeatures &f = client->ndef()->get(wasnode);
3653 if (f.node_dig_prediction == "air") {
3654 client->removeNode(nodepos);
3655 } else if (!f.node_dig_prediction.empty()) {
3657 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3659 client->addNode(nodepos, id, true);
3661 // implicit else: no prediction
3664 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3666 if (m_cache_enable_particles) {
3667 const ContentFeatures &features =
3668 client->getNodeDefManager()->get(wasnode);
3669 client->getParticleManager()->addDiggingParticles(client,
3670 player, nodepos, wasnode, features);
3674 // Send event to trigger sound
3675 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3678 if (runData.dig_time_complete < 100000.0) {
3679 runData.dig_time += dtime;
3681 runData.dig_time = 0;
3682 client->setCrack(-1, nodepos);
3685 camera->setDigging(0); // Dig animation
3688 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3689 const CameraOrientation &cam)
3691 TimeTaker tt_update("Game::updateFrame()");
3692 LocalPlayer *player = client->getEnv().getLocalPlayer();
3698 if (draw_control->range_all) {
3699 runData.fog_range = 100000 * BS;
3701 runData.fog_range = draw_control->wanted_range * BS;
3705 Calculate general brightness
3707 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3708 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3709 float direct_brightness;
3712 if (m_cache_enable_noclip && m_cache_enable_free_move) {
3713 direct_brightness = time_brightness;
3714 sunlight_seen = true;
3716 float old_brightness = sky->getBrightness();
3717 direct_brightness = client->getEnv().getClientMap()
3718 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3719 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3723 float time_of_day_smooth = runData.time_of_day_smooth;
3724 float time_of_day = client->getEnv().getTimeOfDayF();
3726 static const float maxsm = 0.05f;
3727 static const float todsm = 0.05f;
3729 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3730 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3731 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3732 time_of_day_smooth = time_of_day;
3734 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3735 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3736 + (time_of_day + 1.0) * todsm;
3738 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3739 + time_of_day * todsm;
3741 runData.time_of_day_smooth = time_of_day_smooth;
3743 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3744 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3745 player->getPitch());
3751 if (sky->getCloudsVisible()) {
3752 clouds->setVisible(true);
3753 clouds->step(dtime);
3754 // camera->getPosition is not enough for 3rd person views
3755 v3f camera_node_position = camera->getCameraNode()->getPosition();
3756 v3s16 camera_offset = camera->getOffset();
3757 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3758 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3759 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3760 clouds->update(camera_node_position,
3761 sky->getCloudColor());
3762 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3763 // if inside clouds, and fog enabled, use that as sky
3765 video::SColor clouds_dark = clouds->getColor()
3766 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3767 sky->overrideColors(clouds_dark, clouds->getColor());
3768 sky->setInClouds(true);
3769 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3770 // do not draw clouds after all
3771 clouds->setVisible(false);
3774 clouds->setVisible(false);
3781 client->getParticleManager()->step(dtime);
3787 if (m_cache_enable_fog) {
3790 video::EFT_FOG_LINEAR,
3791 runData.fog_range * m_cache_fog_start,
3792 runData.fog_range * 1.0,
3800 video::EFT_FOG_LINEAR,
3810 Get chat messages from client
3813 v2u32 screensize = driver->getScreenSize();
3815 updateChat(dtime, screensize);
3821 if (player->getWieldIndex() != runData.new_playeritem)
3822 client->setPlayerItem(runData.new_playeritem);
3824 if (client->updateWieldedItem()) {
3825 // Update wielded tool
3826 ItemStack selected_item, hand_item;
3827 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3828 camera->wield(tool_item);
3832 Update block draw list every 200ms or when camera direction has
3835 runData.update_draw_list_timer += dtime;
3837 v3f camera_direction = camera->getDirection();
3838 if (runData.update_draw_list_timer >= 0.2
3839 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3840 || m_camera_offset_changed) {
3841 runData.update_draw_list_timer = 0;
3842 client->getEnv().getClientMap().updateDrawList();
3843 runData.update_draw_list_last_cam_dir = camera_direction;
3846 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3849 make sure menu is on top
3850 1. Delete formspec menu reference if menu was removed
3851 2. Else, make sure formspec menu is on top
3853 auto formspec = m_game_ui->getFormspecGUI();
3854 do { // breakable. only runs for one iteration
3858 if (formspec->getReferenceCount() == 1) {
3859 m_game_ui->deleteFormspec();
3863 auto &loc = formspec->getFormspecLocation();
3864 if (loc.type == InventoryLocation::NODEMETA) {
3865 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3866 if (!meta || meta->getString("formspec").empty()) {
3867 formspec->quitMenu();
3873 guiroot->bringToFront(formspec);
3879 const video::SColor &skycolor = sky->getSkyColor();
3881 TimeTaker tt_draw("Draw scene");
3882 driver->beginScene(true, true, skycolor);
3884 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3885 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3886 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3887 bool draw_crosshair = (
3888 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3889 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3890 #ifdef HAVE_TOUCHSCREENGUI
3892 draw_crosshair = !g_settings->getBool("touchtarget");
3893 } catch (SettingNotFoundException) {
3896 RenderingEngine::draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3897 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3902 if (m_game_ui->m_flags.show_profiler_graph)
3903 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3908 if (runData.damage_flash > 0.0f) {
3909 video::SColor color(runData.damage_flash, 180, 0, 0);
3910 driver->draw2DRectangle(color,
3911 core::rect<s32>(0, 0, screensize.X, screensize.Y),
3914 runData.damage_flash -= 384.0f * dtime;
3920 if (player->hurt_tilt_timer > 0.0f) {
3921 player->hurt_tilt_timer -= dtime * 6.0f;
3923 if (player->hurt_tilt_timer < 0.0f)
3924 player->hurt_tilt_strength = 0.0f;
3928 Update minimap pos and rotation
3930 if (mapper && m_game_ui->m_flags.show_hud) {
3931 mapper->setPos(floatToInt(player->getPosition(), BS));
3932 mapper->setAngle(player->getYaw());
3938 if (++m_reset_HW_buffer_counter > 500) {
3940 Periodically remove all mesh HW buffers.
3942 Work around for a quirk in Irrlicht where a HW buffer is only
3943 released after 20000 iterations (triggered from endScene()).
3945 Without this, all loaded but unused meshes will retain their HW
3946 buffers for at least 5 minutes, at which point looking up the HW buffers
3947 becomes a bottleneck and the framerate drops (as much as 30%).
3949 Tests showed that numbers between 50 and 1000 are good, so picked 500.
3950 There are no other public Irrlicht APIs that allow interacting with the
3951 HW buffers without tracking the status of every individual mesh.
3953 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
3955 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
3956 driver->removeAllHardwareBuffers();
3957 m_reset_HW_buffer_counter = 0;
3961 stats->drawtime = tt_draw.stop(true);
3962 g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
3963 g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
3966 /* Log times and stuff for visualization */
3967 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
3969 Profiler::GraphValues values;
3970 g_profiler->graphGet(values);
3976 /****************************************************************************
3978 ****************************************************************************/
3980 /* On some computers framerate doesn't seem to be automatically limited
3982 inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
3984 // not using getRealTime is necessary for wine
3985 device->getTimer()->tick(); // Maker sure device time is up-to-date
3986 u32 time = device->getTimer()->getTime();
3987 u32 last_time = fps_timings->last_time;
3989 if (time > last_time) // Make sure time hasn't overflowed
3990 fps_timings->busy_time = time - last_time;
3992 fps_timings->busy_time = 0;
3994 u32 frametime_min = 1000 / (
3995 device->isWindowFocused() && !g_menumgr.pausesGame()
3996 ? g_settings->getFloat("fps_max")
3997 : g_settings->getFloat("fps_max_unfocused"));
3999 if (fps_timings->busy_time < frametime_min) {
4000 fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
4001 device->sleep(fps_timings->sleep_time);
4003 fps_timings->sleep_time = 0;
4006 /* Get the new value of the device timer. Note that device->sleep() may
4007 * not sleep for the entire requested time as sleep may be interrupted and
4008 * therefore it is arguably more accurate to get the new time from the
4009 * device rather than calculating it by adding sleep_time to time.
4012 device->getTimer()->tick(); // Update device timer
4013 time = device->getTimer()->getTime();
4015 if (time > last_time) // Make sure last_time hasn't overflowed
4016 *dtime = (time - last_time) / 1000.0;
4020 fps_timings->last_time = time;
4023 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4025 const wchar_t *wmsg = wgettext(msg);
4026 RenderingEngine::draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4031 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4033 ((Game *)data)->readSettings();
4036 void Game::readSettings()
4038 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4039 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4040 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4041 m_cache_enable_particles = g_settings->getBool("enable_particles");
4042 m_cache_enable_fog = g_settings->getBool("enable_fog");
4043 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
4044 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4045 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
4047 m_cache_enable_noclip = g_settings->getBool("noclip");
4048 m_cache_enable_free_move = g_settings->getBool("free_move");
4050 m_cache_fog_start = g_settings->getFloat("fog_start");
4052 m_cache_cam_smoothing = 0;
4053 if (g_settings->getBool("cinematic"))
4054 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4056 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4058 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4059 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4060 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4062 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4065 /****************************************************************************/
4066 /****************************************************************************
4068 ****************************************************************************/
4069 /****************************************************************************/
4071 void Game::extendedResourceCleanup()
4073 // Extended resource accounting
4074 infostream << "Irrlicht resources after cleanup:" << std::endl;
4075 infostream << "\tRemaining meshes : "
4076 << RenderingEngine::get_mesh_cache()->getMeshCount() << std::endl;
4077 infostream << "\tRemaining textures : "
4078 << driver->getTextureCount() << std::endl;
4080 for (unsigned int i = 0; i < driver->getTextureCount(); i++) {
4081 irr::video::ITexture *texture = driver->getTextureByIndex(i);
4082 infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str()
4086 clearTextureNameCache();
4087 infostream << "\tRemaining materials: "
4088 << driver-> getMaterialRendererCount()
4089 << " (note: irrlicht doesn't support removing renderers)" << std::endl;
4092 void Game::showDeathFormspec()
4094 static std::string formspec_str =
4095 std::string("formspec_version[1]") +
4097 "bgcolor[#320000b4;true]"
4098 "label[4.85,1.35;" + gettext("You died") + "]"
4099 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4103 /* Note: FormspecFormSource and LocalFormspecHandler *
4104 * are deleted by guiFormSpecMenu */
4105 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4106 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4108 auto *&formspec = m_game_ui->getFormspecGUI();
4109 GUIFormSpecMenu::create(formspec, client, &input->joystick,
4110 fs_src, txt_dst, client->getFormspecPrepend(), sound);
4111 formspec->setFocus("btn_respawn");
4114 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4115 void Game::showPauseMenu()
4118 static const std::string control_text = strgettext("Default Controls:\n"
4119 "No menu visible:\n"
4120 "- single tap: button activate\n"
4121 "- double tap: place/use\n"
4122 "- slide finger: look around\n"
4123 "Menu/Inventory visible:\n"
4124 "- double tap (outside):\n"
4126 "- touch stack, touch slot:\n"
4128 "- touch&drag, tap 2nd finger\n"
4129 " --> place single item to slot\n"
4132 static const std::string control_text_template = strgettext("Controls:\n"
4133 "- %s: move forwards\n"
4134 "- %s: move backwards\n"
4136 "- %s: move right\n"
4137 "- %s: jump/climb up\n"
4140 "- %s: sneak/climb down\n"
4143 "- Mouse: turn/look\n"
4144 "- Mouse wheel: select item\n"
4148 char control_text_buf[600];
4150 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4151 GET_KEY_NAME(keymap_forward),
4152 GET_KEY_NAME(keymap_backward),
4153 GET_KEY_NAME(keymap_left),
4154 GET_KEY_NAME(keymap_right),
4155 GET_KEY_NAME(keymap_jump),
4156 GET_KEY_NAME(keymap_dig),
4157 GET_KEY_NAME(keymap_place),
4158 GET_KEY_NAME(keymap_sneak),
4159 GET_KEY_NAME(keymap_drop),
4160 GET_KEY_NAME(keymap_inventory),
4161 GET_KEY_NAME(keymap_chat)
4164 std::string control_text = std::string(control_text_buf);
4165 str_formspec_escape(control_text);
4168 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4169 std::ostringstream os;
4171 os << "formspec_version[1]" << SIZE_TAG
4172 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4173 << strgettext("Continue") << "]";
4175 if (!simple_singleplayer_mode) {
4176 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4177 << strgettext("Change Password") << "]";
4179 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4184 if (g_settings->getBool("enable_sound")) {
4185 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4186 << strgettext("Sound Volume") << "]";
4189 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4190 << strgettext("Change Keys") << "]";
4192 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4193 << strgettext("Exit to Menu") << "]";
4194 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4195 << strgettext("Exit to OS") << "]"
4196 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4197 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4199 << strgettext("Game info:") << "\n";
4200 const std::string &address = client->getAddressName();
4201 static const std::string mode = strgettext("- Mode: ");
4202 if (!simple_singleplayer_mode) {
4203 Address serverAddress = client->getServerAddress();
4204 if (!address.empty()) {
4205 os << mode << strgettext("Remote server") << "\n"
4206 << strgettext("- Address: ") << address;
4208 os << mode << strgettext("Hosting server");
4210 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4212 os << mode << strgettext("Singleplayer") << "\n";
4214 if (simple_singleplayer_mode || address.empty()) {
4215 static const std::string on = strgettext("On");
4216 static const std::string off = strgettext("Off");
4217 const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
4218 const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
4219 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4220 os << strgettext("- Damage: ") << damage << "\n"
4221 << strgettext("- Creative Mode: ") << creative << "\n";
4222 if (!simple_singleplayer_mode) {
4223 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4224 //~ PvP = Player versus Player
4225 os << strgettext("- PvP: ") << pvp << "\n"
4226 << strgettext("- Public: ") << announced << "\n";
4227 std::string server_name = g_settings->get("server_name");
4228 str_formspec_escape(server_name);
4229 if (announced == on && !server_name.empty())
4230 os << strgettext("- Server Name: ") << server_name;
4237 /* Note: FormspecFormSource and LocalFormspecHandler *
4238 * are deleted by guiFormSpecMenu */
4239 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4240 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4242 auto *&formspec = m_game_ui->getFormspecGUI();
4243 GUIFormSpecMenu::create(formspec, client, &input->joystick,
4244 fs_src, txt_dst, client->getFormspecPrepend(), sound);
4245 formspec->setFocus("btn_continue");
4246 formspec->doPause = true;
4248 if (simple_singleplayer_mode)
4252 /****************************************************************************/
4253 /****************************************************************************
4254 extern function for launching the game
4255 ****************************************************************************/
4256 /****************************************************************************/
4258 void the_game(bool *kill,
4259 InputHandler *input,
4260 const GameStartData &start_data,
4261 std::string &error_message,
4262 ChatBackend &chat_backend,
4263 bool *reconnect_requested) // Used for local game
4267 /* Make a copy of the server address because if a local singleplayer server
4268 * is created then this is updated and we don't want to change the value
4269 * passed to us by the calling function
4274 if (game.startup(kill, input, start_data, error_message,
4275 reconnect_requested, &chat_backend)) {
4279 } catch (SerializationError &e) {
4280 error_message = std::string("A serialization error occurred:\n")
4281 + e.what() + "\n\nThe server is probably "
4282 " running a different version of " PROJECT_NAME_C ".";
4283 errorstream << error_message << std::endl;
4284 } catch (ServerError &e) {
4285 error_message = e.what();
4286 errorstream << "ServerError: " << error_message << std::endl;
4287 } catch (ModError &e) {
4288 error_message = std::string("ModError: ") + e.what() +
4289 strgettext("\nCheck debug.txt for details.");
4290 errorstream << error_message << std::endl;