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();
1747 // debug UI and wireframe
1748 bool has_debug = client->checkPrivilege("debug");
1749 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
1751 if (m_game_ui->m_flags.show_basic_debug) {
1752 if (!has_basic_debug)
1753 m_game_ui->m_flags.show_basic_debug = false;
1754 } else if (m_game_ui->m_flags.show_minimal_debug) {
1755 if (has_basic_debug)
1756 m_game_ui->m_flags.show_basic_debug = true;
1758 if (!has_basic_debug)
1759 hud->disableBlockBounds();
1761 draw_control->show_wireframe = false;
1764 draw_control->allow_noclip = m_cache_enable_noclip && client->checkPrivilege("noclip");
1767 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1770 float profiler_print_interval =
1771 g_settings->getFloat("profiler_print_interval");
1772 bool print_to_log = true;
1774 if (profiler_print_interval == 0) {
1775 print_to_log = false;
1776 profiler_print_interval = 3;
1779 if (profiler_interval.step(dtime, profiler_print_interval)) {
1781 infostream << "Profiler:" << std::endl;
1782 g_profiler->print(infostream);
1785 m_game_ui->updateProfiler();
1786 g_profiler->clear();
1789 // Update update graphs
1790 g_profiler->graphAdd("Time non-rendering [us]",
1791 draw_times.busy_time - stats.drawtime);
1793 g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
1794 g_profiler->graphAdd("FPS", 1.0f / dtime);
1797 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1804 /* Time average and jitter calculation
1806 jp = &stats->dtime_jitter;
1807 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1809 jitter = dtime - jp->avg;
1811 if (jitter > jp->max)
1814 jp->counter += dtime;
1816 if (jp->counter > 0.0) {
1818 jp->max_sample = jp->max;
1819 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1823 /* Busytime average and jitter calculation
1825 jp = &stats->busy_time_jitter;
1826 jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
1828 jitter = draw_times.getBusyMs() - jp->avg;
1830 if (jitter > jp->max)
1832 if (jitter < jp->min)
1835 jp->counter += dtime;
1837 if (jp->counter > 0.0) {
1839 jp->max_sample = jp->max;
1840 jp->min_sample = jp->min;
1848 /****************************************************************************
1850 ****************************************************************************/
1852 void Game::processUserInput(f32 dtime)
1854 // Reset input if window not active or some menu is active
1855 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1857 #ifdef HAVE_TOUCHSCREENGUI
1858 g_touchscreengui->hide();
1861 #ifdef HAVE_TOUCHSCREENGUI
1862 else if (g_touchscreengui) {
1863 /* on touchscreengui step may generate own input events which ain't
1864 * what we want in case we just did clear them */
1865 g_touchscreengui->show();
1866 g_touchscreengui->step(dtime);
1870 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1871 gui_chat_console->closeConsoleAtOnce();
1874 // Input handler step() (used by the random input generator)
1878 auto formspec = m_game_ui->getFormspecGUI();
1880 formspec->getAndroidUIInput();
1882 handleAndroidChatInput();
1885 // Increase timer for double tap of "keymap_jump"
1886 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1887 runData.jump_timer += dtime;
1890 processItemSelection(&runData.new_playeritem);
1894 void Game::processKeyInput()
1896 if (wasKeyDown(KeyType::DROP)) {
1897 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1898 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1899 toggleAutoforward();
1900 } else if (wasKeyDown(KeyType::BACKWARD)) {
1901 if (g_settings->getBool("continuous_forward"))
1902 toggleAutoforward();
1903 } else if (wasKeyDown(KeyType::INVENTORY)) {
1905 } else if (input->cancelPressed()) {
1907 m_android_chat_open = false;
1909 if (!gui_chat_console->isOpenInhibited()) {
1912 } else if (wasKeyDown(KeyType::CHAT)) {
1913 openConsole(0.2, L"");
1914 } else if (wasKeyDown(KeyType::CMD)) {
1915 openConsole(0.2, L"/");
1916 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1917 if (client->modsLoaded())
1918 openConsole(0.2, L".");
1920 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1921 } else if (wasKeyDown(KeyType::CONSOLE)) {
1922 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1923 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1925 } else if (wasKeyDown(KeyType::JUMP)) {
1926 toggleFreeMoveAlt();
1927 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1929 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1931 } else if (wasKeyDown(KeyType::NOCLIP)) {
1934 } else if (wasKeyDown(KeyType::MUTE)) {
1935 if (g_settings->getBool("enable_sound")) {
1936 bool new_mute_sound = !g_settings->getBool("mute_sound");
1937 g_settings->setBool("mute_sound", new_mute_sound);
1939 m_game_ui->showTranslatedStatusText("Sound muted");
1941 m_game_ui->showTranslatedStatusText("Sound unmuted");
1943 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1945 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1946 if (g_settings->getBool("enable_sound")) {
1947 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1948 g_settings->setFloat("sound_volume", new_volume);
1949 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1950 m_game_ui->showStatusText(msg);
1952 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1954 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1955 if (g_settings->getBool("enable_sound")) {
1956 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1957 g_settings->setFloat("sound_volume", new_volume);
1958 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1959 m_game_ui->showStatusText(msg);
1961 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1964 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1965 || wasKeyDown(KeyType::DEC_VOLUME)) {
1966 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1968 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1970 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1971 client->makeScreenshot();
1972 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
1973 toggleBlockBounds();
1974 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1975 m_game_ui->toggleHud();
1976 } else if (wasKeyDown(KeyType::MINIMAP)) {
1977 toggleMinimap(isKeyDown(KeyType::SNEAK));
1978 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1979 m_game_ui->toggleChat();
1980 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1982 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1983 toggleUpdateCamera();
1984 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1986 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1987 m_game_ui->toggleProfiler();
1988 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1989 increaseViewRange();
1990 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1991 decreaseViewRange();
1992 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1993 toggleFullViewRange();
1994 } else if (wasKeyDown(KeyType::ZOOM)) {
1996 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1998 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
2000 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
2002 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
2006 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
2007 runData.reset_jump_timer = false;
2008 runData.jump_timer = 0.0f;
2011 if (quicktune->hasMessage()) {
2012 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2016 void Game::processItemSelection(u16 *new_playeritem)
2018 LocalPlayer *player = client->getEnv().getLocalPlayer();
2020 /* Item selection using mouse wheel
2022 *new_playeritem = player->getWieldIndex();
2024 s32 wheel = input->getMouseWheel();
2025 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2026 player->hud_hotbar_itemcount - 1);
2030 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2033 if (wasKeyDown(KeyType::HOTBAR_PREV))
2037 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2039 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2042 /* Item selection using hotbar slot keys
2044 for (u16 i = 0; i <= max_item; i++) {
2045 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2046 *new_playeritem = i;
2053 void Game::dropSelectedItem(bool single_item)
2055 IDropAction *a = new IDropAction();
2056 a->count = single_item ? 1 : 0;
2057 a->from_inv.setCurrentPlayer();
2058 a->from_list = "main";
2059 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2060 client->inventoryAction(a);
2064 void Game::openInventory()
2067 * Don't permit to open inventory is CAO or player doesn't exists.
2068 * This prevent showing an empty inventory at player load
2071 LocalPlayer *player = client->getEnv().getLocalPlayer();
2072 if (!player || !player->getCAO())
2075 infostream << "Game: Launching inventory" << std::endl;
2077 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2079 InventoryLocation inventoryloc;
2080 inventoryloc.setCurrentPlayer();
2082 if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2087 if (fs_src->getForm().empty()) {
2092 TextDest *txt_dst = new TextDestPlayerInventory(client);
2093 auto *&formspec = m_game_ui->updateFormspec("");
2094 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2095 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2097 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2101 void Game::openConsole(float scale, const wchar_t *line)
2103 assert(scale > 0.0f && scale <= 1.0f);
2106 porting::showInputDialog(gettext("ok"), "", "", 2);
2107 m_android_chat_open = true;
2109 if (gui_chat_console->isOpenInhibited())
2111 gui_chat_console->openConsole(scale);
2113 gui_chat_console->setCloseOnEnter(true);
2114 gui_chat_console->replaceAndAddToHistory(line);
2120 void Game::handleAndroidChatInput()
2122 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2123 std::string text = porting::getInputDialogValue();
2124 client->typeChatMessage(utf8_to_wide(text));
2125 m_android_chat_open = false;
2131 void Game::toggleFreeMove()
2133 bool free_move = !g_settings->getBool("free_move");
2134 g_settings->set("free_move", bool_to_cstr(free_move));
2137 if (client->checkPrivilege("fly")) {
2138 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2140 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2143 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2147 void Game::toggleFreeMoveAlt()
2149 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2152 runData.reset_jump_timer = true;
2156 void Game::togglePitchMove()
2158 bool pitch_move = !g_settings->getBool("pitch_move");
2159 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2162 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2164 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2169 void Game::toggleFast()
2171 bool fast_move = !g_settings->getBool("fast_move");
2172 bool has_fast_privs = client->checkPrivilege("fast");
2173 g_settings->set("fast_move", bool_to_cstr(fast_move));
2176 if (has_fast_privs) {
2177 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2179 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2182 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2185 #ifdef HAVE_TOUCHSCREENGUI
2186 m_cache_hold_aux1 = fast_move && has_fast_privs;
2191 void Game::toggleNoClip()
2193 bool noclip = !g_settings->getBool("noclip");
2194 g_settings->set("noclip", bool_to_cstr(noclip));
2197 if (client->checkPrivilege("noclip")) {
2198 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2200 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2203 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2207 void Game::toggleCinematic()
2209 bool cinematic = !g_settings->getBool("cinematic");
2210 g_settings->set("cinematic", bool_to_cstr(cinematic));
2213 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2215 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2218 void Game::toggleBlockBounds()
2220 LocalPlayer *player = client->getEnv().getLocalPlayer();
2221 if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
2222 m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
2225 enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
2227 case Hud::BLOCK_BOUNDS_OFF:
2228 m_game_ui->showTranslatedStatusText("Block bounds hidden");
2230 case Hud::BLOCK_BOUNDS_CURRENT:
2231 m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
2233 case Hud::BLOCK_BOUNDS_NEAR:
2234 m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
2236 case Hud::BLOCK_BOUNDS_MAX:
2237 m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
2244 // Autoforward by toggling continuous forward.
2245 void Game::toggleAutoforward()
2247 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2248 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2250 if (autorun_enabled)
2251 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2253 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2256 void Game::toggleMinimap(bool shift_pressed)
2258 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2262 mapper->toggleMinimapShape();
2266 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2268 // Not so satisying code to keep compatibility with old fixed mode system
2270 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2272 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2273 m_game_ui->m_flags.show_minimap = false;
2276 // If radar is disabled, try to find a non radar mode or fall back to 0
2277 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2278 while (mapper->getModeIndex() &&
2279 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2282 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2286 // End of 'not so satifying code'
2287 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2288 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2289 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2291 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2294 void Game::toggleFog()
2296 bool fog_enabled = g_settings->getBool("enable_fog");
2297 g_settings->setBool("enable_fog", !fog_enabled);
2299 m_game_ui->showTranslatedStatusText("Fog disabled");
2301 m_game_ui->showTranslatedStatusText("Fog enabled");
2305 void Game::toggleDebug()
2307 LocalPlayer *player = client->getEnv().getLocalPlayer();
2308 bool has_debug = client->checkPrivilege("debug");
2309 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
2310 // Initial: No debug info
2311 // 1x toggle: Debug text
2312 // 2x toggle: Debug text with profiler graph
2313 // 3x toggle: Debug text and wireframe (needs "debug" priv)
2314 // Next toggle: Back to initial
2316 // The debug text can be in 2 modes: minimal and basic.
2317 // * Minimal: Only technical client info that not gameplay-relevant
2318 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
2319 // Basic mode is used when player has the debug HUD flag set,
2320 // otherwise the Minimal mode is used.
2321 if (!m_game_ui->m_flags.show_minimal_debug) {
2322 m_game_ui->m_flags.show_minimal_debug = true;
2323 if (has_basic_debug)
2324 m_game_ui->m_flags.show_basic_debug = true;
2325 m_game_ui->m_flags.show_profiler_graph = false;
2326 draw_control->show_wireframe = false;
2327 m_game_ui->showTranslatedStatusText("Debug info shown");
2328 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2329 if (has_basic_debug)
2330 m_game_ui->m_flags.show_basic_debug = true;
2331 m_game_ui->m_flags.show_profiler_graph = true;
2332 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2333 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2334 if (has_basic_debug)
2335 m_game_ui->m_flags.show_basic_debug = true;
2336 m_game_ui->m_flags.show_profiler_graph = false;
2337 draw_control->show_wireframe = true;
2338 m_game_ui->showTranslatedStatusText("Wireframe shown");
2340 m_game_ui->m_flags.show_minimal_debug = false;
2341 m_game_ui->m_flags.show_basic_debug = false;
2342 m_game_ui->m_flags.show_profiler_graph = false;
2343 draw_control->show_wireframe = false;
2345 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2347 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2353 void Game::toggleUpdateCamera()
2355 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2356 if (m_flags.disable_camera_update)
2357 m_game_ui->showTranslatedStatusText("Camera update disabled");
2359 m_game_ui->showTranslatedStatusText("Camera update enabled");
2363 void Game::increaseViewRange()
2365 s16 range = g_settings->getS16("viewing_range");
2366 s16 range_new = range + 10;
2368 if (range_new > 4000) {
2370 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
2371 m_game_ui->showStatusText(msg);
2373 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2374 m_game_ui->showStatusText(msg);
2376 g_settings->set("viewing_range", itos(range_new));
2380 void Game::decreaseViewRange()
2382 s16 range = g_settings->getS16("viewing_range");
2383 s16 range_new = range - 10;
2385 if (range_new < 20) {
2387 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
2388 m_game_ui->showStatusText(msg);
2390 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2391 m_game_ui->showStatusText(msg);
2393 g_settings->set("viewing_range", itos(range_new));
2397 void Game::toggleFullViewRange()
2399 draw_control->range_all = !draw_control->range_all;
2400 if (draw_control->range_all)
2401 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2403 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2407 void Game::checkZoomEnabled()
2409 LocalPlayer *player = client->getEnv().getLocalPlayer();
2410 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2411 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2414 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2416 if ((device->isWindowActive() && device->isWindowFocused()
2417 && !isMenuActive()) || input->isRandom()) {
2420 if (!input->isRandom()) {
2421 // Mac OSX gets upset if this is set every frame
2422 if (device->getCursorControl()->isVisible())
2423 device->getCursorControl()->setVisible(false);
2427 if (m_first_loop_after_window_activation) {
2428 m_first_loop_after_window_activation = false;
2430 input->setMousePos(driver->getScreenSize().Width / 2,
2431 driver->getScreenSize().Height / 2);
2433 updateCameraOrientation(cam, dtime);
2439 // Mac OSX gets upset if this is set every frame
2440 if (!device->getCursorControl()->isVisible())
2441 device->getCursorControl()->setVisible(true);
2444 m_first_loop_after_window_activation = true;
2449 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2450 // responsiveness independently of FOV.
2451 f32 Game::getSensitivityScaleFactor() const
2453 f32 fov_y = client->getCamera()->getFovY();
2455 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2456 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2458 return tan(fov_y / 2.0f) * 1.3763818698f;
2461 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2463 #ifdef HAVE_TOUCHSCREENGUI
2464 if (g_touchscreengui) {
2465 cam->camera_yaw += g_touchscreengui->getYawChange();
2466 cam->camera_pitch = g_touchscreengui->getPitch();
2469 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2470 v2s32 dist = input->getMousePos() - center;
2472 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2476 f32 sens_scale = getSensitivityScaleFactor();
2477 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2478 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2480 if (dist.X != 0 || dist.Y != 0)
2481 input->setMousePos(center.X, center.Y);
2482 #ifdef HAVE_TOUCHSCREENGUI
2486 if (m_cache_enable_joysticks) {
2487 f32 sens_scale = getSensitivityScaleFactor();
2488 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
2489 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2490 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2493 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2497 void Game::updatePlayerControl(const CameraOrientation &cam)
2499 LocalPlayer *player = client->getEnv().getLocalPlayer();
2501 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2503 PlayerControl control(
2504 isKeyDown(KeyType::FORWARD),
2505 isKeyDown(KeyType::BACKWARD),
2506 isKeyDown(KeyType::LEFT),
2507 isKeyDown(KeyType::RIGHT),
2508 isKeyDown(KeyType::JUMP) || player->getAutojump(),
2509 isKeyDown(KeyType::AUX1),
2510 isKeyDown(KeyType::SNEAK),
2511 isKeyDown(KeyType::ZOOM),
2512 isKeyDown(KeyType::DIG),
2513 isKeyDown(KeyType::PLACE),
2516 input->getMovementSpeed(),
2517 input->getMovementDirection()
2520 // autoforward if set: move towards pointed position at maximum speed
2521 if (player->getPlayerSettings().continuous_forward &&
2522 client->activeObjectsReceived() && !player->isDead()) {
2523 control.movement_speed = 1.0f;
2524 control.movement_direction = 0.0f;
2527 #ifdef HAVE_TOUCHSCREENGUI
2528 /* For touch, simulate holding down AUX1 (fast move) if the user has
2529 * the fast_move setting toggled on. If there is an aux1 key defined for
2530 * touch then its meaning is inverted (i.e. holding aux1 means walk and
2533 if (m_cache_hold_aux1) {
2534 control.aux1 = control.aux1 ^ true;
2538 client->setPlayerControl(control);
2544 inline void Game::step(f32 *dtime)
2546 bool can_be_and_is_paused =
2547 (simple_singleplayer_mode && g_menumgr.pausesGame());
2549 if (can_be_and_is_paused) { // This is for a singleplayer server
2550 *dtime = 0; // No time passes
2552 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
2556 server->step(*dtime);
2558 client->step(*dtime);
2562 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2565 for (auto &&child: node->getChildren())
2566 pauseNodeAnimation(paused, child);
2567 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2569 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2570 float speed = animated_node->getAnimationSpeed();
2573 paused.push_back({grab(animated_node), speed});
2574 animated_node->setAnimationSpeed(0.0f);
2577 void Game::pauseAnimation()
2579 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2582 void Game::resumeAnimation()
2584 for (auto &&pair: paused_animated_nodes)
2585 pair.first->setAnimationSpeed(pair.second);
2586 paused_animated_nodes.clear();
2589 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2590 {&Game::handleClientEvent_None},
2591 {&Game::handleClientEvent_PlayerDamage},
2592 {&Game::handleClientEvent_PlayerForceMove},
2593 {&Game::handleClientEvent_Deathscreen},
2594 {&Game::handleClientEvent_ShowFormSpec},
2595 {&Game::handleClientEvent_ShowLocalFormSpec},
2596 {&Game::handleClientEvent_HandleParticleEvent},
2597 {&Game::handleClientEvent_HandleParticleEvent},
2598 {&Game::handleClientEvent_HandleParticleEvent},
2599 {&Game::handleClientEvent_HudAdd},
2600 {&Game::handleClientEvent_HudRemove},
2601 {&Game::handleClientEvent_HudChange},
2602 {&Game::handleClientEvent_SetSky},
2603 {&Game::handleClientEvent_SetSun},
2604 {&Game::handleClientEvent_SetMoon},
2605 {&Game::handleClientEvent_SetStars},
2606 {&Game::handleClientEvent_OverrideDayNigthRatio},
2607 {&Game::handleClientEvent_CloudParams},
2610 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2612 FATAL_ERROR("ClientEvent type None received");
2615 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2617 if (client->modsLoaded())
2618 client->getScript()->on_damage_taken(event->player_damage.amount);
2620 // Damage flash and hurt tilt are not used at death
2621 if (client->getHP() > 0) {
2622 LocalPlayer *player = client->getEnv().getLocalPlayer();
2624 f32 hp_max = player->getCAO() ?
2625 player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2626 f32 damage_ratio = event->player_damage.amount / hp_max;
2628 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2629 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2631 player->hurt_tilt_timer = 1.5f;
2632 player->hurt_tilt_strength =
2633 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2636 // Play damage sound
2637 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2640 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2642 cam->camera_yaw = event->player_force_move.yaw;
2643 cam->camera_pitch = event->player_force_move.pitch;
2646 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2648 // If client scripting is enabled, deathscreen is handled by CSM code in
2649 // builtin/client/init.lua
2650 if (client->modsLoaded())
2651 client->getScript()->on_death();
2653 showDeathFormspec();
2655 /* Handle visualization */
2656 LocalPlayer *player = client->getEnv().getLocalPlayer();
2657 runData.damage_flash = 0;
2658 player->hurt_tilt_timer = 0;
2659 player->hurt_tilt_strength = 0;
2662 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2664 if (event->show_formspec.formspec->empty()) {
2665 auto formspec = m_game_ui->getFormspecGUI();
2666 if (formspec && (event->show_formspec.formname->empty()
2667 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2668 formspec->quitMenu();
2671 FormspecFormSource *fs_src =
2672 new FormspecFormSource(*(event->show_formspec.formspec));
2673 TextDestPlayerInventory *txt_dst =
2674 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2676 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2677 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2678 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2681 delete event->show_formspec.formspec;
2682 delete event->show_formspec.formname;
2685 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2687 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2688 LocalFormspecHandler *txt_dst =
2689 new LocalFormspecHandler(*event->show_formspec.formname, client);
2690 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2691 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2693 delete event->show_formspec.formspec;
2694 delete event->show_formspec.formname;
2697 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2698 CameraOrientation *cam)
2700 LocalPlayer *player = client->getEnv().getLocalPlayer();
2701 client->getParticleManager()->handleParticleEvent(event, client, player);
2704 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2706 LocalPlayer *player = client->getEnv().getLocalPlayer();
2708 u32 server_id = event->hudadd->server_id;
2709 // ignore if we already have a HUD with that ID
2710 auto i = m_hud_server_to_client.find(server_id);
2711 if (i != m_hud_server_to_client.end()) {
2712 delete event->hudadd;
2716 HudElement *e = new HudElement;
2717 e->type = static_cast<HudElementType>(event->hudadd->type);
2718 e->pos = event->hudadd->pos;
2719 e->name = event->hudadd->name;
2720 e->scale = event->hudadd->scale;
2721 e->text = event->hudadd->text;
2722 e->number = event->hudadd->number;
2723 e->item = event->hudadd->item;
2724 e->dir = event->hudadd->dir;
2725 e->align = event->hudadd->align;
2726 e->offset = event->hudadd->offset;
2727 e->world_pos = event->hudadd->world_pos;
2728 e->size = event->hudadd->size;
2729 e->z_index = event->hudadd->z_index;
2730 e->text2 = event->hudadd->text2;
2731 e->style = event->hudadd->style;
2732 m_hud_server_to_client[server_id] = player->addHud(e);
2734 delete event->hudadd;
2737 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2739 LocalPlayer *player = client->getEnv().getLocalPlayer();
2741 auto i = m_hud_server_to_client.find(event->hudrm.id);
2742 if (i != m_hud_server_to_client.end()) {
2743 HudElement *e = player->removeHud(i->second);
2745 m_hud_server_to_client.erase(i);
2750 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2752 LocalPlayer *player = client->getEnv().getLocalPlayer();
2754 HudElement *e = nullptr;
2756 auto i = m_hud_server_to_client.find(event->hudchange->id);
2757 if (i != m_hud_server_to_client.end()) {
2758 e = player->getHud(i->second);
2762 delete event->hudchange;
2766 #define CASE_SET(statval, prop, dataprop) \
2768 e->prop = event->hudchange->dataprop; \
2771 switch (event->hudchange->stat) {
2772 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2774 CASE_SET(HUD_STAT_NAME, name, sdata);
2776 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2778 CASE_SET(HUD_STAT_TEXT, text, sdata);
2780 CASE_SET(HUD_STAT_NUMBER, number, data);
2782 CASE_SET(HUD_STAT_ITEM, item, data);
2784 CASE_SET(HUD_STAT_DIR, dir, data);
2786 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2788 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2790 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2792 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2794 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2796 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2798 CASE_SET(HUD_STAT_STYLE, style, data);
2803 delete event->hudchange;
2806 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2808 sky->setVisible(false);
2809 // Whether clouds are visible in front of a custom skybox.
2810 sky->setCloudsEnabled(event->set_sky->clouds);
2816 // Clear the old textures out in case we switch rendering type.
2817 sky->clearSkyboxTextures();
2818 // Handle according to type
2819 if (event->set_sky->type == "regular") {
2820 // Shows the mesh skybox
2821 sky->setVisible(true);
2822 // Update mesh based skybox colours if applicable.
2823 sky->setSkyColors(event->set_sky->sky_color);
2824 sky->setHorizonTint(
2825 event->set_sky->fog_sun_tint,
2826 event->set_sky->fog_moon_tint,
2827 event->set_sky->fog_tint_type
2829 } else if (event->set_sky->type == "skybox" &&
2830 event->set_sky->textures.size() == 6) {
2831 // Disable the dyanmic mesh skybox:
2832 sky->setVisible(false);
2834 sky->setFallbackBgColor(event->set_sky->bgcolor);
2835 // Set sunrise and sunset fog tinting:
2836 sky->setHorizonTint(
2837 event->set_sky->fog_sun_tint,
2838 event->set_sky->fog_moon_tint,
2839 event->set_sky->fog_tint_type
2841 // Add textures to skybox.
2842 for (int i = 0; i < 6; i++)
2843 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2845 // Handle everything else as plain color.
2846 if (event->set_sky->type != "plain")
2847 infostream << "Unknown sky type: "
2848 << (event->set_sky->type) << std::endl;
2849 sky->setVisible(false);
2850 sky->setFallbackBgColor(event->set_sky->bgcolor);
2851 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2852 sky->setHorizonTint(
2853 event->set_sky->bgcolor,
2854 event->set_sky->bgcolor,
2859 delete event->set_sky;
2862 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2864 sky->setSunVisible(event->sun_params->visible);
2865 sky->setSunTexture(event->sun_params->texture,
2866 event->sun_params->tonemap, texture_src);
2867 sky->setSunScale(event->sun_params->scale);
2868 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2869 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2870 delete event->sun_params;
2873 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2875 sky->setMoonVisible(event->moon_params->visible);
2876 sky->setMoonTexture(event->moon_params->texture,
2877 event->moon_params->tonemap, texture_src);
2878 sky->setMoonScale(event->moon_params->scale);
2879 delete event->moon_params;
2882 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2884 sky->setStarsVisible(event->star_params->visible);
2885 sky->setStarCount(event->star_params->count);
2886 sky->setStarColor(event->star_params->starcolor);
2887 sky->setStarScale(event->star_params->scale);
2888 delete event->star_params;
2891 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2892 CameraOrientation *cam)
2894 client->getEnv().setDayNightRatioOverride(
2895 event->override_day_night_ratio.do_override,
2896 event->override_day_night_ratio.ratio_f * 1000.0f);
2899 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2904 clouds->setDensity(event->cloud_params.density);
2905 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2906 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2907 clouds->setHeight(event->cloud_params.height);
2908 clouds->setThickness(event->cloud_params.thickness);
2909 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2912 void Game::processClientEvents(CameraOrientation *cam)
2914 while (client->hasClientEvents()) {
2915 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2916 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2917 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2918 (this->*evHandler.handler)(event.get(), cam);
2922 void Game::updateChat(f32 dtime)
2924 // Get new messages from error log buffer
2925 while (!m_chat_log_buf.empty())
2926 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2928 // Get new messages from client
2929 std::wstring message;
2930 while (client->getChatMessage(message)) {
2931 chat_backend->addUnparsedMessage(message);
2934 // Remove old messages
2935 chat_backend->step(dtime);
2937 // Display all messages in a static text element
2938 auto &buf = chat_backend->getRecentBuffer();
2939 if (buf.getLinesModified()) {
2940 buf.resetLinesModified();
2941 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
2944 // Make sure that the size is still correct
2945 m_game_ui->updateChatSize();
2948 void Game::updateCamera(f32 dtime)
2950 LocalPlayer *player = client->getEnv().getLocalPlayer();
2953 For interaction purposes, get info about the held item
2955 - Is it a usable item?
2956 - Can it point to liquids?
2958 ItemStack playeritem;
2960 ItemStack selected, hand;
2961 playeritem = player->getWieldedItem(&selected, &hand);
2964 ToolCapabilities playeritem_toolcap =
2965 playeritem.getToolCapabilities(itemdef_manager);
2967 v3s16 old_camera_offset = camera->getOffset();
2969 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2970 GenericCAO *playercao = player->getCAO();
2972 // If playercao not loaded, don't change camera
2976 camera->toggleCameraMode();
2978 // Make the player visible depending on camera mode.
2979 playercao->updateMeshCulling();
2980 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2983 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2984 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2986 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2987 camera->update(player, dtime, tool_reload_ratio);
2988 camera->step(dtime);
2990 v3f camera_position = camera->getPosition();
2991 v3f camera_direction = camera->getDirection();
2992 f32 camera_fov = camera->getFovMax();
2993 v3s16 camera_offset = camera->getOffset();
2995 m_camera_offset_changed = (camera_offset != old_camera_offset);
2997 if (!m_flags.disable_camera_update) {
2998 client->getEnv().getClientMap().updateCamera(camera_position,
2999 camera_direction, camera_fov, camera_offset);
3001 if (m_camera_offset_changed) {
3002 client->updateCameraOffset(camera_offset);
3003 client->getEnv().updateCameraOffset(camera_offset);
3006 clouds->updateCameraOffset(camera_offset);
3012 void Game::updateSound(f32 dtime)
3014 // Update sound listener
3015 v3s16 camera_offset = camera->getOffset();
3016 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3017 v3f(0, 0, 0), // velocity
3018 camera->getDirection(),
3019 camera->getCameraNode()->getUpVector());
3021 bool mute_sound = g_settings->getBool("mute_sound");
3023 sound->setListenerGain(0.0f);
3025 // Check if volume is in the proper range, else fix it.
3026 float old_volume = g_settings->getFloat("sound_volume");
3027 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3028 sound->setListenerGain(new_volume);
3030 if (old_volume != new_volume) {
3031 g_settings->setFloat("sound_volume", new_volume);
3035 LocalPlayer *player = client->getEnv().getLocalPlayer();
3037 // Tell the sound maker whether to make footstep sounds
3038 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3040 // Update sound maker
3041 if (player->makes_footstep_sound)
3042 soundmaker->step(dtime);
3044 ClientMap &map = client->getEnv().getClientMap();
3045 MapNode n = map.getNode(player->getFootstepNodePos());
3046 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3050 void Game::processPlayerInteraction(f32 dtime, bool show_hud)
3052 LocalPlayer *player = client->getEnv().getLocalPlayer();
3054 const v3f camera_direction = camera->getDirection();
3055 const v3s16 camera_offset = camera->getOffset();
3058 Calculate what block is the crosshair pointing to
3061 ItemStack selected_item, hand_item;
3062 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3064 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3065 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3067 core::line3d<f32> shootline;
3069 switch (camera->getCameraMode()) {
3070 case CAMERA_MODE_FIRST:
3071 // Shoot from camera position, with bobbing
3072 shootline.start = camera->getPosition();
3074 case CAMERA_MODE_THIRD:
3075 // Shoot from player head, no bobbing
3076 shootline.start = camera->getHeadPosition();
3078 case CAMERA_MODE_THIRD_FRONT:
3079 shootline.start = camera->getHeadPosition();
3080 // prevent player pointing anything in front-view
3084 shootline.end = shootline.start + camera_direction * BS * d;
3086 #ifdef HAVE_TOUCHSCREENGUI
3088 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3089 shootline = g_touchscreengui->getShootline();
3090 // Scale shootline to the acual distance the player can reach
3091 shootline.end = shootline.start
3092 + shootline.getVector().normalize() * BS * d;
3093 shootline.start += intToFloat(camera_offset, BS);
3094 shootline.end += intToFloat(camera_offset, BS);
3099 PointedThing pointed = updatePointedThing(shootline,
3100 selected_def.liquids_pointable,
3101 !runData.btn_down_for_dig,
3104 if (pointed != runData.pointed_old)
3105 infostream << "Pointing at " << pointed.dump() << std::endl;
3107 // Note that updating the selection mesh every frame is not particularly efficient,
3108 // but the halo rendering code is already inefficient so there's no point in optimizing it here
3109 hud->updateSelectionMesh(camera_offset);
3111 // Allow digging again if button is not pressed
3112 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3113 runData.digging_blocked = false;
3117 - releasing dig button
3118 - pointing away from node
3120 if (runData.digging) {
3121 if (wasKeyReleased(KeyType::DIG)) {
3122 infostream << "Dig button released (stopped digging)" << std::endl;
3123 runData.digging = false;
3124 } else if (pointed != runData.pointed_old) {
3125 if (pointed.type == POINTEDTHING_NODE
3126 && runData.pointed_old.type == POINTEDTHING_NODE
3127 && pointed.node_undersurface
3128 == runData.pointed_old.node_undersurface) {
3129 // Still pointing to the same node, but a different face.
3132 infostream << "Pointing away from node (stopped digging)" << std::endl;
3133 runData.digging = false;
3134 hud->updateSelectionMesh(camera_offset);
3138 if (!runData.digging) {
3139 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3140 client->setCrack(-1, v3s16(0, 0, 0));
3141 runData.dig_time = 0.0;
3143 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3144 // Remove e.g. torches faster when clicking instead of holding dig button
3145 runData.nodig_delay_timer = 0;
3146 runData.dig_instantly = false;
3149 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3150 runData.btn_down_for_dig = false;
3152 runData.punching = false;
3154 soundmaker->m_player_leftpunch_sound.name = "";
3156 // Prepare for repeating, unless we're not supposed to
3157 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3158 runData.repeat_place_timer += dtime;
3160 runData.repeat_place_timer = 0;
3162 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3163 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3164 !client->getScript()->on_item_use(selected_item, pointed)))
3165 client->interact(INTERACT_USE, pointed);
3166 } else if (pointed.type == POINTEDTHING_NODE) {
3167 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3168 } else if (pointed.type == POINTEDTHING_OBJECT) {
3169 v3f player_position = player->getPosition();
3170 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
3171 handlePointingAtObject(pointed, tool_item, player_position,
3172 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
3173 } else if (isKeyDown(KeyType::DIG)) {
3174 // When button is held down in air, show continuous animation
3175 runData.punching = true;
3176 // Run callback even though item is not usable
3177 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3178 client->getScript()->on_item_use(selected_item, pointed);
3179 } else if (wasKeyPressed(KeyType::PLACE)) {
3180 handlePointingAtNothing(selected_item);
3183 runData.pointed_old = pointed;
3185 if (runData.punching || wasKeyPressed(KeyType::DIG))
3186 camera->setDigging(0); // dig animation
3188 input->clearWasKeyPressed();
3189 input->clearWasKeyReleased();
3190 // Ensure DIG & PLACE are marked as handled
3191 wasKeyDown(KeyType::DIG);
3192 wasKeyDown(KeyType::PLACE);
3194 input->joystick.clearWasKeyPressed(KeyType::DIG);
3195 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3197 input->joystick.clearWasKeyReleased(KeyType::DIG);
3198 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3202 PointedThing Game::updatePointedThing(
3203 const core::line3d<f32> &shootline,
3204 bool liquids_pointable,
3205 bool look_for_object,
3206 const v3s16 &camera_offset)
3208 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3209 selectionboxes->clear();
3210 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3211 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3212 "show_entity_selectionbox");
3214 ClientEnvironment &env = client->getEnv();
3215 ClientMap &map = env.getClientMap();
3216 const NodeDefManager *nodedef = map.getNodeDefManager();
3218 runData.selected_object = NULL;
3219 hud->pointing_at_object = false;
3221 RaycastState s(shootline, look_for_object, liquids_pointable);
3222 PointedThing result;
3223 env.continueRaycast(&s, &result);
3224 if (result.type == POINTEDTHING_OBJECT) {
3225 hud->pointing_at_object = true;
3227 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3228 aabb3f selection_box;
3229 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3230 runData.selected_object->getSelectionBox(&selection_box)) {
3231 v3f pos = runData.selected_object->getPosition();
3232 selectionboxes->push_back(aabb3f(selection_box));
3233 hud->setSelectionPos(pos, camera_offset);
3235 } else if (result.type == POINTEDTHING_NODE) {
3236 // Update selection boxes
3237 MapNode n = map.getNode(result.node_undersurface);
3238 std::vector<aabb3f> boxes;
3239 n.getSelectionBoxes(nodedef, &boxes,
3240 n.getNeighbors(result.node_undersurface, &map));
3243 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3244 i != boxes.end(); ++i) {
3246 box.MinEdge -= v3f(d, d, d);
3247 box.MaxEdge += v3f(d, d, d);
3248 selectionboxes->push_back(box);
3250 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3252 hud->setSelectedFaceNormal(v3f(
3253 result.intersection_normal.X,
3254 result.intersection_normal.Y,
3255 result.intersection_normal.Z));
3258 // Update selection mesh light level and vertex colors
3259 if (!selectionboxes->empty()) {
3260 v3f pf = hud->getSelectionPos();
3261 v3s16 p = floatToInt(pf, BS);
3263 // Get selection mesh light level
3264 MapNode n = map.getNode(p);
3265 u16 node_light = getInteriorLight(n, -1, nodedef);
3266 u16 light_level = node_light;
3268 for (const v3s16 &dir : g_6dirs) {
3269 n = map.getNode(p + dir);
3270 node_light = getInteriorLight(n, -1, nodedef);
3271 if (node_light > light_level)
3272 light_level = node_light;
3275 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3277 final_color_blend(&c, light_level, daynight_ratio);
3279 // Modify final color a bit with time
3280 u32 timer = porting::getTimeMs() % 5000;
3281 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3282 float sin_r = 0.08f * std::sin(timerf);
3283 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3284 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3285 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3286 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3287 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3289 // Set mesh final color
3290 hud->setSelectionMeshColor(c);
3296 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3298 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3299 PointedThing fauxPointed;
3300 fauxPointed.type = POINTEDTHING_NOTHING;
3301 client->interact(INTERACT_ACTIVATE, fauxPointed);
3305 void Game::handlePointingAtNode(const PointedThing &pointed,
3306 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3308 v3s16 nodepos = pointed.node_undersurface;
3309 v3s16 neighbourpos = pointed.node_abovesurface;
3312 Check information text of node
3315 ClientMap &map = client->getEnv().getClientMap();
3317 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3318 && !runData.digging_blocked
3319 && client->checkPrivilege("interact")) {
3320 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3323 // This should be done after digging handling
3324 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3327 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3328 meta->getString("infotext"))));
3330 MapNode n = map.getNode(nodepos);
3332 if (nodedef_manager->get(n).name == "unknown") {
3333 m_game_ui->setInfoText(L"Unknown node");
3337 if ((wasKeyPressed(KeyType::PLACE) ||
3338 runData.repeat_place_timer >= m_repeat_place_time) &&
3339 client->checkPrivilege("interact")) {
3340 runData.repeat_place_timer = 0;
3341 infostream << "Place button pressed while looking at ground" << std::endl;
3343 // Placing animation (always shown for feedback)
3344 camera->setDigging(1);
3346 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3348 // If the wielded item has node placement prediction,
3350 // And also set the sound and send the interact
3351 // But first check for meta formspec and rightclickable
3352 auto &def = selected_item.getDefinition(itemdef_manager);
3353 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3356 if (placed && client->modsLoaded())
3357 client->getScript()->on_placenode(pointed, def);
3361 bool Game::nodePlacement(const ItemDefinition &selected_def,
3362 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3363 const PointedThing &pointed, const NodeMetadata *meta)
3365 const auto &prediction = selected_def.node_placement_prediction;
3367 const NodeDefManager *nodedef = client->ndef();
3368 ClientMap &map = client->getEnv().getClientMap();
3370 bool is_valid_position;
3372 node = map.getNode(nodepos, &is_valid_position);
3373 if (!is_valid_position) {
3374 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3379 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3380 && !isKeyDown(KeyType::SNEAK)) {
3381 // on_rightclick callbacks are called anyway
3382 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3383 client->interact(INTERACT_PLACE, pointed);
3385 infostream << "Launching custom inventory view" << std::endl;
3387 InventoryLocation inventoryloc;
3388 inventoryloc.setNodeMeta(nodepos);
3390 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3391 &client->getEnv().getClientMap(), nodepos);
3392 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3394 auto *&formspec = m_game_ui->updateFormspec("");
3395 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3396 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3398 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3402 // on_rightclick callback
3403 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3404 !isKeyDown(KeyType::SNEAK))) {
3406 client->interact(INTERACT_PLACE, pointed);
3410 verbosestream << "Node placement prediction for "
3411 << selected_def.name << " is " << prediction << std::endl;
3412 v3s16 p = neighbourpos;
3414 // Place inside node itself if buildable_to
3415 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3416 if (is_valid_position) {
3417 if (nodedef->get(n_under).buildable_to) {
3420 node = map.getNode(p, &is_valid_position);
3421 if (is_valid_position && !nodedef->get(node).buildable_to) {
3422 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3424 client->interact(INTERACT_PLACE, pointed);
3430 // Find id of predicted node
3432 bool found = nodedef->getId(prediction, id);
3435 errorstream << "Node placement prediction failed for "
3436 << selected_def.name << " (places " << prediction
3437 << ") - Name not known" << std::endl;
3438 // Handle this as if prediction was empty
3440 client->interact(INTERACT_PLACE, pointed);
3444 const ContentFeatures &predicted_f = nodedef->get(id);
3446 // Predict param2 for facedir and wallmounted nodes
3447 // Compare core.item_place_node() for what the server does
3450 const u8 place_param2 = selected_def.place_param2;
3453 param2 = place_param2;
3454 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3455 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3456 v3s16 dir = nodepos - neighbourpos;
3458 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3459 param2 = dir.Y < 0 ? 1 : 0;
3460 } else if (abs(dir.X) > abs(dir.Z)) {
3461 param2 = dir.X < 0 ? 3 : 2;
3463 param2 = dir.Z < 0 ? 5 : 4;
3465 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3466 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3467 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3469 if (abs(dir.X) > abs(dir.Z)) {
3470 param2 = dir.X < 0 ? 3 : 1;
3472 param2 = dir.Z < 0 ? 2 : 0;
3476 // Check attachment if node is in group attached_node
3477 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3478 const static v3s16 wallmounted_dirs[8] = {
3488 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3489 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3490 pp = p + wallmounted_dirs[param2];
3492 pp = p + v3s16(0, -1, 0);
3494 if (!nodedef->get(map.getNode(pp)).walkable) {
3495 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3497 client->interact(INTERACT_PLACE, pointed);
3503 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3504 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3505 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3506 const auto &indexstr = selected_item.metadata.
3507 getString("palette_index", 0);
3508 if (!indexstr.empty()) {
3509 s32 index = mystoi(indexstr);
3510 if (predicted_f.param_type_2 == CPT2_COLOR) {
3512 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3513 // param2 = pure palette index + other
3514 param2 = (index & 0xf8) | (param2 & 0x07);
3515 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3516 // param2 = pure palette index + other
3517 param2 = (index & 0xe0) | (param2 & 0x1f);
3522 // Add node to client map
3523 MapNode n(id, 0, param2);
3526 LocalPlayer *player = client->getEnv().getLocalPlayer();
3528 // Dont place node when player would be inside new node
3529 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3530 if (!nodedef->get(n).walkable ||
3531 g_settings->getBool("enable_build_where_you_stand") ||
3532 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3533 (nodedef->get(n).walkable &&
3534 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3535 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3536 // This triggers the required mesh update too
3537 client->addNode(p, n);
3539 client->interact(INTERACT_PLACE, pointed);
3540 // A node is predicted, also play a sound
3541 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3544 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3547 } catch (const InvalidPositionException &e) {
3548 errorstream << "Node placement prediction failed for "
3549 << selected_def.name << " (places "
3550 << prediction << ") - Position not loaded" << std::endl;
3551 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3556 void Game::handlePointingAtObject(const PointedThing &pointed,
3557 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3559 std::wstring infotext = unescape_translate(
3560 utf8_to_wide(runData.selected_object->infoText()));
3563 if (!infotext.empty()) {
3566 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3569 m_game_ui->setInfoText(infotext);
3571 if (isKeyDown(KeyType::DIG)) {
3572 bool do_punch = false;
3573 bool do_punch_damage = false;
3575 if (runData.object_hit_delay_timer <= 0.0) {
3577 do_punch_damage = true;
3578 runData.object_hit_delay_timer = object_hit_delay;
3581 if (wasKeyPressed(KeyType::DIG))
3585 infostream << "Punched object" << std::endl;
3586 runData.punching = true;
3589 if (do_punch_damage) {
3590 // Report direct punch
3591 v3f objpos = runData.selected_object->getPosition();
3592 v3f dir = (objpos - player_position).normalize();
3594 bool disable_send = runData.selected_object->directReportPunch(
3595 dir, &tool_item, runData.time_from_last_punch);
3596 runData.time_from_last_punch = 0;
3599 client->interact(INTERACT_START_DIGGING, pointed);
3601 } else if (wasKeyDown(KeyType::PLACE)) {
3602 infostream << "Pressed place button while pointing at object" << std::endl;
3603 client->interact(INTERACT_PLACE, pointed); // place
3608 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3609 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3611 // See also: serverpackethandle.cpp, action == 2
3612 LocalPlayer *player = client->getEnv().getLocalPlayer();
3613 ClientMap &map = client->getEnv().getClientMap();
3614 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3616 // NOTE: Similar piece of code exists on the server side for
3618 // Get digging parameters
3619 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3620 &selected_item.getToolCapabilities(itemdef_manager),
3621 selected_item.wear);
3623 // If can't dig, try hand
3624 if (!params.diggable) {
3625 params = getDigParams(nodedef_manager->get(n).groups,
3626 &hand_item.getToolCapabilities(itemdef_manager));
3629 if (!params.diggable) {
3630 // I guess nobody will wait for this long
3631 runData.dig_time_complete = 10000000.0;
3633 runData.dig_time_complete = params.time;
3635 if (m_cache_enable_particles) {
3636 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3637 client->getParticleManager()->addNodeParticle(client,
3638 player, nodepos, n, features);
3642 if (!runData.digging) {
3643 infostream << "Started digging" << std::endl;
3644 runData.dig_instantly = runData.dig_time_complete == 0;
3645 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3647 client->interact(INTERACT_START_DIGGING, pointed);
3648 runData.digging = true;
3649 runData.btn_down_for_dig = true;
3652 if (!runData.dig_instantly) {
3653 runData.dig_index = (float)crack_animation_length
3655 / runData.dig_time_complete;
3657 // This is for e.g. torches
3658 runData.dig_index = crack_animation_length;
3661 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3663 if (sound_dig.exists() && params.diggable) {
3664 if (sound_dig.name == "__group") {
3665 if (!params.main_group.empty()) {
3666 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3667 soundmaker->m_player_leftpunch_sound.name =
3668 std::string("default_dig_") +
3672 soundmaker->m_player_leftpunch_sound = sound_dig;
3676 // Don't show cracks if not diggable
3677 if (runData.dig_time_complete >= 100000.0) {
3678 } else if (runData.dig_index < crack_animation_length) {
3679 //TimeTaker timer("client.setTempMod");
3680 //infostream<<"dig_index="<<dig_index<<std::endl;
3681 client->setCrack(runData.dig_index, nodepos);
3683 infostream << "Digging completed" << std::endl;
3684 client->setCrack(-1, v3s16(0, 0, 0));
3686 runData.dig_time = 0;
3687 runData.digging = false;
3688 // we successfully dug, now block it from repeating if we want to be safe
3689 if (g_settings->getBool("safe_dig_and_place"))
3690 runData.digging_blocked = true;
3692 runData.nodig_delay_timer =
3693 runData.dig_time_complete / (float)crack_animation_length;
3695 // We don't want a corresponding delay to very time consuming nodes
3696 // and nodes without digging time (e.g. torches) get a fixed delay.
3697 if (runData.nodig_delay_timer > 0.3)
3698 runData.nodig_delay_timer = 0.3;
3699 else if (runData.dig_instantly)
3700 runData.nodig_delay_timer = 0.15;
3702 bool is_valid_position;
3703 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3704 if (is_valid_position) {
3705 if (client->modsLoaded() &&
3706 client->getScript()->on_dignode(nodepos, wasnode)) {
3710 const ContentFeatures &f = client->ndef()->get(wasnode);
3711 if (f.node_dig_prediction == "air") {
3712 client->removeNode(nodepos);
3713 } else if (!f.node_dig_prediction.empty()) {
3715 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3717 client->addNode(nodepos, id, true);
3719 // implicit else: no prediction
3722 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3724 if (m_cache_enable_particles) {
3725 const ContentFeatures &features =
3726 client->getNodeDefManager()->get(wasnode);
3727 client->getParticleManager()->addDiggingParticles(client,
3728 player, nodepos, wasnode, features);
3732 // Send event to trigger sound
3733 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3736 if (runData.dig_time_complete < 100000.0) {
3737 runData.dig_time += dtime;
3739 runData.dig_time = 0;
3740 client->setCrack(-1, nodepos);
3743 camera->setDigging(0); // Dig animation
3746 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3747 const CameraOrientation &cam)
3749 TimeTaker tt_update("Game::updateFrame()");
3750 LocalPlayer *player = client->getEnv().getLocalPlayer();
3756 if (draw_control->range_all) {
3757 runData.fog_range = 100000 * BS;
3759 runData.fog_range = draw_control->wanted_range * BS;
3763 Calculate general brightness
3765 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3766 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3767 float direct_brightness;
3770 // When in noclip mode force same sky brightness as above ground so you
3772 if (draw_control->allow_noclip && m_cache_enable_free_move &&
3773 client->checkPrivilege("fly")) {
3774 direct_brightness = time_brightness;
3775 sunlight_seen = true;
3777 float old_brightness = sky->getBrightness();
3778 direct_brightness = client->getEnv().getClientMap()
3779 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3780 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3784 float time_of_day_smooth = runData.time_of_day_smooth;
3785 float time_of_day = client->getEnv().getTimeOfDayF();
3787 static const float maxsm = 0.05f;
3788 static const float todsm = 0.05f;
3790 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3791 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3792 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3793 time_of_day_smooth = time_of_day;
3795 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3796 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3797 + (time_of_day + 1.0) * todsm;
3799 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3800 + time_of_day * todsm;
3802 runData.time_of_day_smooth = time_of_day_smooth;
3804 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3805 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3806 player->getPitch());
3812 if (sky->getCloudsVisible()) {
3813 clouds->setVisible(true);
3814 clouds->step(dtime);
3815 // camera->getPosition is not enough for 3rd person views
3816 v3f camera_node_position = camera->getCameraNode()->getPosition();
3817 v3s16 camera_offset = camera->getOffset();
3818 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3819 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3820 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3821 clouds->update(camera_node_position,
3822 sky->getCloudColor());
3823 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3824 // if inside clouds, and fog enabled, use that as sky
3826 video::SColor clouds_dark = clouds->getColor()
3827 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3828 sky->overrideColors(clouds_dark, clouds->getColor());
3829 sky->setInClouds(true);
3830 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3831 // do not draw clouds after all
3832 clouds->setVisible(false);
3835 clouds->setVisible(false);
3842 client->getParticleManager()->step(dtime);
3848 if (m_cache_enable_fog) {
3851 video::EFT_FOG_LINEAR,
3852 runData.fog_range * m_cache_fog_start,
3853 runData.fog_range * 1.0,
3861 video::EFT_FOG_LINEAR,
3873 if (player->hurt_tilt_timer > 0.0f) {
3874 player->hurt_tilt_timer -= dtime * 6.0f;
3876 if (player->hurt_tilt_timer < 0.0f)
3877 player->hurt_tilt_strength = 0.0f;
3881 Update minimap pos and rotation
3883 if (mapper && m_game_ui->m_flags.show_hud) {
3884 mapper->setPos(floatToInt(player->getPosition(), BS));
3885 mapper->setAngle(player->getYaw());
3889 Get chat messages from client
3898 if (player->getWieldIndex() != runData.new_playeritem)
3899 client->setPlayerItem(runData.new_playeritem);
3901 if (client->updateWieldedItem()) {
3902 // Update wielded tool
3903 ItemStack selected_item, hand_item;
3904 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3905 camera->wield(tool_item);
3909 Update block draw list every 200ms or when camera direction has
3912 runData.update_draw_list_timer += dtime;
3914 float update_draw_list_delta = 0.2f;
3916 v3f camera_direction = camera->getDirection();
3917 if (runData.update_draw_list_timer >= update_draw_list_delta
3918 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3919 || m_camera_offset_changed
3920 || client->getEnv().getClientMap().needsUpdateDrawList()) {
3921 runData.update_draw_list_timer = 0;
3922 client->getEnv().getClientMap().updateDrawList();
3923 runData.update_draw_list_last_cam_dir = camera_direction;
3926 if (RenderingEngine::get_shadow_renderer()) {
3930 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3933 make sure menu is on top
3934 1. Delete formspec menu reference if menu was removed
3935 2. Else, make sure formspec menu is on top
3937 auto formspec = m_game_ui->getFormspecGUI();
3938 do { // breakable. only runs for one iteration
3942 if (formspec->getReferenceCount() == 1) {
3943 m_game_ui->deleteFormspec();
3947 auto &loc = formspec->getFormspecLocation();
3948 if (loc.type == InventoryLocation::NODEMETA) {
3949 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3950 if (!meta || meta->getString("formspec").empty()) {
3951 formspec->quitMenu();
3957 guiroot->bringToFront(formspec);
3961 ==================== Drawing begins ====================
3963 const video::SColor skycolor = sky->getSkyColor();
3965 TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
3966 driver->beginScene(true, true, skycolor);
3968 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3969 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3970 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3971 bool draw_crosshair = (
3972 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3973 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3974 #ifdef HAVE_TOUCHSCREENGUI
3976 draw_crosshair = !g_settings->getBool("touchtarget");
3977 } catch (SettingNotFoundException) {
3980 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3981 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3986 v2u32 screensize = driver->getScreenSize();
3988 if (m_game_ui->m_flags.show_profiler_graph)
3989 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3994 if (runData.damage_flash > 0.0f) {
3995 video::SColor color(runData.damage_flash, 180, 0, 0);
3996 driver->draw2DRectangle(color,
3997 core::rect<s32>(0, 0, screensize.X, screensize.Y),
4000 runData.damage_flash -= 384.0f * dtime;
4004 ==================== End scene ====================
4006 #if IRRLICHT_VERSION_MT_REVISION < 5
4007 if (++m_reset_HW_buffer_counter > 500) {
4009 Periodically remove all mesh HW buffers.
4011 Work around for a quirk in Irrlicht where a HW buffer is only
4012 released after 20000 iterations (triggered from endScene()).
4014 Without this, all loaded but unused meshes will retain their HW
4015 buffers for at least 5 minutes, at which point looking up the HW buffers
4016 becomes a bottleneck and the framerate drops (as much as 30%).
4018 Tests showed that numbers between 50 and 1000 are good, so picked 500.
4019 There are no other public Irrlicht APIs that allow interacting with the
4020 HW buffers without tracking the status of every individual mesh.
4022 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4024 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4025 driver->removeAllHardwareBuffers();
4026 m_reset_HW_buffer_counter = 0;
4032 stats->drawtime = tt_draw.stop(true);
4033 g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
4034 g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
4037 /* Log times and stuff for visualization */
4038 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4040 Profiler::GraphValues values;
4041 g_profiler->graphGet(values);
4045 /****************************************************************************
4047 *****************************************************************************/
4048 void Game::updateShadows()
4050 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4054 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4056 float timeoftheday = getWickedTimeOfDay(in_timeofday);
4057 bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
4058 bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
4059 shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
4061 timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
4062 const float offset_constant = 10000.0f;
4064 v3f light(0.0f, 0.0f, -1.0f);
4065 light.rotateXZBy(90);
4066 light.rotateXYBy(timeoftheday * 360 - 90);
4067 light.rotateYZBy(sky->getSkyBodyOrbitTilt());
4069 v3f sun_pos = light * offset_constant;
4071 if (shadow->getDirectionalLightCount() == 0)
4072 shadow->addDirectionalLight();
4073 shadow->getDirectionalLight().setDirection(sun_pos);
4074 shadow->setTimeOfDay(in_timeofday);
4076 shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4079 /****************************************************************************
4081 ****************************************************************************/
4083 void FpsControl::reset()
4085 last_time = porting::getTimeUs();
4089 * On some computers framerate doesn't seem to be automatically limited
4091 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
4093 const u64 frametime_min = 1000000.0f / (
4094 device->isWindowFocused() && !g_menumgr.pausesGame()
4095 ? g_settings->getFloat("fps_max")
4096 : g_settings->getFloat("fps_max_unfocused"));
4098 u64 time = porting::getTimeUs();
4100 if (time > last_time) // Make sure time hasn't overflowed
4101 busy_time = time - last_time;
4105 if (busy_time < frametime_min) {
4106 sleep_time = frametime_min - busy_time;
4107 if (sleep_time > 1000)
4108 sleep_ms(sleep_time / 1000);
4113 // Read the timer again to accurately determine how long we actually slept,
4114 // rather than calculating it by adding sleep_time to time.
4115 time = porting::getTimeUs();
4117 if (time > last_time) // Make sure last_time hasn't overflowed
4118 *dtime = (time - last_time) / 1000000.0f;
4125 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4127 const wchar_t *wmsg = wgettext(msg);
4128 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4133 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4135 ((Game *)data)->readSettings();
4138 void Game::readSettings()
4140 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4141 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4142 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4143 m_cache_enable_particles = g_settings->getBool("enable_particles");
4144 m_cache_enable_fog = g_settings->getBool("enable_fog");
4145 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
4146 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4147 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
4149 m_cache_enable_noclip = g_settings->getBool("noclip");
4150 m_cache_enable_free_move = g_settings->getBool("free_move");
4152 m_cache_fog_start = g_settings->getFloat("fog_start");
4154 m_cache_cam_smoothing = 0;
4155 if (g_settings->getBool("cinematic"))
4156 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4158 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4160 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4161 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4162 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4164 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4167 /****************************************************************************/
4168 /****************************************************************************
4170 ****************************************************************************/
4171 /****************************************************************************/
4173 void Game::showDeathFormspec()
4175 static std::string formspec_str =
4176 std::string("formspec_version[1]") +
4178 "bgcolor[#320000b4;true]"
4179 "label[4.85,1.35;" + gettext("You died") + "]"
4180 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4184 /* Note: FormspecFormSource and LocalFormspecHandler *
4185 * are deleted by guiFormSpecMenu */
4186 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4187 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4189 auto *&formspec = m_game_ui->getFormspecGUI();
4190 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4191 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4192 formspec->setFocus("btn_respawn");
4195 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4196 void Game::showPauseMenu()
4198 #ifdef HAVE_TOUCHSCREENGUI
4199 static const std::string control_text = strgettext("Default Controls:\n"
4200 "No menu visible:\n"
4201 "- single tap: button activate\n"
4202 "- double tap: place/use\n"
4203 "- slide finger: look around\n"
4204 "Menu/Inventory visible:\n"
4205 "- double tap (outside):\n"
4207 "- touch stack, touch slot:\n"
4209 "- touch&drag, tap 2nd finger\n"
4210 " --> place single item to slot\n"
4213 static const std::string control_text_template = strgettext("Controls:\n"
4214 "- %s: move forwards\n"
4215 "- %s: move backwards\n"
4217 "- %s: move right\n"
4218 "- %s: jump/climb up\n"
4221 "- %s: sneak/climb down\n"
4224 "- Mouse: turn/look\n"
4225 "- Mouse wheel: select item\n"
4229 char control_text_buf[600];
4231 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4232 GET_KEY_NAME(keymap_forward),
4233 GET_KEY_NAME(keymap_backward),
4234 GET_KEY_NAME(keymap_left),
4235 GET_KEY_NAME(keymap_right),
4236 GET_KEY_NAME(keymap_jump),
4237 GET_KEY_NAME(keymap_dig),
4238 GET_KEY_NAME(keymap_place),
4239 GET_KEY_NAME(keymap_sneak),
4240 GET_KEY_NAME(keymap_drop),
4241 GET_KEY_NAME(keymap_inventory),
4242 GET_KEY_NAME(keymap_chat)
4245 std::string control_text = std::string(control_text_buf);
4246 str_formspec_escape(control_text);
4249 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4250 std::ostringstream os;
4252 os << "formspec_version[1]" << SIZE_TAG
4253 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4254 << strgettext("Continue") << "]";
4256 if (!simple_singleplayer_mode) {
4257 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4258 << strgettext("Change Password") << "]";
4260 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4265 if (g_settings->getBool("enable_sound")) {
4266 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4267 << strgettext("Sound Volume") << "]";
4270 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4271 << strgettext("Change Keys") << "]";
4273 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4274 << strgettext("Exit to Menu") << "]";
4275 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4276 << strgettext("Exit to OS") << "]"
4277 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4278 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4280 << strgettext("Game info:") << "\n";
4281 const std::string &address = client->getAddressName();
4282 static const std::string mode = strgettext("- Mode: ");
4283 if (!simple_singleplayer_mode) {
4284 Address serverAddress = client->getServerAddress();
4285 if (!address.empty()) {
4286 os << mode << strgettext("Remote server") << "\n"
4287 << strgettext("- Address: ") << address;
4289 os << mode << strgettext("Hosting server");
4291 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4293 os << mode << strgettext("Singleplayer") << "\n";
4295 if (simple_singleplayer_mode || address.empty()) {
4296 static const std::string on = strgettext("On");
4297 static const std::string off = strgettext("Off");
4298 // Note: Status of enable_damage and creative_mode settings is intentionally
4299 // NOT shown here because the game might roll its own damage system and/or do
4300 // a per-player Creative Mode, in which case writing it here would mislead.
4301 bool damage = g_settings->getBool("enable_damage");
4302 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4303 if (!simple_singleplayer_mode) {
4305 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4306 //~ PvP = Player versus Player
4307 os << strgettext("- PvP: ") << pvp << "\n";
4309 os << strgettext("- Public: ") << announced << "\n";
4310 std::string server_name = g_settings->get("server_name");
4311 str_formspec_escape(server_name);
4312 if (announced == on && !server_name.empty())
4313 os << strgettext("- Server Name: ") << server_name;
4320 /* Note: FormspecFormSource and LocalFormspecHandler *
4321 * are deleted by guiFormSpecMenu */
4322 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4323 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4325 auto *&formspec = m_game_ui->getFormspecGUI();
4326 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4327 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4328 formspec->setFocus("btn_continue");
4329 formspec->doPause = true;
4331 if (simple_singleplayer_mode)
4335 /****************************************************************************/
4336 /****************************************************************************
4337 extern function for launching the game
4338 ****************************************************************************/
4339 /****************************************************************************/
4341 void the_game(bool *kill,
4342 InputHandler *input,
4343 RenderingEngine *rendering_engine,
4344 const GameStartData &start_data,
4345 std::string &error_message,
4346 ChatBackend &chat_backend,
4347 bool *reconnect_requested) // Used for local game
4351 /* Make a copy of the server address because if a local singleplayer server
4352 * is created then this is updated and we don't want to change the value
4353 * passed to us by the calling function
4358 if (game.startup(kill, input, rendering_engine, start_data,
4359 error_message, reconnect_requested, &chat_backend)) {
4363 } catch (SerializationError &e) {
4364 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
4365 error_message = strgettext("A serialization error occurred:") +"\n"
4366 + e.what() + "\n\n" + ver_err;
4367 errorstream << error_message << std::endl;
4368 } catch (ServerError &e) {
4369 error_message = e.what();
4370 errorstream << "ServerError: " << error_message << std::endl;
4371 } catch (ModError &e) {
4372 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
4373 error_message = std::string("ModError: ") + e.what() +
4374 strgettext("\nCheck debug.txt for details.");
4375 errorstream << error_message << std::endl;