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 const wchar_t *text = nullptr;
1395 if (simple_singleplayer_mode)
1396 text = wgettext("Singleplayer");
1398 text = wgettext("Multiplayer");
1405 str += driver->getName();
1408 device->setWindowCaption(str.c_str());
1410 LocalPlayer *player = client->getEnv().getLocalPlayer();
1411 player->hurt_tilt_timer = 0;
1412 player->hurt_tilt_strength = 0;
1414 hud = new Hud(guienv, client, player, &player->inventory);
1416 mapper = client->getMinimap();
1418 if (mapper && client->modsLoaded())
1419 client->getScript()->on_minimap_ready(mapper);
1424 bool Game::initGui()
1428 // Remove stale "recent" chat messages from previous connections
1429 chat_backend->clearRecentChat();
1431 // Make sure the size of the recent messages buffer is right
1432 chat_backend->applySettings();
1434 // Chat backend and console
1435 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1436 -1, chat_backend, client, &g_menumgr);
1438 #ifdef HAVE_TOUCHSCREENGUI
1440 if (g_touchscreengui)
1441 g_touchscreengui->show();
1448 bool Game::connectToServer(const GameStartData &start_data,
1449 bool *connect_ok, bool *connection_aborted)
1451 *connect_ok = false; // Let's not be overly optimistic
1452 *connection_aborted = false;
1453 bool local_server_mode = false;
1455 showOverlayMessage(N_("Resolving address..."), 0, 15);
1457 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1460 connect_address.Resolve(start_data.address.c_str());
1462 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1463 //connect_address.Resolve("localhost");
1464 if (connect_address.isIPv6()) {
1465 IPv6AddressBytes addr_bytes;
1466 addr_bytes.bytes[15] = 1;
1467 connect_address.setAddress(&addr_bytes);
1469 connect_address.setAddress(127, 0, 0, 1);
1471 local_server_mode = true;
1473 } catch (ResolveError &e) {
1474 *error_message = std::string("Couldn't resolve address: ") + e.what();
1475 errorstream << *error_message << std::endl;
1479 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1480 *error_message = "Unable to connect to " +
1481 connect_address.serializeString() +
1482 " because IPv6 is disabled";
1483 errorstream << *error_message << std::endl;
1487 client = new Client(start_data.name.c_str(),
1488 start_data.password, start_data.address,
1489 *draw_control, texture_src, shader_src,
1490 itemdef_manager, nodedef_manager, sound, eventmgr,
1491 connect_address.isIPv6(), m_game_ui.get());
1493 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1495 infostream << "Connecting to server at ";
1496 connect_address.print(&infostream);
1497 infostream << std::endl;
1499 client->connect(connect_address,
1500 simple_singleplayer_mode || local_server_mode);
1503 Wait for server to accept connection
1509 FpsControl fps_control = { 0 };
1511 f32 wait_time = 0; // in seconds
1513 fps_control.last_time = RenderingEngine::get_timer_time();
1515 while (RenderingEngine::run()) {
1517 limitFps(&fps_control, &dtime);
1519 // Update client and server
1520 client->step(dtime);
1523 server->step(dtime);
1526 if (client->getState() == LC_Init) {
1532 if (*connection_aborted)
1535 if (client->accessDenied()) {
1536 *error_message = "Access denied. Reason: "
1537 + client->accessDeniedReason();
1538 *reconnect_requested = client->reconnectRequested();
1539 errorstream << *error_message << std::endl;
1543 if (input->cancelPressed()) {
1544 *connection_aborted = true;
1545 infostream << "Connect aborted [Escape]" << std::endl;
1549 if (client->m_is_registration_confirmation_state) {
1550 if (registration_confirmation_shown) {
1551 // Keep drawing the GUI
1552 RenderingEngine::draw_menu_scene(guienv, dtime, true);
1554 registration_confirmation_shown = true;
1555 (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
1556 &g_menumgr, client, start_data.name, start_data.password,
1557 connection_aborted, texture_src))->drop();
1561 // Only time out if we aren't waiting for the server we started
1562 if (!start_data.isSinglePlayer() && wait_time > 10) {
1563 *error_message = "Connection timed out.";
1564 errorstream << *error_message << std::endl;
1569 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1572 } catch (con::PeerNotFoundException &e) {
1573 // TODO: Should something be done here? At least an info/error
1581 bool Game::getServerContent(bool *aborted)
1585 FpsControl fps_control = { 0 };
1586 f32 dtime; // in seconds
1588 fps_control.last_time = RenderingEngine::get_timer_time();
1590 while (RenderingEngine::run()) {
1592 limitFps(&fps_control, &dtime);
1594 // Update client and server
1595 client->step(dtime);
1598 server->step(dtime);
1601 if (client->mediaReceived() && client->itemdefReceived() &&
1602 client->nodedefReceived()) {
1607 if (!checkConnection())
1610 if (client->getState() < LC_Init) {
1611 *error_message = "Client disconnected";
1612 errorstream << *error_message << std::endl;
1616 if (input->cancelPressed()) {
1618 infostream << "Connect aborted [Escape]" << std::endl;
1625 if (!client->itemdefReceived()) {
1626 const wchar_t *text = wgettext("Item definitions...");
1628 RenderingEngine::draw_load_screen(text, guienv, texture_src,
1631 } else if (!client->nodedefReceived()) {
1632 const wchar_t *text = wgettext("Node definitions...");
1634 RenderingEngine::draw_load_screen(text, guienv, texture_src,
1638 std::stringstream message;
1639 std::fixed(message);
1640 message.precision(0);
1641 float receive = client->mediaReceiveProgress() * 100;
1642 message << gettext("Media...");
1644 message << " " << receive << "%";
1645 message.precision(2);
1647 if ((USE_CURL == 0) ||
1648 (!g_settings->getBool("enable_remote_media_server"))) {
1649 float cur = client->getCurRate();
1650 std::string cur_unit = gettext("KiB/s");
1654 cur_unit = gettext("MiB/s");
1657 message << " (" << cur << ' ' << cur_unit << ")";
1660 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1661 RenderingEngine::draw_load_screen(utf8_to_wide(message.str()), guienv,
1662 texture_src, dtime, progress);
1670 /****************************************************************************/
1671 /****************************************************************************
1673 ****************************************************************************/
1674 /****************************************************************************/
1676 inline void Game::updateInteractTimers(f32 dtime)
1678 if (runData.nodig_delay_timer >= 0)
1679 runData.nodig_delay_timer -= dtime;
1681 if (runData.object_hit_delay_timer >= 0)
1682 runData.object_hit_delay_timer -= dtime;
1684 runData.time_from_last_punch += dtime;
1688 /* returns false if game should exit, otherwise true
1690 inline bool Game::checkConnection()
1692 if (client->accessDenied()) {
1693 *error_message = "Access denied. Reason: "
1694 + client->accessDeniedReason();
1695 *reconnect_requested = client->reconnectRequested();
1696 errorstream << *error_message << std::endl;
1704 /* returns false if game should exit, otherwise true
1706 inline bool Game::handleCallbacks()
1708 if (g_gamecallback->disconnect_requested) {
1709 g_gamecallback->disconnect_requested = false;
1713 if (g_gamecallback->changepassword_requested) {
1714 (new GUIPasswordChange(guienv, guiroot, -1,
1715 &g_menumgr, client, texture_src))->drop();
1716 g_gamecallback->changepassword_requested = false;
1719 if (g_gamecallback->changevolume_requested) {
1720 (new GUIVolumeChange(guienv, guiroot, -1,
1721 &g_menumgr, texture_src))->drop();
1722 g_gamecallback->changevolume_requested = false;
1725 if (g_gamecallback->keyconfig_requested) {
1726 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1727 &g_menumgr, texture_src))->drop();
1728 g_gamecallback->keyconfig_requested = false;
1731 if (g_gamecallback->keyconfig_changed) {
1732 input->keycache.populate(); // update the cache with new settings
1733 g_gamecallback->keyconfig_changed = false;
1740 void Game::processQueues()
1742 texture_src->processQueue();
1743 itemdef_manager->processQueue(client);
1744 shader_src->processQueue();
1748 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1751 float profiler_print_interval =
1752 g_settings->getFloat("profiler_print_interval");
1753 bool print_to_log = true;
1755 if (profiler_print_interval == 0) {
1756 print_to_log = false;
1757 profiler_print_interval = 3;
1760 if (profiler_interval.step(dtime, profiler_print_interval)) {
1762 infostream << "Profiler:" << std::endl;
1763 g_profiler->print(infostream);
1766 m_game_ui->updateProfiler();
1767 g_profiler->clear();
1770 // Update update graphs
1771 g_profiler->graphAdd("Time non-rendering [ms]",
1772 draw_times.busy_time - stats.drawtime);
1774 g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time);
1775 g_profiler->graphAdd("FPS", 1.0f / dtime);
1778 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1785 /* Time average and jitter calculation
1787 jp = &stats->dtime_jitter;
1788 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1790 jitter = dtime - jp->avg;
1792 if (jitter > jp->max)
1795 jp->counter += dtime;
1797 if (jp->counter > 0.0) {
1799 jp->max_sample = jp->max;
1800 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1804 /* Busytime average and jitter calculation
1806 jp = &stats->busy_time_jitter;
1807 jp->avg = jp->avg + draw_times.busy_time * 0.02;
1809 jitter = draw_times.busy_time - jp->avg;
1811 if (jitter > jp->max)
1813 if (jitter < jp->min)
1816 jp->counter += dtime;
1818 if (jp->counter > 0.0) {
1820 jp->max_sample = jp->max;
1821 jp->min_sample = jp->min;
1829 /****************************************************************************
1831 ****************************************************************************/
1833 void Game::processUserInput(f32 dtime)
1835 // Reset input if window not active or some menu is active
1836 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1838 #ifdef HAVE_TOUCHSCREENGUI
1839 g_touchscreengui->hide();
1842 #ifdef HAVE_TOUCHSCREENGUI
1843 else if (g_touchscreengui) {
1844 /* on touchscreengui step may generate own input events which ain't
1845 * what we want in case we just did clear them */
1846 g_touchscreengui->step(dtime);
1850 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1851 gui_chat_console->closeConsoleAtOnce();
1854 // Input handler step() (used by the random input generator)
1858 auto formspec = m_game_ui->getFormspecGUI();
1860 formspec->getAndroidUIInput();
1862 handleAndroidChatInput();
1865 // Increase timer for double tap of "keymap_jump"
1866 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1867 runData.jump_timer += dtime;
1870 processItemSelection(&runData.new_playeritem);
1874 void Game::processKeyInput()
1876 if (wasKeyDown(KeyType::DROP)) {
1877 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1878 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1879 toggleAutoforward();
1880 } else if (wasKeyDown(KeyType::BACKWARD)) {
1881 if (g_settings->getBool("continuous_forward"))
1882 toggleAutoforward();
1883 } else if (wasKeyDown(KeyType::INVENTORY)) {
1885 } else if (input->cancelPressed()) {
1887 m_android_chat_open = false;
1889 if (!gui_chat_console->isOpenInhibited()) {
1892 } else if (wasKeyDown(KeyType::CHAT)) {
1893 openConsole(0.2, L"");
1894 } else if (wasKeyDown(KeyType::CMD)) {
1895 openConsole(0.2, L"/");
1896 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1897 if (client->modsLoaded())
1898 openConsole(0.2, L".");
1900 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1901 } else if (wasKeyDown(KeyType::CONSOLE)) {
1902 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1903 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1905 } else if (wasKeyDown(KeyType::JUMP)) {
1906 toggleFreeMoveAlt();
1907 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1909 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1911 } else if (wasKeyDown(KeyType::NOCLIP)) {
1914 } else if (wasKeyDown(KeyType::MUTE)) {
1915 if (g_settings->getBool("enable_sound")) {
1916 bool new_mute_sound = !g_settings->getBool("mute_sound");
1917 g_settings->setBool("mute_sound", new_mute_sound);
1919 m_game_ui->showTranslatedStatusText("Sound muted");
1921 m_game_ui->showTranslatedStatusText("Sound unmuted");
1923 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1925 } else if (wasKeyDown(KeyType::INC_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");
1937 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1938 if (g_settings->getBool("enable_sound")) {
1939 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1941 g_settings->setFloat("sound_volume", new_volume);
1942 const wchar_t *str = wgettext("Volume changed to %d%%");
1943 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1945 m_game_ui->showStatusText(buf);
1947 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1950 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1951 || wasKeyDown(KeyType::DEC_VOLUME)) {
1952 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1954 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1956 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1957 client->makeScreenshot();
1958 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1959 m_game_ui->toggleHud();
1960 } else if (wasKeyDown(KeyType::MINIMAP)) {
1961 toggleMinimap(isKeyDown(KeyType::SNEAK));
1962 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1963 m_game_ui->toggleChat();
1964 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1966 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1967 toggleUpdateCamera();
1968 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1970 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1971 m_game_ui->toggleProfiler();
1972 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1973 increaseViewRange();
1974 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1975 decreaseViewRange();
1976 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1977 toggleFullViewRange();
1978 } else if (wasKeyDown(KeyType::ZOOM)) {
1980 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1982 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1984 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1986 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1990 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1991 runData.reset_jump_timer = false;
1992 runData.jump_timer = 0.0f;
1995 if (quicktune->hasMessage()) {
1996 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2000 void Game::processItemSelection(u16 *new_playeritem)
2002 LocalPlayer *player = client->getEnv().getLocalPlayer();
2004 /* Item selection using mouse wheel
2006 *new_playeritem = player->getWieldIndex();
2008 s32 wheel = input->getMouseWheel();
2009 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2010 player->hud_hotbar_itemcount - 1);
2014 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2017 if (wasKeyDown(KeyType::HOTBAR_PREV))
2021 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2023 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2026 /* Item selection using hotbar slot keys
2028 for (u16 i = 0; i <= max_item; i++) {
2029 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2030 *new_playeritem = i;
2037 void Game::dropSelectedItem(bool single_item)
2039 IDropAction *a = new IDropAction();
2040 a->count = single_item ? 1 : 0;
2041 a->from_inv.setCurrentPlayer();
2042 a->from_list = "main";
2043 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2044 client->inventoryAction(a);
2048 void Game::openInventory()
2051 * Don't permit to open inventory is CAO or player doesn't exists.
2052 * This prevent showing an empty inventory at player load
2055 LocalPlayer *player = client->getEnv().getLocalPlayer();
2056 if (!player || !player->getCAO())
2059 infostream << "Game: Launching inventory" << std::endl;
2061 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2063 InventoryLocation inventoryloc;
2064 inventoryloc.setCurrentPlayer();
2066 if (!client->modsLoaded()
2067 || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2068 TextDest *txt_dst = new TextDestPlayerInventory(client);
2069 auto *&formspec = m_game_ui->updateFormspec("");
2070 GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
2071 txt_dst, client->getFormspecPrepend(), sound);
2073 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2078 void Game::openConsole(float scale, const wchar_t *line)
2080 assert(scale > 0.0f && scale <= 1.0f);
2083 porting::showInputDialog(gettext("ok"), "", "", 2);
2084 m_android_chat_open = true;
2086 if (gui_chat_console->isOpenInhibited())
2088 gui_chat_console->openConsole(scale);
2090 gui_chat_console->setCloseOnEnter(true);
2091 gui_chat_console->replaceAndAddToHistory(line);
2097 void Game::handleAndroidChatInput()
2099 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2100 std::string text = porting::getInputDialogValue();
2101 client->typeChatMessage(utf8_to_wide(text));
2102 m_android_chat_open = false;
2108 void Game::toggleFreeMove()
2110 bool free_move = !g_settings->getBool("free_move");
2111 g_settings->set("free_move", bool_to_cstr(free_move));
2114 if (client->checkPrivilege("fly")) {
2115 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2117 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2120 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2124 void Game::toggleFreeMoveAlt()
2126 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2129 runData.reset_jump_timer = true;
2133 void Game::togglePitchMove()
2135 bool pitch_move = !g_settings->getBool("pitch_move");
2136 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2139 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2141 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2146 void Game::toggleFast()
2148 bool fast_move = !g_settings->getBool("fast_move");
2149 bool has_fast_privs = client->checkPrivilege("fast");
2150 g_settings->set("fast_move", bool_to_cstr(fast_move));
2153 if (has_fast_privs) {
2154 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2156 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2159 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2163 m_cache_hold_aux1 = fast_move && has_fast_privs;
2168 void Game::toggleNoClip()
2170 bool noclip = !g_settings->getBool("noclip");
2171 g_settings->set("noclip", bool_to_cstr(noclip));
2174 if (client->checkPrivilege("noclip")) {
2175 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2177 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2180 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2184 void Game::toggleCinematic()
2186 bool cinematic = !g_settings->getBool("cinematic");
2187 g_settings->set("cinematic", bool_to_cstr(cinematic));
2190 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2192 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2195 // Autoforward by toggling continuous forward.
2196 void Game::toggleAutoforward()
2198 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2199 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2201 if (autorun_enabled)
2202 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2204 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2207 void Game::toggleMinimap(bool shift_pressed)
2209 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2213 mapper->toggleMinimapShape();
2217 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2219 // Not so satisying code to keep compatibility with old fixed mode system
2221 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2223 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2224 m_game_ui->m_flags.show_minimap = false;
2227 // If radar is disabled, try to find a non radar mode or fall back to 0
2228 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2229 while (mapper->getModeIndex() &&
2230 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2233 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2237 // End of 'not so satifying code'
2238 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2239 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2240 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2242 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2245 void Game::toggleFog()
2247 bool fog_enabled = g_settings->getBool("enable_fog");
2248 g_settings->setBool("enable_fog", !fog_enabled);
2250 m_game_ui->showTranslatedStatusText("Fog disabled");
2252 m_game_ui->showTranslatedStatusText("Fog enabled");
2256 void Game::toggleDebug()
2258 // Initial / 4x toggle: Chat only
2259 // 1x toggle: Debug text with chat
2260 // 2x toggle: Debug text with profiler graph
2261 // 3x toggle: Debug text and wireframe
2262 if (!m_game_ui->m_flags.show_debug) {
2263 m_game_ui->m_flags.show_debug = true;
2264 m_game_ui->m_flags.show_profiler_graph = false;
2265 draw_control->show_wireframe = false;
2266 m_game_ui->showTranslatedStatusText("Debug info shown");
2267 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2268 m_game_ui->m_flags.show_profiler_graph = true;
2269 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2270 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2271 m_game_ui->m_flags.show_profiler_graph = false;
2272 draw_control->show_wireframe = true;
2273 m_game_ui->showTranslatedStatusText("Wireframe shown");
2275 m_game_ui->m_flags.show_debug = false;
2276 m_game_ui->m_flags.show_profiler_graph = false;
2277 draw_control->show_wireframe = false;
2278 if (client->checkPrivilege("debug")) {
2279 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2281 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2287 void Game::toggleUpdateCamera()
2289 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2290 if (m_flags.disable_camera_update)
2291 m_game_ui->showTranslatedStatusText("Camera update disabled");
2293 m_game_ui->showTranslatedStatusText("Camera update enabled");
2297 void Game::increaseViewRange()
2299 s16 range = g_settings->getS16("viewing_range");
2300 s16 range_new = range + 10;
2304 if (range_new > 4000) {
2306 str = wgettext("Viewing range is at maximum: %d");
2307 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2309 m_game_ui->showStatusText(buf);
2312 str = wgettext("Viewing range changed to %d");
2313 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2315 m_game_ui->showStatusText(buf);
2317 g_settings->set("viewing_range", itos(range_new));
2321 void Game::decreaseViewRange()
2323 s16 range = g_settings->getS16("viewing_range");
2324 s16 range_new = range - 10;
2328 if (range_new < 20) {
2330 str = wgettext("Viewing range is at minimum: %d");
2331 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2333 m_game_ui->showStatusText(buf);
2335 str = wgettext("Viewing range changed to %d");
2336 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2338 m_game_ui->showStatusText(buf);
2340 g_settings->set("viewing_range", itos(range_new));
2344 void Game::toggleFullViewRange()
2346 draw_control->range_all = !draw_control->range_all;
2347 if (draw_control->range_all)
2348 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2350 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2354 void Game::checkZoomEnabled()
2356 LocalPlayer *player = client->getEnv().getLocalPlayer();
2357 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2358 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2361 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2363 if ((device->isWindowActive() && device->isWindowFocused()
2364 && !isMenuActive()) || input->isRandom()) {
2367 if (!input->isRandom()) {
2368 // Mac OSX gets upset if this is set every frame
2369 if (device->getCursorControl()->isVisible())
2370 device->getCursorControl()->setVisible(false);
2374 if (m_first_loop_after_window_activation) {
2375 m_first_loop_after_window_activation = false;
2377 input->setMousePos(driver->getScreenSize().Width / 2,
2378 driver->getScreenSize().Height / 2);
2380 updateCameraOrientation(cam, dtime);
2386 // Mac OSX gets upset if this is set every frame
2387 if (!device->getCursorControl()->isVisible())
2388 device->getCursorControl()->setVisible(true);
2391 m_first_loop_after_window_activation = true;
2396 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2397 // responsiveness independently of FOV.
2398 f32 Game::getSensitivityScaleFactor() const
2400 f32 fov_y = client->getCamera()->getFovY();
2402 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2403 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2405 return tan(fov_y / 2.0f) * 1.3763818698f;
2408 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2410 #ifdef HAVE_TOUCHSCREENGUI
2411 if (g_touchscreengui) {
2412 cam->camera_yaw += g_touchscreengui->getYawChange();
2413 cam->camera_pitch = g_touchscreengui->getPitch();
2416 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2417 v2s32 dist = input->getMousePos() - center;
2419 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2423 f32 sens_scale = getSensitivityScaleFactor();
2424 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2425 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2427 if (dist.X != 0 || dist.Y != 0)
2428 input->setMousePos(center.X, center.Y);
2429 #ifdef HAVE_TOUCHSCREENGUI
2433 if (m_cache_enable_joysticks) {
2434 f32 sens_scale = getSensitivityScaleFactor();
2435 f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime * sens_scale;
2436 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2437 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2440 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2444 void Game::updatePlayerControl(const CameraOrientation &cam)
2446 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2448 // DO NOT use the isKeyDown method for the forward, backward, left, right
2449 // buttons, as the code that uses the controls needs to be able to
2450 // distinguish between the two in order to know when to use joysticks.
2452 PlayerControl control(
2453 input->isKeyDown(KeyType::FORWARD),
2454 input->isKeyDown(KeyType::BACKWARD),
2455 input->isKeyDown(KeyType::LEFT),
2456 input->isKeyDown(KeyType::RIGHT),
2457 isKeyDown(KeyType::JUMP),
2458 isKeyDown(KeyType::AUX1),
2459 isKeyDown(KeyType::SNEAK),
2460 isKeyDown(KeyType::ZOOM),
2461 isKeyDown(KeyType::DIG),
2462 isKeyDown(KeyType::PLACE),
2465 input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE),
2466 input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE)
2469 u32 keypress_bits = (
2470 ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) |
2471 ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) |
2472 ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) |
2473 ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) |
2474 ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) |
2475 ( (u32)(isKeyDown(KeyType::AUX1) & 0x1) << 5) |
2476 ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) |
2477 ( (u32)(isKeyDown(KeyType::DIG) & 0x1) << 7) |
2478 ( (u32)(isKeyDown(KeyType::PLACE) & 0x1) << 8) |
2479 ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9)
2483 /* For Android, simulate holding down AUX1 (fast move) if the user has
2484 * the fast_move setting toggled on. If there is an aux1 key defined for
2485 * Android then its meaning is inverted (i.e. holding aux1 means walk and
2488 if (m_cache_hold_aux1) {
2489 control.aux1 = control.aux1 ^ true;
2490 keypress_bits ^= ((u32)(1U << 5));
2494 LocalPlayer *player = client->getEnv().getLocalPlayer();
2496 // autojump if set: simulate "jump" key
2497 if (player->getAutojump()) {
2498 control.jump = true;
2499 keypress_bits |= 1U << 4;
2502 // autoforward if set: simulate "up" key
2503 if (player->getPlayerSettings().continuous_forward &&
2504 client->activeObjectsReceived() && !player->isDead()) {
2506 keypress_bits |= 1U << 0;
2509 client->setPlayerControl(control);
2510 player->keyPressed = keypress_bits;
2516 inline void Game::step(f32 *dtime)
2518 bool can_be_and_is_paused =
2519 (simple_singleplayer_mode && g_menumgr.pausesGame());
2521 if (can_be_and_is_paused) { // This is for a singleplayer server
2522 *dtime = 0; // No time passes
2524 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
2528 server->step(*dtime);
2530 client->step(*dtime);
2534 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2537 for (auto &&child: node->getChildren())
2538 pauseNodeAnimation(paused, child);
2539 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2541 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2542 float speed = animated_node->getAnimationSpeed();
2545 paused.push_back({grab(animated_node), speed});
2546 animated_node->setAnimationSpeed(0.0f);
2549 void Game::pauseAnimation()
2551 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2554 void Game::resumeAnimation()
2556 for (auto &&pair: paused_animated_nodes)
2557 pair.first->setAnimationSpeed(pair.second);
2558 paused_animated_nodes.clear();
2561 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2562 {&Game::handleClientEvent_None},
2563 {&Game::handleClientEvent_PlayerDamage},
2564 {&Game::handleClientEvent_PlayerForceMove},
2565 {&Game::handleClientEvent_Deathscreen},
2566 {&Game::handleClientEvent_ShowFormSpec},
2567 {&Game::handleClientEvent_ShowLocalFormSpec},
2568 {&Game::handleClientEvent_HandleParticleEvent},
2569 {&Game::handleClientEvent_HandleParticleEvent},
2570 {&Game::handleClientEvent_HandleParticleEvent},
2571 {&Game::handleClientEvent_HudAdd},
2572 {&Game::handleClientEvent_HudRemove},
2573 {&Game::handleClientEvent_HudChange},
2574 {&Game::handleClientEvent_SetSky},
2575 {&Game::handleClientEvent_SetSun},
2576 {&Game::handleClientEvent_SetMoon},
2577 {&Game::handleClientEvent_SetStars},
2578 {&Game::handleClientEvent_OverrideDayNigthRatio},
2579 {&Game::handleClientEvent_CloudParams},
2582 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2584 FATAL_ERROR("ClientEvent type None received");
2587 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2589 if (client->modsLoaded())
2590 client->getScript()->on_damage_taken(event->player_damage.amount);
2592 // Damage flash and hurt tilt are not used at death
2593 if (client->getHP() > 0) {
2594 runData.damage_flash += 95.0f + 3.2f * event->player_damage.amount;
2595 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2597 LocalPlayer *player = client->getEnv().getLocalPlayer();
2599 player->hurt_tilt_timer = 1.5f;
2600 player->hurt_tilt_strength =
2601 rangelim(event->player_damage.amount / 4.0f, 1.0f, 4.0f);
2604 // Play damage sound
2605 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2608 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2610 cam->camera_yaw = event->player_force_move.yaw;
2611 cam->camera_pitch = event->player_force_move.pitch;
2614 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2616 // If client scripting is enabled, deathscreen is handled by CSM code in
2617 // builtin/client/init.lua
2618 if (client->modsLoaded())
2619 client->getScript()->on_death();
2621 showDeathFormspec();
2623 /* Handle visualization */
2624 LocalPlayer *player = client->getEnv().getLocalPlayer();
2625 runData.damage_flash = 0;
2626 player->hurt_tilt_timer = 0;
2627 player->hurt_tilt_strength = 0;
2630 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2632 if (event->show_formspec.formspec->empty()) {
2633 auto formspec = m_game_ui->getFormspecGUI();
2634 if (formspec && (event->show_formspec.formname->empty()
2635 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2636 formspec->quitMenu();
2639 FormspecFormSource *fs_src =
2640 new FormspecFormSource(*(event->show_formspec.formspec));
2641 TextDestPlayerInventory *txt_dst =
2642 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2644 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2645 GUIFormSpecMenu::create(formspec, client, &input->joystick,
2646 fs_src, txt_dst, client->getFormspecPrepend(), sound);
2649 delete event->show_formspec.formspec;
2650 delete event->show_formspec.formname;
2653 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2655 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2656 LocalFormspecHandler *txt_dst =
2657 new LocalFormspecHandler(*event->show_formspec.formname, client);
2658 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, &input->joystick,
2659 fs_src, txt_dst, client->getFormspecPrepend(), sound);
2661 delete event->show_formspec.formspec;
2662 delete event->show_formspec.formname;
2665 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2666 CameraOrientation *cam)
2668 LocalPlayer *player = client->getEnv().getLocalPlayer();
2669 client->getParticleManager()->handleParticleEvent(event, client, player);
2672 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2674 LocalPlayer *player = client->getEnv().getLocalPlayer();
2676 u32 server_id = event->hudadd->server_id;
2677 // ignore if we already have a HUD with that ID
2678 auto i = m_hud_server_to_client.find(server_id);
2679 if (i != m_hud_server_to_client.end()) {
2680 delete event->hudadd;
2684 HudElement *e = new HudElement;
2685 e->type = static_cast<HudElementType>(event->hudadd->type);
2686 e->pos = event->hudadd->pos;
2687 e->name = event->hudadd->name;
2688 e->scale = event->hudadd->scale;
2689 e->text = event->hudadd->text;
2690 e->number = event->hudadd->number;
2691 e->item = event->hudadd->item;
2692 e->dir = event->hudadd->dir;
2693 e->align = event->hudadd->align;
2694 e->offset = event->hudadd->offset;
2695 e->world_pos = event->hudadd->world_pos;
2696 e->size = event->hudadd->size;
2697 e->z_index = event->hudadd->z_index;
2698 e->text2 = event->hudadd->text2;
2699 m_hud_server_to_client[server_id] = player->addHud(e);
2701 delete event->hudadd;
2704 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2706 LocalPlayer *player = client->getEnv().getLocalPlayer();
2708 auto i = m_hud_server_to_client.find(event->hudrm.id);
2709 if (i != m_hud_server_to_client.end()) {
2710 HudElement *e = player->removeHud(i->second);
2712 m_hud_server_to_client.erase(i);
2717 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2719 LocalPlayer *player = client->getEnv().getLocalPlayer();
2721 HudElement *e = nullptr;
2723 auto i = m_hud_server_to_client.find(event->hudchange->id);
2724 if (i != m_hud_server_to_client.end()) {
2725 e = player->getHud(i->second);
2729 delete event->hudchange;
2733 #define CASE_SET(statval, prop, dataprop) \
2735 e->prop = event->hudchange->dataprop; \
2738 switch (event->hudchange->stat) {
2739 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2741 CASE_SET(HUD_STAT_NAME, name, sdata);
2743 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2745 CASE_SET(HUD_STAT_TEXT, text, sdata);
2747 CASE_SET(HUD_STAT_NUMBER, number, data);
2749 CASE_SET(HUD_STAT_ITEM, item, data);
2751 CASE_SET(HUD_STAT_DIR, dir, data);
2753 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2755 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2757 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2759 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2761 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2763 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2768 delete event->hudchange;
2771 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2773 sky->setVisible(false);
2774 // Whether clouds are visible in front of a custom skybox.
2775 sky->setCloudsEnabled(event->set_sky->clouds);
2781 // Clear the old textures out in case we switch rendering type.
2782 sky->clearSkyboxTextures();
2783 // Handle according to type
2784 if (event->set_sky->type == "regular") {
2785 // Shows the mesh skybox
2786 sky->setVisible(true);
2787 // Update mesh based skybox colours if applicable.
2788 sky->setSkyColors(event->set_sky->sky_color);
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 } else if (event->set_sky->type == "skybox" &&
2795 event->set_sky->textures.size() == 6) {
2796 // Disable the dyanmic mesh skybox:
2797 sky->setVisible(false);
2799 sky->setFallbackBgColor(event->set_sky->bgcolor);
2800 // Set sunrise and sunset fog tinting:
2801 sky->setHorizonTint(
2802 event->set_sky->fog_sun_tint,
2803 event->set_sky->fog_moon_tint,
2804 event->set_sky->fog_tint_type
2806 // Add textures to skybox.
2807 for (int i = 0; i < 6; i++)
2808 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2810 // Handle everything else as plain color.
2811 if (event->set_sky->type != "plain")
2812 infostream << "Unknown sky type: "
2813 << (event->set_sky->type) << std::endl;
2814 sky->setVisible(false);
2815 sky->setFallbackBgColor(event->set_sky->bgcolor);
2816 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2817 sky->setHorizonTint(
2818 event->set_sky->bgcolor,
2819 event->set_sky->bgcolor,
2823 delete event->set_sky;
2826 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2828 sky->setSunVisible(event->sun_params->visible);
2829 sky->setSunTexture(event->sun_params->texture,
2830 event->sun_params->tonemap, texture_src);
2831 sky->setSunScale(event->sun_params->scale);
2832 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2833 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2834 delete event->sun_params;
2837 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2839 sky->setMoonVisible(event->moon_params->visible);
2840 sky->setMoonTexture(event->moon_params->texture,
2841 event->moon_params->tonemap, texture_src);
2842 sky->setMoonScale(event->moon_params->scale);
2843 delete event->moon_params;
2846 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2848 sky->setStarsVisible(event->star_params->visible);
2849 sky->setStarCount(event->star_params->count, false);
2850 sky->setStarColor(event->star_params->starcolor);
2851 sky->setStarScale(event->star_params->scale);
2852 delete event->star_params;
2855 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2856 CameraOrientation *cam)
2858 client->getEnv().setDayNightRatioOverride(
2859 event->override_day_night_ratio.do_override,
2860 event->override_day_night_ratio.ratio_f * 1000.0f);
2863 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2868 clouds->setDensity(event->cloud_params.density);
2869 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2870 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2871 clouds->setHeight(event->cloud_params.height);
2872 clouds->setThickness(event->cloud_params.thickness);
2873 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2876 void Game::processClientEvents(CameraOrientation *cam)
2878 while (client->hasClientEvents()) {
2879 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2880 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2881 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2882 (this->*evHandler.handler)(event.get(), cam);
2886 void Game::updateChat(f32 dtime, const v2u32 &screensize)
2888 // Get new messages from error log buffer
2889 while (!m_chat_log_buf.empty())
2890 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2892 // Get new messages from client
2893 std::wstring message;
2894 while (client->getChatMessage(message)) {
2895 chat_backend->addUnparsedMessage(message);
2898 // Remove old messages
2899 chat_backend->step(dtime);
2901 // Display all messages in a static text element
2902 m_game_ui->setChatText(chat_backend->getRecentChat(),
2903 chat_backend->getRecentBuffer().getLineCount());
2906 void Game::updateCamera(u32 busy_time, f32 dtime)
2908 LocalPlayer *player = client->getEnv().getLocalPlayer();
2911 For interaction purposes, get info about the held item
2913 - Is it a usable item?
2914 - Can it point to liquids?
2916 ItemStack playeritem;
2918 ItemStack selected, hand;
2919 playeritem = player->getWieldedItem(&selected, &hand);
2922 ToolCapabilities playeritem_toolcap =
2923 playeritem.getToolCapabilities(itemdef_manager);
2925 v3s16 old_camera_offset = camera->getOffset();
2927 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2928 GenericCAO *playercao = player->getCAO();
2930 // If playercao not loaded, don't change camera
2934 camera->toggleCameraMode();
2936 // Make the player visible depending on camera mode.
2937 playercao->updateMeshCulling();
2938 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2941 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2942 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2944 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2945 camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
2946 camera->step(dtime);
2948 v3f camera_position = camera->getPosition();
2949 v3f camera_direction = camera->getDirection();
2950 f32 camera_fov = camera->getFovMax();
2951 v3s16 camera_offset = camera->getOffset();
2953 m_camera_offset_changed = (camera_offset != old_camera_offset);
2955 if (!m_flags.disable_camera_update) {
2956 client->getEnv().getClientMap().updateCamera(camera_position,
2957 camera_direction, camera_fov, camera_offset);
2959 if (m_camera_offset_changed) {
2960 client->updateCameraOffset(camera_offset);
2961 client->getEnv().updateCameraOffset(camera_offset);
2964 clouds->updateCameraOffset(camera_offset);
2970 void Game::updateSound(f32 dtime)
2972 // Update sound listener
2973 v3s16 camera_offset = camera->getOffset();
2974 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
2975 v3f(0, 0, 0), // velocity
2976 camera->getDirection(),
2977 camera->getCameraNode()->getUpVector());
2979 bool mute_sound = g_settings->getBool("mute_sound");
2981 sound->setListenerGain(0.0f);
2983 // Check if volume is in the proper range, else fix it.
2984 float old_volume = g_settings->getFloat("sound_volume");
2985 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
2986 sound->setListenerGain(new_volume);
2988 if (old_volume != new_volume) {
2989 g_settings->setFloat("sound_volume", new_volume);
2993 LocalPlayer *player = client->getEnv().getLocalPlayer();
2995 // Tell the sound maker whether to make footstep sounds
2996 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
2998 // Update sound maker
2999 if (player->makes_footstep_sound)
3000 soundmaker->step(dtime);
3002 ClientMap &map = client->getEnv().getClientMap();
3003 MapNode n = map.getNode(player->getFootstepNodePos());
3004 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3008 void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
3010 LocalPlayer *player = client->getEnv().getLocalPlayer();
3012 const v3f camera_direction = camera->getDirection();
3013 const v3s16 camera_offset = camera->getOffset();
3016 Calculate what block is the crosshair pointing to
3019 ItemStack selected_item, hand_item;
3020 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3022 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3023 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3025 core::line3d<f32> shootline;
3027 switch (camera->getCameraMode()) {
3028 case CAMERA_MODE_FIRST:
3029 // Shoot from camera position, with bobbing
3030 shootline.start = camera->getPosition();
3032 case CAMERA_MODE_THIRD:
3033 // Shoot from player head, no bobbing
3034 shootline.start = camera->getHeadPosition();
3036 case CAMERA_MODE_THIRD_FRONT:
3037 shootline.start = camera->getHeadPosition();
3038 // prevent player pointing anything in front-view
3042 shootline.end = shootline.start + camera_direction * BS * d;
3044 #ifdef HAVE_TOUCHSCREENGUI
3046 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3047 shootline = g_touchscreengui->getShootline();
3048 // Scale shootline to the acual distance the player can reach
3049 shootline.end = shootline.start
3050 + shootline.getVector().normalize() * BS * d;
3051 shootline.start += intToFloat(camera_offset, BS);
3052 shootline.end += intToFloat(camera_offset, BS);
3057 PointedThing pointed = updatePointedThing(shootline,
3058 selected_def.liquids_pointable,
3059 !runData.btn_down_for_dig,
3062 if (pointed != runData.pointed_old) {
3063 infostream << "Pointing at " << pointed.dump() << std::endl;
3064 hud->updateSelectionMesh(camera_offset);
3067 // Allow digging again if button is not pressed
3068 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3069 runData.digging_blocked = false;
3073 - releasing dig button
3074 - pointing away from node
3076 if (runData.digging) {
3077 if (wasKeyReleased(KeyType::DIG)) {
3078 infostream << "Dig button released (stopped digging)" << std::endl;
3079 runData.digging = false;
3080 } else if (pointed != runData.pointed_old) {
3081 if (pointed.type == POINTEDTHING_NODE
3082 && runData.pointed_old.type == POINTEDTHING_NODE
3083 && pointed.node_undersurface
3084 == runData.pointed_old.node_undersurface) {
3085 // Still pointing to the same node, but a different face.
3088 infostream << "Pointing away from node (stopped digging)" << std::endl;
3089 runData.digging = false;
3090 hud->updateSelectionMesh(camera_offset);
3094 if (!runData.digging) {
3095 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3096 client->setCrack(-1, v3s16(0, 0, 0));
3097 runData.dig_time = 0.0;
3099 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3100 // Remove e.g. torches faster when clicking instead of holding dig button
3101 runData.nodig_delay_timer = 0;
3102 runData.dig_instantly = false;
3105 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3106 runData.btn_down_for_dig = false;
3108 runData.punching = false;
3110 soundmaker->m_player_leftpunch_sound.name = "";
3112 // Prepare for repeating, unless we're not supposed to
3113 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3114 runData.repeat_place_timer += dtime;
3116 runData.repeat_place_timer = 0;
3118 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3119 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3120 !client->getScript()->on_item_use(selected_item, pointed)))
3121 client->interact(INTERACT_USE, pointed);
3122 } else if (pointed.type == POINTEDTHING_NODE) {
3123 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3124 } else if (pointed.type == POINTEDTHING_OBJECT) {
3125 v3f player_position = player->getPosition();
3126 handlePointingAtObject(pointed, tool_item, player_position, show_debug);
3127 } else if (isKeyDown(KeyType::DIG)) {
3128 // When button is held down in air, show continuous animation
3129 runData.punching = true;
3130 // Run callback even though item is not usable
3131 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3132 client->getScript()->on_item_use(selected_item, pointed);
3133 } else if (wasKeyPressed(KeyType::PLACE)) {
3134 handlePointingAtNothing(selected_item);
3137 runData.pointed_old = pointed;
3139 if (runData.punching || wasKeyPressed(KeyType::DIG))
3140 camera->setDigging(0); // dig animation
3142 input->clearWasKeyPressed();
3143 input->clearWasKeyReleased();
3144 // Ensure DIG & PLACE are marked as handled
3145 wasKeyDown(KeyType::DIG);
3146 wasKeyDown(KeyType::PLACE);
3148 input->joystick.clearWasKeyPressed(KeyType::DIG);
3149 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3151 input->joystick.clearWasKeyReleased(KeyType::DIG);
3152 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3156 PointedThing Game::updatePointedThing(
3157 const core::line3d<f32> &shootline,
3158 bool liquids_pointable,
3159 bool look_for_object,
3160 const v3s16 &camera_offset)
3162 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3163 selectionboxes->clear();
3164 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3165 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3166 "show_entity_selectionbox");
3168 ClientEnvironment &env = client->getEnv();
3169 ClientMap &map = env.getClientMap();
3170 const NodeDefManager *nodedef = map.getNodeDefManager();
3172 runData.selected_object = NULL;
3173 hud->pointing_at_object = false;
3175 RaycastState s(shootline, look_for_object, liquids_pointable);
3176 PointedThing result;
3177 env.continueRaycast(&s, &result);
3178 if (result.type == POINTEDTHING_OBJECT) {
3179 hud->pointing_at_object = true;
3181 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3182 aabb3f selection_box;
3183 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3184 runData.selected_object->getSelectionBox(&selection_box)) {
3185 v3f pos = runData.selected_object->getPosition();
3186 selectionboxes->push_back(aabb3f(selection_box));
3187 hud->setSelectionPos(pos, camera_offset);
3189 } else if (result.type == POINTEDTHING_NODE) {
3190 // Update selection boxes
3191 MapNode n = map.getNode(result.node_undersurface);
3192 std::vector<aabb3f> boxes;
3193 n.getSelectionBoxes(nodedef, &boxes,
3194 n.getNeighbors(result.node_undersurface, &map));
3197 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3198 i != boxes.end(); ++i) {
3200 box.MinEdge -= v3f(d, d, d);
3201 box.MaxEdge += v3f(d, d, d);
3202 selectionboxes->push_back(box);
3204 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3206 hud->setSelectedFaceNormal(v3f(
3207 result.intersection_normal.X,
3208 result.intersection_normal.Y,
3209 result.intersection_normal.Z));
3212 // Update selection mesh light level and vertex colors
3213 if (!selectionboxes->empty()) {
3214 v3f pf = hud->getSelectionPos();
3215 v3s16 p = floatToInt(pf, BS);
3217 // Get selection mesh light level
3218 MapNode n = map.getNode(p);
3219 u16 node_light = getInteriorLight(n, -1, nodedef);
3220 u16 light_level = node_light;
3222 for (const v3s16 &dir : g_6dirs) {
3223 n = map.getNode(p + dir);
3224 node_light = getInteriorLight(n, -1, nodedef);
3225 if (node_light > light_level)
3226 light_level = node_light;
3229 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3231 final_color_blend(&c, light_level, daynight_ratio);
3233 // Modify final color a bit with time
3234 u32 timer = porting::getTimeMs() % 5000;
3235 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3236 float sin_r = 0.08f * std::sin(timerf);
3237 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3238 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3239 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3240 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3241 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3243 // Set mesh final color
3244 hud->setSelectionMeshColor(c);
3250 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3252 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3253 PointedThing fauxPointed;
3254 fauxPointed.type = POINTEDTHING_NOTHING;
3255 client->interact(INTERACT_ACTIVATE, fauxPointed);
3259 void Game::handlePointingAtNode(const PointedThing &pointed,
3260 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3262 v3s16 nodepos = pointed.node_undersurface;
3263 v3s16 neighbourpos = pointed.node_abovesurface;
3266 Check information text of node
3269 ClientMap &map = client->getEnv().getClientMap();
3271 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3272 && !runData.digging_blocked
3273 && client->checkPrivilege("interact")) {
3274 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3277 // This should be done after digging handling
3278 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3281 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3282 meta->getString("infotext"))));
3284 MapNode n = map.getNode(nodepos);
3286 if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
3287 m_game_ui->setInfoText(L"Unknown node: " +
3288 utf8_to_wide(nodedef_manager->get(n).name));
3292 if ((wasKeyPressed(KeyType::PLACE) ||
3293 runData.repeat_place_timer >= m_repeat_place_time) &&
3294 client->checkPrivilege("interact")) {
3295 runData.repeat_place_timer = 0;
3296 infostream << "Place button pressed while looking at ground" << std::endl;
3298 // Placing animation (always shown for feedback)
3299 camera->setDigging(1);
3301 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3303 // If the wielded item has node placement prediction,
3305 // And also set the sound and send the interact
3306 // But first check for meta formspec and rightclickable
3307 auto &def = selected_item.getDefinition(itemdef_manager);
3308 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3311 if (placed && client->modsLoaded())
3312 client->getScript()->on_placenode(pointed, def);
3316 bool Game::nodePlacement(const ItemDefinition &selected_def,
3317 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3318 const PointedThing &pointed, const NodeMetadata *meta)
3320 const auto &prediction = selected_def.node_placement_prediction;
3322 const NodeDefManager *nodedef = client->ndef();
3323 ClientMap &map = client->getEnv().getClientMap();
3325 bool is_valid_position;
3327 node = map.getNode(nodepos, &is_valid_position);
3328 if (!is_valid_position) {
3329 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3334 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3335 && !isKeyDown(KeyType::SNEAK)) {
3336 // on_rightclick callbacks are called anyway
3337 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3338 client->interact(INTERACT_PLACE, pointed);
3340 infostream << "Launching custom inventory view" << std::endl;
3342 InventoryLocation inventoryloc;
3343 inventoryloc.setNodeMeta(nodepos);
3345 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3346 &client->getEnv().getClientMap(), nodepos);
3347 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3349 auto *&formspec = m_game_ui->updateFormspec("");
3350 GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
3351 txt_dst, client->getFormspecPrepend(), sound);
3353 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3357 // on_rightclick callback
3358 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3359 !isKeyDown(KeyType::SNEAK))) {
3361 client->interact(INTERACT_PLACE, pointed);
3365 verbosestream << "Node placement prediction for "
3366 << selected_def.name << " is " << prediction << std::endl;
3367 v3s16 p = neighbourpos;
3369 // Place inside node itself if buildable_to
3370 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3371 if (is_valid_position) {
3372 if (nodedef->get(n_under).buildable_to) {
3375 node = map.getNode(p, &is_valid_position);
3376 if (is_valid_position && !nodedef->get(node).buildable_to) {
3377 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3379 client->interact(INTERACT_PLACE, pointed);
3385 // Find id of predicted node
3387 bool found = nodedef->getId(prediction, id);
3390 errorstream << "Node placement prediction failed for "
3391 << selected_def.name << " (places " << prediction
3392 << ") - Name not known" << std::endl;
3393 // Handle this as if prediction was empty
3395 client->interact(INTERACT_PLACE, pointed);
3399 const ContentFeatures &predicted_f = nodedef->get(id);
3401 // Predict param2 for facedir and wallmounted nodes
3402 // Compare core.item_place_node() for what the server does
3405 const u8 place_param2 = selected_def.place_param2;
3408 param2 = place_param2;
3409 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3410 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3411 v3s16 dir = nodepos - neighbourpos;
3413 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3414 param2 = dir.Y < 0 ? 1 : 0;
3415 } else if (abs(dir.X) > abs(dir.Z)) {
3416 param2 = dir.X < 0 ? 3 : 2;
3418 param2 = dir.Z < 0 ? 5 : 4;
3420 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3421 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3422 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3424 if (abs(dir.X) > abs(dir.Z)) {
3425 param2 = dir.X < 0 ? 3 : 1;
3427 param2 = dir.Z < 0 ? 2 : 0;
3431 // Check attachment if node is in group attached_node
3432 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3433 const static v3s16 wallmounted_dirs[8] = {
3443 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3444 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3445 pp = p + wallmounted_dirs[param2];
3447 pp = p + v3s16(0, -1, 0);
3449 if (!nodedef->get(map.getNode(pp)).walkable) {
3450 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3452 client->interact(INTERACT_PLACE, pointed);
3458 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3459 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3460 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3461 const auto &indexstr = selected_item.metadata.
3462 getString("palette_index", 0);
3463 if (!indexstr.empty()) {
3464 s32 index = mystoi(indexstr);
3465 if (predicted_f.param_type_2 == CPT2_COLOR) {
3467 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3468 // param2 = pure palette index + other
3469 param2 = (index & 0xf8) | (param2 & 0x07);
3470 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3471 // param2 = pure palette index + other
3472 param2 = (index & 0xe0) | (param2 & 0x1f);
3477 // Add node to client map
3478 MapNode n(id, 0, param2);
3481 LocalPlayer *player = client->getEnv().getLocalPlayer();
3483 // Dont place node when player would be inside new node
3484 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3485 if (!nodedef->get(n).walkable ||
3486 g_settings->getBool("enable_build_where_you_stand") ||
3487 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3488 (nodedef->get(n).walkable &&
3489 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3490 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3491 // This triggers the required mesh update too
3492 client->addNode(p, n);
3494 client->interact(INTERACT_PLACE, pointed);
3495 // A node is predicted, also play a sound
3496 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3499 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3502 } catch (const InvalidPositionException &e) {
3503 errorstream << "Node placement prediction failed for "
3504 << selected_def.name << " (places "
3505 << prediction << ") - Position not loaded" << std::endl;
3506 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3511 void Game::handlePointingAtObject(const PointedThing &pointed,
3512 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3514 std::wstring infotext = unescape_translate(
3515 utf8_to_wide(runData.selected_object->infoText()));
3518 if (!infotext.empty()) {
3521 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3524 m_game_ui->setInfoText(infotext);
3526 if (isKeyDown(KeyType::DIG)) {
3527 bool do_punch = false;
3528 bool do_punch_damage = false;
3530 if (runData.object_hit_delay_timer <= 0.0) {
3532 do_punch_damage = true;
3533 runData.object_hit_delay_timer = object_hit_delay;
3536 if (wasKeyPressed(KeyType::DIG))
3540 infostream << "Punched object" << std::endl;
3541 runData.punching = true;
3544 if (do_punch_damage) {
3545 // Report direct punch
3546 v3f objpos = runData.selected_object->getPosition();
3547 v3f dir = (objpos - player_position).normalize();
3549 bool disable_send = runData.selected_object->directReportPunch(
3550 dir, &tool_item, runData.time_from_last_punch);
3551 runData.time_from_last_punch = 0;
3554 client->interact(INTERACT_START_DIGGING, pointed);
3556 } else if (wasKeyDown(KeyType::PLACE)) {
3557 infostream << "Pressed place button while pointing at object" << std::endl;
3558 client->interact(INTERACT_PLACE, pointed); // place
3563 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3564 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3566 // See also: serverpackethandle.cpp, action == 2
3567 LocalPlayer *player = client->getEnv().getLocalPlayer();
3568 ClientMap &map = client->getEnv().getClientMap();
3569 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3571 // NOTE: Similar piece of code exists on the server side for
3573 // Get digging parameters
3574 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3575 &selected_item.getToolCapabilities(itemdef_manager));
3577 // If can't dig, try hand
3578 if (!params.diggable) {
3579 params = getDigParams(nodedef_manager->get(n).groups,
3580 &hand_item.getToolCapabilities(itemdef_manager));
3583 if (!params.diggable) {
3584 // I guess nobody will wait for this long
3585 runData.dig_time_complete = 10000000.0;
3587 runData.dig_time_complete = params.time;
3589 if (m_cache_enable_particles) {
3590 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3591 client->getParticleManager()->addNodeParticle(client,
3592 player, nodepos, n, features);
3596 if (!runData.digging) {
3597 infostream << "Started digging" << std::endl;
3598 runData.dig_instantly = runData.dig_time_complete == 0;
3599 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3601 client->interact(INTERACT_START_DIGGING, pointed);
3602 runData.digging = true;
3603 runData.btn_down_for_dig = true;
3606 if (!runData.dig_instantly) {
3607 runData.dig_index = (float)crack_animation_length
3609 / runData.dig_time_complete;
3611 // This is for e.g. torches
3612 runData.dig_index = crack_animation_length;
3615 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3617 if (sound_dig.exists() && params.diggable) {
3618 if (sound_dig.name == "__group") {
3619 if (!params.main_group.empty()) {
3620 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3621 soundmaker->m_player_leftpunch_sound.name =
3622 std::string("default_dig_") +
3626 soundmaker->m_player_leftpunch_sound = sound_dig;
3630 // Don't show cracks if not diggable
3631 if (runData.dig_time_complete >= 100000.0) {
3632 } else if (runData.dig_index < crack_animation_length) {
3633 //TimeTaker timer("client.setTempMod");
3634 //infostream<<"dig_index="<<dig_index<<std::endl;
3635 client->setCrack(runData.dig_index, nodepos);
3637 infostream << "Digging completed" << std::endl;
3638 client->setCrack(-1, v3s16(0, 0, 0));
3640 runData.dig_time = 0;
3641 runData.digging = false;
3642 // we successfully dug, now block it from repeating if we want to be safe
3643 if (g_settings->getBool("safe_dig_and_place"))
3644 runData.digging_blocked = true;
3646 runData.nodig_delay_timer =
3647 runData.dig_time_complete / (float)crack_animation_length;
3649 // We don't want a corresponding delay to very time consuming nodes
3650 // and nodes without digging time (e.g. torches) get a fixed delay.
3651 if (runData.nodig_delay_timer > 0.3)
3652 runData.nodig_delay_timer = 0.3;
3653 else if (runData.dig_instantly)
3654 runData.nodig_delay_timer = 0.15;
3656 bool is_valid_position;
3657 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3658 if (is_valid_position) {
3659 if (client->modsLoaded() &&
3660 client->getScript()->on_dignode(nodepos, wasnode)) {
3664 const ContentFeatures &f = client->ndef()->get(wasnode);
3665 if (f.node_dig_prediction == "air") {
3666 client->removeNode(nodepos);
3667 } else if (!f.node_dig_prediction.empty()) {
3669 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3671 client->addNode(nodepos, id, true);
3673 // implicit else: no prediction
3676 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3678 if (m_cache_enable_particles) {
3679 const ContentFeatures &features =
3680 client->getNodeDefManager()->get(wasnode);
3681 client->getParticleManager()->addDiggingParticles(client,
3682 player, nodepos, wasnode, features);
3686 // Send event to trigger sound
3687 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3690 if (runData.dig_time_complete < 100000.0) {
3691 runData.dig_time += dtime;
3693 runData.dig_time = 0;
3694 client->setCrack(-1, nodepos);
3697 camera->setDigging(0); // Dig animation
3700 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3701 const CameraOrientation &cam)
3703 TimeTaker tt_update("Game::updateFrame()");
3704 LocalPlayer *player = client->getEnv().getLocalPlayer();
3710 if (draw_control->range_all) {
3711 runData.fog_range = 100000 * BS;
3713 runData.fog_range = draw_control->wanted_range * BS;
3717 Calculate general brightness
3719 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3720 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3721 float direct_brightness;
3724 if (m_cache_enable_noclip && m_cache_enable_free_move) {
3725 direct_brightness = time_brightness;
3726 sunlight_seen = true;
3728 float old_brightness = sky->getBrightness();
3729 direct_brightness = client->getEnv().getClientMap()
3730 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3731 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3735 float time_of_day_smooth = runData.time_of_day_smooth;
3736 float time_of_day = client->getEnv().getTimeOfDayF();
3738 static const float maxsm = 0.05f;
3739 static const float todsm = 0.05f;
3741 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3742 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3743 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3744 time_of_day_smooth = time_of_day;
3746 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3747 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3748 + (time_of_day + 1.0) * todsm;
3750 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3751 + time_of_day * todsm;
3753 runData.time_of_day_smooth = time_of_day_smooth;
3755 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3756 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3757 player->getPitch());
3763 if (sky->getCloudsVisible()) {
3764 clouds->setVisible(true);
3765 clouds->step(dtime);
3766 // camera->getPosition is not enough for 3rd person views
3767 v3f camera_node_position = camera->getCameraNode()->getPosition();
3768 v3s16 camera_offset = camera->getOffset();
3769 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3770 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3771 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3772 clouds->update(camera_node_position,
3773 sky->getCloudColor());
3774 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3775 // if inside clouds, and fog enabled, use that as sky
3777 video::SColor clouds_dark = clouds->getColor()
3778 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3779 sky->overrideColors(clouds_dark, clouds->getColor());
3780 sky->setInClouds(true);
3781 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3782 // do not draw clouds after all
3783 clouds->setVisible(false);
3786 clouds->setVisible(false);
3793 client->getParticleManager()->step(dtime);
3799 if (m_cache_enable_fog) {
3802 video::EFT_FOG_LINEAR,
3803 runData.fog_range * m_cache_fog_start,
3804 runData.fog_range * 1.0,
3812 video::EFT_FOG_LINEAR,
3822 Get chat messages from client
3825 v2u32 screensize = driver->getScreenSize();
3827 updateChat(dtime, screensize);
3833 if (player->getWieldIndex() != runData.new_playeritem)
3834 client->setPlayerItem(runData.new_playeritem);
3836 if (client->updateWieldedItem()) {
3837 // Update wielded tool
3838 ItemStack selected_item, hand_item;
3839 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3840 camera->wield(tool_item);
3844 Update block draw list every 200ms or when camera direction has
3847 runData.update_draw_list_timer += dtime;
3849 v3f camera_direction = camera->getDirection();
3850 if (runData.update_draw_list_timer >= 0.2
3851 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3852 || m_camera_offset_changed) {
3853 runData.update_draw_list_timer = 0;
3854 client->getEnv().getClientMap().updateDrawList();
3855 runData.update_draw_list_last_cam_dir = camera_direction;
3858 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3861 make sure menu is on top
3862 1. Delete formspec menu reference if menu was removed
3863 2. Else, make sure formspec menu is on top
3865 auto formspec = m_game_ui->getFormspecGUI();
3866 do { // breakable. only runs for one iteration
3870 if (formspec->getReferenceCount() == 1) {
3871 m_game_ui->deleteFormspec();
3875 auto &loc = formspec->getFormspecLocation();
3876 if (loc.type == InventoryLocation::NODEMETA) {
3877 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3878 if (!meta || meta->getString("formspec").empty()) {
3879 formspec->quitMenu();
3885 guiroot->bringToFront(formspec);
3891 const video::SColor &skycolor = sky->getSkyColor();
3893 TimeTaker tt_draw("Draw scene");
3894 driver->beginScene(true, true, skycolor);
3896 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3897 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3898 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3899 bool draw_crosshair = (
3900 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3901 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3902 #ifdef HAVE_TOUCHSCREENGUI
3904 draw_crosshair = !g_settings->getBool("touchtarget");
3905 } catch (SettingNotFoundException) {
3908 RenderingEngine::draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3909 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3914 if (m_game_ui->m_flags.show_profiler_graph)
3915 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3920 if (runData.damage_flash > 0.0f) {
3921 video::SColor color(runData.damage_flash, 180, 0, 0);
3922 driver->draw2DRectangle(color,
3923 core::rect<s32>(0, 0, screensize.X, screensize.Y),
3926 runData.damage_flash -= 384.0f * dtime;
3932 if (player->hurt_tilt_timer > 0.0f) {
3933 player->hurt_tilt_timer -= dtime * 6.0f;
3935 if (player->hurt_tilt_timer < 0.0f)
3936 player->hurt_tilt_strength = 0.0f;
3940 Update minimap pos and rotation
3942 if (mapper && m_game_ui->m_flags.show_hud) {
3943 mapper->setPos(floatToInt(player->getPosition(), BS));
3944 mapper->setAngle(player->getYaw());
3950 if (++m_reset_HW_buffer_counter > 500) {
3952 Periodically remove all mesh HW buffers.
3954 Work around for a quirk in Irrlicht where a HW buffer is only
3955 released after 20000 iterations (triggered from endScene()).
3957 Without this, all loaded but unused meshes will retain their HW
3958 buffers for at least 5 minutes, at which point looking up the HW buffers
3959 becomes a bottleneck and the framerate drops (as much as 30%).
3961 Tests showed that numbers between 50 and 1000 are good, so picked 500.
3962 There are no other public Irrlicht APIs that allow interacting with the
3963 HW buffers without tracking the status of every individual mesh.
3965 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
3967 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
3968 driver->removeAllHardwareBuffers();
3969 m_reset_HW_buffer_counter = 0;
3973 stats->drawtime = tt_draw.stop(true);
3974 g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
3975 g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
3978 /* Log times and stuff for visualization */
3979 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
3981 Profiler::GraphValues values;
3982 g_profiler->graphGet(values);
3988 /****************************************************************************
3990 ****************************************************************************/
3992 /* On some computers framerate doesn't seem to be automatically limited
3994 inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
3996 // not using getRealTime is necessary for wine
3997 device->getTimer()->tick(); // Maker sure device time is up-to-date
3998 u32 time = device->getTimer()->getTime();
3999 u32 last_time = fps_timings->last_time;
4001 if (time > last_time) // Make sure time hasn't overflowed
4002 fps_timings->busy_time = time - last_time;
4004 fps_timings->busy_time = 0;
4006 u32 frametime_min = 1000 / (
4007 device->isWindowFocused() && !g_menumgr.pausesGame()
4008 ? g_settings->getFloat("fps_max")
4009 : g_settings->getFloat("fps_max_unfocused"));
4011 if (fps_timings->busy_time < frametime_min) {
4012 fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
4013 device->sleep(fps_timings->sleep_time);
4015 fps_timings->sleep_time = 0;
4018 /* Get the new value of the device timer. Note that device->sleep() may
4019 * not sleep for the entire requested time as sleep may be interrupted and
4020 * therefore it is arguably more accurate to get the new time from the
4021 * device rather than calculating it by adding sleep_time to time.
4024 device->getTimer()->tick(); // Update device timer
4025 time = device->getTimer()->getTime();
4027 if (time > last_time) // Make sure last_time hasn't overflowed
4028 *dtime = (time - last_time) / 1000.0;
4032 fps_timings->last_time = time;
4035 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4037 const wchar_t *wmsg = wgettext(msg);
4038 RenderingEngine::draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4043 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4045 ((Game *)data)->readSettings();
4048 void Game::readSettings()
4050 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4051 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4052 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4053 m_cache_enable_particles = g_settings->getBool("enable_particles");
4054 m_cache_enable_fog = g_settings->getBool("enable_fog");
4055 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
4056 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4057 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
4059 m_cache_enable_noclip = g_settings->getBool("noclip");
4060 m_cache_enable_free_move = g_settings->getBool("free_move");
4062 m_cache_fog_start = g_settings->getFloat("fog_start");
4064 m_cache_cam_smoothing = 0;
4065 if (g_settings->getBool("cinematic"))
4066 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4068 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4070 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4071 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4072 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4074 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4077 /****************************************************************************/
4078 /****************************************************************************
4080 ****************************************************************************/
4081 /****************************************************************************/
4083 void Game::extendedResourceCleanup()
4085 // Extended resource accounting
4086 infostream << "Irrlicht resources after cleanup:" << std::endl;
4087 infostream << "\tRemaining meshes : "
4088 << RenderingEngine::get_mesh_cache()->getMeshCount() << std::endl;
4089 infostream << "\tRemaining textures : "
4090 << driver->getTextureCount() << std::endl;
4092 for (unsigned int i = 0; i < driver->getTextureCount(); i++) {
4093 irr::video::ITexture *texture = driver->getTextureByIndex(i);
4094 infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str()
4098 clearTextureNameCache();
4099 infostream << "\tRemaining materials: "
4100 << driver-> getMaterialRendererCount()
4101 << " (note: irrlicht doesn't support removing renderers)" << std::endl;
4104 void Game::showDeathFormspec()
4106 static std::string formspec_str =
4107 std::string("formspec_version[1]") +
4109 "bgcolor[#320000b4;true]"
4110 "label[4.85,1.35;" + gettext("You died") + "]"
4111 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4115 /* Note: FormspecFormSource and LocalFormspecHandler *
4116 * are deleted by guiFormSpecMenu */
4117 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4118 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4120 auto *&formspec = m_game_ui->getFormspecGUI();
4121 GUIFormSpecMenu::create(formspec, client, &input->joystick,
4122 fs_src, txt_dst, client->getFormspecPrepend(), sound);
4123 formspec->setFocus("btn_respawn");
4126 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4127 void Game::showPauseMenu()
4130 static const std::string control_text = strgettext("Default Controls:\n"
4131 "No menu visible:\n"
4132 "- single tap: button activate\n"
4133 "- double tap: place/use\n"
4134 "- slide finger: look around\n"
4135 "Menu/Inventory visible:\n"
4136 "- double tap (outside):\n"
4138 "- touch stack, touch slot:\n"
4140 "- touch&drag, tap 2nd finger\n"
4141 " --> place single item to slot\n"
4144 static const std::string control_text_template = strgettext("Controls:\n"
4145 "- %s: move forwards\n"
4146 "- %s: move backwards\n"
4148 "- %s: move right\n"
4149 "- %s: jump/climb up\n"
4152 "- %s: sneak/climb down\n"
4155 "- Mouse: turn/look\n"
4156 "- Mouse wheel: select item\n"
4160 char control_text_buf[600];
4162 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4163 GET_KEY_NAME(keymap_forward),
4164 GET_KEY_NAME(keymap_backward),
4165 GET_KEY_NAME(keymap_left),
4166 GET_KEY_NAME(keymap_right),
4167 GET_KEY_NAME(keymap_jump),
4168 GET_KEY_NAME(keymap_dig),
4169 GET_KEY_NAME(keymap_place),
4170 GET_KEY_NAME(keymap_sneak),
4171 GET_KEY_NAME(keymap_drop),
4172 GET_KEY_NAME(keymap_inventory),
4173 GET_KEY_NAME(keymap_chat)
4176 std::string control_text = std::string(control_text_buf);
4177 str_formspec_escape(control_text);
4180 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4181 std::ostringstream os;
4183 os << "formspec_version[1]" << SIZE_TAG
4184 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4185 << strgettext("Continue") << "]";
4187 if (!simple_singleplayer_mode) {
4188 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4189 << strgettext("Change Password") << "]";
4191 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4196 if (g_settings->getBool("enable_sound")) {
4197 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4198 << strgettext("Sound Volume") << "]";
4201 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4202 << strgettext("Change Keys") << "]";
4204 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4205 << strgettext("Exit to Menu") << "]";
4206 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4207 << strgettext("Exit to OS") << "]"
4208 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4209 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4211 << strgettext("Game info:") << "\n";
4212 const std::string &address = client->getAddressName();
4213 static const std::string mode = strgettext("- Mode: ");
4214 if (!simple_singleplayer_mode) {
4215 Address serverAddress = client->getServerAddress();
4216 if (!address.empty()) {
4217 os << mode << strgettext("Remote server") << "\n"
4218 << strgettext("- Address: ") << address;
4220 os << mode << strgettext("Hosting server");
4222 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4224 os << mode << strgettext("Singleplayer") << "\n";
4226 if (simple_singleplayer_mode || address.empty()) {
4227 static const std::string on = strgettext("On");
4228 static const std::string off = strgettext("Off");
4229 const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
4230 const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
4231 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4232 os << strgettext("- Damage: ") << damage << "\n"
4233 << strgettext("- Creative Mode: ") << creative << "\n";
4234 if (!simple_singleplayer_mode) {
4235 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4236 //~ PvP = Player versus Player
4237 os << strgettext("- PvP: ") << pvp << "\n"
4238 << strgettext("- Public: ") << announced << "\n";
4239 std::string server_name = g_settings->get("server_name");
4240 str_formspec_escape(server_name);
4241 if (announced == on && !server_name.empty())
4242 os << strgettext("- Server Name: ") << server_name;
4249 /* Note: FormspecFormSource and LocalFormspecHandler *
4250 * are deleted by guiFormSpecMenu */
4251 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4252 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4254 auto *&formspec = m_game_ui->getFormspecGUI();
4255 GUIFormSpecMenu::create(formspec, client, &input->joystick,
4256 fs_src, txt_dst, client->getFormspecPrepend(), sound);
4257 formspec->setFocus("btn_continue");
4258 formspec->doPause = true;
4260 if (simple_singleplayer_mode)
4264 /****************************************************************************/
4265 /****************************************************************************
4266 extern function for launching the game
4267 ****************************************************************************/
4268 /****************************************************************************/
4270 void the_game(bool *kill,
4271 InputHandler *input,
4272 const GameStartData &start_data,
4273 std::string &error_message,
4274 ChatBackend &chat_backend,
4275 bool *reconnect_requested) // Used for local game
4279 /* Make a copy of the server address because if a local singleplayer server
4280 * is created then this is updated and we don't want to change the value
4281 * passed to us by the calling function
4286 if (game.startup(kill, input, start_data, error_message,
4287 reconnect_requested, &chat_backend)) {
4291 } catch (SerializationError &e) {
4292 error_message = std::string("A serialization error occurred:\n")
4293 + e.what() + "\n\nThe server is probably "
4294 " running a different version of " PROJECT_NAME_C ".";
4295 errorstream << error_message << std::endl;
4296 } catch (ServerError &e) {
4297 error_message = e.what();
4298 errorstream << "ServerError: " << error_message << std::endl;
4299 } catch (ModError &e) {
4300 error_message = std::string("ModError: ") + e.what() +
4301 strgettext("\nCheck debug.txt for details.");
4302 errorstream << error_message << std::endl;