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 #if IRRLICHT_VERSION_MT_REVISION < 5
911 int m_reset_HW_buffer_counter = 0;
914 #ifdef HAVE_TOUCHSCREENGUI
915 bool m_cache_hold_aux1;
918 bool m_android_chat_open;
923 m_chat_log_buf(g_logger),
924 m_game_ui(new GameUI())
926 g_settings->registerChangedCallback("doubletap_jump",
927 &settingChangedCallback, this);
928 g_settings->registerChangedCallback("enable_clouds",
929 &settingChangedCallback, this);
930 g_settings->registerChangedCallback("doubletap_joysticks",
931 &settingChangedCallback, this);
932 g_settings->registerChangedCallback("enable_particles",
933 &settingChangedCallback, this);
934 g_settings->registerChangedCallback("enable_fog",
935 &settingChangedCallback, this);
936 g_settings->registerChangedCallback("mouse_sensitivity",
937 &settingChangedCallback, this);
938 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
939 &settingChangedCallback, this);
940 g_settings->registerChangedCallback("repeat_place_time",
941 &settingChangedCallback, this);
942 g_settings->registerChangedCallback("noclip",
943 &settingChangedCallback, this);
944 g_settings->registerChangedCallback("free_move",
945 &settingChangedCallback, this);
946 g_settings->registerChangedCallback("cinematic",
947 &settingChangedCallback, this);
948 g_settings->registerChangedCallback("cinematic_camera_smoothing",
949 &settingChangedCallback, this);
950 g_settings->registerChangedCallback("camera_smoothing",
951 &settingChangedCallback, this);
955 #ifdef HAVE_TOUCHSCREENGUI
956 m_cache_hold_aux1 = false; // This is initialised properly later
963 /****************************************************************************
965 ****************************************************************************/
974 delete server; // deleted first to stop all server threads
982 delete nodedef_manager;
983 delete itemdef_manager;
986 clearTextureNameCache();
988 g_settings->deregisterChangedCallback("doubletap_jump",
989 &settingChangedCallback, this);
990 g_settings->deregisterChangedCallback("enable_clouds",
991 &settingChangedCallback, this);
992 g_settings->deregisterChangedCallback("enable_particles",
993 &settingChangedCallback, this);
994 g_settings->deregisterChangedCallback("enable_fog",
995 &settingChangedCallback, this);
996 g_settings->deregisterChangedCallback("mouse_sensitivity",
997 &settingChangedCallback, this);
998 g_settings->deregisterChangedCallback("repeat_place_time",
999 &settingChangedCallback, this);
1000 g_settings->deregisterChangedCallback("noclip",
1001 &settingChangedCallback, this);
1002 g_settings->deregisterChangedCallback("free_move",
1003 &settingChangedCallback, this);
1004 g_settings->deregisterChangedCallback("cinematic",
1005 &settingChangedCallback, this);
1006 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
1007 &settingChangedCallback, this);
1008 g_settings->deregisterChangedCallback("camera_smoothing",
1009 &settingChangedCallback, this);
1012 bool Game::startup(bool *kill,
1013 InputHandler *input,
1014 RenderingEngine *rendering_engine,
1015 const GameStartData &start_data,
1016 std::string &error_message,
1018 ChatBackend *chat_backend)
1022 m_rendering_engine = rendering_engine;
1023 device = m_rendering_engine->get_raw_device();
1025 this->error_message = &error_message;
1026 reconnect_requested = reconnect;
1027 this->input = input;
1028 this->chat_backend = chat_backend;
1029 simple_singleplayer_mode = start_data.isSinglePlayer();
1031 input->keycache.populate();
1033 driver = device->getVideoDriver();
1034 smgr = m_rendering_engine->get_scene_manager();
1036 smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1039 runData = GameRunData();
1040 runData.time_from_last_punch = 10.0;
1042 m_game_ui->initFlags();
1044 m_invert_mouse = g_settings->getBool("invert_mouse");
1045 m_first_loop_after_window_activation = true;
1047 g_client_translations->clear();
1049 // address can change if simple_singleplayer_mode
1050 if (!init(start_data.world_spec.path, start_data.address,
1051 start_data.socket_port, start_data.game_spec))
1054 if (!createClient(start_data))
1057 m_rendering_engine->initialize(client, hud);
1065 ProfilerGraph graph;
1066 RunStats stats = {};
1067 CameraOrientation cam_view_target = {};
1068 CameraOrientation cam_view = {};
1069 FpsControl draw_times;
1070 f32 dtime; // in seconds
1072 /* Clear the profiler */
1073 Profiler::GraphValues dummyvalues;
1074 g_profiler->graphGet(dummyvalues);
1078 set_light_table(g_settings->getFloat("display_gamma"));
1080 #ifdef HAVE_TOUCHSCREENGUI
1081 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1082 && client->checkPrivilege("fast");
1085 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1086 g_settings->getU16("screen_h"));
1088 while (m_rendering_engine->run()
1089 && !(*kill || g_gamecallback->shutdown_requested
1090 || (server && server->isShutdownRequested()))) {
1092 const irr::core::dimension2d<u32> ¤t_screen_size =
1093 m_rendering_engine->get_video_driver()->getScreenSize();
1094 // Verify if window size has changed and save it if it's the case
1095 // Ensure evaluating settings->getBool after verifying screensize
1096 // First condition is cheaper
1097 if (previous_screen_size != current_screen_size &&
1098 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1099 g_settings->getBool("autosave_screensize")) {
1100 g_settings->setU16("screen_w", current_screen_size.Width);
1101 g_settings->setU16("screen_h", current_screen_size.Height);
1102 previous_screen_size = current_screen_size;
1105 // Calculate dtime =
1106 // m_rendering_engine->run() from this iteration
1107 // + Sleep time until the wanted FPS are reached
1108 draw_times.limit(device, &dtime);
1110 // Prepare render data for next iteration
1112 updateStats(&stats, draw_times, dtime);
1113 updateInteractTimers(dtime);
1115 if (!checkConnection())
1117 if (!handleCallbacks())
1122 m_game_ui->clearInfoText();
1123 hud->resizeHotbar();
1126 updateProfilers(stats, draw_times, dtime);
1127 processUserInput(dtime);
1128 // Update camera before player movement to avoid camera lag of one frame
1129 updateCameraDirection(&cam_view_target, dtime);
1130 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1131 cam_view.camera_yaw) * m_cache_cam_smoothing;
1132 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1133 cam_view.camera_pitch) * m_cache_cam_smoothing;
1134 updatePlayerControl(cam_view);
1136 processClientEvents(&cam_view_target);
1138 updateCamera(dtime);
1140 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
1141 updateFrame(&graph, &stats, dtime, cam_view);
1142 updateProfilerGraphs(&graph);
1144 // Update if minimap has been disabled by the server
1145 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1147 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1154 void Game::shutdown()
1156 m_rendering_engine->finalize();
1157 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1158 if (g_settings->get("3d_mode") == "pageflip") {
1159 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1162 auto formspec = m_game_ui->getFormspecGUI();
1164 formspec->quitMenu();
1166 #ifdef HAVE_TOUCHSCREENGUI
1167 g_touchscreengui->hide();
1170 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1175 if (gui_chat_console)
1176 gui_chat_console->drop();
1182 while (g_menumgr.menuCount() > 0) {
1183 g_menumgr.m_stack.front()->setVisible(false);
1184 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1187 m_game_ui->deleteFormspec();
1189 chat_backend->addMessage(L"", L"# Disconnected.");
1190 chat_backend->addMessage(L"", L"");
1191 m_chat_log_buf.clear();
1195 while (!client->isShutdown()) {
1196 assert(texture_src != NULL);
1197 assert(shader_src != NULL);
1198 texture_src->processQueue();
1199 shader_src->processQueue();
1206 /****************************************************************************/
1207 /****************************************************************************
1209 ****************************************************************************/
1210 /****************************************************************************/
1213 const std::string &map_dir,
1214 const std::string &address,
1216 const SubgameSpec &gamespec)
1218 texture_src = createTextureSource();
1220 showOverlayMessage(N_("Loading..."), 0, 0);
1222 shader_src = createShaderSource();
1224 itemdef_manager = createItemDefManager();
1225 nodedef_manager = createNodeDefManager();
1227 eventmgr = new EventManager();
1228 quicktune = new QuicktuneShortcutter();
1230 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1231 && eventmgr && quicktune))
1237 // Create a server if not connecting to an existing one
1238 if (address.empty()) {
1239 if (!createSingleplayerServer(map_dir, gamespec, port))
1246 bool Game::initSound()
1249 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1250 infostream << "Attempting to use OpenAL audio" << std::endl;
1251 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1253 infostream << "Failed to initialize OpenAL audio" << std::endl;
1255 infostream << "Sound disabled." << std::endl;
1259 infostream << "Using dummy audio." << std::endl;
1260 sound = &dummySoundManager;
1261 sound_is_dummy = true;
1264 soundmaker = new SoundMaker(sound, nodedef_manager);
1268 soundmaker->registerReceiver(eventmgr);
1273 bool Game::createSingleplayerServer(const std::string &map_dir,
1274 const SubgameSpec &gamespec, u16 port)
1276 showOverlayMessage(N_("Creating server..."), 0, 5);
1278 std::string bind_str = g_settings->get("bind_address");
1279 Address bind_addr(0, 0, 0, 0, port);
1281 if (g_settings->getBool("ipv6_server")) {
1282 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1286 bind_addr.Resolve(bind_str.c_str());
1287 } catch (ResolveError &e) {
1288 infostream << "Resolving bind address \"" << bind_str
1289 << "\" failed: " << e.what()
1290 << " -- Listening on all addresses." << std::endl;
1293 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1294 *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
1295 bind_addr.serializeString().c_str());
1296 errorstream << *error_message << std::endl;
1300 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1301 false, nullptr, error_message);
1307 bool Game::createClient(const GameStartData &start_data)
1309 showOverlayMessage(N_("Creating client..."), 0, 10);
1311 draw_control = new MapDrawControl();
1315 bool could_connect, connect_aborted;
1316 #ifdef HAVE_TOUCHSCREENGUI
1317 if (g_touchscreengui) {
1318 g_touchscreengui->init(texture_src);
1319 g_touchscreengui->hide();
1322 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1325 if (!could_connect) {
1326 if (error_message->empty() && !connect_aborted) {
1327 // Should not happen if error messages are set properly
1328 *error_message = gettext("Connection failed for unknown reason");
1329 errorstream << *error_message << std::endl;
1334 if (!getServerContent(&connect_aborted)) {
1335 if (error_message->empty() && !connect_aborted) {
1336 // Should not happen if error messages are set properly
1337 *error_message = gettext("Connection failed for unknown reason");
1338 errorstream << *error_message << std::endl;
1343 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1344 &m_flags.force_fog_off, &runData.fog_range, client);
1345 shader_src->addShaderConstantSetterFactory(scsf);
1347 // Update cached textures, meshes and materials
1348 client->afterContentReceived();
1352 camera = new Camera(*draw_control, client, m_rendering_engine);
1353 if (client->modsLoaded())
1354 client->getScript()->on_camera_ready(camera);
1355 client->setCamera(camera);
1359 if (m_cache_enable_clouds)
1360 clouds = new Clouds(smgr, -1, time(0));
1364 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
1366 skybox = NULL; // This is used/set later on in the main run loop
1368 /* Pre-calculated values
1370 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1372 v2u32 size = t->getOriginalSize();
1373 crack_animation_length = size.Y / size.X;
1375 crack_animation_length = 5;
1381 /* Set window caption
1383 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1385 str += utf8_to_wide(g_version_hash);
1387 const wchar_t *text = nullptr;
1388 if (simple_singleplayer_mode)
1389 text = wgettext("Singleplayer");
1391 text = wgettext("Multiplayer");
1398 str += driver->getName();
1401 device->setWindowCaption(str.c_str());
1403 LocalPlayer *player = client->getEnv().getLocalPlayer();
1404 player->hurt_tilt_timer = 0;
1405 player->hurt_tilt_strength = 0;
1407 hud = new Hud(client, player, &player->inventory);
1409 mapper = client->getMinimap();
1411 if (mapper && client->modsLoaded())
1412 client->getScript()->on_minimap_ready(mapper);
1417 bool Game::initGui()
1421 // Remove stale "recent" chat messages from previous connections
1422 chat_backend->clearRecentChat();
1424 // Make sure the size of the recent messages buffer is right
1425 chat_backend->applySettings();
1427 // Chat backend and console
1428 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1429 -1, chat_backend, client, &g_menumgr);
1431 #ifdef HAVE_TOUCHSCREENGUI
1433 if (g_touchscreengui)
1434 g_touchscreengui->show();
1441 bool Game::connectToServer(const GameStartData &start_data,
1442 bool *connect_ok, bool *connection_aborted)
1444 *connect_ok = false; // Let's not be overly optimistic
1445 *connection_aborted = false;
1446 bool local_server_mode = false;
1448 showOverlayMessage(N_("Resolving address..."), 0, 15);
1450 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1453 connect_address.Resolve(start_data.address.c_str());
1455 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1456 if (connect_address.isIPv6()) {
1457 IPv6AddressBytes addr_bytes;
1458 addr_bytes.bytes[15] = 1;
1459 connect_address.setAddress(&addr_bytes);
1461 connect_address.setAddress(127, 0, 0, 1);
1463 local_server_mode = true;
1465 } catch (ResolveError &e) {
1466 *error_message = fmtgettext("Couldn't resolve address: %s", e.what());
1468 errorstream << *error_message << std::endl;
1472 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1473 *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
1474 errorstream << *error_message << std::endl;
1479 client = new Client(start_data.name.c_str(),
1480 start_data.password, start_data.address,
1481 *draw_control, texture_src, shader_src,
1482 itemdef_manager, nodedef_manager, sound, eventmgr,
1483 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get());
1484 client->migrateModStorage();
1485 } catch (const BaseException &e) {
1486 *error_message = fmtgettext("Error creating client: %s", e.what());
1487 errorstream << *error_message << std::endl;
1491 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1493 infostream << "Connecting to server at ";
1494 connect_address.print(infostream);
1495 infostream << std::endl;
1497 client->connect(connect_address,
1498 simple_singleplayer_mode || local_server_mode);
1501 Wait for server to accept connection
1507 FpsControl fps_control;
1509 f32 wait_time = 0; // in seconds
1511 fps_control.reset();
1513 while (m_rendering_engine->run()) {
1515 fps_control.limit(device, &dtime);
1517 // Update client and server
1518 client->step(dtime);
1521 server->step(dtime);
1524 if (client->getState() == LC_Init) {
1530 if (*connection_aborted)
1533 if (client->accessDenied()) {
1534 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1535 *reconnect_requested = client->reconnectRequested();
1536 errorstream << *error_message << std::endl;
1540 if (input->cancelPressed()) {
1541 *connection_aborted = true;
1542 infostream << "Connect aborted [Escape]" << std::endl;
1546 if (client->m_is_registration_confirmation_state) {
1547 if (registration_confirmation_shown) {
1548 // Keep drawing the GUI
1549 m_rendering_engine->draw_menu_scene(guienv, dtime, true);
1551 registration_confirmation_shown = true;
1552 (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
1553 &g_menumgr, client, start_data.name, start_data.password,
1554 connection_aborted, texture_src))->drop();
1558 // Only time out if we aren't waiting for the server we started
1559 if (!start_data.address.empty() && wait_time > 10) {
1560 *error_message = gettext("Connection timed out.");
1561 errorstream << *error_message << std::endl;
1566 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1569 } catch (con::PeerNotFoundException &e) {
1570 // TODO: Should something be done here? At least an info/error
1578 bool Game::getServerContent(bool *aborted)
1582 FpsControl fps_control;
1583 f32 dtime; // in seconds
1585 fps_control.reset();
1587 while (m_rendering_engine->run()) {
1589 fps_control.limit(device, &dtime);
1591 // Update client and server
1592 client->step(dtime);
1595 server->step(dtime);
1598 if (client->mediaReceived() && client->itemdefReceived() &&
1599 client->nodedefReceived()) {
1604 if (!checkConnection())
1607 if (client->getState() < LC_Init) {
1608 *error_message = gettext("Client disconnected");
1609 errorstream << *error_message << std::endl;
1613 if (input->cancelPressed()) {
1615 infostream << "Connect aborted [Escape]" << std::endl;
1622 if (!client->itemdefReceived()) {
1623 const wchar_t *text = wgettext("Item definitions...");
1625 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1628 } else if (!client->nodedefReceived()) {
1629 const wchar_t *text = wgettext("Node definitions...");
1631 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1635 std::ostringstream message;
1636 std::fixed(message);
1637 message.precision(0);
1638 float receive = client->mediaReceiveProgress() * 100;
1639 message << gettext("Media...");
1641 message << " " << receive << "%";
1642 message.precision(2);
1644 if ((USE_CURL == 0) ||
1645 (!g_settings->getBool("enable_remote_media_server"))) {
1646 float cur = client->getCurRate();
1647 std::string cur_unit = gettext("KiB/s");
1651 cur_unit = gettext("MiB/s");
1654 message << " (" << cur << ' ' << cur_unit << ")";
1657 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1658 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
1659 texture_src, dtime, progress);
1667 /****************************************************************************/
1668 /****************************************************************************
1670 ****************************************************************************/
1671 /****************************************************************************/
1673 inline void Game::updateInteractTimers(f32 dtime)
1675 if (runData.nodig_delay_timer >= 0)
1676 runData.nodig_delay_timer -= dtime;
1678 if (runData.object_hit_delay_timer >= 0)
1679 runData.object_hit_delay_timer -= dtime;
1681 runData.time_from_last_punch += dtime;
1685 /* returns false if game should exit, otherwise true
1687 inline bool Game::checkConnection()
1689 if (client->accessDenied()) {
1690 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1691 *reconnect_requested = client->reconnectRequested();
1692 errorstream << *error_message << std::endl;
1700 /* returns false if game should exit, otherwise true
1702 inline bool Game::handleCallbacks()
1704 if (g_gamecallback->disconnect_requested) {
1705 g_gamecallback->disconnect_requested = false;
1709 if (g_gamecallback->changepassword_requested) {
1710 (new GUIPasswordChange(guienv, guiroot, -1,
1711 &g_menumgr, client, texture_src))->drop();
1712 g_gamecallback->changepassword_requested = false;
1715 if (g_gamecallback->changevolume_requested) {
1716 (new GUIVolumeChange(guienv, guiroot, -1,
1717 &g_menumgr, texture_src))->drop();
1718 g_gamecallback->changevolume_requested = false;
1721 if (g_gamecallback->keyconfig_requested) {
1722 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1723 &g_menumgr, texture_src))->drop();
1724 g_gamecallback->keyconfig_requested = false;
1727 if (g_gamecallback->keyconfig_changed) {
1728 input->keycache.populate(); // update the cache with new settings
1729 g_gamecallback->keyconfig_changed = false;
1736 void Game::processQueues()
1738 texture_src->processQueue();
1739 itemdef_manager->processQueue(client);
1740 shader_src->processQueue();
1743 void Game::updateDebugState()
1745 LocalPlayer *player = client->getEnv().getLocalPlayer();
1746 bool has_debug = client->checkPrivilege("debug");
1747 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
1749 if (m_game_ui->m_flags.show_basic_debug) {
1750 if (!has_basic_debug)
1751 m_game_ui->m_flags.show_basic_debug = false;
1752 } else if (m_game_ui->m_flags.show_minimal_debug) {
1753 if (has_basic_debug)
1754 m_game_ui->m_flags.show_basic_debug = true;
1756 if (!has_basic_debug)
1757 hud->disableBlockBounds();
1759 draw_control->show_wireframe = false;
1762 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1765 float profiler_print_interval =
1766 g_settings->getFloat("profiler_print_interval");
1767 bool print_to_log = true;
1769 if (profiler_print_interval == 0) {
1770 print_to_log = false;
1771 profiler_print_interval = 3;
1774 if (profiler_interval.step(dtime, profiler_print_interval)) {
1776 infostream << "Profiler:" << std::endl;
1777 g_profiler->print(infostream);
1780 m_game_ui->updateProfiler();
1781 g_profiler->clear();
1784 // Update update graphs
1785 g_profiler->graphAdd("Time non-rendering [us]",
1786 draw_times.busy_time - stats.drawtime);
1788 g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
1789 g_profiler->graphAdd("FPS", 1.0f / dtime);
1792 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1799 /* Time average and jitter calculation
1801 jp = &stats->dtime_jitter;
1802 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1804 jitter = dtime - jp->avg;
1806 if (jitter > jp->max)
1809 jp->counter += dtime;
1811 if (jp->counter > 0.0) {
1813 jp->max_sample = jp->max;
1814 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1818 /* Busytime average and jitter calculation
1820 jp = &stats->busy_time_jitter;
1821 jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
1823 jitter = draw_times.getBusyMs() - jp->avg;
1825 if (jitter > jp->max)
1827 if (jitter < jp->min)
1830 jp->counter += dtime;
1832 if (jp->counter > 0.0) {
1834 jp->max_sample = jp->max;
1835 jp->min_sample = jp->min;
1843 /****************************************************************************
1845 ****************************************************************************/
1847 void Game::processUserInput(f32 dtime)
1849 // Reset input if window not active or some menu is active
1850 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1852 #ifdef HAVE_TOUCHSCREENGUI
1853 g_touchscreengui->hide();
1856 #ifdef HAVE_TOUCHSCREENGUI
1857 else if (g_touchscreengui) {
1858 /* on touchscreengui step may generate own input events which ain't
1859 * what we want in case we just did clear them */
1860 g_touchscreengui->show();
1861 g_touchscreengui->step(dtime);
1865 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1866 gui_chat_console->closeConsoleAtOnce();
1869 // Input handler step() (used by the random input generator)
1873 auto formspec = m_game_ui->getFormspecGUI();
1875 formspec->getAndroidUIInput();
1877 handleAndroidChatInput();
1880 // Increase timer for double tap of "keymap_jump"
1881 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1882 runData.jump_timer += dtime;
1885 processItemSelection(&runData.new_playeritem);
1889 void Game::processKeyInput()
1891 if (wasKeyDown(KeyType::DROP)) {
1892 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1893 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1894 toggleAutoforward();
1895 } else if (wasKeyDown(KeyType::BACKWARD)) {
1896 if (g_settings->getBool("continuous_forward"))
1897 toggleAutoforward();
1898 } else if (wasKeyDown(KeyType::INVENTORY)) {
1900 } else if (input->cancelPressed()) {
1902 m_android_chat_open = false;
1904 if (!gui_chat_console->isOpenInhibited()) {
1907 } else if (wasKeyDown(KeyType::CHAT)) {
1908 openConsole(0.2, L"");
1909 } else if (wasKeyDown(KeyType::CMD)) {
1910 openConsole(0.2, L"/");
1911 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1912 if (client->modsLoaded())
1913 openConsole(0.2, L".");
1915 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1916 } else if (wasKeyDown(KeyType::CONSOLE)) {
1917 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1918 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1920 } else if (wasKeyDown(KeyType::JUMP)) {
1921 toggleFreeMoveAlt();
1922 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1924 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1926 } else if (wasKeyDown(KeyType::NOCLIP)) {
1929 } else if (wasKeyDown(KeyType::MUTE)) {
1930 if (g_settings->getBool("enable_sound")) {
1931 bool new_mute_sound = !g_settings->getBool("mute_sound");
1932 g_settings->setBool("mute_sound", new_mute_sound);
1934 m_game_ui->showTranslatedStatusText("Sound muted");
1936 m_game_ui->showTranslatedStatusText("Sound unmuted");
1938 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1940 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1941 if (g_settings->getBool("enable_sound")) {
1942 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1943 g_settings->setFloat("sound_volume", new_volume);
1944 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1945 m_game_ui->showStatusText(msg);
1947 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1949 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1950 if (g_settings->getBool("enable_sound")) {
1951 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1952 g_settings->setFloat("sound_volume", new_volume);
1953 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1954 m_game_ui->showStatusText(msg);
1956 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1959 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1960 || wasKeyDown(KeyType::DEC_VOLUME)) {
1961 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1963 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1965 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1966 client->makeScreenshot();
1967 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
1968 toggleBlockBounds();
1969 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1970 m_game_ui->toggleHud();
1971 } else if (wasKeyDown(KeyType::MINIMAP)) {
1972 toggleMinimap(isKeyDown(KeyType::SNEAK));
1973 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1974 m_game_ui->toggleChat();
1975 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1977 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1978 toggleUpdateCamera();
1979 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1981 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1982 m_game_ui->toggleProfiler();
1983 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1984 increaseViewRange();
1985 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1986 decreaseViewRange();
1987 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1988 toggleFullViewRange();
1989 } else if (wasKeyDown(KeyType::ZOOM)) {
1991 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1993 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1995 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1997 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
2001 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
2002 runData.reset_jump_timer = false;
2003 runData.jump_timer = 0.0f;
2006 if (quicktune->hasMessage()) {
2007 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2011 void Game::processItemSelection(u16 *new_playeritem)
2013 LocalPlayer *player = client->getEnv().getLocalPlayer();
2015 /* Item selection using mouse wheel
2017 *new_playeritem = player->getWieldIndex();
2019 s32 wheel = input->getMouseWheel();
2020 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2021 player->hud_hotbar_itemcount - 1);
2025 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2028 if (wasKeyDown(KeyType::HOTBAR_PREV))
2032 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2034 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2037 /* Item selection using hotbar slot keys
2039 for (u16 i = 0; i <= max_item; i++) {
2040 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2041 *new_playeritem = i;
2048 void Game::dropSelectedItem(bool single_item)
2050 IDropAction *a = new IDropAction();
2051 a->count = single_item ? 1 : 0;
2052 a->from_inv.setCurrentPlayer();
2053 a->from_list = "main";
2054 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2055 client->inventoryAction(a);
2059 void Game::openInventory()
2062 * Don't permit to open inventory is CAO or player doesn't exists.
2063 * This prevent showing an empty inventory at player load
2066 LocalPlayer *player = client->getEnv().getLocalPlayer();
2067 if (!player || !player->getCAO())
2070 infostream << "Game: Launching inventory" << std::endl;
2072 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2074 InventoryLocation inventoryloc;
2075 inventoryloc.setCurrentPlayer();
2077 if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2082 if (fs_src->getForm().empty()) {
2087 TextDest *txt_dst = new TextDestPlayerInventory(client);
2088 auto *&formspec = m_game_ui->updateFormspec("");
2089 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2090 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2092 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2096 void Game::openConsole(float scale, const wchar_t *line)
2098 assert(scale > 0.0f && scale <= 1.0f);
2101 porting::showInputDialog(gettext("ok"), "", "", 2);
2102 m_android_chat_open = true;
2104 if (gui_chat_console->isOpenInhibited())
2106 gui_chat_console->openConsole(scale);
2108 gui_chat_console->setCloseOnEnter(true);
2109 gui_chat_console->replaceAndAddToHistory(line);
2115 void Game::handleAndroidChatInput()
2117 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2118 std::string text = porting::getInputDialogValue();
2119 client->typeChatMessage(utf8_to_wide(text));
2120 m_android_chat_open = false;
2126 void Game::toggleFreeMove()
2128 bool free_move = !g_settings->getBool("free_move");
2129 g_settings->set("free_move", bool_to_cstr(free_move));
2132 if (client->checkPrivilege("fly")) {
2133 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2135 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2138 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2142 void Game::toggleFreeMoveAlt()
2144 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2147 runData.reset_jump_timer = true;
2151 void Game::togglePitchMove()
2153 bool pitch_move = !g_settings->getBool("pitch_move");
2154 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2157 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2159 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2164 void Game::toggleFast()
2166 bool fast_move = !g_settings->getBool("fast_move");
2167 bool has_fast_privs = client->checkPrivilege("fast");
2168 g_settings->set("fast_move", bool_to_cstr(fast_move));
2171 if (has_fast_privs) {
2172 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2174 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2177 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2180 #ifdef HAVE_TOUCHSCREENGUI
2181 m_cache_hold_aux1 = fast_move && has_fast_privs;
2186 void Game::toggleNoClip()
2188 bool noclip = !g_settings->getBool("noclip");
2189 g_settings->set("noclip", bool_to_cstr(noclip));
2192 if (client->checkPrivilege("noclip")) {
2193 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2195 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2198 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2202 void Game::toggleCinematic()
2204 bool cinematic = !g_settings->getBool("cinematic");
2205 g_settings->set("cinematic", bool_to_cstr(cinematic));
2208 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2210 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2213 void Game::toggleBlockBounds()
2215 LocalPlayer *player = client->getEnv().getLocalPlayer();
2216 if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
2217 m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
2220 enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
2222 case Hud::BLOCK_BOUNDS_OFF:
2223 m_game_ui->showTranslatedStatusText("Block bounds hidden");
2225 case Hud::BLOCK_BOUNDS_CURRENT:
2226 m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
2228 case Hud::BLOCK_BOUNDS_NEAR:
2229 m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
2231 case Hud::BLOCK_BOUNDS_MAX:
2232 m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
2239 // Autoforward by toggling continuous forward.
2240 void Game::toggleAutoforward()
2242 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2243 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2245 if (autorun_enabled)
2246 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2248 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2251 void Game::toggleMinimap(bool shift_pressed)
2253 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2257 mapper->toggleMinimapShape();
2261 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2263 // Not so satisying code to keep compatibility with old fixed mode system
2265 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2267 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2268 m_game_ui->m_flags.show_minimap = false;
2271 // If radar is disabled, try to find a non radar mode or fall back to 0
2272 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2273 while (mapper->getModeIndex() &&
2274 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2277 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2281 // End of 'not so satifying code'
2282 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2283 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2284 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2286 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2289 void Game::toggleFog()
2291 bool fog_enabled = g_settings->getBool("enable_fog");
2292 g_settings->setBool("enable_fog", !fog_enabled);
2294 m_game_ui->showTranslatedStatusText("Fog disabled");
2296 m_game_ui->showTranslatedStatusText("Fog enabled");
2300 void Game::toggleDebug()
2302 LocalPlayer *player = client->getEnv().getLocalPlayer();
2303 bool has_debug = client->checkPrivilege("debug");
2304 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
2305 // Initial: No debug info
2306 // 1x toggle: Debug text
2307 // 2x toggle: Debug text with profiler graph
2308 // 3x toggle: Debug text and wireframe (needs "debug" priv)
2309 // Next toggle: Back to initial
2311 // The debug text can be in 2 modes: minimal and basic.
2312 // * Minimal: Only technical client info that not gameplay-relevant
2313 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
2314 // Basic mode is used when player has the debug HUD flag set,
2315 // otherwise the Minimal mode is used.
2316 if (!m_game_ui->m_flags.show_minimal_debug) {
2317 m_game_ui->m_flags.show_minimal_debug = true;
2318 if (has_basic_debug)
2319 m_game_ui->m_flags.show_basic_debug = true;
2320 m_game_ui->m_flags.show_profiler_graph = false;
2321 draw_control->show_wireframe = false;
2322 m_game_ui->showTranslatedStatusText("Debug info shown");
2323 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2324 if (has_basic_debug)
2325 m_game_ui->m_flags.show_basic_debug = true;
2326 m_game_ui->m_flags.show_profiler_graph = true;
2327 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2328 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2329 if (has_basic_debug)
2330 m_game_ui->m_flags.show_basic_debug = true;
2331 m_game_ui->m_flags.show_profiler_graph = false;
2332 draw_control->show_wireframe = true;
2333 m_game_ui->showTranslatedStatusText("Wireframe shown");
2335 m_game_ui->m_flags.show_minimal_debug = false;
2336 m_game_ui->m_flags.show_basic_debug = false;
2337 m_game_ui->m_flags.show_profiler_graph = false;
2338 draw_control->show_wireframe = false;
2340 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2342 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2348 void Game::toggleUpdateCamera()
2350 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2351 if (m_flags.disable_camera_update)
2352 m_game_ui->showTranslatedStatusText("Camera update disabled");
2354 m_game_ui->showTranslatedStatusText("Camera update enabled");
2358 void Game::increaseViewRange()
2360 s16 range = g_settings->getS16("viewing_range");
2361 s16 range_new = range + 10;
2363 if (range_new > 4000) {
2365 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
2366 m_game_ui->showStatusText(msg);
2368 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2369 m_game_ui->showStatusText(msg);
2371 g_settings->set("viewing_range", itos(range_new));
2375 void Game::decreaseViewRange()
2377 s16 range = g_settings->getS16("viewing_range");
2378 s16 range_new = range - 10;
2380 if (range_new < 20) {
2382 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
2383 m_game_ui->showStatusText(msg);
2385 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2386 m_game_ui->showStatusText(msg);
2388 g_settings->set("viewing_range", itos(range_new));
2392 void Game::toggleFullViewRange()
2394 draw_control->range_all = !draw_control->range_all;
2395 if (draw_control->range_all)
2396 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2398 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2402 void Game::checkZoomEnabled()
2404 LocalPlayer *player = client->getEnv().getLocalPlayer();
2405 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2406 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2409 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2411 if ((device->isWindowActive() && device->isWindowFocused()
2412 && !isMenuActive()) || input->isRandom()) {
2415 if (!input->isRandom()) {
2416 // Mac OSX gets upset if this is set every frame
2417 if (device->getCursorControl()->isVisible())
2418 device->getCursorControl()->setVisible(false);
2422 if (m_first_loop_after_window_activation) {
2423 m_first_loop_after_window_activation = false;
2425 input->setMousePos(driver->getScreenSize().Width / 2,
2426 driver->getScreenSize().Height / 2);
2428 updateCameraOrientation(cam, dtime);
2434 // Mac OSX gets upset if this is set every frame
2435 if (!device->getCursorControl()->isVisible())
2436 device->getCursorControl()->setVisible(true);
2439 m_first_loop_after_window_activation = true;
2444 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2445 // responsiveness independently of FOV.
2446 f32 Game::getSensitivityScaleFactor() const
2448 f32 fov_y = client->getCamera()->getFovY();
2450 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2451 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2453 return tan(fov_y / 2.0f) * 1.3763818698f;
2456 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2458 #ifdef HAVE_TOUCHSCREENGUI
2459 if (g_touchscreengui) {
2460 cam->camera_yaw += g_touchscreengui->getYawChange();
2461 cam->camera_pitch = g_touchscreengui->getPitch();
2464 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2465 v2s32 dist = input->getMousePos() - center;
2467 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2471 f32 sens_scale = getSensitivityScaleFactor();
2472 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2473 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2475 if (dist.X != 0 || dist.Y != 0)
2476 input->setMousePos(center.X, center.Y);
2477 #ifdef HAVE_TOUCHSCREENGUI
2481 if (m_cache_enable_joysticks) {
2482 f32 sens_scale = getSensitivityScaleFactor();
2483 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
2484 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2485 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2488 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2492 void Game::updatePlayerControl(const CameraOrientation &cam)
2494 LocalPlayer *player = client->getEnv().getLocalPlayer();
2496 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2498 PlayerControl control(
2499 isKeyDown(KeyType::FORWARD),
2500 isKeyDown(KeyType::BACKWARD),
2501 isKeyDown(KeyType::LEFT),
2502 isKeyDown(KeyType::RIGHT),
2503 isKeyDown(KeyType::JUMP) || player->getAutojump(),
2504 isKeyDown(KeyType::AUX1),
2505 isKeyDown(KeyType::SNEAK),
2506 isKeyDown(KeyType::ZOOM),
2507 isKeyDown(KeyType::DIG),
2508 isKeyDown(KeyType::PLACE),
2511 input->getMovementSpeed(),
2512 input->getMovementDirection()
2515 // autoforward if set: move towards pointed position at maximum speed
2516 if (player->getPlayerSettings().continuous_forward &&
2517 client->activeObjectsReceived() && !player->isDead()) {
2518 control.movement_speed = 1.0f;
2519 control.movement_direction = 0.0f;
2522 #ifdef HAVE_TOUCHSCREENGUI
2523 /* For touch, simulate holding down AUX1 (fast move) if the user has
2524 * the fast_move setting toggled on. If there is an aux1 key defined for
2525 * touch then its meaning is inverted (i.e. holding aux1 means walk and
2528 if (m_cache_hold_aux1) {
2529 control.aux1 = control.aux1 ^ true;
2533 client->setPlayerControl(control);
2539 inline void Game::step(f32 *dtime)
2541 bool can_be_and_is_paused =
2542 (simple_singleplayer_mode && g_menumgr.pausesGame());
2544 if (can_be_and_is_paused) { // This is for a singleplayer server
2545 *dtime = 0; // No time passes
2547 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
2551 server->step(*dtime);
2553 client->step(*dtime);
2557 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2560 for (auto &&child: node->getChildren())
2561 pauseNodeAnimation(paused, child);
2562 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2564 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2565 float speed = animated_node->getAnimationSpeed();
2568 paused.push_back({grab(animated_node), speed});
2569 animated_node->setAnimationSpeed(0.0f);
2572 void Game::pauseAnimation()
2574 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2577 void Game::resumeAnimation()
2579 for (auto &&pair: paused_animated_nodes)
2580 pair.first->setAnimationSpeed(pair.second);
2581 paused_animated_nodes.clear();
2584 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2585 {&Game::handleClientEvent_None},
2586 {&Game::handleClientEvent_PlayerDamage},
2587 {&Game::handleClientEvent_PlayerForceMove},
2588 {&Game::handleClientEvent_Deathscreen},
2589 {&Game::handleClientEvent_ShowFormSpec},
2590 {&Game::handleClientEvent_ShowLocalFormSpec},
2591 {&Game::handleClientEvent_HandleParticleEvent},
2592 {&Game::handleClientEvent_HandleParticleEvent},
2593 {&Game::handleClientEvent_HandleParticleEvent},
2594 {&Game::handleClientEvent_HudAdd},
2595 {&Game::handleClientEvent_HudRemove},
2596 {&Game::handleClientEvent_HudChange},
2597 {&Game::handleClientEvent_SetSky},
2598 {&Game::handleClientEvent_SetSun},
2599 {&Game::handleClientEvent_SetMoon},
2600 {&Game::handleClientEvent_SetStars},
2601 {&Game::handleClientEvent_OverrideDayNigthRatio},
2602 {&Game::handleClientEvent_CloudParams},
2605 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2607 FATAL_ERROR("ClientEvent type None received");
2610 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2612 if (client->modsLoaded())
2613 client->getScript()->on_damage_taken(event->player_damage.amount);
2615 // Damage flash and hurt tilt are not used at death
2616 if (client->getHP() > 0) {
2617 LocalPlayer *player = client->getEnv().getLocalPlayer();
2619 f32 hp_max = player->getCAO() ?
2620 player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2621 f32 damage_ratio = event->player_damage.amount / hp_max;
2623 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2624 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2626 player->hurt_tilt_timer = 1.5f;
2627 player->hurt_tilt_strength =
2628 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2631 // Play damage sound
2632 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2635 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2637 cam->camera_yaw = event->player_force_move.yaw;
2638 cam->camera_pitch = event->player_force_move.pitch;
2641 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2643 // If client scripting is enabled, deathscreen is handled by CSM code in
2644 // builtin/client/init.lua
2645 if (client->modsLoaded())
2646 client->getScript()->on_death();
2648 showDeathFormspec();
2650 /* Handle visualization */
2651 LocalPlayer *player = client->getEnv().getLocalPlayer();
2652 runData.damage_flash = 0;
2653 player->hurt_tilt_timer = 0;
2654 player->hurt_tilt_strength = 0;
2657 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2659 if (event->show_formspec.formspec->empty()) {
2660 auto formspec = m_game_ui->getFormspecGUI();
2661 if (formspec && (event->show_formspec.formname->empty()
2662 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2663 formspec->quitMenu();
2666 FormspecFormSource *fs_src =
2667 new FormspecFormSource(*(event->show_formspec.formspec));
2668 TextDestPlayerInventory *txt_dst =
2669 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2671 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2672 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2673 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2676 delete event->show_formspec.formspec;
2677 delete event->show_formspec.formname;
2680 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2682 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2683 LocalFormspecHandler *txt_dst =
2684 new LocalFormspecHandler(*event->show_formspec.formname, client);
2685 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2686 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2688 delete event->show_formspec.formspec;
2689 delete event->show_formspec.formname;
2692 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2693 CameraOrientation *cam)
2695 LocalPlayer *player = client->getEnv().getLocalPlayer();
2696 client->getParticleManager()->handleParticleEvent(event, client, player);
2699 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2701 LocalPlayer *player = client->getEnv().getLocalPlayer();
2703 u32 server_id = event->hudadd->server_id;
2704 // ignore if we already have a HUD with that ID
2705 auto i = m_hud_server_to_client.find(server_id);
2706 if (i != m_hud_server_to_client.end()) {
2707 delete event->hudadd;
2711 HudElement *e = new HudElement;
2712 e->type = static_cast<HudElementType>(event->hudadd->type);
2713 e->pos = event->hudadd->pos;
2714 e->name = event->hudadd->name;
2715 e->scale = event->hudadd->scale;
2716 e->text = event->hudadd->text;
2717 e->number = event->hudadd->number;
2718 e->item = event->hudadd->item;
2719 e->dir = event->hudadd->dir;
2720 e->align = event->hudadd->align;
2721 e->offset = event->hudadd->offset;
2722 e->world_pos = event->hudadd->world_pos;
2723 e->size = event->hudadd->size;
2724 e->z_index = event->hudadd->z_index;
2725 e->text2 = event->hudadd->text2;
2726 e->style = event->hudadd->style;
2727 m_hud_server_to_client[server_id] = player->addHud(e);
2729 delete event->hudadd;
2732 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2734 LocalPlayer *player = client->getEnv().getLocalPlayer();
2736 auto i = m_hud_server_to_client.find(event->hudrm.id);
2737 if (i != m_hud_server_to_client.end()) {
2738 HudElement *e = player->removeHud(i->second);
2740 m_hud_server_to_client.erase(i);
2745 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2747 LocalPlayer *player = client->getEnv().getLocalPlayer();
2749 HudElement *e = nullptr;
2751 auto i = m_hud_server_to_client.find(event->hudchange->id);
2752 if (i != m_hud_server_to_client.end()) {
2753 e = player->getHud(i->second);
2757 delete event->hudchange;
2761 #define CASE_SET(statval, prop, dataprop) \
2763 e->prop = event->hudchange->dataprop; \
2766 switch (event->hudchange->stat) {
2767 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2769 CASE_SET(HUD_STAT_NAME, name, sdata);
2771 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2773 CASE_SET(HUD_STAT_TEXT, text, sdata);
2775 CASE_SET(HUD_STAT_NUMBER, number, data);
2777 CASE_SET(HUD_STAT_ITEM, item, data);
2779 CASE_SET(HUD_STAT_DIR, dir, data);
2781 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2783 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2785 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2787 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2789 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2791 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2793 CASE_SET(HUD_STAT_STYLE, style, data);
2798 delete event->hudchange;
2801 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2803 sky->setVisible(false);
2804 // Whether clouds are visible in front of a custom skybox.
2805 sky->setCloudsEnabled(event->set_sky->clouds);
2811 // Clear the old textures out in case we switch rendering type.
2812 sky->clearSkyboxTextures();
2813 // Handle according to type
2814 if (event->set_sky->type == "regular") {
2815 // Shows the mesh skybox
2816 sky->setVisible(true);
2817 // Update mesh based skybox colours if applicable.
2818 sky->setSkyColors(event->set_sky->sky_color);
2819 sky->setHorizonTint(
2820 event->set_sky->fog_sun_tint,
2821 event->set_sky->fog_moon_tint,
2822 event->set_sky->fog_tint_type
2824 } else if (event->set_sky->type == "skybox" &&
2825 event->set_sky->textures.size() == 6) {
2826 // Disable the dyanmic mesh skybox:
2827 sky->setVisible(false);
2829 sky->setFallbackBgColor(event->set_sky->bgcolor);
2830 // Set sunrise and sunset fog tinting:
2831 sky->setHorizonTint(
2832 event->set_sky->fog_sun_tint,
2833 event->set_sky->fog_moon_tint,
2834 event->set_sky->fog_tint_type
2836 // Add textures to skybox.
2837 for (int i = 0; i < 6; i++)
2838 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2840 // Handle everything else as plain color.
2841 if (event->set_sky->type != "plain")
2842 infostream << "Unknown sky type: "
2843 << (event->set_sky->type) << std::endl;
2844 sky->setVisible(false);
2845 sky->setFallbackBgColor(event->set_sky->bgcolor);
2846 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2847 sky->setHorizonTint(
2848 event->set_sky->bgcolor,
2849 event->set_sky->bgcolor,
2854 delete event->set_sky;
2857 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2859 sky->setSunVisible(event->sun_params->visible);
2860 sky->setSunTexture(event->sun_params->texture,
2861 event->sun_params->tonemap, texture_src);
2862 sky->setSunScale(event->sun_params->scale);
2863 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2864 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2865 delete event->sun_params;
2868 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2870 sky->setMoonVisible(event->moon_params->visible);
2871 sky->setMoonTexture(event->moon_params->texture,
2872 event->moon_params->tonemap, texture_src);
2873 sky->setMoonScale(event->moon_params->scale);
2874 delete event->moon_params;
2877 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2879 sky->setStarsVisible(event->star_params->visible);
2880 sky->setStarCount(event->star_params->count);
2881 sky->setStarColor(event->star_params->starcolor);
2882 sky->setStarScale(event->star_params->scale);
2883 delete event->star_params;
2886 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2887 CameraOrientation *cam)
2889 client->getEnv().setDayNightRatioOverride(
2890 event->override_day_night_ratio.do_override,
2891 event->override_day_night_ratio.ratio_f * 1000.0f);
2894 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2899 clouds->setDensity(event->cloud_params.density);
2900 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2901 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2902 clouds->setHeight(event->cloud_params.height);
2903 clouds->setThickness(event->cloud_params.thickness);
2904 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2907 void Game::processClientEvents(CameraOrientation *cam)
2909 while (client->hasClientEvents()) {
2910 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2911 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2912 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2913 (this->*evHandler.handler)(event.get(), cam);
2917 void Game::updateChat(f32 dtime)
2919 // Get new messages from error log buffer
2920 while (!m_chat_log_buf.empty())
2921 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2923 // Get new messages from client
2924 std::wstring message;
2925 while (client->getChatMessage(message)) {
2926 chat_backend->addUnparsedMessage(message);
2929 // Remove old messages
2930 chat_backend->step(dtime);
2932 // Display all messages in a static text element
2933 auto &buf = chat_backend->getRecentBuffer();
2934 if (buf.getLinesModified()) {
2935 buf.resetLinesModified();
2936 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
2939 // Make sure that the size is still correct
2940 m_game_ui->updateChatSize();
2943 void Game::updateCamera(f32 dtime)
2945 LocalPlayer *player = client->getEnv().getLocalPlayer();
2948 For interaction purposes, get info about the held item
2950 - Is it a usable item?
2951 - Can it point to liquids?
2953 ItemStack playeritem;
2955 ItemStack selected, hand;
2956 playeritem = player->getWieldedItem(&selected, &hand);
2959 ToolCapabilities playeritem_toolcap =
2960 playeritem.getToolCapabilities(itemdef_manager);
2962 v3s16 old_camera_offset = camera->getOffset();
2964 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2965 GenericCAO *playercao = player->getCAO();
2967 // If playercao not loaded, don't change camera
2971 camera->toggleCameraMode();
2973 // Make the player visible depending on camera mode.
2974 playercao->updateMeshCulling();
2975 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2978 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2979 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2981 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2982 camera->update(player, dtime, tool_reload_ratio);
2983 camera->step(dtime);
2985 v3f camera_position = camera->getPosition();
2986 v3f camera_direction = camera->getDirection();
2987 f32 camera_fov = camera->getFovMax();
2988 v3s16 camera_offset = camera->getOffset();
2990 m_camera_offset_changed = (camera_offset != old_camera_offset);
2992 if (!m_flags.disable_camera_update) {
2993 client->getEnv().getClientMap().updateCamera(camera_position,
2994 camera_direction, camera_fov, camera_offset);
2996 if (m_camera_offset_changed) {
2997 client->updateCameraOffset(camera_offset);
2998 client->getEnv().updateCameraOffset(camera_offset);
3001 clouds->updateCameraOffset(camera_offset);
3007 void Game::updateSound(f32 dtime)
3009 // Update sound listener
3010 v3s16 camera_offset = camera->getOffset();
3011 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3012 v3f(0, 0, 0), // velocity
3013 camera->getDirection(),
3014 camera->getCameraNode()->getUpVector());
3016 bool mute_sound = g_settings->getBool("mute_sound");
3018 sound->setListenerGain(0.0f);
3020 // Check if volume is in the proper range, else fix it.
3021 float old_volume = g_settings->getFloat("sound_volume");
3022 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3023 sound->setListenerGain(new_volume);
3025 if (old_volume != new_volume) {
3026 g_settings->setFloat("sound_volume", new_volume);
3030 LocalPlayer *player = client->getEnv().getLocalPlayer();
3032 // Tell the sound maker whether to make footstep sounds
3033 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3035 // Update sound maker
3036 if (player->makes_footstep_sound)
3037 soundmaker->step(dtime);
3039 ClientMap &map = client->getEnv().getClientMap();
3040 MapNode n = map.getNode(player->getFootstepNodePos());
3041 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3045 void Game::processPlayerInteraction(f32 dtime, bool show_hud)
3047 LocalPlayer *player = client->getEnv().getLocalPlayer();
3049 const v3f camera_direction = camera->getDirection();
3050 const v3s16 camera_offset = camera->getOffset();
3053 Calculate what block is the crosshair pointing to
3056 ItemStack selected_item, hand_item;
3057 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3059 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3060 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3062 core::line3d<f32> shootline;
3064 switch (camera->getCameraMode()) {
3065 case CAMERA_MODE_FIRST:
3066 // Shoot from camera position, with bobbing
3067 shootline.start = camera->getPosition();
3069 case CAMERA_MODE_THIRD:
3070 // Shoot from player head, no bobbing
3071 shootline.start = camera->getHeadPosition();
3073 case CAMERA_MODE_THIRD_FRONT:
3074 shootline.start = camera->getHeadPosition();
3075 // prevent player pointing anything in front-view
3079 shootline.end = shootline.start + camera_direction * BS * d;
3081 #ifdef HAVE_TOUCHSCREENGUI
3083 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3084 shootline = g_touchscreengui->getShootline();
3085 // Scale shootline to the acual distance the player can reach
3086 shootline.end = shootline.start
3087 + shootline.getVector().normalize() * BS * d;
3088 shootline.start += intToFloat(camera_offset, BS);
3089 shootline.end += intToFloat(camera_offset, BS);
3094 PointedThing pointed = updatePointedThing(shootline,
3095 selected_def.liquids_pointable,
3096 !runData.btn_down_for_dig,
3099 if (pointed != runData.pointed_old)
3100 infostream << "Pointing at " << pointed.dump() << std::endl;
3102 // Note that updating the selection mesh every frame is not particularly efficient,
3103 // but the halo rendering code is already inefficient so there's no point in optimizing it here
3104 hud->updateSelectionMesh(camera_offset);
3106 // Allow digging again if button is not pressed
3107 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3108 runData.digging_blocked = false;
3112 - releasing dig button
3113 - pointing away from node
3115 if (runData.digging) {
3116 if (wasKeyReleased(KeyType::DIG)) {
3117 infostream << "Dig button released (stopped digging)" << std::endl;
3118 runData.digging = false;
3119 } else if (pointed != runData.pointed_old) {
3120 if (pointed.type == POINTEDTHING_NODE
3121 && runData.pointed_old.type == POINTEDTHING_NODE
3122 && pointed.node_undersurface
3123 == runData.pointed_old.node_undersurface) {
3124 // Still pointing to the same node, but a different face.
3127 infostream << "Pointing away from node (stopped digging)" << std::endl;
3128 runData.digging = false;
3129 hud->updateSelectionMesh(camera_offset);
3133 if (!runData.digging) {
3134 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3135 client->setCrack(-1, v3s16(0, 0, 0));
3136 runData.dig_time = 0.0;
3138 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3139 // Remove e.g. torches faster when clicking instead of holding dig button
3140 runData.nodig_delay_timer = 0;
3141 runData.dig_instantly = false;
3144 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3145 runData.btn_down_for_dig = false;
3147 runData.punching = false;
3149 soundmaker->m_player_leftpunch_sound.name = "";
3151 // Prepare for repeating, unless we're not supposed to
3152 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3153 runData.repeat_place_timer += dtime;
3155 runData.repeat_place_timer = 0;
3157 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3158 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3159 !client->getScript()->on_item_use(selected_item, pointed)))
3160 client->interact(INTERACT_USE, pointed);
3161 } else if (pointed.type == POINTEDTHING_NODE) {
3162 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3163 } else if (pointed.type == POINTEDTHING_OBJECT) {
3164 v3f player_position = player->getPosition();
3165 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
3166 handlePointingAtObject(pointed, tool_item, player_position,
3167 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
3168 } else if (isKeyDown(KeyType::DIG)) {
3169 // When button is held down in air, show continuous animation
3170 runData.punching = true;
3171 // Run callback even though item is not usable
3172 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3173 client->getScript()->on_item_use(selected_item, pointed);
3174 } else if (wasKeyPressed(KeyType::PLACE)) {
3175 handlePointingAtNothing(selected_item);
3178 runData.pointed_old = pointed;
3180 if (runData.punching || wasKeyPressed(KeyType::DIG))
3181 camera->setDigging(0); // dig animation
3183 input->clearWasKeyPressed();
3184 input->clearWasKeyReleased();
3185 // Ensure DIG & PLACE are marked as handled
3186 wasKeyDown(KeyType::DIG);
3187 wasKeyDown(KeyType::PLACE);
3189 input->joystick.clearWasKeyPressed(KeyType::DIG);
3190 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3192 input->joystick.clearWasKeyReleased(KeyType::DIG);
3193 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3197 PointedThing Game::updatePointedThing(
3198 const core::line3d<f32> &shootline,
3199 bool liquids_pointable,
3200 bool look_for_object,
3201 const v3s16 &camera_offset)
3203 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3204 selectionboxes->clear();
3205 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3206 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3207 "show_entity_selectionbox");
3209 ClientEnvironment &env = client->getEnv();
3210 ClientMap &map = env.getClientMap();
3211 const NodeDefManager *nodedef = map.getNodeDefManager();
3213 runData.selected_object = NULL;
3214 hud->pointing_at_object = false;
3216 RaycastState s(shootline, look_for_object, liquids_pointable);
3217 PointedThing result;
3218 env.continueRaycast(&s, &result);
3219 if (result.type == POINTEDTHING_OBJECT) {
3220 hud->pointing_at_object = true;
3222 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3223 aabb3f selection_box;
3224 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3225 runData.selected_object->getSelectionBox(&selection_box)) {
3226 v3f pos = runData.selected_object->getPosition();
3227 selectionboxes->push_back(aabb3f(selection_box));
3228 hud->setSelectionPos(pos, camera_offset);
3230 } else if (result.type == POINTEDTHING_NODE) {
3231 // Update selection boxes
3232 MapNode n = map.getNode(result.node_undersurface);
3233 std::vector<aabb3f> boxes;
3234 n.getSelectionBoxes(nodedef, &boxes,
3235 n.getNeighbors(result.node_undersurface, &map));
3238 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3239 i != boxes.end(); ++i) {
3241 box.MinEdge -= v3f(d, d, d);
3242 box.MaxEdge += v3f(d, d, d);
3243 selectionboxes->push_back(box);
3245 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3247 hud->setSelectedFaceNormal(v3f(
3248 result.intersection_normal.X,
3249 result.intersection_normal.Y,
3250 result.intersection_normal.Z));
3253 // Update selection mesh light level and vertex colors
3254 if (!selectionboxes->empty()) {
3255 v3f pf = hud->getSelectionPos();
3256 v3s16 p = floatToInt(pf, BS);
3258 // Get selection mesh light level
3259 MapNode n = map.getNode(p);
3260 u16 node_light = getInteriorLight(n, -1, nodedef);
3261 u16 light_level = node_light;
3263 for (const v3s16 &dir : g_6dirs) {
3264 n = map.getNode(p + dir);
3265 node_light = getInteriorLight(n, -1, nodedef);
3266 if (node_light > light_level)
3267 light_level = node_light;
3270 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3272 final_color_blend(&c, light_level, daynight_ratio);
3274 // Modify final color a bit with time
3275 u32 timer = porting::getTimeMs() % 5000;
3276 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3277 float sin_r = 0.08f * std::sin(timerf);
3278 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3279 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3280 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3281 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3282 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3284 // Set mesh final color
3285 hud->setSelectionMeshColor(c);
3291 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3293 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3294 PointedThing fauxPointed;
3295 fauxPointed.type = POINTEDTHING_NOTHING;
3296 client->interact(INTERACT_ACTIVATE, fauxPointed);
3300 void Game::handlePointingAtNode(const PointedThing &pointed,
3301 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3303 v3s16 nodepos = pointed.node_undersurface;
3304 v3s16 neighbourpos = pointed.node_abovesurface;
3307 Check information text of node
3310 ClientMap &map = client->getEnv().getClientMap();
3312 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3313 && !runData.digging_blocked
3314 && client->checkPrivilege("interact")) {
3315 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3318 // This should be done after digging handling
3319 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3322 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3323 meta->getString("infotext"))));
3325 MapNode n = map.getNode(nodepos);
3327 if (nodedef_manager->get(n).name == "unknown") {
3328 m_game_ui->setInfoText(L"Unknown node");
3332 if ((wasKeyPressed(KeyType::PLACE) ||
3333 runData.repeat_place_timer >= m_repeat_place_time) &&
3334 client->checkPrivilege("interact")) {
3335 runData.repeat_place_timer = 0;
3336 infostream << "Place button pressed while looking at ground" << std::endl;
3338 // Placing animation (always shown for feedback)
3339 camera->setDigging(1);
3341 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3343 // If the wielded item has node placement prediction,
3345 // And also set the sound and send the interact
3346 // But first check for meta formspec and rightclickable
3347 auto &def = selected_item.getDefinition(itemdef_manager);
3348 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3351 if (placed && client->modsLoaded())
3352 client->getScript()->on_placenode(pointed, def);
3356 bool Game::nodePlacement(const ItemDefinition &selected_def,
3357 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3358 const PointedThing &pointed, const NodeMetadata *meta)
3360 const auto &prediction = selected_def.node_placement_prediction;
3362 const NodeDefManager *nodedef = client->ndef();
3363 ClientMap &map = client->getEnv().getClientMap();
3365 bool is_valid_position;
3367 node = map.getNode(nodepos, &is_valid_position);
3368 if (!is_valid_position) {
3369 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3374 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3375 && !isKeyDown(KeyType::SNEAK)) {
3376 // on_rightclick callbacks are called anyway
3377 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3378 client->interact(INTERACT_PLACE, pointed);
3380 infostream << "Launching custom inventory view" << std::endl;
3382 InventoryLocation inventoryloc;
3383 inventoryloc.setNodeMeta(nodepos);
3385 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3386 &client->getEnv().getClientMap(), nodepos);
3387 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3389 auto *&formspec = m_game_ui->updateFormspec("");
3390 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3391 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3393 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3397 // on_rightclick callback
3398 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3399 !isKeyDown(KeyType::SNEAK))) {
3401 client->interact(INTERACT_PLACE, pointed);
3405 verbosestream << "Node placement prediction for "
3406 << selected_def.name << " is " << prediction << std::endl;
3407 v3s16 p = neighbourpos;
3409 // Place inside node itself if buildable_to
3410 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3411 if (is_valid_position) {
3412 if (nodedef->get(n_under).buildable_to) {
3415 node = map.getNode(p, &is_valid_position);
3416 if (is_valid_position && !nodedef->get(node).buildable_to) {
3417 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3419 client->interact(INTERACT_PLACE, pointed);
3425 // Find id of predicted node
3427 bool found = nodedef->getId(prediction, id);
3430 errorstream << "Node placement prediction failed for "
3431 << selected_def.name << " (places " << prediction
3432 << ") - Name not known" << std::endl;
3433 // Handle this as if prediction was empty
3435 client->interact(INTERACT_PLACE, pointed);
3439 const ContentFeatures &predicted_f = nodedef->get(id);
3441 // Predict param2 for facedir and wallmounted nodes
3442 // Compare core.item_place_node() for what the server does
3445 const u8 place_param2 = selected_def.place_param2;
3448 param2 = place_param2;
3449 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3450 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3451 v3s16 dir = nodepos - neighbourpos;
3453 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3454 param2 = dir.Y < 0 ? 1 : 0;
3455 } else if (abs(dir.X) > abs(dir.Z)) {
3456 param2 = dir.X < 0 ? 3 : 2;
3458 param2 = dir.Z < 0 ? 5 : 4;
3460 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3461 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3462 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3464 if (abs(dir.X) > abs(dir.Z)) {
3465 param2 = dir.X < 0 ? 3 : 1;
3467 param2 = dir.Z < 0 ? 2 : 0;
3471 // Check attachment if node is in group attached_node
3472 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3473 const static v3s16 wallmounted_dirs[8] = {
3483 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3484 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3485 pp = p + wallmounted_dirs[param2];
3487 pp = p + v3s16(0, -1, 0);
3489 if (!nodedef->get(map.getNode(pp)).walkable) {
3490 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3492 client->interact(INTERACT_PLACE, pointed);
3498 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3499 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3500 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3501 const auto &indexstr = selected_item.metadata.
3502 getString("palette_index", 0);
3503 if (!indexstr.empty()) {
3504 s32 index = mystoi(indexstr);
3505 if (predicted_f.param_type_2 == CPT2_COLOR) {
3507 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3508 // param2 = pure palette index + other
3509 param2 = (index & 0xf8) | (param2 & 0x07);
3510 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3511 // param2 = pure palette index + other
3512 param2 = (index & 0xe0) | (param2 & 0x1f);
3517 // Add node to client map
3518 MapNode n(id, 0, param2);
3521 LocalPlayer *player = client->getEnv().getLocalPlayer();
3523 // Dont place node when player would be inside new node
3524 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3525 if (!nodedef->get(n).walkable ||
3526 g_settings->getBool("enable_build_where_you_stand") ||
3527 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3528 (nodedef->get(n).walkable &&
3529 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3530 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3531 // This triggers the required mesh update too
3532 client->addNode(p, n);
3534 client->interact(INTERACT_PLACE, pointed);
3535 // A node is predicted, also play a sound
3536 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3539 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3542 } catch (const InvalidPositionException &e) {
3543 errorstream << "Node placement prediction failed for "
3544 << selected_def.name << " (places "
3545 << prediction << ") - Position not loaded" << std::endl;
3546 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3551 void Game::handlePointingAtObject(const PointedThing &pointed,
3552 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3554 std::wstring infotext = unescape_translate(
3555 utf8_to_wide(runData.selected_object->infoText()));
3558 if (!infotext.empty()) {
3561 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3564 m_game_ui->setInfoText(infotext);
3566 if (isKeyDown(KeyType::DIG)) {
3567 bool do_punch = false;
3568 bool do_punch_damage = false;
3570 if (runData.object_hit_delay_timer <= 0.0) {
3572 do_punch_damage = true;
3573 runData.object_hit_delay_timer = object_hit_delay;
3576 if (wasKeyPressed(KeyType::DIG))
3580 infostream << "Punched object" << std::endl;
3581 runData.punching = true;
3584 if (do_punch_damage) {
3585 // Report direct punch
3586 v3f objpos = runData.selected_object->getPosition();
3587 v3f dir = (objpos - player_position).normalize();
3589 bool disable_send = runData.selected_object->directReportPunch(
3590 dir, &tool_item, runData.time_from_last_punch);
3591 runData.time_from_last_punch = 0;
3594 client->interact(INTERACT_START_DIGGING, pointed);
3596 } else if (wasKeyDown(KeyType::PLACE)) {
3597 infostream << "Pressed place button while pointing at object" << std::endl;
3598 client->interact(INTERACT_PLACE, pointed); // place
3603 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3604 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3606 // See also: serverpackethandle.cpp, action == 2
3607 LocalPlayer *player = client->getEnv().getLocalPlayer();
3608 ClientMap &map = client->getEnv().getClientMap();
3609 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3611 // NOTE: Similar piece of code exists on the server side for
3613 // Get digging parameters
3614 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3615 &selected_item.getToolCapabilities(itemdef_manager),
3616 selected_item.wear);
3618 // If can't dig, try hand
3619 if (!params.diggable) {
3620 params = getDigParams(nodedef_manager->get(n).groups,
3621 &hand_item.getToolCapabilities(itemdef_manager));
3624 if (!params.diggable) {
3625 // I guess nobody will wait for this long
3626 runData.dig_time_complete = 10000000.0;
3628 runData.dig_time_complete = params.time;
3630 if (m_cache_enable_particles) {
3631 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3632 client->getParticleManager()->addNodeParticle(client,
3633 player, nodepos, n, features);
3637 if (!runData.digging) {
3638 infostream << "Started digging" << std::endl;
3639 runData.dig_instantly = runData.dig_time_complete == 0;
3640 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3642 client->interact(INTERACT_START_DIGGING, pointed);
3643 runData.digging = true;
3644 runData.btn_down_for_dig = true;
3647 if (!runData.dig_instantly) {
3648 runData.dig_index = (float)crack_animation_length
3650 / runData.dig_time_complete;
3652 // This is for e.g. torches
3653 runData.dig_index = crack_animation_length;
3656 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3658 if (sound_dig.exists() && params.diggable) {
3659 if (sound_dig.name == "__group") {
3660 if (!params.main_group.empty()) {
3661 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3662 soundmaker->m_player_leftpunch_sound.name =
3663 std::string("default_dig_") +
3667 soundmaker->m_player_leftpunch_sound = sound_dig;
3671 // Don't show cracks if not diggable
3672 if (runData.dig_time_complete >= 100000.0) {
3673 } else if (runData.dig_index < crack_animation_length) {
3674 //TimeTaker timer("client.setTempMod");
3675 //infostream<<"dig_index="<<dig_index<<std::endl;
3676 client->setCrack(runData.dig_index, nodepos);
3678 infostream << "Digging completed" << std::endl;
3679 client->setCrack(-1, v3s16(0, 0, 0));
3681 runData.dig_time = 0;
3682 runData.digging = false;
3683 // we successfully dug, now block it from repeating if we want to be safe
3684 if (g_settings->getBool("safe_dig_and_place"))
3685 runData.digging_blocked = true;
3687 runData.nodig_delay_timer =
3688 runData.dig_time_complete / (float)crack_animation_length;
3690 // We don't want a corresponding delay to very time consuming nodes
3691 // and nodes without digging time (e.g. torches) get a fixed delay.
3692 if (runData.nodig_delay_timer > 0.3)
3693 runData.nodig_delay_timer = 0.3;
3694 else if (runData.dig_instantly)
3695 runData.nodig_delay_timer = 0.15;
3697 bool is_valid_position;
3698 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3699 if (is_valid_position) {
3700 if (client->modsLoaded() &&
3701 client->getScript()->on_dignode(nodepos, wasnode)) {
3705 const ContentFeatures &f = client->ndef()->get(wasnode);
3706 if (f.node_dig_prediction == "air") {
3707 client->removeNode(nodepos);
3708 } else if (!f.node_dig_prediction.empty()) {
3710 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3712 client->addNode(nodepos, id, true);
3714 // implicit else: no prediction
3717 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3719 if (m_cache_enable_particles) {
3720 const ContentFeatures &features =
3721 client->getNodeDefManager()->get(wasnode);
3722 client->getParticleManager()->addDiggingParticles(client,
3723 player, nodepos, wasnode, features);
3727 // Send event to trigger sound
3728 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3731 if (runData.dig_time_complete < 100000.0) {
3732 runData.dig_time += dtime;
3734 runData.dig_time = 0;
3735 client->setCrack(-1, nodepos);
3738 camera->setDigging(0); // Dig animation
3741 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3742 const CameraOrientation &cam)
3744 TimeTaker tt_update("Game::updateFrame()");
3745 LocalPlayer *player = client->getEnv().getLocalPlayer();
3751 if (draw_control->range_all) {
3752 runData.fog_range = 100000 * BS;
3754 runData.fog_range = draw_control->wanted_range * BS;
3758 Calculate general brightness
3760 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3761 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3762 float direct_brightness;
3765 if (m_cache_enable_noclip && m_cache_enable_free_move) {
3766 direct_brightness = time_brightness;
3767 sunlight_seen = true;
3769 float old_brightness = sky->getBrightness();
3770 direct_brightness = client->getEnv().getClientMap()
3771 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3772 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3776 float time_of_day_smooth = runData.time_of_day_smooth;
3777 float time_of_day = client->getEnv().getTimeOfDayF();
3779 static const float maxsm = 0.05f;
3780 static const float todsm = 0.05f;
3782 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3783 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3784 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3785 time_of_day_smooth = time_of_day;
3787 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3788 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3789 + (time_of_day + 1.0) * todsm;
3791 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3792 + time_of_day * todsm;
3794 runData.time_of_day_smooth = time_of_day_smooth;
3796 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3797 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3798 player->getPitch());
3804 if (sky->getCloudsVisible()) {
3805 clouds->setVisible(true);
3806 clouds->step(dtime);
3807 // camera->getPosition is not enough for 3rd person views
3808 v3f camera_node_position = camera->getCameraNode()->getPosition();
3809 v3s16 camera_offset = camera->getOffset();
3810 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3811 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3812 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3813 clouds->update(camera_node_position,
3814 sky->getCloudColor());
3815 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3816 // if inside clouds, and fog enabled, use that as sky
3818 video::SColor clouds_dark = clouds->getColor()
3819 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3820 sky->overrideColors(clouds_dark, clouds->getColor());
3821 sky->setInClouds(true);
3822 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3823 // do not draw clouds after all
3824 clouds->setVisible(false);
3827 clouds->setVisible(false);
3834 client->getParticleManager()->step(dtime);
3840 if (m_cache_enable_fog) {
3843 video::EFT_FOG_LINEAR,
3844 runData.fog_range * m_cache_fog_start,
3845 runData.fog_range * 1.0,
3853 video::EFT_FOG_LINEAR,
3865 if (player->hurt_tilt_timer > 0.0f) {
3866 player->hurt_tilt_timer -= dtime * 6.0f;
3868 if (player->hurt_tilt_timer < 0.0f)
3869 player->hurt_tilt_strength = 0.0f;
3873 Update minimap pos and rotation
3875 if (mapper && m_game_ui->m_flags.show_hud) {
3876 mapper->setPos(floatToInt(player->getPosition(), BS));
3877 mapper->setAngle(player->getYaw());
3881 Get chat messages from client
3890 if (player->getWieldIndex() != runData.new_playeritem)
3891 client->setPlayerItem(runData.new_playeritem);
3893 if (client->updateWieldedItem()) {
3894 // Update wielded tool
3895 ItemStack selected_item, hand_item;
3896 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3897 camera->wield(tool_item);
3901 Update block draw list every 200ms or when camera direction has
3904 runData.update_draw_list_timer += dtime;
3906 float update_draw_list_delta = 0.2f;
3908 v3f camera_direction = camera->getDirection();
3909 if (runData.update_draw_list_timer >= update_draw_list_delta
3910 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3911 || m_camera_offset_changed
3912 || client->getEnv().getClientMap().needsUpdateDrawList()) {
3913 runData.update_draw_list_timer = 0;
3914 client->getEnv().getClientMap().updateDrawList();
3915 runData.update_draw_list_last_cam_dir = camera_direction;
3918 if (RenderingEngine::get_shadow_renderer()) {
3922 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3925 make sure menu is on top
3926 1. Delete formspec menu reference if menu was removed
3927 2. Else, make sure formspec menu is on top
3929 auto formspec = m_game_ui->getFormspecGUI();
3930 do { // breakable. only runs for one iteration
3934 if (formspec->getReferenceCount() == 1) {
3935 m_game_ui->deleteFormspec();
3939 auto &loc = formspec->getFormspecLocation();
3940 if (loc.type == InventoryLocation::NODEMETA) {
3941 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3942 if (!meta || meta->getString("formspec").empty()) {
3943 formspec->quitMenu();
3949 guiroot->bringToFront(formspec);
3953 ==================== Drawing begins ====================
3955 const video::SColor skycolor = sky->getSkyColor();
3957 TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
3958 driver->beginScene(true, true, skycolor);
3960 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3961 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3962 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3963 bool draw_crosshair = (
3964 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3965 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3966 #ifdef HAVE_TOUCHSCREENGUI
3968 draw_crosshair = !g_settings->getBool("touchtarget");
3969 } catch (SettingNotFoundException) {
3972 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3973 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3978 v2u32 screensize = driver->getScreenSize();
3980 if (m_game_ui->m_flags.show_profiler_graph)
3981 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3986 if (runData.damage_flash > 0.0f) {
3987 video::SColor color(runData.damage_flash, 180, 0, 0);
3988 driver->draw2DRectangle(color,
3989 core::rect<s32>(0, 0, screensize.X, screensize.Y),
3992 runData.damage_flash -= 384.0f * dtime;
3996 ==================== End scene ====================
3998 #if IRRLICHT_VERSION_MT_REVISION < 5
3999 if (++m_reset_HW_buffer_counter > 500) {
4001 Periodically remove all mesh HW buffers.
4003 Work around for a quirk in Irrlicht where a HW buffer is only
4004 released after 20000 iterations (triggered from endScene()).
4006 Without this, all loaded but unused meshes will retain their HW
4007 buffers for at least 5 minutes, at which point looking up the HW buffers
4008 becomes a bottleneck and the framerate drops (as much as 30%).
4010 Tests showed that numbers between 50 and 1000 are good, so picked 500.
4011 There are no other public Irrlicht APIs that allow interacting with the
4012 HW buffers without tracking the status of every individual mesh.
4014 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4016 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4017 driver->removeAllHardwareBuffers();
4018 m_reset_HW_buffer_counter = 0;
4024 stats->drawtime = tt_draw.stop(true);
4025 g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
4026 g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
4029 /* Log times and stuff for visualization */
4030 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4032 Profiler::GraphValues values;
4033 g_profiler->graphGet(values);
4037 /****************************************************************************
4039 *****************************************************************************/
4040 void Game::updateShadows()
4042 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4046 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4048 float timeoftheday = getWickedTimeOfDay(in_timeofday);
4049 bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
4050 bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
4051 shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
4053 timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
4054 const float offset_constant = 10000.0f;
4056 v3f light(0.0f, 0.0f, -1.0f);
4057 light.rotateXZBy(90);
4058 light.rotateXYBy(timeoftheday * 360 - 90);
4059 light.rotateYZBy(sky->getSkyBodyOrbitTilt());
4061 v3f sun_pos = light * offset_constant;
4063 if (shadow->getDirectionalLightCount() == 0)
4064 shadow->addDirectionalLight();
4065 shadow->getDirectionalLight().setDirection(sun_pos);
4066 shadow->setTimeOfDay(in_timeofday);
4068 shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4071 /****************************************************************************
4073 ****************************************************************************/
4075 void FpsControl::reset()
4077 last_time = porting::getTimeUs();
4081 * On some computers framerate doesn't seem to be automatically limited
4083 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
4085 const u64 frametime_min = 1000000.0f / (
4086 device->isWindowFocused() && !g_menumgr.pausesGame()
4087 ? g_settings->getFloat("fps_max")
4088 : g_settings->getFloat("fps_max_unfocused"));
4090 u64 time = porting::getTimeUs();
4092 if (time > last_time) // Make sure time hasn't overflowed
4093 busy_time = time - last_time;
4097 if (busy_time < frametime_min) {
4098 sleep_time = frametime_min - busy_time;
4099 if (sleep_time > 1000)
4100 sleep_ms(sleep_time / 1000);
4105 // Read the timer again to accurately determine how long we actually slept,
4106 // rather than calculating it by adding sleep_time to time.
4107 time = porting::getTimeUs();
4109 if (time > last_time) // Make sure last_time hasn't overflowed
4110 *dtime = (time - last_time) / 1000000.0f;
4117 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4119 const wchar_t *wmsg = wgettext(msg);
4120 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4125 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4127 ((Game *)data)->readSettings();
4130 void Game::readSettings()
4132 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4133 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4134 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4135 m_cache_enable_particles = g_settings->getBool("enable_particles");
4136 m_cache_enable_fog = g_settings->getBool("enable_fog");
4137 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
4138 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4139 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
4141 m_cache_enable_noclip = g_settings->getBool("noclip");
4142 m_cache_enable_free_move = g_settings->getBool("free_move");
4144 m_cache_fog_start = g_settings->getFloat("fog_start");
4146 m_cache_cam_smoothing = 0;
4147 if (g_settings->getBool("cinematic"))
4148 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4150 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4152 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4153 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4154 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4156 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4159 /****************************************************************************/
4160 /****************************************************************************
4162 ****************************************************************************/
4163 /****************************************************************************/
4165 void Game::showDeathFormspec()
4167 static std::string formspec_str =
4168 std::string("formspec_version[1]") +
4170 "bgcolor[#320000b4;true]"
4171 "label[4.85,1.35;" + gettext("You died") + "]"
4172 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4176 /* Note: FormspecFormSource and LocalFormspecHandler *
4177 * are deleted by guiFormSpecMenu */
4178 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4179 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4181 auto *&formspec = m_game_ui->getFormspecGUI();
4182 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4183 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4184 formspec->setFocus("btn_respawn");
4187 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4188 void Game::showPauseMenu()
4190 #ifdef HAVE_TOUCHSCREENGUI
4191 static const std::string control_text = strgettext("Default Controls:\n"
4192 "No menu visible:\n"
4193 "- single tap: button activate\n"
4194 "- double tap: place/use\n"
4195 "- slide finger: look around\n"
4196 "Menu/Inventory visible:\n"
4197 "- double tap (outside):\n"
4199 "- touch stack, touch slot:\n"
4201 "- touch&drag, tap 2nd finger\n"
4202 " --> place single item to slot\n"
4205 static const std::string control_text_template = strgettext("Controls:\n"
4206 "- %s: move forwards\n"
4207 "- %s: move backwards\n"
4209 "- %s: move right\n"
4210 "- %s: jump/climb up\n"
4213 "- %s: sneak/climb down\n"
4216 "- Mouse: turn/look\n"
4217 "- Mouse wheel: select item\n"
4221 char control_text_buf[600];
4223 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4224 GET_KEY_NAME(keymap_forward),
4225 GET_KEY_NAME(keymap_backward),
4226 GET_KEY_NAME(keymap_left),
4227 GET_KEY_NAME(keymap_right),
4228 GET_KEY_NAME(keymap_jump),
4229 GET_KEY_NAME(keymap_dig),
4230 GET_KEY_NAME(keymap_place),
4231 GET_KEY_NAME(keymap_sneak),
4232 GET_KEY_NAME(keymap_drop),
4233 GET_KEY_NAME(keymap_inventory),
4234 GET_KEY_NAME(keymap_chat)
4237 std::string control_text = std::string(control_text_buf);
4238 str_formspec_escape(control_text);
4241 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4242 std::ostringstream os;
4244 os << "formspec_version[1]" << SIZE_TAG
4245 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4246 << strgettext("Continue") << "]";
4248 if (!simple_singleplayer_mode) {
4249 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4250 << strgettext("Change Password") << "]";
4252 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4257 if (g_settings->getBool("enable_sound")) {
4258 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4259 << strgettext("Sound Volume") << "]";
4262 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4263 << strgettext("Change Keys") << "]";
4265 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4266 << strgettext("Exit to Menu") << "]";
4267 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4268 << strgettext("Exit to OS") << "]"
4269 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4270 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4272 << strgettext("Game info:") << "\n";
4273 const std::string &address = client->getAddressName();
4274 static const std::string mode = strgettext("- Mode: ");
4275 if (!simple_singleplayer_mode) {
4276 Address serverAddress = client->getServerAddress();
4277 if (!address.empty()) {
4278 os << mode << strgettext("Remote server") << "\n"
4279 << strgettext("- Address: ") << address;
4281 os << mode << strgettext("Hosting server");
4283 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4285 os << mode << strgettext("Singleplayer") << "\n";
4287 if (simple_singleplayer_mode || address.empty()) {
4288 static const std::string on = strgettext("On");
4289 static const std::string off = strgettext("Off");
4290 // Note: Status of enable_damage and creative_mode settings is intentionally
4291 // NOT shown here because the game might roll its own damage system and/or do
4292 // a per-player Creative Mode, in which case writing it here would mislead.
4293 bool damage = g_settings->getBool("enable_damage");
4294 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4295 if (!simple_singleplayer_mode) {
4297 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4298 //~ PvP = Player versus Player
4299 os << strgettext("- PvP: ") << pvp << "\n";
4301 os << strgettext("- Public: ") << announced << "\n";
4302 std::string server_name = g_settings->get("server_name");
4303 str_formspec_escape(server_name);
4304 if (announced == on && !server_name.empty())
4305 os << strgettext("- Server Name: ") << server_name;
4312 /* Note: FormspecFormSource and LocalFormspecHandler *
4313 * are deleted by guiFormSpecMenu */
4314 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4315 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4317 auto *&formspec = m_game_ui->getFormspecGUI();
4318 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4319 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4320 formspec->setFocus("btn_continue");
4321 formspec->doPause = true;
4323 if (simple_singleplayer_mode)
4327 /****************************************************************************/
4328 /****************************************************************************
4329 extern function for launching the game
4330 ****************************************************************************/
4331 /****************************************************************************/
4333 void the_game(bool *kill,
4334 InputHandler *input,
4335 RenderingEngine *rendering_engine,
4336 const GameStartData &start_data,
4337 std::string &error_message,
4338 ChatBackend &chat_backend,
4339 bool *reconnect_requested) // Used for local game
4343 /* Make a copy of the server address because if a local singleplayer server
4344 * is created then this is updated and we don't want to change the value
4345 * passed to us by the calling function
4350 if (game.startup(kill, input, rendering_engine, start_data,
4351 error_message, reconnect_requested, &chat_backend)) {
4355 } catch (SerializationError &e) {
4356 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
4357 error_message = strgettext("A serialization error occurred:") +"\n"
4358 + e.what() + "\n\n" + ver_err;
4359 errorstream << error_message << std::endl;
4360 } catch (ServerError &e) {
4361 error_message = e.what();
4362 errorstream << "ServerError: " << error_message << std::endl;
4363 } catch (ModError &e) {
4364 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
4365 error_message = std::string("ModError: ") + e.what() +
4366 strgettext("\nCheck debug.txt for details.");
4367 errorstream << error_message << std::endl;