3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "client/renderingengine.h"
27 #include "client/clientevent.h"
28 #include "client/gameui.h"
29 #include "client/inputhandler.h"
30 #include "client/tile.h" // For TextureSource
31 #include "client/keys.h"
32 #include "client/joystick_controller.h"
33 #include "clientmap.h"
36 #include "content_cao.h"
37 #include "content/subgames.h"
38 #include "client/event_manager.h"
39 #include "fontengine.h"
43 #include "gameparams.h"
45 #include "gui/guiChatConsole.h"
46 #include "gui/guiConfirmRegistration.h"
47 #include "gui/guiFormSpecMenu.h"
48 #include "gui/guiKeyChangeMenu.h"
49 #include "gui/guiPasswordChange.h"
50 #include "gui/guiVolumeChange.h"
51 #include "gui/mainmenumanager.h"
52 #include "gui/profilergraph.h"
55 #include "nodedef.h" // Needed for determining pointing to nodes
56 #include "nodemetadata.h"
57 #include "particles.h"
65 #include "translation.h"
66 #include "util/basic_macros.h"
67 #include "util/directiontables.h"
68 #include "util/pointedthing.h"
69 #include "util/quicktune_shortcutter.h"
70 #include "irrlicht_changes/static_text.h"
73 #include "script/scripting_client.h"
77 #include "client/sound_openal.h"
79 #include "client/sound.h"
85 struct TextDestNodeMetadata : public TextDest
87 TextDestNodeMetadata(v3s16 p, Client *client)
92 // This is deprecated I guess? -celeron55
93 void gotText(const std::wstring &text)
95 std::string ntext = wide_to_utf8(text);
96 infostream << "Submitting 'text' field of node at (" << m_p.X << ","
97 << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
99 fields["text"] = ntext;
100 m_client->sendNodemetaFields(m_p, "", fields);
102 void gotText(const StringMap &fields)
104 m_client->sendNodemetaFields(m_p, "", fields);
111 struct TextDestPlayerInventory : public TextDest
113 TextDestPlayerInventory(Client *client)
118 TextDestPlayerInventory(Client *client, const std::string &formname)
121 m_formname = formname;
123 void gotText(const StringMap &fields)
125 m_client->sendInventoryFields(m_formname, fields);
131 struct LocalFormspecHandler : public TextDest
133 LocalFormspecHandler(const std::string &formname)
135 m_formname = formname;
138 LocalFormspecHandler(const std::string &formname, Client *client):
141 m_formname = formname;
144 void gotText(const StringMap &fields)
146 if (m_formname == "MT_PAUSE_MENU") {
147 if (fields.find("btn_sound") != fields.end()) {
148 g_gamecallback->changeVolume();
152 if (fields.find("btn_key_config") != fields.end()) {
153 g_gamecallback->keyConfig();
157 if (fields.find("btn_exit_menu") != fields.end()) {
158 g_gamecallback->disconnect();
162 if (fields.find("btn_exit_os") != fields.end()) {
163 g_gamecallback->exitToOS();
165 RenderingEngine::get_raw_device()->closeDevice();
170 if (fields.find("btn_change_password") != fields.end()) {
171 g_gamecallback->changePassword();
178 if (m_formname == "MT_DEATH_SCREEN") {
179 assert(m_client != 0);
180 m_client->sendRespawn();
184 if (m_client->modsLoaded())
185 m_client->getScript()->on_formspec_input(m_formname, fields);
188 Client *m_client = nullptr;
191 /* Form update callback */
193 class NodeMetadataFormSource: public IFormSource
196 NodeMetadataFormSource(ClientMap *map, v3s16 p):
201 const std::string &getForm() const
203 static const std::string empty_string = "";
204 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
209 return meta->getString("formspec");
212 virtual std::string resolveText(const std::string &str)
214 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
219 return meta->resolveString(str);
226 class PlayerInventoryFormSource: public IFormSource
229 PlayerInventoryFormSource(Client *client):
234 const std::string &getForm() const
236 LocalPlayer *player = m_client->getEnv().getLocalPlayer();
237 return player->inventory_formspec;
243 class NodeDugEvent: public MtEvent
249 NodeDugEvent(v3s16 p, MapNode n):
253 MtEvent::Type getType() const
255 return MtEvent::NODE_DUG;
261 ISoundManager *m_sound;
262 const NodeDefManager *m_ndef;
264 bool makes_footstep_sound;
265 float m_player_step_timer;
266 float m_player_jump_timer;
268 SimpleSoundSpec m_player_step_sound;
269 SimpleSoundSpec m_player_leftpunch_sound;
270 SimpleSoundSpec m_player_rightpunch_sound;
272 SoundMaker(ISoundManager *sound, const NodeDefManager *ndef):
275 makes_footstep_sound(true),
276 m_player_step_timer(0.0f),
277 m_player_jump_timer(0.0f)
281 void playPlayerStep()
283 if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
284 m_player_step_timer = 0.03;
285 if (makes_footstep_sound)
286 m_sound->playSound(m_player_step_sound, false);
290 void playPlayerJump()
292 if (m_player_jump_timer <= 0.0f) {
293 m_player_jump_timer = 0.2f;
294 m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f), false);
298 static void viewBobbingStep(MtEvent *e, void *data)
300 SoundMaker *sm = (SoundMaker *)data;
301 sm->playPlayerStep();
304 static void playerRegainGround(MtEvent *e, void *data)
306 SoundMaker *sm = (SoundMaker *)data;
307 sm->playPlayerStep();
310 static void playerJump(MtEvent *e, void *data)
312 SoundMaker *sm = (SoundMaker *)data;
313 sm->playPlayerJump();
316 static void cameraPunchLeft(MtEvent *e, void *data)
318 SoundMaker *sm = (SoundMaker *)data;
319 sm->m_sound->playSound(sm->m_player_leftpunch_sound, false);
322 static void cameraPunchRight(MtEvent *e, void *data)
324 SoundMaker *sm = (SoundMaker *)data;
325 sm->m_sound->playSound(sm->m_player_rightpunch_sound, false);
328 static void nodeDug(MtEvent *e, void *data)
330 SoundMaker *sm = (SoundMaker *)data;
331 NodeDugEvent *nde = (NodeDugEvent *)e;
332 sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug, false);
335 static void playerDamage(MtEvent *e, void *data)
337 SoundMaker *sm = (SoundMaker *)data;
338 sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false);
341 static void playerFallingDamage(MtEvent *e, void *data)
343 SoundMaker *sm = (SoundMaker *)data;
344 sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false);
347 void registerReceiver(MtEventManager *mgr)
349 mgr->reg(MtEvent::VIEW_BOBBING_STEP, SoundMaker::viewBobbingStep, this);
350 mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, this);
351 mgr->reg(MtEvent::PLAYER_JUMP, SoundMaker::playerJump, this);
352 mgr->reg(MtEvent::CAMERA_PUNCH_LEFT, SoundMaker::cameraPunchLeft, this);
353 mgr->reg(MtEvent::CAMERA_PUNCH_RIGHT, SoundMaker::cameraPunchRight, this);
354 mgr->reg(MtEvent::NODE_DUG, SoundMaker::nodeDug, this);
355 mgr->reg(MtEvent::PLAYER_DAMAGE, SoundMaker::playerDamage, this);
356 mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, this);
359 void step(float dtime)
361 m_player_step_timer -= dtime;
362 m_player_jump_timer -= dtime;
366 // Locally stored sounds don't need to be preloaded because of this
367 class GameOnDemandSoundFetcher: public OnDemandSoundFetcher
369 std::set<std::string> m_fetched;
371 void paths_insert(std::set<std::string> &dst_paths,
372 const std::string &base,
373 const std::string &name)
375 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg");
376 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg");
377 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg");
378 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg");
379 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg");
380 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg");
381 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg");
382 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg");
383 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg");
384 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg");
385 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg");
388 void fetchSounds(const std::string &name,
389 std::set<std::string> &dst_paths,
390 std::set<std::string> &dst_datas)
392 if (m_fetched.count(name))
395 m_fetched.insert(name);
397 paths_insert(dst_paths, porting::path_share, name);
398 paths_insert(dst_paths, porting::path_user, name);
403 typedef s32 SamplerLayer_t;
406 class GameGlobalShaderConstantSetter : public IShaderConstantSetter
409 bool *m_force_fog_off;
412 CachedPixelShaderSetting<float, 4> m_sky_bg_color;
413 CachedPixelShaderSetting<float> m_fog_distance;
414 CachedVertexShaderSetting<float> m_animation_timer_vertex;
415 CachedPixelShaderSetting<float> m_animation_timer_pixel;
416 CachedPixelShaderSetting<float, 3> m_day_light;
417 CachedPixelShaderSetting<float, 4> m_star_color;
418 CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
419 CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
420 CachedPixelShaderSetting<float, 3> m_minimap_yaw;
421 CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
422 CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
423 CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
424 CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture;
428 void onSettingsChange(const std::string &name)
430 if (name == "enable_fog")
431 m_fog_enabled = g_settings->getBool("enable_fog");
434 static void settingsCallback(const std::string &name, void *userdata)
436 reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
439 void setSky(Sky *sky) { m_sky = sky; }
441 GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
442 f32 *fog_range, Client *client) :
444 m_force_fog_off(force_fog_off),
445 m_fog_range(fog_range),
446 m_sky_bg_color("skyBgColor"),
447 m_fog_distance("fogDistance"),
448 m_animation_timer_vertex("animationTimer"),
449 m_animation_timer_pixel("animationTimer"),
450 m_day_light("dayLight"),
451 m_star_color("starColor"),
452 m_eye_position_pixel("eyePosition"),
453 m_eye_position_vertex("eyePosition"),
454 m_minimap_yaw("yawVec"),
455 m_camera_offset_pixel("cameraOffset"),
456 m_camera_offset_vertex("cameraOffset"),
457 m_base_texture("baseTexture"),
458 m_normal_texture("normalTexture"),
461 g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
462 m_fog_enabled = g_settings->getBool("enable_fog");
465 ~GameGlobalShaderConstantSetter()
467 g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
470 void onSetConstants(video::IMaterialRendererServices *services) override
473 video::SColor bgcolor = m_sky->getBgColor();
474 video::SColorf bgcolorf(bgcolor);
475 float bgcolorfa[4] = {
481 m_sky_bg_color.set(bgcolorfa, services);
484 float fog_distance = 10000 * BS;
486 if (m_fog_enabled && !*m_force_fog_off)
487 fog_distance = *m_fog_range;
489 m_fog_distance.set(&fog_distance, services);
491 u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
492 video::SColorf sunlight;
493 get_sunlight_color(&sunlight, daynight_ratio);
498 m_day_light.set(dnc, services);
500 video::SColorf star_color = m_sky->getCurrentStarColor();
501 float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a};
502 m_star_color.set(clr, services);
504 u32 animation_timer = porting::getTimeMs() % 1000000;
505 float animation_timer_f = (float)animation_timer / 100000.f;
506 m_animation_timer_vertex.set(&animation_timer_f, services);
507 m_animation_timer_pixel.set(&animation_timer_f, services);
509 float eye_position_array[3];
510 v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
511 epos.getAs3Values(eye_position_array);
512 m_eye_position_pixel.set(eye_position_array, services);
513 m_eye_position_vertex.set(eye_position_array, services);
515 if (m_client->getMinimap()) {
516 float minimap_yaw_array[3];
517 v3f minimap_yaw = m_client->getMinimap()->getYawVec();
518 minimap_yaw.getAs3Values(minimap_yaw_array);
519 m_minimap_yaw.set(minimap_yaw_array, services);
522 float camera_offset_array[3];
523 v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
524 offset.getAs3Values(camera_offset_array);
525 m_camera_offset_pixel.set(camera_offset_array, services);
526 m_camera_offset_vertex.set(camera_offset_array, services);
528 SamplerLayer_t base_tex = 0, normal_tex = 1;
529 m_base_texture.set(&base_tex, services);
530 m_normal_texture.set(&normal_tex, services);
535 class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
538 bool *m_force_fog_off;
541 std::vector<GameGlobalShaderConstantSetter *> created_nosky;
543 GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
544 f32 *fog_range, Client *client) :
546 m_force_fog_off(force_fog_off),
547 m_fog_range(fog_range),
551 void setSky(Sky *sky) {
553 for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
554 ggscs->setSky(m_sky);
556 created_nosky.clear();
559 virtual IShaderConstantSetter* create()
561 auto *scs = new GameGlobalShaderConstantSetter(
562 m_sky, m_force_fog_off, m_fog_range, m_client);
564 created_nosky.push_back(scs);
569 #ifdef HAVE_TOUCHSCREENGUI
570 #define SIZE_TAG "size[11,5.5]"
572 #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
575 /****************************************************************************
576 ****************************************************************************/
578 const static float object_hit_delay = 0.2;
581 FpsControl() : last_time(0), busy_time(0), sleep_time(0) {}
585 void limit(IrrlichtDevice *device, f32 *dtime);
587 u32 getBusyMs() const { return busy_time / 1000; }
589 // all values in microseconds (us)
590 u64 last_time, busy_time, sleep_time;
594 /* The reason the following structs are not anonymous structs within the
595 * class is that they are not used by the majority of member functions and
596 * many functions that do require objects of thse types do not modify them
597 * (so they can be passed as a const qualified parameter)
603 PointedThing pointed_old;
606 bool btn_down_for_dig;
608 bool digging_blocked;
609 bool reset_jump_timer;
610 float nodig_delay_timer;
612 float dig_time_complete;
613 float repeat_place_timer;
614 float object_hit_delay_timer;
615 float time_from_last_punch;
616 ClientActiveObject *selected_object;
620 float update_draw_list_timer;
624 v3f update_draw_list_last_cam_dir;
626 float time_of_day_smooth;
631 struct ClientEventHandler
633 void (Game::*handler)(ClientEvent *, CameraOrientation *);
636 /****************************************************************************
638 ****************************************************************************/
640 using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
642 /* This is not intended to be a public class. If a public class becomes
643 * desirable then it may be better to create another 'wrapper' class that
644 * hides most of the stuff in this class (nothing in this class is required
645 * by any other file) but exposes the public methods/data only.
652 bool startup(bool *kill,
654 RenderingEngine *rendering_engine,
655 const GameStartData &game_params,
656 std::string &error_message,
658 ChatBackend *chat_backend);
665 // Basic initialisation
666 bool init(const std::string &map_dir, const std::string &address,
667 u16 port, const SubgameSpec &gamespec);
669 bool createSingleplayerServer(const std::string &map_dir,
670 const SubgameSpec &gamespec, u16 port);
673 bool createClient(const GameStartData &start_data);
677 bool connectToServer(const GameStartData &start_data,
678 bool *connect_ok, bool *aborted);
679 bool getServerContent(bool *aborted);
683 void updateInteractTimers(f32 dtime);
684 bool checkConnection();
685 bool handleCallbacks();
686 void processQueues();
687 void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
688 void updateDebugState();
689 void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
690 void updateProfilerGraphs(ProfilerGraph *graph);
693 void processUserInput(f32 dtime);
694 void processKeyInput();
695 void processItemSelection(u16 *new_playeritem);
697 void dropSelectedItem(bool single_item = false);
698 void openInventory();
699 void openConsole(float scale, const wchar_t *line=NULL);
700 void toggleFreeMove();
701 void toggleFreeMoveAlt();
702 void togglePitchMove();
705 void toggleCinematic();
706 void toggleBlockBounds();
707 void toggleAutoforward();
709 void toggleMinimap(bool shift_pressed);
712 void toggleUpdateCamera();
714 void increaseViewRange();
715 void decreaseViewRange();
716 void toggleFullViewRange();
717 void checkZoomEnabled();
719 void updateCameraDirection(CameraOrientation *cam, float dtime);
720 void updateCameraOrientation(CameraOrientation *cam, float dtime);
721 void updatePlayerControl(const CameraOrientation &cam);
722 void step(f32 *dtime);
723 void processClientEvents(CameraOrientation *cam);
724 void updateCamera(f32 dtime);
725 void updateSound(f32 dtime);
726 void processPlayerInteraction(f32 dtime, bool show_hud);
728 * Returns the object or node the player is pointing at.
729 * Also updates the selected thing in the Hud.
731 * @param[in] shootline the shootline, starting from
732 * the camera position. This also gives the maximal distance
734 * @param[in] liquids_pointable if false, liquids are ignored
735 * @param[in] look_for_object if false, objects are ignored
736 * @param[in] camera_offset offset of the camera
737 * @param[out] selected_object the selected object or
740 PointedThing updatePointedThing(
741 const core::line3d<f32> &shootline, bool liquids_pointable,
742 bool look_for_object, const v3s16 &camera_offset);
743 void handlePointingAtNothing(const ItemStack &playerItem);
744 void handlePointingAtNode(const PointedThing &pointed,
745 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
746 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
747 const v3f &player_position, bool show_debug);
748 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
749 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
750 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
751 const CameraOrientation &cam);
752 void updateShadows();
755 void showOverlayMessage(const char *msg, float dtime, int percent,
756 bool draw_clouds = true);
758 static void settingChangedCallback(const std::string &setting_name, void *data);
761 inline bool isKeyDown(GameKeyType k)
763 return input->isKeyDown(k);
765 inline bool wasKeyDown(GameKeyType k)
767 return input->wasKeyDown(k);
769 inline bool wasKeyPressed(GameKeyType k)
771 return input->wasKeyPressed(k);
773 inline bool wasKeyReleased(GameKeyType k)
775 return input->wasKeyReleased(k);
779 void handleAndroidChatInput();
784 bool force_fog_off = false;
785 bool disable_camera_update = false;
788 void showDeathFormspec();
789 void showPauseMenu();
791 void pauseAnimation();
792 void resumeAnimation();
794 // ClientEvent handlers
795 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
796 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
797 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
798 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
799 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
800 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
801 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
802 CameraOrientation *cam);
803 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
804 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
805 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
806 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
807 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
808 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
809 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
810 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
811 CameraOrientation *cam);
812 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
814 void updateChat(f32 dtime);
816 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
817 const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
818 const NodeMetadata *meta);
819 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
821 f32 getSensitivityScaleFactor() const;
823 InputHandler *input = nullptr;
825 Client *client = nullptr;
826 Server *server = nullptr;
828 IWritableTextureSource *texture_src = nullptr;
829 IWritableShaderSource *shader_src = nullptr;
831 // When created, these will be filled with data received from the server
832 IWritableItemDefManager *itemdef_manager = nullptr;
833 NodeDefManager *nodedef_manager = nullptr;
835 GameOnDemandSoundFetcher soundfetcher; // useful when testing
836 ISoundManager *sound = nullptr;
837 bool sound_is_dummy = false;
838 SoundMaker *soundmaker = nullptr;
840 ChatBackend *chat_backend = nullptr;
841 LogOutputBuffer m_chat_log_buf;
843 EventManager *eventmgr = nullptr;
844 QuicktuneShortcutter *quicktune = nullptr;
845 bool registration_confirmation_shown = false;
847 std::unique_ptr<GameUI> m_game_ui;
848 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
849 MapDrawControl *draw_control = nullptr;
850 Camera *camera = nullptr;
851 Clouds *clouds = nullptr; // Free using ->Drop()
852 Sky *sky = nullptr; // Free using ->Drop()
854 Minimap *mapper = nullptr;
856 // Map server hud ids to client hud ids
857 std::unordered_map<u32, u32> m_hud_server_to_client;
863 This class does take ownership/responsibily for cleaning up etc of any of
864 these items (e.g. device)
866 IrrlichtDevice *device;
867 RenderingEngine *m_rendering_engine;
868 video::IVideoDriver *driver;
869 scene::ISceneManager *smgr;
871 std::string *error_message;
872 bool *reconnect_requested;
873 scene::ISceneNode *skybox;
874 PausedNodesList paused_animated_nodes;
876 bool simple_singleplayer_mode;
879 /* Pre-calculated values
881 int crack_animation_length;
883 IntervalLimiter profiler_interval;
886 * TODO: Local caching of settings is not optimal and should at some stage
887 * be updated to use a global settings object for getting thse values
888 * (as opposed to the this local caching). This can be addressed in
891 bool m_cache_doubletap_jump;
892 bool m_cache_enable_clouds;
893 bool m_cache_enable_joysticks;
894 bool m_cache_enable_particles;
895 bool m_cache_enable_fog;
896 bool m_cache_enable_noclip;
897 bool m_cache_enable_free_move;
898 f32 m_cache_mouse_sensitivity;
899 f32 m_cache_joystick_frustum_sensitivity;
900 f32 m_repeat_place_time;
901 f32 m_cache_cam_smoothing;
902 f32 m_cache_fog_start;
904 bool m_invert_mouse = false;
905 bool m_first_loop_after_window_activation = false;
906 bool m_camera_offset_changed = false;
908 bool m_does_lost_focus_pause_game = false;
910 int m_reset_HW_buffer_counter = 0;
911 #ifdef HAVE_TOUCHSCREENGUI
912 bool m_cache_hold_aux1;
915 bool m_android_chat_open;
920 m_chat_log_buf(g_logger),
921 m_game_ui(new GameUI())
923 g_settings->registerChangedCallback("doubletap_jump",
924 &settingChangedCallback, this);
925 g_settings->registerChangedCallback("enable_clouds",
926 &settingChangedCallback, this);
927 g_settings->registerChangedCallback("doubletap_joysticks",
928 &settingChangedCallback, this);
929 g_settings->registerChangedCallback("enable_particles",
930 &settingChangedCallback, this);
931 g_settings->registerChangedCallback("enable_fog",
932 &settingChangedCallback, this);
933 g_settings->registerChangedCallback("mouse_sensitivity",
934 &settingChangedCallback, this);
935 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
936 &settingChangedCallback, this);
937 g_settings->registerChangedCallback("repeat_place_time",
938 &settingChangedCallback, this);
939 g_settings->registerChangedCallback("noclip",
940 &settingChangedCallback, this);
941 g_settings->registerChangedCallback("free_move",
942 &settingChangedCallback, this);
943 g_settings->registerChangedCallback("cinematic",
944 &settingChangedCallback, this);
945 g_settings->registerChangedCallback("cinematic_camera_smoothing",
946 &settingChangedCallback, this);
947 g_settings->registerChangedCallback("camera_smoothing",
948 &settingChangedCallback, this);
952 #ifdef HAVE_TOUCHSCREENGUI
953 m_cache_hold_aux1 = false; // This is initialised properly later
960 /****************************************************************************
962 ****************************************************************************/
971 delete server; // deleted first to stop all server threads
979 delete nodedef_manager;
980 delete itemdef_manager;
983 clearTextureNameCache();
985 g_settings->deregisterChangedCallback("doubletap_jump",
986 &settingChangedCallback, this);
987 g_settings->deregisterChangedCallback("enable_clouds",
988 &settingChangedCallback, this);
989 g_settings->deregisterChangedCallback("enable_particles",
990 &settingChangedCallback, this);
991 g_settings->deregisterChangedCallback("enable_fog",
992 &settingChangedCallback, this);
993 g_settings->deregisterChangedCallback("mouse_sensitivity",
994 &settingChangedCallback, this);
995 g_settings->deregisterChangedCallback("repeat_place_time",
996 &settingChangedCallback, this);
997 g_settings->deregisterChangedCallback("noclip",
998 &settingChangedCallback, this);
999 g_settings->deregisterChangedCallback("free_move",
1000 &settingChangedCallback, this);
1001 g_settings->deregisterChangedCallback("cinematic",
1002 &settingChangedCallback, this);
1003 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
1004 &settingChangedCallback, this);
1005 g_settings->deregisterChangedCallback("camera_smoothing",
1006 &settingChangedCallback, this);
1009 bool Game::startup(bool *kill,
1010 InputHandler *input,
1011 RenderingEngine *rendering_engine,
1012 const GameStartData &start_data,
1013 std::string &error_message,
1015 ChatBackend *chat_backend)
1019 m_rendering_engine = rendering_engine;
1020 device = m_rendering_engine->get_raw_device();
1022 this->error_message = &error_message;
1023 reconnect_requested = reconnect;
1024 this->input = input;
1025 this->chat_backend = chat_backend;
1026 simple_singleplayer_mode = start_data.isSinglePlayer();
1028 input->keycache.populate();
1030 driver = device->getVideoDriver();
1031 smgr = m_rendering_engine->get_scene_manager();
1033 smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1036 runData = GameRunData();
1037 runData.time_from_last_punch = 10.0;
1039 m_game_ui->initFlags();
1041 m_invert_mouse = g_settings->getBool("invert_mouse");
1042 m_first_loop_after_window_activation = true;
1044 g_client_translations->clear();
1046 // address can change if simple_singleplayer_mode
1047 if (!init(start_data.world_spec.path, start_data.address,
1048 start_data.socket_port, start_data.game_spec))
1051 if (!createClient(start_data))
1054 m_rendering_engine->initialize(client, hud);
1062 ProfilerGraph graph;
1063 RunStats stats = { 0 };
1064 CameraOrientation cam_view_target = { 0 };
1065 CameraOrientation cam_view = { 0 };
1066 FpsControl draw_times;
1067 f32 dtime; // in seconds
1069 /* Clear the profiler */
1070 Profiler::GraphValues dummyvalues;
1071 g_profiler->graphGet(dummyvalues);
1075 set_light_table(g_settings->getFloat("display_gamma"));
1077 #ifdef HAVE_TOUCHSCREENGUI
1078 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1079 && client->checkPrivilege("fast");
1082 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1083 g_settings->getU16("screen_h"));
1085 while (m_rendering_engine->run()
1086 && !(*kill || g_gamecallback->shutdown_requested
1087 || (server && server->isShutdownRequested()))) {
1089 const irr::core::dimension2d<u32> ¤t_screen_size =
1090 m_rendering_engine->get_video_driver()->getScreenSize();
1091 // Verify if window size has changed and save it if it's the case
1092 // Ensure evaluating settings->getBool after verifying screensize
1093 // First condition is cheaper
1094 if (previous_screen_size != current_screen_size &&
1095 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1096 g_settings->getBool("autosave_screensize")) {
1097 g_settings->setU16("screen_w", current_screen_size.Width);
1098 g_settings->setU16("screen_h", current_screen_size.Height);
1099 previous_screen_size = current_screen_size;
1102 // Calculate dtime =
1103 // m_rendering_engine->run() from this iteration
1104 // + Sleep time until the wanted FPS are reached
1105 draw_times.limit(device, &dtime);
1107 // Prepare render data for next iteration
1109 updateStats(&stats, draw_times, dtime);
1110 updateInteractTimers(dtime);
1112 if (!checkConnection())
1114 if (!handleCallbacks())
1119 m_game_ui->clearInfoText();
1120 hud->resizeHotbar();
1123 updateProfilers(stats, draw_times, dtime);
1124 processUserInput(dtime);
1125 // Update camera before player movement to avoid camera lag of one frame
1126 updateCameraDirection(&cam_view_target, dtime);
1127 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1128 cam_view.camera_yaw) * m_cache_cam_smoothing;
1129 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1130 cam_view.camera_pitch) * m_cache_cam_smoothing;
1131 updatePlayerControl(cam_view);
1133 processClientEvents(&cam_view_target);
1135 updateCamera(dtime);
1137 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
1138 updateFrame(&graph, &stats, dtime, cam_view);
1139 updateProfilerGraphs(&graph);
1141 // Update if minimap has been disabled by the server
1142 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1144 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1151 void Game::shutdown()
1153 m_rendering_engine->finalize();
1154 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1155 if (g_settings->get("3d_mode") == "pageflip") {
1156 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1159 auto formspec = m_game_ui->getFormspecGUI();
1161 formspec->quitMenu();
1163 #ifdef HAVE_TOUCHSCREENGUI
1164 g_touchscreengui->hide();
1167 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1172 if (gui_chat_console)
1173 gui_chat_console->drop();
1179 while (g_menumgr.menuCount() > 0) {
1180 g_menumgr.m_stack.front()->setVisible(false);
1181 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1184 m_game_ui->deleteFormspec();
1186 chat_backend->addMessage(L"", L"# Disconnected.");
1187 chat_backend->addMessage(L"", L"");
1188 m_chat_log_buf.clear();
1192 while (!client->isShutdown()) {
1193 assert(texture_src != NULL);
1194 assert(shader_src != NULL);
1195 texture_src->processQueue();
1196 shader_src->processQueue();
1203 /****************************************************************************/
1204 /****************************************************************************
1206 ****************************************************************************/
1207 /****************************************************************************/
1210 const std::string &map_dir,
1211 const std::string &address,
1213 const SubgameSpec &gamespec)
1215 texture_src = createTextureSource();
1217 showOverlayMessage(N_("Loading..."), 0, 0);
1219 shader_src = createShaderSource();
1221 itemdef_manager = createItemDefManager();
1222 nodedef_manager = createNodeDefManager();
1224 eventmgr = new EventManager();
1225 quicktune = new QuicktuneShortcutter();
1227 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1228 && eventmgr && quicktune))
1234 // Create a server if not connecting to an existing one
1235 if (address.empty()) {
1236 if (!createSingleplayerServer(map_dir, gamespec, port))
1243 bool Game::initSound()
1246 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1247 infostream << "Attempting to use OpenAL audio" << std::endl;
1248 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1250 infostream << "Failed to initialize OpenAL audio" << std::endl;
1252 infostream << "Sound disabled." << std::endl;
1256 infostream << "Using dummy audio." << std::endl;
1257 sound = &dummySoundManager;
1258 sound_is_dummy = true;
1261 soundmaker = new SoundMaker(sound, nodedef_manager);
1265 soundmaker->registerReceiver(eventmgr);
1270 bool Game::createSingleplayerServer(const std::string &map_dir,
1271 const SubgameSpec &gamespec, u16 port)
1273 showOverlayMessage(N_("Creating server..."), 0, 5);
1275 std::string bind_str = g_settings->get("bind_address");
1276 Address bind_addr(0, 0, 0, 0, port);
1278 if (g_settings->getBool("ipv6_server")) {
1279 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1283 bind_addr.Resolve(bind_str.c_str());
1284 } catch (ResolveError &e) {
1285 infostream << "Resolving bind address \"" << bind_str
1286 << "\" failed: " << e.what()
1287 << " -- Listening on all addresses." << std::endl;
1290 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1291 *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
1292 bind_addr.serializeString().c_str());
1293 errorstream << *error_message << std::endl;
1297 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1298 false, nullptr, error_message);
1304 bool Game::createClient(const GameStartData &start_data)
1306 showOverlayMessage(N_("Creating client..."), 0, 10);
1308 draw_control = new MapDrawControl();
1312 bool could_connect, connect_aborted;
1313 #ifdef HAVE_TOUCHSCREENGUI
1314 if (g_touchscreengui) {
1315 g_touchscreengui->init(texture_src);
1316 g_touchscreengui->hide();
1319 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1322 if (!could_connect) {
1323 if (error_message->empty() && !connect_aborted) {
1324 // Should not happen if error messages are set properly
1325 *error_message = gettext("Connection failed for unknown reason");
1326 errorstream << *error_message << std::endl;
1331 if (!getServerContent(&connect_aborted)) {
1332 if (error_message->empty() && !connect_aborted) {
1333 // Should not happen if error messages are set properly
1334 *error_message = gettext("Connection failed for unknown reason");
1335 errorstream << *error_message << std::endl;
1340 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1341 &m_flags.force_fog_off, &runData.fog_range, client);
1342 shader_src->addShaderConstantSetterFactory(scsf);
1344 // Update cached textures, meshes and materials
1345 client->afterContentReceived();
1349 camera = new Camera(*draw_control, client, m_rendering_engine);
1350 if (client->modsLoaded())
1351 client->getScript()->on_camera_ready(camera);
1352 client->setCamera(camera);
1356 if (m_cache_enable_clouds)
1357 clouds = new Clouds(smgr, -1, time(0));
1361 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
1363 skybox = NULL; // This is used/set later on in the main run loop
1365 /* Pre-calculated values
1367 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1369 v2u32 size = t->getOriginalSize();
1370 crack_animation_length = size.Y / size.X;
1372 crack_animation_length = 5;
1378 /* Set window caption
1380 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1382 str += utf8_to_wide(g_version_hash);
1384 const wchar_t *text = nullptr;
1385 if (simple_singleplayer_mode)
1386 text = wgettext("Singleplayer");
1388 text = wgettext("Multiplayer");
1395 str += driver->getName();
1398 device->setWindowCaption(str.c_str());
1400 LocalPlayer *player = client->getEnv().getLocalPlayer();
1401 player->hurt_tilt_timer = 0;
1402 player->hurt_tilt_strength = 0;
1404 hud = new Hud(client, player, &player->inventory);
1406 mapper = client->getMinimap();
1408 if (mapper && client->modsLoaded())
1409 client->getScript()->on_minimap_ready(mapper);
1414 bool Game::initGui()
1418 // Remove stale "recent" chat messages from previous connections
1419 chat_backend->clearRecentChat();
1421 // Make sure the size of the recent messages buffer is right
1422 chat_backend->applySettings();
1424 // Chat backend and console
1425 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1426 -1, chat_backend, client, &g_menumgr);
1428 #ifdef HAVE_TOUCHSCREENGUI
1430 if (g_touchscreengui)
1431 g_touchscreengui->show();
1438 bool Game::connectToServer(const GameStartData &start_data,
1439 bool *connect_ok, bool *connection_aborted)
1441 *connect_ok = false; // Let's not be overly optimistic
1442 *connection_aborted = false;
1443 bool local_server_mode = false;
1445 showOverlayMessage(N_("Resolving address..."), 0, 15);
1447 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1450 connect_address.Resolve(start_data.address.c_str());
1452 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1453 if (connect_address.isIPv6()) {
1454 IPv6AddressBytes addr_bytes;
1455 addr_bytes.bytes[15] = 1;
1456 connect_address.setAddress(&addr_bytes);
1458 connect_address.setAddress(127, 0, 0, 1);
1460 local_server_mode = true;
1462 } catch (ResolveError &e) {
1463 *error_message = fmtgettext("Couldn't resolve address: %s", e.what());
1465 errorstream << *error_message << std::endl;
1469 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1470 *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
1471 errorstream << *error_message << std::endl;
1476 client = new Client(start_data.name.c_str(),
1477 start_data.password, start_data.address,
1478 *draw_control, texture_src, shader_src,
1479 itemdef_manager, nodedef_manager, sound, eventmgr,
1480 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get());
1481 client->migrateModStorage();
1482 } catch (const BaseException &e) {
1483 *error_message = fmtgettext("Error creating client: %s", e.what());
1484 errorstream << *error_message << std::endl;
1488 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1490 infostream << "Connecting to server at ";
1491 connect_address.print(&infostream);
1492 infostream << std::endl;
1494 client->connect(connect_address,
1495 simple_singleplayer_mode || local_server_mode);
1498 Wait for server to accept connection
1504 FpsControl fps_control;
1506 f32 wait_time = 0; // in seconds
1508 fps_control.reset();
1510 while (m_rendering_engine->run()) {
1512 fps_control.limit(device, &dtime);
1514 // Update client and server
1515 client->step(dtime);
1518 server->step(dtime);
1521 if (client->getState() == LC_Init) {
1527 if (*connection_aborted)
1530 if (client->accessDenied()) {
1531 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1532 *reconnect_requested = client->reconnectRequested();
1533 errorstream << *error_message << std::endl;
1537 if (input->cancelPressed()) {
1538 *connection_aborted = true;
1539 infostream << "Connect aborted [Escape]" << std::endl;
1543 if (client->m_is_registration_confirmation_state) {
1544 if (registration_confirmation_shown) {
1545 // Keep drawing the GUI
1546 m_rendering_engine->draw_menu_scene(guienv, dtime, true);
1548 registration_confirmation_shown = true;
1549 (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
1550 &g_menumgr, client, start_data.name, start_data.password,
1551 connection_aborted, texture_src))->drop();
1555 // Only time out if we aren't waiting for the server we started
1556 if (!start_data.address.empty() && wait_time > 10) {
1557 *error_message = gettext("Connection timed out.");
1558 errorstream << *error_message << std::endl;
1563 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1566 } catch (con::PeerNotFoundException &e) {
1567 // TODO: Should something be done here? At least an info/error
1575 bool Game::getServerContent(bool *aborted)
1579 FpsControl fps_control;
1580 f32 dtime; // in seconds
1582 fps_control.reset();
1584 while (m_rendering_engine->run()) {
1586 fps_control.limit(device, &dtime);
1588 // Update client and server
1589 client->step(dtime);
1592 server->step(dtime);
1595 if (client->mediaReceived() && client->itemdefReceived() &&
1596 client->nodedefReceived()) {
1601 if (!checkConnection())
1604 if (client->getState() < LC_Init) {
1605 *error_message = gettext("Client disconnected");
1606 errorstream << *error_message << std::endl;
1610 if (input->cancelPressed()) {
1612 infostream << "Connect aborted [Escape]" << std::endl;
1619 if (!client->itemdefReceived()) {
1620 const wchar_t *text = wgettext("Item definitions...");
1622 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1625 } else if (!client->nodedefReceived()) {
1626 const wchar_t *text = wgettext("Node definitions...");
1628 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1632 std::ostringstream message;
1633 std::fixed(message);
1634 message.precision(0);
1635 float receive = client->mediaReceiveProgress() * 100;
1636 message << gettext("Media...");
1638 message << " " << receive << "%";
1639 message.precision(2);
1641 if ((USE_CURL == 0) ||
1642 (!g_settings->getBool("enable_remote_media_server"))) {
1643 float cur = client->getCurRate();
1644 std::string cur_unit = gettext("KiB/s");
1648 cur_unit = gettext("MiB/s");
1651 message << " (" << cur << ' ' << cur_unit << ")";
1654 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1655 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
1656 texture_src, dtime, progress);
1664 /****************************************************************************/
1665 /****************************************************************************
1667 ****************************************************************************/
1668 /****************************************************************************/
1670 inline void Game::updateInteractTimers(f32 dtime)
1672 if (runData.nodig_delay_timer >= 0)
1673 runData.nodig_delay_timer -= dtime;
1675 if (runData.object_hit_delay_timer >= 0)
1676 runData.object_hit_delay_timer -= dtime;
1678 runData.time_from_last_punch += dtime;
1682 /* returns false if game should exit, otherwise true
1684 inline bool Game::checkConnection()
1686 if (client->accessDenied()) {
1687 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1688 *reconnect_requested = client->reconnectRequested();
1689 errorstream << *error_message << std::endl;
1697 /* returns false if game should exit, otherwise true
1699 inline bool Game::handleCallbacks()
1701 if (g_gamecallback->disconnect_requested) {
1702 g_gamecallback->disconnect_requested = false;
1706 if (g_gamecallback->changepassword_requested) {
1707 (new GUIPasswordChange(guienv, guiroot, -1,
1708 &g_menumgr, client, texture_src))->drop();
1709 g_gamecallback->changepassword_requested = false;
1712 if (g_gamecallback->changevolume_requested) {
1713 (new GUIVolumeChange(guienv, guiroot, -1,
1714 &g_menumgr, texture_src))->drop();
1715 g_gamecallback->changevolume_requested = false;
1718 if (g_gamecallback->keyconfig_requested) {
1719 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1720 &g_menumgr, texture_src))->drop();
1721 g_gamecallback->keyconfig_requested = false;
1724 if (g_gamecallback->keyconfig_changed) {
1725 input->keycache.populate(); // update the cache with new settings
1726 g_gamecallback->keyconfig_changed = false;
1733 void Game::processQueues()
1735 texture_src->processQueue();
1736 itemdef_manager->processQueue(client);
1737 shader_src->processQueue();
1740 void Game::updateDebugState()
1742 LocalPlayer *player = client->getEnv().getLocalPlayer();
1743 bool has_debug = client->checkPrivilege("debug");
1744 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
1746 if (m_game_ui->m_flags.show_basic_debug) {
1747 if (!has_basic_debug)
1748 m_game_ui->m_flags.show_basic_debug = false;
1749 } else if (m_game_ui->m_flags.show_minimal_debug) {
1750 if (has_basic_debug)
1751 m_game_ui->m_flags.show_basic_debug = true;
1753 if (!has_basic_debug)
1754 hud->disableBlockBounds();
1756 draw_control->show_wireframe = false;
1759 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1762 float profiler_print_interval =
1763 g_settings->getFloat("profiler_print_interval");
1764 bool print_to_log = true;
1766 if (profiler_print_interval == 0) {
1767 print_to_log = false;
1768 profiler_print_interval = 3;
1771 if (profiler_interval.step(dtime, profiler_print_interval)) {
1773 infostream << "Profiler:" << std::endl;
1774 g_profiler->print(infostream);
1777 m_game_ui->updateProfiler();
1778 g_profiler->clear();
1781 // Update update graphs
1782 g_profiler->graphAdd("Time non-rendering [us]",
1783 draw_times.busy_time - stats.drawtime);
1785 g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
1786 g_profiler->graphAdd("FPS", 1.0f / dtime);
1789 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1796 /* Time average and jitter calculation
1798 jp = &stats->dtime_jitter;
1799 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1801 jitter = dtime - jp->avg;
1803 if (jitter > jp->max)
1806 jp->counter += dtime;
1808 if (jp->counter > 0.0) {
1810 jp->max_sample = jp->max;
1811 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1815 /* Busytime average and jitter calculation
1817 jp = &stats->busy_time_jitter;
1818 jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
1820 jitter = draw_times.getBusyMs() - jp->avg;
1822 if (jitter > jp->max)
1824 if (jitter < jp->min)
1827 jp->counter += dtime;
1829 if (jp->counter > 0.0) {
1831 jp->max_sample = jp->max;
1832 jp->min_sample = jp->min;
1840 /****************************************************************************
1842 ****************************************************************************/
1844 void Game::processUserInput(f32 dtime)
1846 // Reset input if window not active or some menu is active
1847 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1849 #ifdef HAVE_TOUCHSCREENGUI
1850 g_touchscreengui->hide();
1853 #ifdef HAVE_TOUCHSCREENGUI
1854 else if (g_touchscreengui) {
1855 /* on touchscreengui step may generate own input events which ain't
1856 * what we want in case we just did clear them */
1857 g_touchscreengui->show();
1858 g_touchscreengui->step(dtime);
1862 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1863 gui_chat_console->closeConsoleAtOnce();
1866 // Input handler step() (used by the random input generator)
1870 auto formspec = m_game_ui->getFormspecGUI();
1872 formspec->getAndroidUIInput();
1874 handleAndroidChatInput();
1877 // Increase timer for double tap of "keymap_jump"
1878 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1879 runData.jump_timer += dtime;
1882 processItemSelection(&runData.new_playeritem);
1886 void Game::processKeyInput()
1888 if (wasKeyDown(KeyType::DROP)) {
1889 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1890 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1891 toggleAutoforward();
1892 } else if (wasKeyDown(KeyType::BACKWARD)) {
1893 if (g_settings->getBool("continuous_forward"))
1894 toggleAutoforward();
1895 } else if (wasKeyDown(KeyType::INVENTORY)) {
1897 } else if (input->cancelPressed()) {
1899 m_android_chat_open = false;
1901 if (!gui_chat_console->isOpenInhibited()) {
1904 } else if (wasKeyDown(KeyType::CHAT)) {
1905 openConsole(0.2, L"");
1906 } else if (wasKeyDown(KeyType::CMD)) {
1907 openConsole(0.2, L"/");
1908 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1909 if (client->modsLoaded())
1910 openConsole(0.2, L".");
1912 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1913 } else if (wasKeyDown(KeyType::CONSOLE)) {
1914 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1915 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1917 } else if (wasKeyDown(KeyType::JUMP)) {
1918 toggleFreeMoveAlt();
1919 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1921 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1923 } else if (wasKeyDown(KeyType::NOCLIP)) {
1926 } else if (wasKeyDown(KeyType::MUTE)) {
1927 if (g_settings->getBool("enable_sound")) {
1928 bool new_mute_sound = !g_settings->getBool("mute_sound");
1929 g_settings->setBool("mute_sound", new_mute_sound);
1931 m_game_ui->showTranslatedStatusText("Sound muted");
1933 m_game_ui->showTranslatedStatusText("Sound unmuted");
1935 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1937 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1938 if (g_settings->getBool("enable_sound")) {
1939 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1940 g_settings->setFloat("sound_volume", new_volume);
1941 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1942 m_game_ui->showStatusText(msg);
1944 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1946 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1947 if (g_settings->getBool("enable_sound")) {
1948 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1949 g_settings->setFloat("sound_volume", new_volume);
1950 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1951 m_game_ui->showStatusText(msg);
1953 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1956 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1957 || wasKeyDown(KeyType::DEC_VOLUME)) {
1958 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1960 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1962 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1963 client->makeScreenshot();
1964 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
1965 toggleBlockBounds();
1966 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1967 m_game_ui->toggleHud();
1968 } else if (wasKeyDown(KeyType::MINIMAP)) {
1969 toggleMinimap(isKeyDown(KeyType::SNEAK));
1970 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1971 m_game_ui->toggleChat();
1972 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1974 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1975 toggleUpdateCamera();
1976 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1978 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1979 m_game_ui->toggleProfiler();
1980 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1981 increaseViewRange();
1982 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1983 decreaseViewRange();
1984 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1985 toggleFullViewRange();
1986 } else if (wasKeyDown(KeyType::ZOOM)) {
1988 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1990 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1992 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1994 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1998 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1999 runData.reset_jump_timer = false;
2000 runData.jump_timer = 0.0f;
2003 if (quicktune->hasMessage()) {
2004 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2008 void Game::processItemSelection(u16 *new_playeritem)
2010 LocalPlayer *player = client->getEnv().getLocalPlayer();
2012 /* Item selection using mouse wheel
2014 *new_playeritem = player->getWieldIndex();
2016 s32 wheel = input->getMouseWheel();
2017 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2018 player->hud_hotbar_itemcount - 1);
2022 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2025 if (wasKeyDown(KeyType::HOTBAR_PREV))
2029 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2031 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2034 /* Item selection using hotbar slot keys
2036 for (u16 i = 0; i <= max_item; i++) {
2037 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2038 *new_playeritem = i;
2045 void Game::dropSelectedItem(bool single_item)
2047 IDropAction *a = new IDropAction();
2048 a->count = single_item ? 1 : 0;
2049 a->from_inv.setCurrentPlayer();
2050 a->from_list = "main";
2051 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2052 client->inventoryAction(a);
2056 void Game::openInventory()
2059 * Don't permit to open inventory is CAO or player doesn't exists.
2060 * This prevent showing an empty inventory at player load
2063 LocalPlayer *player = client->getEnv().getLocalPlayer();
2064 if (!player || !player->getCAO())
2067 infostream << "Game: Launching inventory" << std::endl;
2069 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2071 InventoryLocation inventoryloc;
2072 inventoryloc.setCurrentPlayer();
2074 if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2079 if (fs_src->getForm().empty()) {
2084 TextDest *txt_dst = new TextDestPlayerInventory(client);
2085 auto *&formspec = m_game_ui->updateFormspec("");
2086 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2087 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2089 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2093 void Game::openConsole(float scale, const wchar_t *line)
2095 assert(scale > 0.0f && scale <= 1.0f);
2098 porting::showInputDialog(gettext("ok"), "", "", 2);
2099 m_android_chat_open = true;
2101 if (gui_chat_console->isOpenInhibited())
2103 gui_chat_console->openConsole(scale);
2105 gui_chat_console->setCloseOnEnter(true);
2106 gui_chat_console->replaceAndAddToHistory(line);
2112 void Game::handleAndroidChatInput()
2114 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2115 std::string text = porting::getInputDialogValue();
2116 client->typeChatMessage(utf8_to_wide(text));
2117 m_android_chat_open = false;
2123 void Game::toggleFreeMove()
2125 bool free_move = !g_settings->getBool("free_move");
2126 g_settings->set("free_move", bool_to_cstr(free_move));
2129 if (client->checkPrivilege("fly")) {
2130 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2132 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2135 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2139 void Game::toggleFreeMoveAlt()
2141 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2144 runData.reset_jump_timer = true;
2148 void Game::togglePitchMove()
2150 bool pitch_move = !g_settings->getBool("pitch_move");
2151 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2154 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2156 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2161 void Game::toggleFast()
2163 bool fast_move = !g_settings->getBool("fast_move");
2164 bool has_fast_privs = client->checkPrivilege("fast");
2165 g_settings->set("fast_move", bool_to_cstr(fast_move));
2168 if (has_fast_privs) {
2169 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2171 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2174 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2177 #ifdef HAVE_TOUCHSCREENGUI
2178 m_cache_hold_aux1 = fast_move && has_fast_privs;
2183 void Game::toggleNoClip()
2185 bool noclip = !g_settings->getBool("noclip");
2186 g_settings->set("noclip", bool_to_cstr(noclip));
2189 if (client->checkPrivilege("noclip")) {
2190 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2192 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2195 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2199 void Game::toggleCinematic()
2201 bool cinematic = !g_settings->getBool("cinematic");
2202 g_settings->set("cinematic", bool_to_cstr(cinematic));
2205 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2207 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2210 void Game::toggleBlockBounds()
2212 LocalPlayer *player = client->getEnv().getLocalPlayer();
2213 if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
2214 m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
2217 enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
2219 case Hud::BLOCK_BOUNDS_OFF:
2220 m_game_ui->showTranslatedStatusText("Block bounds hidden");
2222 case Hud::BLOCK_BOUNDS_CURRENT:
2223 m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
2225 case Hud::BLOCK_BOUNDS_NEAR:
2226 m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
2228 case Hud::BLOCK_BOUNDS_MAX:
2229 m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
2236 // Autoforward by toggling continuous forward.
2237 void Game::toggleAutoforward()
2239 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2240 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2242 if (autorun_enabled)
2243 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2245 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2248 void Game::toggleMinimap(bool shift_pressed)
2250 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2254 mapper->toggleMinimapShape();
2258 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2260 // Not so satisying code to keep compatibility with old fixed mode system
2262 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2264 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2265 m_game_ui->m_flags.show_minimap = false;
2268 // If radar is disabled, try to find a non radar mode or fall back to 0
2269 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2270 while (mapper->getModeIndex() &&
2271 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2274 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2278 // End of 'not so satifying code'
2279 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2280 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2281 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2283 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2286 void Game::toggleFog()
2288 bool fog_enabled = g_settings->getBool("enable_fog");
2289 g_settings->setBool("enable_fog", !fog_enabled);
2291 m_game_ui->showTranslatedStatusText("Fog disabled");
2293 m_game_ui->showTranslatedStatusText("Fog enabled");
2297 void Game::toggleDebug()
2299 LocalPlayer *player = client->getEnv().getLocalPlayer();
2300 bool has_debug = client->checkPrivilege("debug");
2301 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
2302 // Initial: No debug info
2303 // 1x toggle: Debug text
2304 // 2x toggle: Debug text with profiler graph
2305 // 3x toggle: Debug text and wireframe (needs "debug" priv)
2306 // Next toggle: Back to initial
2308 // The debug text can be in 2 modes: minimal and basic.
2309 // * Minimal: Only technical client info that not gameplay-relevant
2310 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
2311 // Basic mode is used when player has the debug HUD flag set,
2312 // otherwise the Minimal mode is used.
2313 if (!m_game_ui->m_flags.show_minimal_debug) {
2314 m_game_ui->m_flags.show_minimal_debug = true;
2315 if (has_basic_debug)
2316 m_game_ui->m_flags.show_basic_debug = true;
2317 m_game_ui->m_flags.show_profiler_graph = false;
2318 draw_control->show_wireframe = false;
2319 m_game_ui->showTranslatedStatusText("Debug info shown");
2320 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2321 if (has_basic_debug)
2322 m_game_ui->m_flags.show_basic_debug = true;
2323 m_game_ui->m_flags.show_profiler_graph = true;
2324 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2325 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2326 if (has_basic_debug)
2327 m_game_ui->m_flags.show_basic_debug = true;
2328 m_game_ui->m_flags.show_profiler_graph = false;
2329 draw_control->show_wireframe = true;
2330 m_game_ui->showTranslatedStatusText("Wireframe shown");
2332 m_game_ui->m_flags.show_minimal_debug = false;
2333 m_game_ui->m_flags.show_basic_debug = false;
2334 m_game_ui->m_flags.show_profiler_graph = false;
2335 draw_control->show_wireframe = false;
2337 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2339 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2345 void Game::toggleUpdateCamera()
2347 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2348 if (m_flags.disable_camera_update)
2349 m_game_ui->showTranslatedStatusText("Camera update disabled");
2351 m_game_ui->showTranslatedStatusText("Camera update enabled");
2355 void Game::increaseViewRange()
2357 s16 range = g_settings->getS16("viewing_range");
2358 s16 range_new = range + 10;
2360 if (range_new > 4000) {
2362 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
2363 m_game_ui->showStatusText(msg);
2365 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2366 m_game_ui->showStatusText(msg);
2368 g_settings->set("viewing_range", itos(range_new));
2372 void Game::decreaseViewRange()
2374 s16 range = g_settings->getS16("viewing_range");
2375 s16 range_new = range - 10;
2377 if (range_new < 20) {
2379 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
2380 m_game_ui->showStatusText(msg);
2382 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2383 m_game_ui->showStatusText(msg);
2385 g_settings->set("viewing_range", itos(range_new));
2389 void Game::toggleFullViewRange()
2391 draw_control->range_all = !draw_control->range_all;
2392 if (draw_control->range_all)
2393 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2395 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2399 void Game::checkZoomEnabled()
2401 LocalPlayer *player = client->getEnv().getLocalPlayer();
2402 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2403 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2406 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2408 if ((device->isWindowActive() && device->isWindowFocused()
2409 && !isMenuActive()) || input->isRandom()) {
2412 if (!input->isRandom()) {
2413 // Mac OSX gets upset if this is set every frame
2414 if (device->getCursorControl()->isVisible())
2415 device->getCursorControl()->setVisible(false);
2419 if (m_first_loop_after_window_activation) {
2420 m_first_loop_after_window_activation = false;
2422 input->setMousePos(driver->getScreenSize().Width / 2,
2423 driver->getScreenSize().Height / 2);
2425 updateCameraOrientation(cam, dtime);
2431 // Mac OSX gets upset if this is set every frame
2432 if (!device->getCursorControl()->isVisible())
2433 device->getCursorControl()->setVisible(true);
2436 m_first_loop_after_window_activation = true;
2441 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2442 // responsiveness independently of FOV.
2443 f32 Game::getSensitivityScaleFactor() const
2445 f32 fov_y = client->getCamera()->getFovY();
2447 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2448 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2450 return tan(fov_y / 2.0f) * 1.3763818698f;
2453 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2455 #ifdef HAVE_TOUCHSCREENGUI
2456 if (g_touchscreengui) {
2457 cam->camera_yaw += g_touchscreengui->getYawChange();
2458 cam->camera_pitch = g_touchscreengui->getPitch();
2461 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2462 v2s32 dist = input->getMousePos() - center;
2464 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2468 f32 sens_scale = getSensitivityScaleFactor();
2469 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2470 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2472 if (dist.X != 0 || dist.Y != 0)
2473 input->setMousePos(center.X, center.Y);
2474 #ifdef HAVE_TOUCHSCREENGUI
2478 if (m_cache_enable_joysticks) {
2479 f32 sens_scale = getSensitivityScaleFactor();
2480 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
2481 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2482 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2485 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2489 void Game::updatePlayerControl(const CameraOrientation &cam)
2491 LocalPlayer *player = client->getEnv().getLocalPlayer();
2493 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2495 PlayerControl control(
2496 isKeyDown(KeyType::FORWARD),
2497 isKeyDown(KeyType::BACKWARD),
2498 isKeyDown(KeyType::LEFT),
2499 isKeyDown(KeyType::RIGHT),
2500 isKeyDown(KeyType::JUMP) || player->getAutojump(),
2501 isKeyDown(KeyType::AUX1),
2502 isKeyDown(KeyType::SNEAK),
2503 isKeyDown(KeyType::ZOOM),
2504 isKeyDown(KeyType::DIG),
2505 isKeyDown(KeyType::PLACE),
2508 input->getMovementSpeed(),
2509 input->getMovementDirection()
2512 // autoforward if set: move towards pointed position at maximum speed
2513 if (player->getPlayerSettings().continuous_forward &&
2514 client->activeObjectsReceived() && !player->isDead()) {
2515 control.movement_speed = 1.0f;
2516 control.movement_direction = 0.0f;
2519 #ifdef HAVE_TOUCHSCREENGUI
2520 /* For touch, simulate holding down AUX1 (fast move) if the user has
2521 * the fast_move setting toggled on. If there is an aux1 key defined for
2522 * touch then its meaning is inverted (i.e. holding aux1 means walk and
2525 if (m_cache_hold_aux1) {
2526 control.aux1 = control.aux1 ^ true;
2530 client->setPlayerControl(control);
2536 inline void Game::step(f32 *dtime)
2538 bool can_be_and_is_paused =
2539 (simple_singleplayer_mode && g_menumgr.pausesGame());
2541 if (can_be_and_is_paused) { // This is for a singleplayer server
2542 *dtime = 0; // No time passes
2544 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
2548 server->step(*dtime);
2550 client->step(*dtime);
2554 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2557 for (auto &&child: node->getChildren())
2558 pauseNodeAnimation(paused, child);
2559 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2561 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2562 float speed = animated_node->getAnimationSpeed();
2565 paused.push_back({grab(animated_node), speed});
2566 animated_node->setAnimationSpeed(0.0f);
2569 void Game::pauseAnimation()
2571 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2574 void Game::resumeAnimation()
2576 for (auto &&pair: paused_animated_nodes)
2577 pair.first->setAnimationSpeed(pair.second);
2578 paused_animated_nodes.clear();
2581 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2582 {&Game::handleClientEvent_None},
2583 {&Game::handleClientEvent_PlayerDamage},
2584 {&Game::handleClientEvent_PlayerForceMove},
2585 {&Game::handleClientEvent_Deathscreen},
2586 {&Game::handleClientEvent_ShowFormSpec},
2587 {&Game::handleClientEvent_ShowLocalFormSpec},
2588 {&Game::handleClientEvent_HandleParticleEvent},
2589 {&Game::handleClientEvent_HandleParticleEvent},
2590 {&Game::handleClientEvent_HandleParticleEvent},
2591 {&Game::handleClientEvent_HudAdd},
2592 {&Game::handleClientEvent_HudRemove},
2593 {&Game::handleClientEvent_HudChange},
2594 {&Game::handleClientEvent_SetSky},
2595 {&Game::handleClientEvent_SetSun},
2596 {&Game::handleClientEvent_SetMoon},
2597 {&Game::handleClientEvent_SetStars},
2598 {&Game::handleClientEvent_OverrideDayNigthRatio},
2599 {&Game::handleClientEvent_CloudParams},
2602 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2604 FATAL_ERROR("ClientEvent type None received");
2607 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2609 if (client->modsLoaded())
2610 client->getScript()->on_damage_taken(event->player_damage.amount);
2612 // Damage flash and hurt tilt are not used at death
2613 if (client->getHP() > 0) {
2614 LocalPlayer *player = client->getEnv().getLocalPlayer();
2616 f32 hp_max = player->getCAO() ?
2617 player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2618 f32 damage_ratio = event->player_damage.amount / hp_max;
2620 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2621 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2623 player->hurt_tilt_timer = 1.5f;
2624 player->hurt_tilt_strength =
2625 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2628 // Play damage sound
2629 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2632 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2634 cam->camera_yaw = event->player_force_move.yaw;
2635 cam->camera_pitch = event->player_force_move.pitch;
2638 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2640 // If client scripting is enabled, deathscreen is handled by CSM code in
2641 // builtin/client/init.lua
2642 if (client->modsLoaded())
2643 client->getScript()->on_death();
2645 showDeathFormspec();
2647 /* Handle visualization */
2648 LocalPlayer *player = client->getEnv().getLocalPlayer();
2649 runData.damage_flash = 0;
2650 player->hurt_tilt_timer = 0;
2651 player->hurt_tilt_strength = 0;
2654 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2656 if (event->show_formspec.formspec->empty()) {
2657 auto formspec = m_game_ui->getFormspecGUI();
2658 if (formspec && (event->show_formspec.formname->empty()
2659 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2660 formspec->quitMenu();
2663 FormspecFormSource *fs_src =
2664 new FormspecFormSource(*(event->show_formspec.formspec));
2665 TextDestPlayerInventory *txt_dst =
2666 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2668 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2669 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2670 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2673 delete event->show_formspec.formspec;
2674 delete event->show_formspec.formname;
2677 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2679 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2680 LocalFormspecHandler *txt_dst =
2681 new LocalFormspecHandler(*event->show_formspec.formname, client);
2682 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2683 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2685 delete event->show_formspec.formspec;
2686 delete event->show_formspec.formname;
2689 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2690 CameraOrientation *cam)
2692 LocalPlayer *player = client->getEnv().getLocalPlayer();
2693 client->getParticleManager()->handleParticleEvent(event, client, player);
2696 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2698 LocalPlayer *player = client->getEnv().getLocalPlayer();
2700 u32 server_id = event->hudadd->server_id;
2701 // ignore if we already have a HUD with that ID
2702 auto i = m_hud_server_to_client.find(server_id);
2703 if (i != m_hud_server_to_client.end()) {
2704 delete event->hudadd;
2708 HudElement *e = new HudElement;
2709 e->type = static_cast<HudElementType>(event->hudadd->type);
2710 e->pos = event->hudadd->pos;
2711 e->name = event->hudadd->name;
2712 e->scale = event->hudadd->scale;
2713 e->text = event->hudadd->text;
2714 e->number = event->hudadd->number;
2715 e->item = event->hudadd->item;
2716 e->dir = event->hudadd->dir;
2717 e->align = event->hudadd->align;
2718 e->offset = event->hudadd->offset;
2719 e->world_pos = event->hudadd->world_pos;
2720 e->size = event->hudadd->size;
2721 e->z_index = event->hudadd->z_index;
2722 e->text2 = event->hudadd->text2;
2723 e->style = event->hudadd->style;
2724 m_hud_server_to_client[server_id] = player->addHud(e);
2726 delete event->hudadd;
2729 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2731 LocalPlayer *player = client->getEnv().getLocalPlayer();
2733 auto i = m_hud_server_to_client.find(event->hudrm.id);
2734 if (i != m_hud_server_to_client.end()) {
2735 HudElement *e = player->removeHud(i->second);
2737 m_hud_server_to_client.erase(i);
2742 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2744 LocalPlayer *player = client->getEnv().getLocalPlayer();
2746 HudElement *e = nullptr;
2748 auto i = m_hud_server_to_client.find(event->hudchange->id);
2749 if (i != m_hud_server_to_client.end()) {
2750 e = player->getHud(i->second);
2754 delete event->hudchange;
2758 #define CASE_SET(statval, prop, dataprop) \
2760 e->prop = event->hudchange->dataprop; \
2763 switch (event->hudchange->stat) {
2764 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2766 CASE_SET(HUD_STAT_NAME, name, sdata);
2768 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2770 CASE_SET(HUD_STAT_TEXT, text, sdata);
2772 CASE_SET(HUD_STAT_NUMBER, number, data);
2774 CASE_SET(HUD_STAT_ITEM, item, data);
2776 CASE_SET(HUD_STAT_DIR, dir, data);
2778 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2780 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2782 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2784 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2786 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2788 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2790 CASE_SET(HUD_STAT_STYLE, style, data);
2795 delete event->hudchange;
2798 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2800 sky->setVisible(false);
2801 // Whether clouds are visible in front of a custom skybox.
2802 sky->setCloudsEnabled(event->set_sky->clouds);
2808 // Clear the old textures out in case we switch rendering type.
2809 sky->clearSkyboxTextures();
2810 // Handle according to type
2811 if (event->set_sky->type == "regular") {
2812 // Shows the mesh skybox
2813 sky->setVisible(true);
2814 // Update mesh based skybox colours if applicable.
2815 sky->setSkyColors(event->set_sky->sky_color);
2816 sky->setHorizonTint(
2817 event->set_sky->fog_sun_tint,
2818 event->set_sky->fog_moon_tint,
2819 event->set_sky->fog_tint_type
2821 } else if (event->set_sky->type == "skybox" &&
2822 event->set_sky->textures.size() == 6) {
2823 // Disable the dyanmic mesh skybox:
2824 sky->setVisible(false);
2826 sky->setFallbackBgColor(event->set_sky->bgcolor);
2827 // Set sunrise and sunset fog tinting:
2828 sky->setHorizonTint(
2829 event->set_sky->fog_sun_tint,
2830 event->set_sky->fog_moon_tint,
2831 event->set_sky->fog_tint_type
2833 // Add textures to skybox.
2834 for (int i = 0; i < 6; i++)
2835 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2837 // Handle everything else as plain color.
2838 if (event->set_sky->type != "plain")
2839 infostream << "Unknown sky type: "
2840 << (event->set_sky->type) << std::endl;
2841 sky->setVisible(false);
2842 sky->setFallbackBgColor(event->set_sky->bgcolor);
2843 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2844 sky->setHorizonTint(
2845 event->set_sky->bgcolor,
2846 event->set_sky->bgcolor,
2851 delete event->set_sky;
2854 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2856 sky->setSunVisible(event->sun_params->visible);
2857 sky->setSunTexture(event->sun_params->texture,
2858 event->sun_params->tonemap, texture_src);
2859 sky->setSunScale(event->sun_params->scale);
2860 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2861 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2862 delete event->sun_params;
2865 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2867 sky->setMoonVisible(event->moon_params->visible);
2868 sky->setMoonTexture(event->moon_params->texture,
2869 event->moon_params->tonemap, texture_src);
2870 sky->setMoonScale(event->moon_params->scale);
2871 delete event->moon_params;
2874 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2876 sky->setStarsVisible(event->star_params->visible);
2877 sky->setStarCount(event->star_params->count);
2878 sky->setStarColor(event->star_params->starcolor);
2879 sky->setStarScale(event->star_params->scale);
2880 delete event->star_params;
2883 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2884 CameraOrientation *cam)
2886 client->getEnv().setDayNightRatioOverride(
2887 event->override_day_night_ratio.do_override,
2888 event->override_day_night_ratio.ratio_f * 1000.0f);
2891 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2896 clouds->setDensity(event->cloud_params.density);
2897 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2898 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2899 clouds->setHeight(event->cloud_params.height);
2900 clouds->setThickness(event->cloud_params.thickness);
2901 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2904 void Game::processClientEvents(CameraOrientation *cam)
2906 while (client->hasClientEvents()) {
2907 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2908 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2909 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2910 (this->*evHandler.handler)(event.get(), cam);
2914 void Game::updateChat(f32 dtime)
2916 // Get new messages from error log buffer
2917 while (!m_chat_log_buf.empty())
2918 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2920 // Get new messages from client
2921 std::wstring message;
2922 while (client->getChatMessage(message)) {
2923 chat_backend->addUnparsedMessage(message);
2926 // Remove old messages
2927 chat_backend->step(dtime);
2929 // Display all messages in a static text element
2930 auto &buf = chat_backend->getRecentBuffer();
2931 if (buf.getLinesModified()) {
2932 buf.resetLinesModified();
2933 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
2936 // Make sure that the size is still correct
2937 m_game_ui->updateChatSize();
2940 void Game::updateCamera(f32 dtime)
2942 LocalPlayer *player = client->getEnv().getLocalPlayer();
2945 For interaction purposes, get info about the held item
2947 - Is it a usable item?
2948 - Can it point to liquids?
2950 ItemStack playeritem;
2952 ItemStack selected, hand;
2953 playeritem = player->getWieldedItem(&selected, &hand);
2956 ToolCapabilities playeritem_toolcap =
2957 playeritem.getToolCapabilities(itemdef_manager);
2959 v3s16 old_camera_offset = camera->getOffset();
2961 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2962 GenericCAO *playercao = player->getCAO();
2964 // If playercao not loaded, don't change camera
2968 camera->toggleCameraMode();
2970 // Make the player visible depending on camera mode.
2971 playercao->updateMeshCulling();
2972 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2975 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2976 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2978 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2979 camera->update(player, dtime, tool_reload_ratio);
2980 camera->step(dtime);
2982 v3f camera_position = camera->getPosition();
2983 v3f camera_direction = camera->getDirection();
2984 f32 camera_fov = camera->getFovMax();
2985 v3s16 camera_offset = camera->getOffset();
2987 m_camera_offset_changed = (camera_offset != old_camera_offset);
2989 if (!m_flags.disable_camera_update) {
2990 client->getEnv().getClientMap().updateCamera(camera_position,
2991 camera_direction, camera_fov, camera_offset);
2993 if (m_camera_offset_changed) {
2994 client->updateCameraOffset(camera_offset);
2995 client->getEnv().updateCameraOffset(camera_offset);
2998 clouds->updateCameraOffset(camera_offset);
3004 void Game::updateSound(f32 dtime)
3006 // Update sound listener
3007 v3s16 camera_offset = camera->getOffset();
3008 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3009 v3f(0, 0, 0), // velocity
3010 camera->getDirection(),
3011 camera->getCameraNode()->getUpVector());
3013 bool mute_sound = g_settings->getBool("mute_sound");
3015 sound->setListenerGain(0.0f);
3017 // Check if volume is in the proper range, else fix it.
3018 float old_volume = g_settings->getFloat("sound_volume");
3019 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3020 sound->setListenerGain(new_volume);
3022 if (old_volume != new_volume) {
3023 g_settings->setFloat("sound_volume", new_volume);
3027 LocalPlayer *player = client->getEnv().getLocalPlayer();
3029 // Tell the sound maker whether to make footstep sounds
3030 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3032 // Update sound maker
3033 if (player->makes_footstep_sound)
3034 soundmaker->step(dtime);
3036 ClientMap &map = client->getEnv().getClientMap();
3037 MapNode n = map.getNode(player->getFootstepNodePos());
3038 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3042 void Game::processPlayerInteraction(f32 dtime, bool show_hud)
3044 LocalPlayer *player = client->getEnv().getLocalPlayer();
3046 const v3f camera_direction = camera->getDirection();
3047 const v3s16 camera_offset = camera->getOffset();
3050 Calculate what block is the crosshair pointing to
3053 ItemStack selected_item, hand_item;
3054 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3056 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3057 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3059 core::line3d<f32> shootline;
3061 switch (camera->getCameraMode()) {
3062 case CAMERA_MODE_FIRST:
3063 // Shoot from camera position, with bobbing
3064 shootline.start = camera->getPosition();
3066 case CAMERA_MODE_THIRD:
3067 // Shoot from player head, no bobbing
3068 shootline.start = camera->getHeadPosition();
3070 case CAMERA_MODE_THIRD_FRONT:
3071 shootline.start = camera->getHeadPosition();
3072 // prevent player pointing anything in front-view
3076 shootline.end = shootline.start + camera_direction * BS * d;
3078 #ifdef HAVE_TOUCHSCREENGUI
3080 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3081 shootline = g_touchscreengui->getShootline();
3082 // Scale shootline to the acual distance the player can reach
3083 shootline.end = shootline.start
3084 + shootline.getVector().normalize() * BS * d;
3085 shootline.start += intToFloat(camera_offset, BS);
3086 shootline.end += intToFloat(camera_offset, BS);
3091 PointedThing pointed = updatePointedThing(shootline,
3092 selected_def.liquids_pointable,
3093 !runData.btn_down_for_dig,
3096 if (pointed != runData.pointed_old) {
3097 infostream << "Pointing at " << pointed.dump() << std::endl;
3098 hud->updateSelectionMesh(camera_offset);
3101 // Allow digging again if button is not pressed
3102 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3103 runData.digging_blocked = false;
3107 - releasing dig button
3108 - pointing away from node
3110 if (runData.digging) {
3111 if (wasKeyReleased(KeyType::DIG)) {
3112 infostream << "Dig button released (stopped digging)" << std::endl;
3113 runData.digging = false;
3114 } else if (pointed != runData.pointed_old) {
3115 if (pointed.type == POINTEDTHING_NODE
3116 && runData.pointed_old.type == POINTEDTHING_NODE
3117 && pointed.node_undersurface
3118 == runData.pointed_old.node_undersurface) {
3119 // Still pointing to the same node, but a different face.
3122 infostream << "Pointing away from node (stopped digging)" << std::endl;
3123 runData.digging = false;
3124 hud->updateSelectionMesh(camera_offset);
3128 if (!runData.digging) {
3129 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3130 client->setCrack(-1, v3s16(0, 0, 0));
3131 runData.dig_time = 0.0;
3133 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3134 // Remove e.g. torches faster when clicking instead of holding dig button
3135 runData.nodig_delay_timer = 0;
3136 runData.dig_instantly = false;
3139 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3140 runData.btn_down_for_dig = false;
3142 runData.punching = false;
3144 soundmaker->m_player_leftpunch_sound.name = "";
3146 // Prepare for repeating, unless we're not supposed to
3147 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3148 runData.repeat_place_timer += dtime;
3150 runData.repeat_place_timer = 0;
3152 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3153 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3154 !client->getScript()->on_item_use(selected_item, pointed)))
3155 client->interact(INTERACT_USE, pointed);
3156 } else if (pointed.type == POINTEDTHING_NODE) {
3157 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3158 } else if (pointed.type == POINTEDTHING_OBJECT) {
3159 v3f player_position = player->getPosition();
3160 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
3161 handlePointingAtObject(pointed, tool_item, player_position,
3162 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
3163 } else if (isKeyDown(KeyType::DIG)) {
3164 // When button is held down in air, show continuous animation
3165 runData.punching = true;
3166 // Run callback even though item is not usable
3167 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3168 client->getScript()->on_item_use(selected_item, pointed);
3169 } else if (wasKeyPressed(KeyType::PLACE)) {
3170 handlePointingAtNothing(selected_item);
3173 runData.pointed_old = pointed;
3175 if (runData.punching || wasKeyPressed(KeyType::DIG))
3176 camera->setDigging(0); // dig animation
3178 input->clearWasKeyPressed();
3179 input->clearWasKeyReleased();
3180 // Ensure DIG & PLACE are marked as handled
3181 wasKeyDown(KeyType::DIG);
3182 wasKeyDown(KeyType::PLACE);
3184 input->joystick.clearWasKeyPressed(KeyType::DIG);
3185 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3187 input->joystick.clearWasKeyReleased(KeyType::DIG);
3188 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3192 PointedThing Game::updatePointedThing(
3193 const core::line3d<f32> &shootline,
3194 bool liquids_pointable,
3195 bool look_for_object,
3196 const v3s16 &camera_offset)
3198 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3199 selectionboxes->clear();
3200 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3201 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3202 "show_entity_selectionbox");
3204 ClientEnvironment &env = client->getEnv();
3205 ClientMap &map = env.getClientMap();
3206 const NodeDefManager *nodedef = map.getNodeDefManager();
3208 runData.selected_object = NULL;
3209 hud->pointing_at_object = false;
3211 RaycastState s(shootline, look_for_object, liquids_pointable);
3212 PointedThing result;
3213 env.continueRaycast(&s, &result);
3214 if (result.type == POINTEDTHING_OBJECT) {
3215 hud->pointing_at_object = true;
3217 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3218 aabb3f selection_box;
3219 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3220 runData.selected_object->getSelectionBox(&selection_box)) {
3221 v3f pos = runData.selected_object->getPosition();
3222 selectionboxes->push_back(aabb3f(selection_box));
3223 hud->setSelectionPos(pos, camera_offset);
3225 } else if (result.type == POINTEDTHING_NODE) {
3226 // Update selection boxes
3227 MapNode n = map.getNode(result.node_undersurface);
3228 std::vector<aabb3f> boxes;
3229 n.getSelectionBoxes(nodedef, &boxes,
3230 n.getNeighbors(result.node_undersurface, &map));
3233 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3234 i != boxes.end(); ++i) {
3236 box.MinEdge -= v3f(d, d, d);
3237 box.MaxEdge += v3f(d, d, d);
3238 selectionboxes->push_back(box);
3240 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3242 hud->setSelectedFaceNormal(v3f(
3243 result.intersection_normal.X,
3244 result.intersection_normal.Y,
3245 result.intersection_normal.Z));
3248 // Update selection mesh light level and vertex colors
3249 if (!selectionboxes->empty()) {
3250 v3f pf = hud->getSelectionPos();
3251 v3s16 p = floatToInt(pf, BS);
3253 // Get selection mesh light level
3254 MapNode n = map.getNode(p);
3255 u16 node_light = getInteriorLight(n, -1, nodedef);
3256 u16 light_level = node_light;
3258 for (const v3s16 &dir : g_6dirs) {
3259 n = map.getNode(p + dir);
3260 node_light = getInteriorLight(n, -1, nodedef);
3261 if (node_light > light_level)
3262 light_level = node_light;
3265 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3267 final_color_blend(&c, light_level, daynight_ratio);
3269 // Modify final color a bit with time
3270 u32 timer = porting::getTimeMs() % 5000;
3271 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3272 float sin_r = 0.08f * std::sin(timerf);
3273 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3274 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3275 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3276 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3277 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3279 // Set mesh final color
3280 hud->setSelectionMeshColor(c);
3286 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3288 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3289 PointedThing fauxPointed;
3290 fauxPointed.type = POINTEDTHING_NOTHING;
3291 client->interact(INTERACT_ACTIVATE, fauxPointed);
3295 void Game::handlePointingAtNode(const PointedThing &pointed,
3296 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3298 v3s16 nodepos = pointed.node_undersurface;
3299 v3s16 neighbourpos = pointed.node_abovesurface;
3302 Check information text of node
3305 ClientMap &map = client->getEnv().getClientMap();
3307 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3308 && !runData.digging_blocked
3309 && client->checkPrivilege("interact")) {
3310 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3313 // This should be done after digging handling
3314 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3317 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3318 meta->getString("infotext"))));
3320 MapNode n = map.getNode(nodepos);
3322 if (nodedef_manager->get(n).name == "unknown") {
3323 m_game_ui->setInfoText(L"Unknown node");
3327 if ((wasKeyPressed(KeyType::PLACE) ||
3328 runData.repeat_place_timer >= m_repeat_place_time) &&
3329 client->checkPrivilege("interact")) {
3330 runData.repeat_place_timer = 0;
3331 infostream << "Place button pressed while looking at ground" << std::endl;
3333 // Placing animation (always shown for feedback)
3334 camera->setDigging(1);
3336 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3338 // If the wielded item has node placement prediction,
3340 // And also set the sound and send the interact
3341 // But first check for meta formspec and rightclickable
3342 auto &def = selected_item.getDefinition(itemdef_manager);
3343 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3346 if (placed && client->modsLoaded())
3347 client->getScript()->on_placenode(pointed, def);
3351 bool Game::nodePlacement(const ItemDefinition &selected_def,
3352 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3353 const PointedThing &pointed, const NodeMetadata *meta)
3355 const auto &prediction = selected_def.node_placement_prediction;
3357 const NodeDefManager *nodedef = client->ndef();
3358 ClientMap &map = client->getEnv().getClientMap();
3360 bool is_valid_position;
3362 node = map.getNode(nodepos, &is_valid_position);
3363 if (!is_valid_position) {
3364 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3369 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3370 && !isKeyDown(KeyType::SNEAK)) {
3371 // on_rightclick callbacks are called anyway
3372 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3373 client->interact(INTERACT_PLACE, pointed);
3375 infostream << "Launching custom inventory view" << std::endl;
3377 InventoryLocation inventoryloc;
3378 inventoryloc.setNodeMeta(nodepos);
3380 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3381 &client->getEnv().getClientMap(), nodepos);
3382 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3384 auto *&formspec = m_game_ui->updateFormspec("");
3385 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3386 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3388 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3392 // on_rightclick callback
3393 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3394 !isKeyDown(KeyType::SNEAK))) {
3396 client->interact(INTERACT_PLACE, pointed);
3400 verbosestream << "Node placement prediction for "
3401 << selected_def.name << " is " << prediction << std::endl;
3402 v3s16 p = neighbourpos;
3404 // Place inside node itself if buildable_to
3405 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3406 if (is_valid_position) {
3407 if (nodedef->get(n_under).buildable_to) {
3410 node = map.getNode(p, &is_valid_position);
3411 if (is_valid_position && !nodedef->get(node).buildable_to) {
3412 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3414 client->interact(INTERACT_PLACE, pointed);
3420 // Find id of predicted node
3422 bool found = nodedef->getId(prediction, id);
3425 errorstream << "Node placement prediction failed for "
3426 << selected_def.name << " (places " << prediction
3427 << ") - Name not known" << std::endl;
3428 // Handle this as if prediction was empty
3430 client->interact(INTERACT_PLACE, pointed);
3434 const ContentFeatures &predicted_f = nodedef->get(id);
3436 // Predict param2 for facedir and wallmounted nodes
3437 // Compare core.item_place_node() for what the server does
3440 const u8 place_param2 = selected_def.place_param2;
3443 param2 = place_param2;
3444 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3445 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3446 v3s16 dir = nodepos - neighbourpos;
3448 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3449 param2 = dir.Y < 0 ? 1 : 0;
3450 } else if (abs(dir.X) > abs(dir.Z)) {
3451 param2 = dir.X < 0 ? 3 : 2;
3453 param2 = dir.Z < 0 ? 5 : 4;
3455 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3456 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3457 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3459 if (abs(dir.X) > abs(dir.Z)) {
3460 param2 = dir.X < 0 ? 3 : 1;
3462 param2 = dir.Z < 0 ? 2 : 0;
3466 // Check attachment if node is in group attached_node
3467 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3468 const static v3s16 wallmounted_dirs[8] = {
3478 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3479 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3480 pp = p + wallmounted_dirs[param2];
3482 pp = p + v3s16(0, -1, 0);
3484 if (!nodedef->get(map.getNode(pp)).walkable) {
3485 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3487 client->interact(INTERACT_PLACE, pointed);
3493 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3494 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3495 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3496 const auto &indexstr = selected_item.metadata.
3497 getString("palette_index", 0);
3498 if (!indexstr.empty()) {
3499 s32 index = mystoi(indexstr);
3500 if (predicted_f.param_type_2 == CPT2_COLOR) {
3502 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3503 // param2 = pure palette index + other
3504 param2 = (index & 0xf8) | (param2 & 0x07);
3505 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3506 // param2 = pure palette index + other
3507 param2 = (index & 0xe0) | (param2 & 0x1f);
3512 // Add node to client map
3513 MapNode n(id, 0, param2);
3516 LocalPlayer *player = client->getEnv().getLocalPlayer();
3518 // Dont place node when player would be inside new node
3519 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3520 if (!nodedef->get(n).walkable ||
3521 g_settings->getBool("enable_build_where_you_stand") ||
3522 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3523 (nodedef->get(n).walkable &&
3524 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3525 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3526 // This triggers the required mesh update too
3527 client->addNode(p, n);
3529 client->interact(INTERACT_PLACE, pointed);
3530 // A node is predicted, also play a sound
3531 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3534 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3537 } catch (const InvalidPositionException &e) {
3538 errorstream << "Node placement prediction failed for "
3539 << selected_def.name << " (places "
3540 << prediction << ") - Position not loaded" << std::endl;
3541 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3546 void Game::handlePointingAtObject(const PointedThing &pointed,
3547 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3549 std::wstring infotext = unescape_translate(
3550 utf8_to_wide(runData.selected_object->infoText()));
3553 if (!infotext.empty()) {
3556 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3559 m_game_ui->setInfoText(infotext);
3561 if (isKeyDown(KeyType::DIG)) {
3562 bool do_punch = false;
3563 bool do_punch_damage = false;
3565 if (runData.object_hit_delay_timer <= 0.0) {
3567 do_punch_damage = true;
3568 runData.object_hit_delay_timer = object_hit_delay;
3571 if (wasKeyPressed(KeyType::DIG))
3575 infostream << "Punched object" << std::endl;
3576 runData.punching = true;
3579 if (do_punch_damage) {
3580 // Report direct punch
3581 v3f objpos = runData.selected_object->getPosition();
3582 v3f dir = (objpos - player_position).normalize();
3584 bool disable_send = runData.selected_object->directReportPunch(
3585 dir, &tool_item, runData.time_from_last_punch);
3586 runData.time_from_last_punch = 0;
3589 client->interact(INTERACT_START_DIGGING, pointed);
3591 } else if (wasKeyDown(KeyType::PLACE)) {
3592 infostream << "Pressed place button while pointing at object" << std::endl;
3593 client->interact(INTERACT_PLACE, pointed); // place
3598 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3599 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3601 // See also: serverpackethandle.cpp, action == 2
3602 LocalPlayer *player = client->getEnv().getLocalPlayer();
3603 ClientMap &map = client->getEnv().getClientMap();
3604 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3606 // NOTE: Similar piece of code exists on the server side for
3608 // Get digging parameters
3609 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3610 &selected_item.getToolCapabilities(itemdef_manager),
3611 selected_item.wear);
3613 // If can't dig, try hand
3614 if (!params.diggable) {
3615 params = getDigParams(nodedef_manager->get(n).groups,
3616 &hand_item.getToolCapabilities(itemdef_manager));
3619 if (!params.diggable) {
3620 // I guess nobody will wait for this long
3621 runData.dig_time_complete = 10000000.0;
3623 runData.dig_time_complete = params.time;
3625 if (m_cache_enable_particles) {
3626 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3627 client->getParticleManager()->addNodeParticle(client,
3628 player, nodepos, n, features);
3632 if (!runData.digging) {
3633 infostream << "Started digging" << std::endl;
3634 runData.dig_instantly = runData.dig_time_complete == 0;
3635 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3637 client->interact(INTERACT_START_DIGGING, pointed);
3638 runData.digging = true;
3639 runData.btn_down_for_dig = true;
3642 if (!runData.dig_instantly) {
3643 runData.dig_index = (float)crack_animation_length
3645 / runData.dig_time_complete;
3647 // This is for e.g. torches
3648 runData.dig_index = crack_animation_length;
3651 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3653 if (sound_dig.exists() && params.diggable) {
3654 if (sound_dig.name == "__group") {
3655 if (!params.main_group.empty()) {
3656 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3657 soundmaker->m_player_leftpunch_sound.name =
3658 std::string("default_dig_") +
3662 soundmaker->m_player_leftpunch_sound = sound_dig;
3666 // Don't show cracks if not diggable
3667 if (runData.dig_time_complete >= 100000.0) {
3668 } else if (runData.dig_index < crack_animation_length) {
3669 //TimeTaker timer("client.setTempMod");
3670 //infostream<<"dig_index="<<dig_index<<std::endl;
3671 client->setCrack(runData.dig_index, nodepos);
3673 infostream << "Digging completed" << std::endl;
3674 client->setCrack(-1, v3s16(0, 0, 0));
3676 runData.dig_time = 0;
3677 runData.digging = false;
3678 // we successfully dug, now block it from repeating if we want to be safe
3679 if (g_settings->getBool("safe_dig_and_place"))
3680 runData.digging_blocked = true;
3682 runData.nodig_delay_timer =
3683 runData.dig_time_complete / (float)crack_animation_length;
3685 // We don't want a corresponding delay to very time consuming nodes
3686 // and nodes without digging time (e.g. torches) get a fixed delay.
3687 if (runData.nodig_delay_timer > 0.3)
3688 runData.nodig_delay_timer = 0.3;
3689 else if (runData.dig_instantly)
3690 runData.nodig_delay_timer = 0.15;
3692 bool is_valid_position;
3693 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3694 if (is_valid_position) {
3695 if (client->modsLoaded() &&
3696 client->getScript()->on_dignode(nodepos, wasnode)) {
3700 const ContentFeatures &f = client->ndef()->get(wasnode);
3701 if (f.node_dig_prediction == "air") {
3702 client->removeNode(nodepos);
3703 } else if (!f.node_dig_prediction.empty()) {
3705 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3707 client->addNode(nodepos, id, true);
3709 // implicit else: no prediction
3712 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3714 if (m_cache_enable_particles) {
3715 const ContentFeatures &features =
3716 client->getNodeDefManager()->get(wasnode);
3717 client->getParticleManager()->addDiggingParticles(client,
3718 player, nodepos, wasnode, features);
3722 // Send event to trigger sound
3723 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3726 if (runData.dig_time_complete < 100000.0) {
3727 runData.dig_time += dtime;
3729 runData.dig_time = 0;
3730 client->setCrack(-1, nodepos);
3733 camera->setDigging(0); // Dig animation
3736 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3737 const CameraOrientation &cam)
3739 TimeTaker tt_update("Game::updateFrame()");
3740 LocalPlayer *player = client->getEnv().getLocalPlayer();
3746 if (draw_control->range_all) {
3747 runData.fog_range = 100000 * BS;
3749 runData.fog_range = draw_control->wanted_range * BS;
3753 Calculate general brightness
3755 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3756 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3757 float direct_brightness;
3760 if (m_cache_enable_noclip && m_cache_enable_free_move) {
3761 direct_brightness = time_brightness;
3762 sunlight_seen = true;
3764 float old_brightness = sky->getBrightness();
3765 direct_brightness = client->getEnv().getClientMap()
3766 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3767 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3771 float time_of_day_smooth = runData.time_of_day_smooth;
3772 float time_of_day = client->getEnv().getTimeOfDayF();
3774 static const float maxsm = 0.05f;
3775 static const float todsm = 0.05f;
3777 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3778 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3779 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3780 time_of_day_smooth = time_of_day;
3782 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3783 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3784 + (time_of_day + 1.0) * todsm;
3786 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3787 + time_of_day * todsm;
3789 runData.time_of_day_smooth = time_of_day_smooth;
3791 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3792 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3793 player->getPitch());
3799 if (sky->getCloudsVisible()) {
3800 clouds->setVisible(true);
3801 clouds->step(dtime);
3802 // camera->getPosition is not enough for 3rd person views
3803 v3f camera_node_position = camera->getCameraNode()->getPosition();
3804 v3s16 camera_offset = camera->getOffset();
3805 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3806 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3807 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3808 clouds->update(camera_node_position,
3809 sky->getCloudColor());
3810 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3811 // if inside clouds, and fog enabled, use that as sky
3813 video::SColor clouds_dark = clouds->getColor()
3814 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3815 sky->overrideColors(clouds_dark, clouds->getColor());
3816 sky->setInClouds(true);
3817 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3818 // do not draw clouds after all
3819 clouds->setVisible(false);
3822 clouds->setVisible(false);
3829 client->getParticleManager()->step(dtime);
3835 if (m_cache_enable_fog) {
3838 video::EFT_FOG_LINEAR,
3839 runData.fog_range * m_cache_fog_start,
3840 runData.fog_range * 1.0,
3848 video::EFT_FOG_LINEAR,
3860 if (player->hurt_tilt_timer > 0.0f) {
3861 player->hurt_tilt_timer -= dtime * 6.0f;
3863 if (player->hurt_tilt_timer < 0.0f)
3864 player->hurt_tilt_strength = 0.0f;
3868 Update minimap pos and rotation
3870 if (mapper && m_game_ui->m_flags.show_hud) {
3871 mapper->setPos(floatToInt(player->getPosition(), BS));
3872 mapper->setAngle(player->getYaw());
3876 Get chat messages from client
3885 if (player->getWieldIndex() != runData.new_playeritem)
3886 client->setPlayerItem(runData.new_playeritem);
3888 if (client->updateWieldedItem()) {
3889 // Update wielded tool
3890 ItemStack selected_item, hand_item;
3891 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3892 camera->wield(tool_item);
3896 Update block draw list every 200ms or when camera direction has
3899 runData.update_draw_list_timer += dtime;
3901 float update_draw_list_delta = 0.2f;
3903 v3f camera_direction = camera->getDirection();
3904 if (runData.update_draw_list_timer >= update_draw_list_delta
3905 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3906 || m_camera_offset_changed
3907 || client->getEnv().getClientMap().needsUpdateDrawList()) {
3908 runData.update_draw_list_timer = 0;
3909 client->getEnv().getClientMap().updateDrawList();
3910 runData.update_draw_list_last_cam_dir = camera_direction;
3913 if (RenderingEngine::get_shadow_renderer()) {
3917 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3920 make sure menu is on top
3921 1. Delete formspec menu reference if menu was removed
3922 2. Else, make sure formspec menu is on top
3924 auto formspec = m_game_ui->getFormspecGUI();
3925 do { // breakable. only runs for one iteration
3929 if (formspec->getReferenceCount() == 1) {
3930 m_game_ui->deleteFormspec();
3934 auto &loc = formspec->getFormspecLocation();
3935 if (loc.type == InventoryLocation::NODEMETA) {
3936 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3937 if (!meta || meta->getString("formspec").empty()) {
3938 formspec->quitMenu();
3944 guiroot->bringToFront(formspec);
3948 ==================== Drawing begins ====================
3950 const video::SColor skycolor = sky->getSkyColor();
3952 TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
3953 driver->beginScene(true, true, skycolor);
3955 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3956 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3957 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3958 bool draw_crosshair = (
3959 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3960 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3961 #ifdef HAVE_TOUCHSCREENGUI
3963 draw_crosshair = !g_settings->getBool("touchtarget");
3964 } catch (SettingNotFoundException) {
3967 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3968 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3973 v2u32 screensize = driver->getScreenSize();
3975 if (m_game_ui->m_flags.show_profiler_graph)
3976 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3981 if (runData.damage_flash > 0.0f) {
3982 video::SColor color(runData.damage_flash, 180, 0, 0);
3983 driver->draw2DRectangle(color,
3984 core::rect<s32>(0, 0, screensize.X, screensize.Y),
3987 runData.damage_flash -= 384.0f * dtime;
3991 ==================== End scene ====================
3993 if (++m_reset_HW_buffer_counter > 500) {
3995 Periodically remove all mesh HW buffers.
3997 Work around for a quirk in Irrlicht where a HW buffer is only
3998 released after 20000 iterations (triggered from endScene()).
4000 Without this, all loaded but unused meshes will retain their HW
4001 buffers for at least 5 minutes, at which point looking up the HW buffers
4002 becomes a bottleneck and the framerate drops (as much as 30%).
4004 Tests showed that numbers between 50 and 1000 are good, so picked 500.
4005 There are no other public Irrlicht APIs that allow interacting with the
4006 HW buffers without tracking the status of every individual mesh.
4008 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4010 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4011 driver->removeAllHardwareBuffers();
4012 m_reset_HW_buffer_counter = 0;
4017 stats->drawtime = tt_draw.stop(true);
4018 g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
4019 g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
4022 /* Log times and stuff for visualization */
4023 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4025 Profiler::GraphValues values;
4026 g_profiler->graphGet(values);
4030 /****************************************************************************
4032 *****************************************************************************/
4033 void Game::updateShadows()
4035 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4039 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4041 float timeoftheday = getWickedTimeOfDay(in_timeofday);
4042 bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
4043 bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
4044 shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
4046 timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
4047 const float offset_constant = 10000.0f;
4049 v3f light(0.0f, 0.0f, -1.0f);
4050 light.rotateXZBy(90);
4051 light.rotateXYBy(timeoftheday * 360 - 90);
4052 light.rotateYZBy(sky->getSkyBodyOrbitTilt());
4054 v3f sun_pos = light * offset_constant;
4056 if (shadow->getDirectionalLightCount() == 0)
4057 shadow->addDirectionalLight();
4058 shadow->getDirectionalLight().setDirection(sun_pos);
4059 shadow->setTimeOfDay(in_timeofday);
4061 shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4064 /****************************************************************************
4066 ****************************************************************************/
4068 void FpsControl::reset()
4070 last_time = porting::getTimeUs();
4074 * On some computers framerate doesn't seem to be automatically limited
4076 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
4078 const u64 frametime_min = 1000000.0f / (
4079 device->isWindowFocused() && !g_menumgr.pausesGame()
4080 ? g_settings->getFloat("fps_max")
4081 : g_settings->getFloat("fps_max_unfocused"));
4083 u64 time = porting::getTimeUs();
4085 if (time > last_time) // Make sure time hasn't overflowed
4086 busy_time = time - last_time;
4090 if (busy_time < frametime_min) {
4091 sleep_time = frametime_min - busy_time;
4092 if (sleep_time > 1000)
4093 sleep_ms(sleep_time / 1000);
4098 // Read the timer again to accurately determine how long we actually slept,
4099 // rather than calculating it by adding sleep_time to time.
4100 time = porting::getTimeUs();
4102 if (time > last_time) // Make sure last_time hasn't overflowed
4103 *dtime = (time - last_time) / 1000000.0f;
4110 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4112 const wchar_t *wmsg = wgettext(msg);
4113 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4118 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4120 ((Game *)data)->readSettings();
4123 void Game::readSettings()
4125 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4126 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4127 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4128 m_cache_enable_particles = g_settings->getBool("enable_particles");
4129 m_cache_enable_fog = g_settings->getBool("enable_fog");
4130 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
4131 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4132 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
4134 m_cache_enable_noclip = g_settings->getBool("noclip");
4135 m_cache_enable_free_move = g_settings->getBool("free_move");
4137 m_cache_fog_start = g_settings->getFloat("fog_start");
4139 m_cache_cam_smoothing = 0;
4140 if (g_settings->getBool("cinematic"))
4141 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4143 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4145 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4146 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4147 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4149 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4152 /****************************************************************************/
4153 /****************************************************************************
4155 ****************************************************************************/
4156 /****************************************************************************/
4158 void Game::showDeathFormspec()
4160 static std::string formspec_str =
4161 std::string("formspec_version[1]") +
4163 "bgcolor[#320000b4;true]"
4164 "label[4.85,1.35;" + gettext("You died") + "]"
4165 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4169 /* Note: FormspecFormSource and LocalFormspecHandler *
4170 * are deleted by guiFormSpecMenu */
4171 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4172 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4174 auto *&formspec = m_game_ui->getFormspecGUI();
4175 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4176 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4177 formspec->setFocus("btn_respawn");
4180 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4181 void Game::showPauseMenu()
4183 #ifdef HAVE_TOUCHSCREENGUI
4184 static const std::string control_text = strgettext("Default Controls:\n"
4185 "No menu visible:\n"
4186 "- single tap: button activate\n"
4187 "- double tap: place/use\n"
4188 "- slide finger: look around\n"
4189 "Menu/Inventory visible:\n"
4190 "- double tap (outside):\n"
4192 "- touch stack, touch slot:\n"
4194 "- touch&drag, tap 2nd finger\n"
4195 " --> place single item to slot\n"
4198 static const std::string control_text_template = strgettext("Controls:\n"
4199 "- %s: move forwards\n"
4200 "- %s: move backwards\n"
4202 "- %s: move right\n"
4203 "- %s: jump/climb up\n"
4206 "- %s: sneak/climb down\n"
4209 "- Mouse: turn/look\n"
4210 "- Mouse wheel: select item\n"
4214 char control_text_buf[600];
4216 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4217 GET_KEY_NAME(keymap_forward),
4218 GET_KEY_NAME(keymap_backward),
4219 GET_KEY_NAME(keymap_left),
4220 GET_KEY_NAME(keymap_right),
4221 GET_KEY_NAME(keymap_jump),
4222 GET_KEY_NAME(keymap_dig),
4223 GET_KEY_NAME(keymap_place),
4224 GET_KEY_NAME(keymap_sneak),
4225 GET_KEY_NAME(keymap_drop),
4226 GET_KEY_NAME(keymap_inventory),
4227 GET_KEY_NAME(keymap_chat)
4230 std::string control_text = std::string(control_text_buf);
4231 str_formspec_escape(control_text);
4234 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4235 std::ostringstream os;
4237 os << "formspec_version[1]" << SIZE_TAG
4238 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4239 << strgettext("Continue") << "]";
4241 if (!simple_singleplayer_mode) {
4242 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4243 << strgettext("Change Password") << "]";
4245 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4250 if (g_settings->getBool("enable_sound")) {
4251 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4252 << strgettext("Sound Volume") << "]";
4255 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4256 << strgettext("Change Keys") << "]";
4258 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4259 << strgettext("Exit to Menu") << "]";
4260 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4261 << strgettext("Exit to OS") << "]"
4262 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4263 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4265 << strgettext("Game info:") << "\n";
4266 const std::string &address = client->getAddressName();
4267 static const std::string mode = strgettext("- Mode: ");
4268 if (!simple_singleplayer_mode) {
4269 Address serverAddress = client->getServerAddress();
4270 if (!address.empty()) {
4271 os << mode << strgettext("Remote server") << "\n"
4272 << strgettext("- Address: ") << address;
4274 os << mode << strgettext("Hosting server");
4276 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4278 os << mode << strgettext("Singleplayer") << "\n";
4280 if (simple_singleplayer_mode || address.empty()) {
4281 static const std::string on = strgettext("On");
4282 static const std::string off = strgettext("Off");
4283 // Note: Status of enable_damage and creative_mode settings is intentionally
4284 // NOT shown here because the game might roll its own damage system and/or do
4285 // a per-player Creative Mode, in which case writing it here would mislead.
4286 bool damage = g_settings->getBool("enable_damage");
4287 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4288 if (!simple_singleplayer_mode) {
4290 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4291 //~ PvP = Player versus Player
4292 os << strgettext("- PvP: ") << pvp << "\n";
4294 os << strgettext("- Public: ") << announced << "\n";
4295 std::string server_name = g_settings->get("server_name");
4296 str_formspec_escape(server_name);
4297 if (announced == on && !server_name.empty())
4298 os << strgettext("- Server Name: ") << server_name;
4305 /* Note: FormspecFormSource and LocalFormspecHandler *
4306 * are deleted by guiFormSpecMenu */
4307 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4308 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4310 auto *&formspec = m_game_ui->getFormspecGUI();
4311 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4312 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4313 formspec->setFocus("btn_continue");
4314 formspec->doPause = true;
4316 if (simple_singleplayer_mode)
4320 /****************************************************************************/
4321 /****************************************************************************
4322 extern function for launching the game
4323 ****************************************************************************/
4324 /****************************************************************************/
4326 void the_game(bool *kill,
4327 InputHandler *input,
4328 RenderingEngine *rendering_engine,
4329 const GameStartData &start_data,
4330 std::string &error_message,
4331 ChatBackend &chat_backend,
4332 bool *reconnect_requested) // Used for local game
4336 /* Make a copy of the server address because if a local singleplayer server
4337 * is created then this is updated and we don't want to change the value
4338 * passed to us by the calling function
4343 if (game.startup(kill, input, rendering_engine, start_data,
4344 error_message, reconnect_requested, &chat_backend)) {
4348 } catch (SerializationError &e) {
4349 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
4350 error_message = strgettext("A serialization error occurred:") +"\n"
4351 + e.what() + "\n\n" + ver_err;
4352 errorstream << error_message << std::endl;
4353 } catch (ServerError &e) {
4354 error_message = e.what();
4355 errorstream << "ServerError: " << error_message << std::endl;
4356 } catch (ModError &e) {
4357 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
4358 error_message = std::string("ModError: ") + e.what() +
4359 strgettext("\nCheck debug.txt for details.");
4360 errorstream << error_message << std::endl;