3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "client/renderingengine.h"
27 #include "client/clientevent.h"
28 #include "client/gameui.h"
29 #include "client/inputhandler.h"
30 #include "client/tile.h" // For TextureSource
31 #include "client/keys.h"
32 #include "client/joystick_controller.h"
33 #include "clientmap.h"
36 #include "content_cao.h"
37 #include "content/subgames.h"
38 #include "client/event_manager.h"
39 #include "fontengine.h"
43 #include "gameparams.h"
45 #include "gui/guiChatConsole.h"
46 #include "gui/guiConfirmRegistration.h"
47 #include "gui/guiFormSpecMenu.h"
48 #include "gui/guiKeyChangeMenu.h"
49 #include "gui/guiPasswordChange.h"
50 #include "gui/guiVolumeChange.h"
51 #include "gui/mainmenumanager.h"
52 #include "gui/profilergraph.h"
55 #include "nodedef.h" // Needed for determining pointing to nodes
56 #include "nodemetadata.h"
57 #include "particles.h"
65 #include "translation.h"
66 #include "util/basic_macros.h"
67 #include "util/directiontables.h"
68 #include "util/pointedthing.h"
69 #include "util/quicktune_shortcutter.h"
70 #include "irrlicht_changes/static_text.h"
73 #include "script/scripting_client.h"
77 #include "client/sound_openal.h"
79 #include "client/sound.h"
85 struct TextDestNodeMetadata : public TextDest
87 TextDestNodeMetadata(v3s16 p, Client *client)
92 // This is deprecated I guess? -celeron55
93 void gotText(const std::wstring &text)
95 std::string ntext = wide_to_utf8(text);
96 infostream << "Submitting 'text' field of node at (" << m_p.X << ","
97 << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
99 fields["text"] = ntext;
100 m_client->sendNodemetaFields(m_p, "", fields);
102 void gotText(const StringMap &fields)
104 m_client->sendNodemetaFields(m_p, "", fields);
111 struct TextDestPlayerInventory : public TextDest
113 TextDestPlayerInventory(Client *client)
118 TextDestPlayerInventory(Client *client, const std::string &formname)
121 m_formname = formname;
123 void gotText(const StringMap &fields)
125 m_client->sendInventoryFields(m_formname, fields);
131 struct LocalFormspecHandler : public TextDest
133 LocalFormspecHandler(const std::string &formname)
135 m_formname = formname;
138 LocalFormspecHandler(const std::string &formname, Client *client):
141 m_formname = formname;
144 void gotText(const StringMap &fields)
146 if (m_formname == "MT_PAUSE_MENU") {
147 if (fields.find("btn_sound") != fields.end()) {
148 g_gamecallback->changeVolume();
152 if (fields.find("btn_key_config") != fields.end()) {
153 g_gamecallback->keyConfig();
157 if (fields.find("btn_exit_menu") != fields.end()) {
158 g_gamecallback->disconnect();
162 if (fields.find("btn_exit_os") != fields.end()) {
163 g_gamecallback->exitToOS();
165 RenderingEngine::get_raw_device()->closeDevice();
170 if (fields.find("btn_change_password") != fields.end()) {
171 g_gamecallback->changePassword();
178 if (m_formname == "MT_DEATH_SCREEN") {
179 assert(m_client != 0);
180 m_client->sendRespawn();
184 if (m_client->modsLoaded())
185 m_client->getScript()->on_formspec_input(m_formname, fields);
188 Client *m_client = nullptr;
191 /* Form update callback */
193 class NodeMetadataFormSource: public IFormSource
196 NodeMetadataFormSource(ClientMap *map, v3s16 p):
201 const std::string &getForm() const
203 static const std::string empty_string = "";
204 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
209 return meta->getString("formspec");
212 virtual std::string resolveText(const std::string &str)
214 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
219 return meta->resolveString(str);
226 class PlayerInventoryFormSource: public IFormSource
229 PlayerInventoryFormSource(Client *client):
234 const std::string &getForm() const
236 LocalPlayer *player = m_client->getEnv().getLocalPlayer();
237 return player->inventory_formspec;
243 class NodeDugEvent: public MtEvent
249 NodeDugEvent(v3s16 p, MapNode n):
253 MtEvent::Type getType() const
255 return MtEvent::NODE_DUG;
261 ISoundManager *m_sound;
262 const NodeDefManager *m_ndef;
264 bool makes_footstep_sound;
265 float m_player_step_timer;
266 float m_player_jump_timer;
268 SimpleSoundSpec m_player_step_sound;
269 SimpleSoundSpec m_player_leftpunch_sound;
270 SimpleSoundSpec m_player_rightpunch_sound;
272 SoundMaker(ISoundManager *sound, const NodeDefManager *ndef):
275 makes_footstep_sound(true),
276 m_player_step_timer(0.0f),
277 m_player_jump_timer(0.0f)
281 void playPlayerStep()
283 if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
284 m_player_step_timer = 0.03;
285 if (makes_footstep_sound)
286 m_sound->playSound(m_player_step_sound, false);
290 void playPlayerJump()
292 if (m_player_jump_timer <= 0.0f) {
293 m_player_jump_timer = 0.2f;
294 m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f), false);
298 static void viewBobbingStep(MtEvent *e, void *data)
300 SoundMaker *sm = (SoundMaker *)data;
301 sm->playPlayerStep();
304 static void playerRegainGround(MtEvent *e, void *data)
306 SoundMaker *sm = (SoundMaker *)data;
307 sm->playPlayerStep();
310 static void playerJump(MtEvent *e, void *data)
312 SoundMaker *sm = (SoundMaker *)data;
313 sm->playPlayerJump();
316 static void cameraPunchLeft(MtEvent *e, void *data)
318 SoundMaker *sm = (SoundMaker *)data;
319 sm->m_sound->playSound(sm->m_player_leftpunch_sound, false);
322 static void cameraPunchRight(MtEvent *e, void *data)
324 SoundMaker *sm = (SoundMaker *)data;
325 sm->m_sound->playSound(sm->m_player_rightpunch_sound, false);
328 static void nodeDug(MtEvent *e, void *data)
330 SoundMaker *sm = (SoundMaker *)data;
331 NodeDugEvent *nde = (NodeDugEvent *)e;
332 sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug, false);
335 static void playerDamage(MtEvent *e, void *data)
337 SoundMaker *sm = (SoundMaker *)data;
338 sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false);
341 static void playerFallingDamage(MtEvent *e, void *data)
343 SoundMaker *sm = (SoundMaker *)data;
344 sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false);
347 void registerReceiver(MtEventManager *mgr)
349 mgr->reg(MtEvent::VIEW_BOBBING_STEP, SoundMaker::viewBobbingStep, this);
350 mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, this);
351 mgr->reg(MtEvent::PLAYER_JUMP, SoundMaker::playerJump, this);
352 mgr->reg(MtEvent::CAMERA_PUNCH_LEFT, SoundMaker::cameraPunchLeft, this);
353 mgr->reg(MtEvent::CAMERA_PUNCH_RIGHT, SoundMaker::cameraPunchRight, this);
354 mgr->reg(MtEvent::NODE_DUG, SoundMaker::nodeDug, this);
355 mgr->reg(MtEvent::PLAYER_DAMAGE, SoundMaker::playerDamage, this);
356 mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, this);
359 void step(float dtime)
361 m_player_step_timer -= dtime;
362 m_player_jump_timer -= dtime;
366 // Locally stored sounds don't need to be preloaded because of this
367 class GameOnDemandSoundFetcher: public OnDemandSoundFetcher
369 std::set<std::string> m_fetched;
371 void paths_insert(std::set<std::string> &dst_paths,
372 const std::string &base,
373 const std::string &name)
375 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg");
376 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg");
377 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg");
378 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg");
379 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg");
380 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg");
381 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg");
382 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg");
383 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg");
384 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg");
385 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg");
388 void fetchSounds(const std::string &name,
389 std::set<std::string> &dst_paths,
390 std::set<std::string> &dst_datas)
392 if (m_fetched.count(name))
395 m_fetched.insert(name);
397 paths_insert(dst_paths, porting::path_share, name);
398 paths_insert(dst_paths, porting::path_user, name);
403 typedef s32 SamplerLayer_t;
406 class GameGlobalShaderConstantSetter : public IShaderConstantSetter
409 bool *m_force_fog_off;
412 CachedPixelShaderSetting<float, 4> m_sky_bg_color;
413 CachedPixelShaderSetting<float> m_fog_distance;
414 CachedVertexShaderSetting<float> m_animation_timer_vertex;
415 CachedPixelShaderSetting<float> m_animation_timer_pixel;
416 CachedPixelShaderSetting<float, 3> m_day_light;
417 CachedPixelShaderSetting<float, 4> m_star_color;
418 CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
419 CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
420 CachedPixelShaderSetting<float, 3> m_minimap_yaw;
421 CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
422 CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
423 CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
424 CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture;
428 void onSettingsChange(const std::string &name)
430 if (name == "enable_fog")
431 m_fog_enabled = g_settings->getBool("enable_fog");
434 static void settingsCallback(const std::string &name, void *userdata)
436 reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
439 void setSky(Sky *sky) { m_sky = sky; }
441 GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
442 f32 *fog_range, Client *client) :
444 m_force_fog_off(force_fog_off),
445 m_fog_range(fog_range),
446 m_sky_bg_color("skyBgColor"),
447 m_fog_distance("fogDistance"),
448 m_animation_timer_vertex("animationTimer"),
449 m_animation_timer_pixel("animationTimer"),
450 m_day_light("dayLight"),
451 m_star_color("starColor"),
452 m_eye_position_pixel("eyePosition"),
453 m_eye_position_vertex("eyePosition"),
454 m_minimap_yaw("yawVec"),
455 m_camera_offset_pixel("cameraOffset"),
456 m_camera_offset_vertex("cameraOffset"),
457 m_base_texture("baseTexture"),
458 m_normal_texture("normalTexture"),
461 g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
462 m_fog_enabled = g_settings->getBool("enable_fog");
465 ~GameGlobalShaderConstantSetter()
467 g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
470 void onSetConstants(video::IMaterialRendererServices *services) override
473 video::SColor bgcolor = m_sky->getBgColor();
474 video::SColorf bgcolorf(bgcolor);
475 float bgcolorfa[4] = {
481 m_sky_bg_color.set(bgcolorfa, services);
484 float fog_distance = 10000 * BS;
486 if (m_fog_enabled && !*m_force_fog_off)
487 fog_distance = *m_fog_range;
489 m_fog_distance.set(&fog_distance, services);
491 u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
492 video::SColorf sunlight;
493 get_sunlight_color(&sunlight, daynight_ratio);
498 m_day_light.set(dnc, services);
500 video::SColorf star_color = m_sky->getCurrentStarColor();
501 float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a};
502 m_star_color.set(clr, services);
504 u32 animation_timer = porting::getTimeMs() % 1000000;
505 float animation_timer_f = (float)animation_timer / 100000.f;
506 m_animation_timer_vertex.set(&animation_timer_f, services);
507 m_animation_timer_pixel.set(&animation_timer_f, services);
509 float eye_position_array[3];
510 v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
511 epos.getAs3Values(eye_position_array);
512 m_eye_position_pixel.set(eye_position_array, services);
513 m_eye_position_vertex.set(eye_position_array, services);
515 if (m_client->getMinimap()) {
516 float minimap_yaw_array[3];
517 v3f minimap_yaw = m_client->getMinimap()->getYawVec();
518 minimap_yaw.getAs3Values(minimap_yaw_array);
519 m_minimap_yaw.set(minimap_yaw_array, services);
522 float camera_offset_array[3];
523 v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
524 offset.getAs3Values(camera_offset_array);
525 m_camera_offset_pixel.set(camera_offset_array, services);
526 m_camera_offset_vertex.set(camera_offset_array, services);
528 SamplerLayer_t base_tex = 0, normal_tex = 1;
529 m_base_texture.set(&base_tex, services);
530 m_normal_texture.set(&normal_tex, services);
535 class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
538 bool *m_force_fog_off;
541 std::vector<GameGlobalShaderConstantSetter *> created_nosky;
543 GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
544 f32 *fog_range, Client *client) :
546 m_force_fog_off(force_fog_off),
547 m_fog_range(fog_range),
551 void setSky(Sky *sky) {
553 for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
554 ggscs->setSky(m_sky);
556 created_nosky.clear();
559 virtual IShaderConstantSetter* create()
561 auto *scs = new GameGlobalShaderConstantSetter(
562 m_sky, m_force_fog_off, m_fog_range, m_client);
564 created_nosky.push_back(scs);
569 #ifdef HAVE_TOUCHSCREENGUI
570 #define SIZE_TAG "size[11,5.5]"
572 #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
575 /****************************************************************************
576 ****************************************************************************/
578 const static float object_hit_delay = 0.2;
581 FpsControl() : last_time(0), busy_time(0), sleep_time(0) {}
585 void limit(IrrlichtDevice *device, f32 *dtime);
587 u32 getBusyMs() const { return busy_time / 1000; }
589 // all values in microseconds (us)
590 u64 last_time, busy_time, sleep_time;
594 /* The reason the following structs are not anonymous structs within the
595 * class is that they are not used by the majority of member functions and
596 * many functions that do require objects of thse types do not modify them
597 * (so they can be passed as a const qualified parameter)
603 PointedThing pointed_old;
606 bool btn_down_for_dig;
608 bool digging_blocked;
609 bool reset_jump_timer;
610 float nodig_delay_timer;
612 float dig_time_complete;
613 float repeat_place_timer;
614 float object_hit_delay_timer;
615 float time_from_last_punch;
616 ClientActiveObject *selected_object;
620 float update_draw_list_timer;
624 v3f update_draw_list_last_cam_dir;
626 float time_of_day_smooth;
631 struct ClientEventHandler
633 void (Game::*handler)(ClientEvent *, CameraOrientation *);
636 /****************************************************************************
638 ****************************************************************************/
640 using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
642 /* This is not intended to be a public class. If a public class becomes
643 * desirable then it may be better to create another 'wrapper' class that
644 * hides most of the stuff in this class (nothing in this class is required
645 * by any other file) but exposes the public methods/data only.
652 bool startup(bool *kill,
654 RenderingEngine *rendering_engine,
655 const GameStartData &game_params,
656 std::string &error_message,
658 ChatBackend *chat_backend);
665 // Basic initialisation
666 bool init(const std::string &map_dir, const std::string &address,
667 u16 port, const SubgameSpec &gamespec);
669 bool createSingleplayerServer(const std::string &map_dir,
670 const SubgameSpec &gamespec, u16 port);
673 bool createClient(const GameStartData &start_data);
677 bool connectToServer(const GameStartData &start_data,
678 bool *connect_ok, bool *aborted);
679 bool getServerContent(bool *aborted);
683 void updateInteractTimers(f32 dtime);
684 bool checkConnection();
685 bool handleCallbacks();
686 void processQueues();
687 void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
688 void updateDebugState();
689 void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
690 void updateProfilerGraphs(ProfilerGraph *graph);
693 void processUserInput(f32 dtime);
694 void processKeyInput();
695 void processItemSelection(u16 *new_playeritem);
697 void dropSelectedItem(bool single_item = false);
698 void openInventory();
699 void openConsole(float scale, const wchar_t *line=NULL);
700 void toggleFreeMove();
701 void toggleFreeMoveAlt();
702 void togglePitchMove();
705 void toggleCinematic();
706 void toggleBlockBounds();
707 void toggleAutoforward();
709 void toggleMinimap(bool shift_pressed);
712 void toggleUpdateCamera();
714 void increaseViewRange();
715 void decreaseViewRange();
716 void toggleFullViewRange();
717 void checkZoomEnabled();
719 void updateCameraDirection(CameraOrientation *cam, float dtime);
720 void updateCameraOrientation(CameraOrientation *cam, float dtime);
721 void updatePlayerControl(const CameraOrientation &cam);
722 void step(f32 *dtime);
723 void processClientEvents(CameraOrientation *cam);
724 void updateCamera(f32 dtime);
725 void updateSound(f32 dtime);
726 void processPlayerInteraction(f32 dtime, bool show_hud);
728 * Returns the object or node the player is pointing at.
729 * Also updates the selected thing in the Hud.
731 * @param[in] shootline the shootline, starting from
732 * the camera position. This also gives the maximal distance
734 * @param[in] liquids_pointable if false, liquids are ignored
735 * @param[in] look_for_object if false, objects are ignored
736 * @param[in] camera_offset offset of the camera
737 * @param[out] selected_object the selected object or
740 PointedThing updatePointedThing(
741 const core::line3d<f32> &shootline, bool liquids_pointable,
742 bool look_for_object, const v3s16 &camera_offset);
743 void handlePointingAtNothing(const ItemStack &playerItem);
744 void handlePointingAtNode(const PointedThing &pointed,
745 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
746 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
747 const v3f &player_position, bool show_debug);
748 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
749 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
750 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
751 const CameraOrientation &cam);
752 void updateShadows();
755 void showOverlayMessage(const char *msg, float dtime, int percent,
756 bool draw_clouds = true);
758 static void settingChangedCallback(const std::string &setting_name, void *data);
761 inline bool isKeyDown(GameKeyType k)
763 return input->isKeyDown(k);
765 inline bool wasKeyDown(GameKeyType k)
767 return input->wasKeyDown(k);
769 inline bool wasKeyPressed(GameKeyType k)
771 return input->wasKeyPressed(k);
773 inline bool wasKeyReleased(GameKeyType k)
775 return input->wasKeyReleased(k);
779 void handleAndroidChatInput();
784 bool force_fog_off = false;
785 bool disable_camera_update = false;
788 void showDeathFormspec();
789 void showPauseMenu();
791 void pauseAnimation();
792 void resumeAnimation();
794 // ClientEvent handlers
795 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
796 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
797 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
798 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
799 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
800 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
801 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
802 CameraOrientation *cam);
803 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
804 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
805 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
806 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
807 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
808 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
809 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
810 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
811 CameraOrientation *cam);
812 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
814 void updateChat(f32 dtime);
816 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
817 const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
818 const NodeMetadata *meta);
819 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
821 f32 getSensitivityScaleFactor() const;
823 InputHandler *input = nullptr;
825 Client *client = nullptr;
826 Server *server = nullptr;
828 IWritableTextureSource *texture_src = nullptr;
829 IWritableShaderSource *shader_src = nullptr;
831 // When created, these will be filled with data received from the server
832 IWritableItemDefManager *itemdef_manager = nullptr;
833 NodeDefManager *nodedef_manager = nullptr;
835 GameOnDemandSoundFetcher soundfetcher; // useful when testing
836 ISoundManager *sound = nullptr;
837 bool sound_is_dummy = false;
838 SoundMaker *soundmaker = nullptr;
840 ChatBackend *chat_backend = nullptr;
841 LogOutputBuffer m_chat_log_buf;
843 EventManager *eventmgr = nullptr;
844 QuicktuneShortcutter *quicktune = nullptr;
845 bool registration_confirmation_shown = false;
847 std::unique_ptr<GameUI> m_game_ui;
848 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
849 MapDrawControl *draw_control = nullptr;
850 Camera *camera = nullptr;
851 Clouds *clouds = nullptr; // Free using ->Drop()
852 Sky *sky = nullptr; // Free using ->Drop()
854 Minimap *mapper = nullptr;
856 // Map server hud ids to client hud ids
857 std::unordered_map<u32, u32> m_hud_server_to_client;
863 This class does take ownership/responsibily for cleaning up etc of any of
864 these items (e.g. device)
866 IrrlichtDevice *device;
867 RenderingEngine *m_rendering_engine;
868 video::IVideoDriver *driver;
869 scene::ISceneManager *smgr;
871 std::string *error_message;
872 bool *reconnect_requested;
873 scene::ISceneNode *skybox;
874 PausedNodesList paused_animated_nodes;
876 bool simple_singleplayer_mode;
879 /* Pre-calculated values
881 int crack_animation_length;
883 IntervalLimiter profiler_interval;
886 * TODO: Local caching of settings is not optimal and should at some stage
887 * be updated to use a global settings object for getting thse values
888 * (as opposed to the this local caching). This can be addressed in
891 bool m_cache_doubletap_jump;
892 bool m_cache_enable_clouds;
893 bool m_cache_enable_joysticks;
894 bool m_cache_enable_particles;
895 bool m_cache_enable_fog;
896 bool m_cache_enable_noclip;
897 bool m_cache_enable_free_move;
898 f32 m_cache_mouse_sensitivity;
899 f32 m_cache_joystick_frustum_sensitivity;
900 f32 m_repeat_place_time;
901 f32 m_cache_cam_smoothing;
902 f32 m_cache_fog_start;
904 bool m_invert_mouse = false;
905 bool m_first_loop_after_window_activation = false;
906 bool m_camera_offset_changed = false;
908 bool m_does_lost_focus_pause_game = false;
910 #if IRRLICHT_VERSION_MT_REVISION < 5
911 int m_reset_HW_buffer_counter = 0;
914 #ifdef HAVE_TOUCHSCREENGUI
915 bool m_cache_hold_aux1;
918 bool m_android_chat_open;
923 m_chat_log_buf(g_logger),
924 m_game_ui(new GameUI())
926 g_settings->registerChangedCallback("doubletap_jump",
927 &settingChangedCallback, this);
928 g_settings->registerChangedCallback("enable_clouds",
929 &settingChangedCallback, this);
930 g_settings->registerChangedCallback("doubletap_joysticks",
931 &settingChangedCallback, this);
932 g_settings->registerChangedCallback("enable_particles",
933 &settingChangedCallback, this);
934 g_settings->registerChangedCallback("enable_fog",
935 &settingChangedCallback, this);
936 g_settings->registerChangedCallback("mouse_sensitivity",
937 &settingChangedCallback, this);
938 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
939 &settingChangedCallback, this);
940 g_settings->registerChangedCallback("repeat_place_time",
941 &settingChangedCallback, this);
942 g_settings->registerChangedCallback("noclip",
943 &settingChangedCallback, this);
944 g_settings->registerChangedCallback("free_move",
945 &settingChangedCallback, this);
946 g_settings->registerChangedCallback("cinematic",
947 &settingChangedCallback, this);
948 g_settings->registerChangedCallback("cinematic_camera_smoothing",
949 &settingChangedCallback, this);
950 g_settings->registerChangedCallback("camera_smoothing",
951 &settingChangedCallback, this);
955 #ifdef HAVE_TOUCHSCREENGUI
956 m_cache_hold_aux1 = false; // This is initialised properly later
963 /****************************************************************************
965 ****************************************************************************/
974 delete server; // deleted first to stop all server threads
982 delete nodedef_manager;
983 delete itemdef_manager;
986 clearTextureNameCache();
988 g_settings->deregisterChangedCallback("doubletap_jump",
989 &settingChangedCallback, this);
990 g_settings->deregisterChangedCallback("enable_clouds",
991 &settingChangedCallback, this);
992 g_settings->deregisterChangedCallback("enable_particles",
993 &settingChangedCallback, this);
994 g_settings->deregisterChangedCallback("enable_fog",
995 &settingChangedCallback, this);
996 g_settings->deregisterChangedCallback("mouse_sensitivity",
997 &settingChangedCallback, this);
998 g_settings->deregisterChangedCallback("repeat_place_time",
999 &settingChangedCallback, this);
1000 g_settings->deregisterChangedCallback("noclip",
1001 &settingChangedCallback, this);
1002 g_settings->deregisterChangedCallback("free_move",
1003 &settingChangedCallback, this);
1004 g_settings->deregisterChangedCallback("cinematic",
1005 &settingChangedCallback, this);
1006 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
1007 &settingChangedCallback, this);
1008 g_settings->deregisterChangedCallback("camera_smoothing",
1009 &settingChangedCallback, this);
1012 bool Game::startup(bool *kill,
1013 InputHandler *input,
1014 RenderingEngine *rendering_engine,
1015 const GameStartData &start_data,
1016 std::string &error_message,
1018 ChatBackend *chat_backend)
1022 m_rendering_engine = rendering_engine;
1023 device = m_rendering_engine->get_raw_device();
1025 this->error_message = &error_message;
1026 reconnect_requested = reconnect;
1027 this->input = input;
1028 this->chat_backend = chat_backend;
1029 simple_singleplayer_mode = start_data.isSinglePlayer();
1031 input->keycache.populate();
1033 driver = device->getVideoDriver();
1034 smgr = m_rendering_engine->get_scene_manager();
1036 smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1039 runData = GameRunData();
1040 runData.time_from_last_punch = 10.0;
1042 m_game_ui->initFlags();
1044 m_invert_mouse = g_settings->getBool("invert_mouse");
1045 m_first_loop_after_window_activation = true;
1047 g_client_translations->clear();
1049 // address can change if simple_singleplayer_mode
1050 if (!init(start_data.world_spec.path, start_data.address,
1051 start_data.socket_port, start_data.game_spec))
1054 if (!createClient(start_data))
1057 m_rendering_engine->initialize(client, hud);
1065 ProfilerGraph graph;
1066 RunStats stats = {};
1067 CameraOrientation cam_view_target = {};
1068 CameraOrientation cam_view = {};
1069 FpsControl draw_times;
1070 f32 dtime; // in seconds
1072 /* Clear the profiler */
1073 Profiler::GraphValues dummyvalues;
1074 g_profiler->graphGet(dummyvalues);
1078 set_light_table(g_settings->getFloat("display_gamma"));
1080 #ifdef HAVE_TOUCHSCREENGUI
1081 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1082 && client->checkPrivilege("fast");
1085 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1086 g_settings->getU16("screen_h"));
1088 while (m_rendering_engine->run()
1089 && !(*kill || g_gamecallback->shutdown_requested
1090 || (server && server->isShutdownRequested()))) {
1092 const irr::core::dimension2d<u32> ¤t_screen_size =
1093 m_rendering_engine->get_video_driver()->getScreenSize();
1094 // Verify if window size has changed and save it if it's the case
1095 // Ensure evaluating settings->getBool after verifying screensize
1096 // First condition is cheaper
1097 if (previous_screen_size != current_screen_size &&
1098 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1099 g_settings->getBool("autosave_screensize")) {
1100 g_settings->setU16("screen_w", current_screen_size.Width);
1101 g_settings->setU16("screen_h", current_screen_size.Height);
1102 previous_screen_size = current_screen_size;
1105 // Calculate dtime =
1106 // m_rendering_engine->run() from this iteration
1107 // + Sleep time until the wanted FPS are reached
1108 draw_times.limit(device, &dtime);
1110 // Prepare render data for next iteration
1112 updateStats(&stats, draw_times, dtime);
1113 updateInteractTimers(dtime);
1115 if (!checkConnection())
1117 if (!handleCallbacks())
1122 m_game_ui->clearInfoText();
1123 hud->resizeHotbar();
1126 updateProfilers(stats, draw_times, dtime);
1127 processUserInput(dtime);
1128 // Update camera before player movement to avoid camera lag of one frame
1129 updateCameraDirection(&cam_view_target, dtime);
1130 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1131 cam_view.camera_yaw) * m_cache_cam_smoothing;
1132 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1133 cam_view.camera_pitch) * m_cache_cam_smoothing;
1134 updatePlayerControl(cam_view);
1136 processClientEvents(&cam_view_target);
1138 updateCamera(dtime);
1140 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
1141 updateFrame(&graph, &stats, dtime, cam_view);
1142 updateProfilerGraphs(&graph);
1144 // Update if minimap has been disabled by the server
1145 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1147 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1154 void Game::shutdown()
1156 m_rendering_engine->finalize();
1157 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1158 if (g_settings->get("3d_mode") == "pageflip") {
1159 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1162 auto formspec = m_game_ui->getFormspecGUI();
1164 formspec->quitMenu();
1166 #ifdef HAVE_TOUCHSCREENGUI
1167 g_touchscreengui->hide();
1170 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1175 if (gui_chat_console)
1176 gui_chat_console->drop();
1182 while (g_menumgr.menuCount() > 0) {
1183 g_menumgr.m_stack.front()->setVisible(false);
1184 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1187 m_game_ui->deleteFormspec();
1189 chat_backend->addMessage(L"", L"# Disconnected.");
1190 chat_backend->addMessage(L"", L"");
1191 m_chat_log_buf.clear();
1195 while (!client->isShutdown()) {
1196 assert(texture_src != NULL);
1197 assert(shader_src != NULL);
1198 texture_src->processQueue();
1199 shader_src->processQueue();
1206 /****************************************************************************/
1207 /****************************************************************************
1209 ****************************************************************************/
1210 /****************************************************************************/
1213 const std::string &map_dir,
1214 const std::string &address,
1216 const SubgameSpec &gamespec)
1218 texture_src = createTextureSource();
1220 showOverlayMessage(N_("Loading..."), 0, 0);
1222 shader_src = createShaderSource();
1224 itemdef_manager = createItemDefManager();
1225 nodedef_manager = createNodeDefManager();
1227 eventmgr = new EventManager();
1228 quicktune = new QuicktuneShortcutter();
1230 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1231 && eventmgr && quicktune))
1237 // Create a server if not connecting to an existing one
1238 if (address.empty()) {
1239 if (!createSingleplayerServer(map_dir, gamespec, port))
1246 bool Game::initSound()
1249 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1250 infostream << "Attempting to use OpenAL audio" << std::endl;
1251 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1253 infostream << "Failed to initialize OpenAL audio" << std::endl;
1255 infostream << "Sound disabled." << std::endl;
1259 infostream << "Using dummy audio." << std::endl;
1260 sound = &dummySoundManager;
1261 sound_is_dummy = true;
1264 soundmaker = new SoundMaker(sound, nodedef_manager);
1268 soundmaker->registerReceiver(eventmgr);
1273 bool Game::createSingleplayerServer(const std::string &map_dir,
1274 const SubgameSpec &gamespec, u16 port)
1276 showOverlayMessage(N_("Creating server..."), 0, 5);
1278 std::string bind_str = g_settings->get("bind_address");
1279 Address bind_addr(0, 0, 0, 0, port);
1281 if (g_settings->getBool("ipv6_server")) {
1282 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1286 bind_addr.Resolve(bind_str.c_str());
1287 } catch (ResolveError &e) {
1288 infostream << "Resolving bind address \"" << bind_str
1289 << "\" failed: " << e.what()
1290 << " -- Listening on all addresses." << std::endl;
1293 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1294 *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
1295 bind_addr.serializeString().c_str());
1296 errorstream << *error_message << std::endl;
1300 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1301 false, nullptr, error_message);
1307 bool Game::createClient(const GameStartData &start_data)
1309 showOverlayMessage(N_("Creating client..."), 0, 10);
1311 draw_control = new MapDrawControl();
1315 bool could_connect, connect_aborted;
1316 #ifdef HAVE_TOUCHSCREENGUI
1317 if (g_touchscreengui) {
1318 g_touchscreengui->init(texture_src);
1319 g_touchscreengui->hide();
1322 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1325 if (!could_connect) {
1326 if (error_message->empty() && !connect_aborted) {
1327 // Should not happen if error messages are set properly
1328 *error_message = gettext("Connection failed for unknown reason");
1329 errorstream << *error_message << std::endl;
1334 if (!getServerContent(&connect_aborted)) {
1335 if (error_message->empty() && !connect_aborted) {
1336 // Should not happen if error messages are set properly
1337 *error_message = gettext("Connection failed for unknown reason");
1338 errorstream << *error_message << std::endl;
1343 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1344 &m_flags.force_fog_off, &runData.fog_range, client);
1345 shader_src->addShaderConstantSetterFactory(scsf);
1347 // Update cached textures, meshes and materials
1348 client->afterContentReceived();
1352 camera = new Camera(*draw_control, client, m_rendering_engine);
1353 if (client->modsLoaded())
1354 client->getScript()->on_camera_ready(camera);
1355 client->setCamera(camera);
1359 if (m_cache_enable_clouds)
1360 clouds = new Clouds(smgr, -1, time(0));
1364 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
1366 skybox = NULL; // This is used/set later on in the main run loop
1368 /* Pre-calculated values
1370 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1372 v2u32 size = t->getOriginalSize();
1373 crack_animation_length = size.Y / size.X;
1375 crack_animation_length = 5;
1381 /* Set window caption
1383 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1385 str += utf8_to_wide(g_version_hash);
1387 const wchar_t *text = nullptr;
1388 if (simple_singleplayer_mode)
1389 text = wgettext("Singleplayer");
1391 text = wgettext("Multiplayer");
1398 str += driver->getName();
1401 device->setWindowCaption(str.c_str());
1403 LocalPlayer *player = client->getEnv().getLocalPlayer();
1404 player->hurt_tilt_timer = 0;
1405 player->hurt_tilt_strength = 0;
1407 hud = new Hud(client, player, &player->inventory);
1409 mapper = client->getMinimap();
1411 if (mapper && client->modsLoaded())
1412 client->getScript()->on_minimap_ready(mapper);
1417 bool Game::initGui()
1421 // Remove stale "recent" chat messages from previous connections
1422 chat_backend->clearRecentChat();
1424 // Make sure the size of the recent messages buffer is right
1425 chat_backend->applySettings();
1427 // Chat backend and console
1428 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1429 -1, chat_backend, client, &g_menumgr);
1431 #ifdef HAVE_TOUCHSCREENGUI
1433 if (g_touchscreengui)
1434 g_touchscreengui->show();
1441 bool Game::connectToServer(const GameStartData &start_data,
1442 bool *connect_ok, bool *connection_aborted)
1444 *connect_ok = false; // Let's not be overly optimistic
1445 *connection_aborted = false;
1446 bool local_server_mode = false;
1448 showOverlayMessage(N_("Resolving address..."), 0, 15);
1450 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1453 connect_address.Resolve(start_data.address.c_str());
1455 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1456 if (connect_address.isIPv6()) {
1457 IPv6AddressBytes addr_bytes;
1458 addr_bytes.bytes[15] = 1;
1459 connect_address.setAddress(&addr_bytes);
1461 connect_address.setAddress(127, 0, 0, 1);
1463 local_server_mode = true;
1465 } catch (ResolveError &e) {
1466 *error_message = fmtgettext("Couldn't resolve address: %s", e.what());
1468 errorstream << *error_message << std::endl;
1472 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1473 *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
1474 errorstream << *error_message << std::endl;
1479 client = new Client(start_data.name.c_str(),
1480 start_data.password, start_data.address,
1481 *draw_control, texture_src, shader_src,
1482 itemdef_manager, nodedef_manager, sound, eventmgr,
1483 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get());
1484 client->migrateModStorage();
1485 } catch (const BaseException &e) {
1486 *error_message = fmtgettext("Error creating client: %s", e.what());
1487 errorstream << *error_message << std::endl;
1491 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1493 infostream << "Connecting to server at ";
1494 connect_address.print(infostream);
1495 infostream << std::endl;
1497 client->connect(connect_address,
1498 simple_singleplayer_mode || local_server_mode);
1501 Wait for server to accept connection
1507 FpsControl fps_control;
1509 f32 wait_time = 0; // in seconds
1511 fps_control.reset();
1513 while (m_rendering_engine->run()) {
1515 fps_control.limit(device, &dtime);
1517 // Update client and server
1518 client->step(dtime);
1521 server->step(dtime);
1524 if (client->getState() == LC_Init) {
1530 if (*connection_aborted)
1533 if (client->accessDenied()) {
1534 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1535 *reconnect_requested = client->reconnectRequested();
1536 errorstream << *error_message << std::endl;
1540 if (input->cancelPressed()) {
1541 *connection_aborted = true;
1542 infostream << "Connect aborted [Escape]" << std::endl;
1546 if (client->m_is_registration_confirmation_state) {
1547 if (registration_confirmation_shown) {
1548 // Keep drawing the GUI
1549 m_rendering_engine->draw_menu_scene(guienv, dtime, true);
1551 registration_confirmation_shown = true;
1552 (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
1553 &g_menumgr, client, start_data.name, start_data.password,
1554 connection_aborted, texture_src))->drop();
1558 // Only time out if we aren't waiting for the server we started
1559 if (!start_data.address.empty() && wait_time > 10) {
1560 *error_message = gettext("Connection timed out.");
1561 errorstream << *error_message << std::endl;
1566 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1569 } catch (con::PeerNotFoundException &e) {
1570 // TODO: Should something be done here? At least an info/error
1578 bool Game::getServerContent(bool *aborted)
1582 FpsControl fps_control;
1583 f32 dtime; // in seconds
1585 fps_control.reset();
1587 while (m_rendering_engine->run()) {
1589 fps_control.limit(device, &dtime);
1591 // Update client and server
1592 client->step(dtime);
1595 server->step(dtime);
1598 if (client->mediaReceived() && client->itemdefReceived() &&
1599 client->nodedefReceived()) {
1604 if (!checkConnection())
1607 if (client->getState() < LC_Init) {
1608 *error_message = gettext("Client disconnected");
1609 errorstream << *error_message << std::endl;
1613 if (input->cancelPressed()) {
1615 infostream << "Connect aborted [Escape]" << std::endl;
1622 if (!client->itemdefReceived()) {
1623 const wchar_t *text = wgettext("Item definitions...");
1625 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1628 } else if (!client->nodedefReceived()) {
1629 const wchar_t *text = wgettext("Node definitions...");
1631 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1635 std::ostringstream message;
1636 std::fixed(message);
1637 message.precision(0);
1638 float receive = client->mediaReceiveProgress() * 100;
1639 message << gettext("Media...");
1641 message << " " << receive << "%";
1642 message.precision(2);
1644 if ((USE_CURL == 0) ||
1645 (!g_settings->getBool("enable_remote_media_server"))) {
1646 float cur = client->getCurRate();
1647 std::string cur_unit = gettext("KiB/s");
1651 cur_unit = gettext("MiB/s");
1654 message << " (" << cur << ' ' << cur_unit << ")";
1657 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1658 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
1659 texture_src, dtime, progress);
1667 /****************************************************************************/
1668 /****************************************************************************
1670 ****************************************************************************/
1671 /****************************************************************************/
1673 inline void Game::updateInteractTimers(f32 dtime)
1675 if (runData.nodig_delay_timer >= 0)
1676 runData.nodig_delay_timer -= dtime;
1678 if (runData.object_hit_delay_timer >= 0)
1679 runData.object_hit_delay_timer -= dtime;
1681 runData.time_from_last_punch += dtime;
1685 /* returns false if game should exit, otherwise true
1687 inline bool Game::checkConnection()
1689 if (client->accessDenied()) {
1690 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1691 *reconnect_requested = client->reconnectRequested();
1692 errorstream << *error_message << std::endl;
1700 /* returns false if game should exit, otherwise true
1702 inline bool Game::handleCallbacks()
1704 if (g_gamecallback->disconnect_requested) {
1705 g_gamecallback->disconnect_requested = false;
1709 if (g_gamecallback->changepassword_requested) {
1710 (new GUIPasswordChange(guienv, guiroot, -1,
1711 &g_menumgr, client, texture_src))->drop();
1712 g_gamecallback->changepassword_requested = false;
1715 if (g_gamecallback->changevolume_requested) {
1716 (new GUIVolumeChange(guienv, guiroot, -1,
1717 &g_menumgr, texture_src))->drop();
1718 g_gamecallback->changevolume_requested = false;
1721 if (g_gamecallback->keyconfig_requested) {
1722 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1723 &g_menumgr, texture_src))->drop();
1724 g_gamecallback->keyconfig_requested = false;
1727 if (g_gamecallback->keyconfig_changed) {
1728 input->keycache.populate(); // update the cache with new settings
1729 g_gamecallback->keyconfig_changed = false;
1736 void Game::processQueues()
1738 texture_src->processQueue();
1739 itemdef_manager->processQueue(client);
1740 shader_src->processQueue();
1743 void Game::updateDebugState()
1745 LocalPlayer *player = client->getEnv().getLocalPlayer();
1746 bool has_debug = client->checkPrivilege("debug");
1747 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
1749 if (m_game_ui->m_flags.show_basic_debug) {
1750 if (!has_basic_debug)
1751 m_game_ui->m_flags.show_basic_debug = false;
1752 } else if (m_game_ui->m_flags.show_minimal_debug) {
1753 if (has_basic_debug)
1754 m_game_ui->m_flags.show_basic_debug = true;
1756 if (!has_basic_debug)
1757 hud->disableBlockBounds();
1759 draw_control->show_wireframe = false;
1762 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1765 float profiler_print_interval =
1766 g_settings->getFloat("profiler_print_interval");
1767 bool print_to_log = true;
1769 if (profiler_print_interval == 0) {
1770 print_to_log = false;
1771 profiler_print_interval = 3;
1774 if (profiler_interval.step(dtime, profiler_print_interval)) {
1776 infostream << "Profiler:" << std::endl;
1777 g_profiler->print(infostream);
1780 m_game_ui->updateProfiler();
1781 g_profiler->clear();
1784 // Update update graphs
1785 g_profiler->graphAdd("Time non-rendering [us]",
1786 draw_times.busy_time - stats.drawtime);
1788 g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
1789 g_profiler->graphAdd("FPS", 1.0f / dtime);
1792 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1799 /* Time average and jitter calculation
1801 jp = &stats->dtime_jitter;
1802 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1804 jitter = dtime - jp->avg;
1806 if (jitter > jp->max)
1809 jp->counter += dtime;
1811 if (jp->counter > 0.0) {
1813 jp->max_sample = jp->max;
1814 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1818 /* Busytime average and jitter calculation
1820 jp = &stats->busy_time_jitter;
1821 jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
1823 jitter = draw_times.getBusyMs() - jp->avg;
1825 if (jitter > jp->max)
1827 if (jitter < jp->min)
1830 jp->counter += dtime;
1832 if (jp->counter > 0.0) {
1834 jp->max_sample = jp->max;
1835 jp->min_sample = jp->min;
1843 /****************************************************************************
1845 ****************************************************************************/
1847 void Game::processUserInput(f32 dtime)
1849 // Reset input if window not active or some menu is active
1850 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1852 #ifdef HAVE_TOUCHSCREENGUI
1853 g_touchscreengui->hide();
1856 #ifdef HAVE_TOUCHSCREENGUI
1857 else if (g_touchscreengui) {
1858 /* on touchscreengui step may generate own input events which ain't
1859 * what we want in case we just did clear them */
1860 g_touchscreengui->show();
1861 g_touchscreengui->step(dtime);
1865 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1866 gui_chat_console->closeConsoleAtOnce();
1869 // Input handler step() (used by the random input generator)
1873 auto formspec = m_game_ui->getFormspecGUI();
1875 formspec->getAndroidUIInput();
1877 handleAndroidChatInput();
1880 // Increase timer for double tap of "keymap_jump"
1881 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1882 runData.jump_timer += dtime;
1885 processItemSelection(&runData.new_playeritem);
1889 void Game::processKeyInput()
1891 if (wasKeyDown(KeyType::DROP)) {
1892 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1893 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1894 toggleAutoforward();
1895 } else if (wasKeyDown(KeyType::BACKWARD)) {
1896 if (g_settings->getBool("continuous_forward"))
1897 toggleAutoforward();
1898 } else if (wasKeyDown(KeyType::INVENTORY)) {
1900 } else if (input->cancelPressed()) {
1902 m_android_chat_open = false;
1904 if (!gui_chat_console->isOpenInhibited()) {
1907 } else if (wasKeyDown(KeyType::CHAT)) {
1908 openConsole(0.2, L"");
1909 } else if (wasKeyDown(KeyType::CMD)) {
1910 openConsole(0.2, L"/");
1911 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1912 if (client->modsLoaded())
1913 openConsole(0.2, L".");
1915 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1916 } else if (wasKeyDown(KeyType::CONSOLE)) {
1917 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1918 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1920 } else if (wasKeyDown(KeyType::JUMP)) {
1921 toggleFreeMoveAlt();
1922 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1924 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1926 } else if (wasKeyDown(KeyType::NOCLIP)) {
1929 } else if (wasKeyDown(KeyType::MUTE)) {
1930 if (g_settings->getBool("enable_sound")) {
1931 bool new_mute_sound = !g_settings->getBool("mute_sound");
1932 g_settings->setBool("mute_sound", new_mute_sound);
1934 m_game_ui->showTranslatedStatusText("Sound muted");
1936 m_game_ui->showTranslatedStatusText("Sound unmuted");
1938 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1940 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1941 if (g_settings->getBool("enable_sound")) {
1942 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1943 g_settings->setFloat("sound_volume", new_volume);
1944 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1945 m_game_ui->showStatusText(msg);
1947 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1949 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1950 if (g_settings->getBool("enable_sound")) {
1951 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1952 g_settings->setFloat("sound_volume", new_volume);
1953 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1954 m_game_ui->showStatusText(msg);
1956 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1959 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1960 || wasKeyDown(KeyType::DEC_VOLUME)) {
1961 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1963 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1965 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1966 client->makeScreenshot();
1967 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
1968 toggleBlockBounds();
1969 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1970 m_game_ui->toggleHud();
1971 } else if (wasKeyDown(KeyType::MINIMAP)) {
1972 toggleMinimap(isKeyDown(KeyType::SNEAK));
1973 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1974 m_game_ui->toggleChat();
1975 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1977 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1978 toggleUpdateCamera();
1979 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1981 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1982 m_game_ui->toggleProfiler();
1983 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1984 increaseViewRange();
1985 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1986 decreaseViewRange();
1987 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1988 toggleFullViewRange();
1989 } else if (wasKeyDown(KeyType::ZOOM)) {
1991 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1993 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1995 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1997 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
2001 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
2002 runData.reset_jump_timer = false;
2003 runData.jump_timer = 0.0f;
2006 if (quicktune->hasMessage()) {
2007 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2011 void Game::processItemSelection(u16 *new_playeritem)
2013 LocalPlayer *player = client->getEnv().getLocalPlayer();
2015 /* Item selection using mouse wheel
2017 *new_playeritem = player->getWieldIndex();
2019 s32 wheel = input->getMouseWheel();
2020 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2021 player->hud_hotbar_itemcount - 1);
2025 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2028 if (wasKeyDown(KeyType::HOTBAR_PREV))
2032 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2034 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2037 /* Item selection using hotbar slot keys
2039 for (u16 i = 0; i <= max_item; i++) {
2040 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2041 *new_playeritem = i;
2048 void Game::dropSelectedItem(bool single_item)
2050 IDropAction *a = new IDropAction();
2051 a->count = single_item ? 1 : 0;
2052 a->from_inv.setCurrentPlayer();
2053 a->from_list = "main";
2054 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2055 client->inventoryAction(a);
2059 void Game::openInventory()
2062 * Don't permit to open inventory is CAO or player doesn't exists.
2063 * This prevent showing an empty inventory at player load
2066 LocalPlayer *player = client->getEnv().getLocalPlayer();
2067 if (!player || !player->getCAO())
2070 infostream << "Game: Launching inventory" << std::endl;
2072 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2074 InventoryLocation inventoryloc;
2075 inventoryloc.setCurrentPlayer();
2077 if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2082 if (fs_src->getForm().empty()) {
2087 TextDest *txt_dst = new TextDestPlayerInventory(client);
2088 auto *&formspec = m_game_ui->updateFormspec("");
2089 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2090 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2092 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2096 void Game::openConsole(float scale, const wchar_t *line)
2098 assert(scale > 0.0f && scale <= 1.0f);
2101 porting::showInputDialog(gettext("ok"), "", "", 2);
2102 m_android_chat_open = true;
2104 if (gui_chat_console->isOpenInhibited())
2106 gui_chat_console->openConsole(scale);
2108 gui_chat_console->setCloseOnEnter(true);
2109 gui_chat_console->replaceAndAddToHistory(line);
2115 void Game::handleAndroidChatInput()
2117 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2118 std::string text = porting::getInputDialogValue();
2119 client->typeChatMessage(utf8_to_wide(text));
2120 m_android_chat_open = false;
2126 void Game::toggleFreeMove()
2128 bool free_move = !g_settings->getBool("free_move");
2129 g_settings->set("free_move", bool_to_cstr(free_move));
2132 if (client->checkPrivilege("fly")) {
2133 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2135 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2138 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2142 void Game::toggleFreeMoveAlt()
2144 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2147 runData.reset_jump_timer = true;
2151 void Game::togglePitchMove()
2153 bool pitch_move = !g_settings->getBool("pitch_move");
2154 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2157 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2159 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2164 void Game::toggleFast()
2166 bool fast_move = !g_settings->getBool("fast_move");
2167 bool has_fast_privs = client->checkPrivilege("fast");
2168 g_settings->set("fast_move", bool_to_cstr(fast_move));
2171 if (has_fast_privs) {
2172 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2174 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2177 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2180 #ifdef HAVE_TOUCHSCREENGUI
2181 m_cache_hold_aux1 = fast_move && has_fast_privs;
2186 void Game::toggleNoClip()
2188 bool noclip = !g_settings->getBool("noclip");
2189 g_settings->set("noclip", bool_to_cstr(noclip));
2192 if (client->checkPrivilege("noclip")) {
2193 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2195 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2198 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2202 void Game::toggleCinematic()
2204 bool cinematic = !g_settings->getBool("cinematic");
2205 g_settings->set("cinematic", bool_to_cstr(cinematic));
2208 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2210 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2213 void Game::toggleBlockBounds()
2215 LocalPlayer *player = client->getEnv().getLocalPlayer();
2216 if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
2217 m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
2220 enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
2222 case Hud::BLOCK_BOUNDS_OFF:
2223 m_game_ui->showTranslatedStatusText("Block bounds hidden");
2225 case Hud::BLOCK_BOUNDS_CURRENT:
2226 m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
2228 case Hud::BLOCK_BOUNDS_NEAR:
2229 m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
2231 case Hud::BLOCK_BOUNDS_MAX:
2232 m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
2239 // Autoforward by toggling continuous forward.
2240 void Game::toggleAutoforward()
2242 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2243 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2245 if (autorun_enabled)
2246 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2248 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2251 void Game::toggleMinimap(bool shift_pressed)
2253 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2257 mapper->toggleMinimapShape();
2261 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2263 // Not so satisying code to keep compatibility with old fixed mode system
2265 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2267 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2268 m_game_ui->m_flags.show_minimap = false;
2271 // If radar is disabled, try to find a non radar mode or fall back to 0
2272 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2273 while (mapper->getModeIndex() &&
2274 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2277 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2281 // End of 'not so satifying code'
2282 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2283 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2284 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2286 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2289 void Game::toggleFog()
2291 bool fog_enabled = g_settings->getBool("enable_fog");
2292 g_settings->setBool("enable_fog", !fog_enabled);
2294 m_game_ui->showTranslatedStatusText("Fog disabled");
2296 m_game_ui->showTranslatedStatusText("Fog enabled");
2300 void Game::toggleDebug()
2302 LocalPlayer *player = client->getEnv().getLocalPlayer();
2303 bool has_debug = client->checkPrivilege("debug");
2304 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
2305 // Initial: No debug info
2306 // 1x toggle: Debug text
2307 // 2x toggle: Debug text with profiler graph
2308 // 3x toggle: Debug text and wireframe (needs "debug" priv)
2309 // Next toggle: Back to initial
2311 // The debug text can be in 2 modes: minimal and basic.
2312 // * Minimal: Only technical client info that not gameplay-relevant
2313 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
2314 // Basic mode is used when player has the debug HUD flag set,
2315 // otherwise the Minimal mode is used.
2316 if (!m_game_ui->m_flags.show_minimal_debug) {
2317 m_game_ui->m_flags.show_minimal_debug = true;
2318 if (has_basic_debug)
2319 m_game_ui->m_flags.show_basic_debug = true;
2320 m_game_ui->m_flags.show_profiler_graph = false;
2321 draw_control->show_wireframe = false;
2322 m_game_ui->showTranslatedStatusText("Debug info shown");
2323 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2324 if (has_basic_debug)
2325 m_game_ui->m_flags.show_basic_debug = true;
2326 m_game_ui->m_flags.show_profiler_graph = true;
2327 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2328 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2329 if (has_basic_debug)
2330 m_game_ui->m_flags.show_basic_debug = true;
2331 m_game_ui->m_flags.show_profiler_graph = false;
2332 draw_control->show_wireframe = true;
2333 m_game_ui->showTranslatedStatusText("Wireframe shown");
2335 m_game_ui->m_flags.show_minimal_debug = false;
2336 m_game_ui->m_flags.show_basic_debug = false;
2337 m_game_ui->m_flags.show_profiler_graph = false;
2338 draw_control->show_wireframe = false;
2340 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2342 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2348 void Game::toggleUpdateCamera()
2350 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2351 if (m_flags.disable_camera_update)
2352 m_game_ui->showTranslatedStatusText("Camera update disabled");
2354 m_game_ui->showTranslatedStatusText("Camera update enabled");
2358 void Game::increaseViewRange()
2360 s16 range = g_settings->getS16("viewing_range");
2361 s16 range_new = range + 10;
2363 if (range_new > 4000) {
2365 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
2366 m_game_ui->showStatusText(msg);
2368 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2369 m_game_ui->showStatusText(msg);
2371 g_settings->set("viewing_range", itos(range_new));
2375 void Game::decreaseViewRange()
2377 s16 range = g_settings->getS16("viewing_range");
2378 s16 range_new = range - 10;
2380 if (range_new < 20) {
2382 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
2383 m_game_ui->showStatusText(msg);
2385 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2386 m_game_ui->showStatusText(msg);
2388 g_settings->set("viewing_range", itos(range_new));
2392 void Game::toggleFullViewRange()
2394 draw_control->range_all = !draw_control->range_all;
2395 if (draw_control->range_all)
2396 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2398 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2402 void Game::checkZoomEnabled()
2404 LocalPlayer *player = client->getEnv().getLocalPlayer();
2405 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2406 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2409 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2411 if ((device->isWindowActive() && device->isWindowFocused()
2412 && !isMenuActive()) || input->isRandom()) {
2415 if (!input->isRandom()) {
2416 // Mac OSX gets upset if this is set every frame
2417 if (device->getCursorControl()->isVisible())
2418 device->getCursorControl()->setVisible(false);
2422 if (m_first_loop_after_window_activation) {
2423 m_first_loop_after_window_activation = false;
2425 input->setMousePos(driver->getScreenSize().Width / 2,
2426 driver->getScreenSize().Height / 2);
2428 updateCameraOrientation(cam, dtime);
2434 // Mac OSX gets upset if this is set every frame
2435 if (!device->getCursorControl()->isVisible())
2436 device->getCursorControl()->setVisible(true);
2439 m_first_loop_after_window_activation = true;
2444 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2445 // responsiveness independently of FOV.
2446 f32 Game::getSensitivityScaleFactor() const
2448 f32 fov_y = client->getCamera()->getFovY();
2450 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2451 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2453 return tan(fov_y / 2.0f) * 1.3763818698f;
2456 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2458 #ifdef HAVE_TOUCHSCREENGUI
2459 if (g_touchscreengui) {
2460 cam->camera_yaw += g_touchscreengui->getYawChange();
2461 cam->camera_pitch = g_touchscreengui->getPitch();
2464 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2465 v2s32 dist = input->getMousePos() - center;
2467 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2471 f32 sens_scale = getSensitivityScaleFactor();
2472 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2473 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2475 if (dist.X != 0 || dist.Y != 0)
2476 input->setMousePos(center.X, center.Y);
2477 #ifdef HAVE_TOUCHSCREENGUI
2481 if (m_cache_enable_joysticks) {
2482 f32 sens_scale = getSensitivityScaleFactor();
2483 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
2484 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2485 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2488 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2492 void Game::updatePlayerControl(const CameraOrientation &cam)
2494 LocalPlayer *player = client->getEnv().getLocalPlayer();
2496 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2498 PlayerControl control(
2499 isKeyDown(KeyType::FORWARD),
2500 isKeyDown(KeyType::BACKWARD),
2501 isKeyDown(KeyType::LEFT),
2502 isKeyDown(KeyType::RIGHT),
2503 isKeyDown(KeyType::JUMP) || player->getAutojump(),
2504 isKeyDown(KeyType::AUX1),
2505 isKeyDown(KeyType::SNEAK),
2506 isKeyDown(KeyType::ZOOM),
2507 isKeyDown(KeyType::DIG),
2508 isKeyDown(KeyType::PLACE),
2511 input->getMovementSpeed(),
2512 input->getMovementDirection()
2515 // autoforward if set: move towards pointed position at maximum speed
2516 if (player->getPlayerSettings().continuous_forward &&
2517 client->activeObjectsReceived() && !player->isDead()) {
2518 control.movement_speed = 1.0f;
2519 control.movement_direction = 0.0f;
2522 #ifdef HAVE_TOUCHSCREENGUI
2523 /* For touch, simulate holding down AUX1 (fast move) if the user has
2524 * the fast_move setting toggled on. If there is an aux1 key defined for
2525 * touch then its meaning is inverted (i.e. holding aux1 means walk and
2528 if (m_cache_hold_aux1) {
2529 control.aux1 = control.aux1 ^ true;
2533 client->setPlayerControl(control);
2539 inline void Game::step(f32 *dtime)
2541 bool can_be_and_is_paused =
2542 (simple_singleplayer_mode && g_menumgr.pausesGame());
2544 if (can_be_and_is_paused) { // This is for a singleplayer server
2545 *dtime = 0; // No time passes
2547 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
2551 server->step(*dtime);
2553 client->step(*dtime);
2557 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2560 for (auto &&child: node->getChildren())
2561 pauseNodeAnimation(paused, child);
2562 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2564 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2565 float speed = animated_node->getAnimationSpeed();
2568 paused.push_back({grab(animated_node), speed});
2569 animated_node->setAnimationSpeed(0.0f);
2572 void Game::pauseAnimation()
2574 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2577 void Game::resumeAnimation()
2579 for (auto &&pair: paused_animated_nodes)
2580 pair.first->setAnimationSpeed(pair.second);
2581 paused_animated_nodes.clear();
2584 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2585 {&Game::handleClientEvent_None},
2586 {&Game::handleClientEvent_PlayerDamage},
2587 {&Game::handleClientEvent_PlayerForceMove},
2588 {&Game::handleClientEvent_Deathscreen},
2589 {&Game::handleClientEvent_ShowFormSpec},
2590 {&Game::handleClientEvent_ShowLocalFormSpec},
2591 {&Game::handleClientEvent_HandleParticleEvent},
2592 {&Game::handleClientEvent_HandleParticleEvent},
2593 {&Game::handleClientEvent_HandleParticleEvent},
2594 {&Game::handleClientEvent_HudAdd},
2595 {&Game::handleClientEvent_HudRemove},
2596 {&Game::handleClientEvent_HudChange},
2597 {&Game::handleClientEvent_SetSky},
2598 {&Game::handleClientEvent_SetSun},
2599 {&Game::handleClientEvent_SetMoon},
2600 {&Game::handleClientEvent_SetStars},
2601 {&Game::handleClientEvent_OverrideDayNigthRatio},
2602 {&Game::handleClientEvent_CloudParams},
2605 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2607 FATAL_ERROR("ClientEvent type None received");
2610 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2612 if (client->modsLoaded())
2613 client->getScript()->on_damage_taken(event->player_damage.amount);
2615 // Damage flash and hurt tilt are not used at death
2616 if (client->getHP() > 0) {
2617 LocalPlayer *player = client->getEnv().getLocalPlayer();
2619 f32 hp_max = player->getCAO() ?
2620 player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2621 f32 damage_ratio = event->player_damage.amount / hp_max;
2623 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2624 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2626 player->hurt_tilt_timer = 1.5f;
2627 player->hurt_tilt_strength =
2628 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2631 // Play damage sound
2632 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2635 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2637 cam->camera_yaw = event->player_force_move.yaw;
2638 cam->camera_pitch = event->player_force_move.pitch;
2641 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2643 // If client scripting is enabled, deathscreen is handled by CSM code in
2644 // builtin/client/init.lua
2645 if (client->modsLoaded())
2646 client->getScript()->on_death();
2648 showDeathFormspec();
2650 /* Handle visualization */
2651 LocalPlayer *player = client->getEnv().getLocalPlayer();
2652 runData.damage_flash = 0;
2653 player->hurt_tilt_timer = 0;
2654 player->hurt_tilt_strength = 0;
2657 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2659 if (event->show_formspec.formspec->empty()) {
2660 auto formspec = m_game_ui->getFormspecGUI();
2661 if (formspec && (event->show_formspec.formname->empty()
2662 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2663 formspec->quitMenu();
2666 FormspecFormSource *fs_src =
2667 new FormspecFormSource(*(event->show_formspec.formspec));
2668 TextDestPlayerInventory *txt_dst =
2669 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2671 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2672 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2673 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2676 delete event->show_formspec.formspec;
2677 delete event->show_formspec.formname;
2680 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2682 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2683 LocalFormspecHandler *txt_dst =
2684 new LocalFormspecHandler(*event->show_formspec.formname, client);
2685 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2686 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2688 delete event->show_formspec.formspec;
2689 delete event->show_formspec.formname;
2692 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2693 CameraOrientation *cam)
2695 LocalPlayer *player = client->getEnv().getLocalPlayer();
2696 client->getParticleManager()->handleParticleEvent(event, client, player);
2699 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2701 LocalPlayer *player = client->getEnv().getLocalPlayer();
2703 u32 server_id = event->hudadd->server_id;
2704 // ignore if we already have a HUD with that ID
2705 auto i = m_hud_server_to_client.find(server_id);
2706 if (i != m_hud_server_to_client.end()) {
2707 delete event->hudadd;
2711 HudElement *e = new HudElement;
2712 e->type = static_cast<HudElementType>(event->hudadd->type);
2713 e->pos = event->hudadd->pos;
2714 e->name = event->hudadd->name;
2715 e->scale = event->hudadd->scale;
2716 e->text = event->hudadd->text;
2717 e->number = event->hudadd->number;
2718 e->item = event->hudadd->item;
2719 e->dir = event->hudadd->dir;
2720 e->align = event->hudadd->align;
2721 e->offset = event->hudadd->offset;
2722 e->world_pos = event->hudadd->world_pos;
2723 e->size = event->hudadd->size;
2724 e->z_index = event->hudadd->z_index;
2725 e->text2 = event->hudadd->text2;
2726 e->style = event->hudadd->style;
2727 m_hud_server_to_client[server_id] = player->addHud(e);
2729 delete event->hudadd;
2732 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2734 LocalPlayer *player = client->getEnv().getLocalPlayer();
2736 auto i = m_hud_server_to_client.find(event->hudrm.id);
2737 if (i != m_hud_server_to_client.end()) {
2738 HudElement *e = player->removeHud(i->second);
2740 m_hud_server_to_client.erase(i);
2745 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2747 LocalPlayer *player = client->getEnv().getLocalPlayer();
2749 HudElement *e = nullptr;
2751 auto i = m_hud_server_to_client.find(event->hudchange->id);
2752 if (i != m_hud_server_to_client.end()) {
2753 e = player->getHud(i->second);
2757 delete event->hudchange;
2761 #define CASE_SET(statval, prop, dataprop) \
2763 e->prop = event->hudchange->dataprop; \
2766 switch (event->hudchange->stat) {
2767 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2769 CASE_SET(HUD_STAT_NAME, name, sdata);
2771 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2773 CASE_SET(HUD_STAT_TEXT, text, sdata);
2775 CASE_SET(HUD_STAT_NUMBER, number, data);
2777 CASE_SET(HUD_STAT_ITEM, item, data);
2779 CASE_SET(HUD_STAT_DIR, dir, data);
2781 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2783 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2785 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2787 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2789 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2791 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2793 CASE_SET(HUD_STAT_STYLE, style, data);
2798 delete event->hudchange;
2801 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2803 sky->setVisible(false);
2804 // Whether clouds are visible in front of a custom skybox.
2805 sky->setCloudsEnabled(event->set_sky->clouds);
2811 // Clear the old textures out in case we switch rendering type.
2812 sky->clearSkyboxTextures();
2813 // Handle according to type
2814 if (event->set_sky->type == "regular") {
2815 // Shows the mesh skybox
2816 sky->setVisible(true);
2817 // Update mesh based skybox colours if applicable.
2818 sky->setSkyColors(event->set_sky->sky_color);
2819 sky->setHorizonTint(
2820 event->set_sky->fog_sun_tint,
2821 event->set_sky->fog_moon_tint,
2822 event->set_sky->fog_tint_type
2824 } else if (event->set_sky->type == "skybox" &&
2825 event->set_sky->textures.size() == 6) {
2826 // Disable the dyanmic mesh skybox:
2827 sky->setVisible(false);
2829 sky->setFallbackBgColor(event->set_sky->bgcolor);
2830 // Set sunrise and sunset fog tinting:
2831 sky->setHorizonTint(
2832 event->set_sky->fog_sun_tint,
2833 event->set_sky->fog_moon_tint,
2834 event->set_sky->fog_tint_type
2836 // Add textures to skybox.
2837 for (int i = 0; i < 6; i++)
2838 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2840 // Handle everything else as plain color.
2841 if (event->set_sky->type != "plain")
2842 infostream << "Unknown sky type: "
2843 << (event->set_sky->type) << std::endl;
2844 sky->setVisible(false);
2845 sky->setFallbackBgColor(event->set_sky->bgcolor);
2846 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2847 sky->setHorizonTint(
2848 event->set_sky->bgcolor,
2849 event->set_sky->bgcolor,
2854 delete event->set_sky;
2857 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2859 sky->setSunVisible(event->sun_params->visible);
2860 sky->setSunTexture(event->sun_params->texture,
2861 event->sun_params->tonemap, texture_src);
2862 sky->setSunScale(event->sun_params->scale);
2863 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2864 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2865 delete event->sun_params;
2868 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2870 sky->setMoonVisible(event->moon_params->visible);
2871 sky->setMoonTexture(event->moon_params->texture,
2872 event->moon_params->tonemap, texture_src);
2873 sky->setMoonScale(event->moon_params->scale);
2874 delete event->moon_params;
2877 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2879 sky->setStarsVisible(event->star_params->visible);
2880 sky->setStarCount(event->star_params->count);
2881 sky->setStarColor(event->star_params->starcolor);
2882 sky->setStarScale(event->star_params->scale);
2883 delete event->star_params;
2886 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2887 CameraOrientation *cam)
2889 client->getEnv().setDayNightRatioOverride(
2890 event->override_day_night_ratio.do_override,
2891 event->override_day_night_ratio.ratio_f * 1000.0f);
2894 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2899 clouds->setDensity(event->cloud_params.density);
2900 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2901 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2902 clouds->setHeight(event->cloud_params.height);
2903 clouds->setThickness(event->cloud_params.thickness);
2904 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2907 void Game::processClientEvents(CameraOrientation *cam)
2909 while (client->hasClientEvents()) {
2910 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2911 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2912 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2913 (this->*evHandler.handler)(event.get(), cam);
2917 void Game::updateChat(f32 dtime)
2919 // Get new messages from error log buffer
2920 while (!m_chat_log_buf.empty())
2921 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2923 // Get new messages from client
2924 std::wstring message;
2925 while (client->getChatMessage(message)) {
2926 chat_backend->addUnparsedMessage(message);
2929 // Remove old messages
2930 chat_backend->step(dtime);
2932 // Display all messages in a static text element
2933 auto &buf = chat_backend->getRecentBuffer();
2934 if (buf.getLinesModified()) {
2935 buf.resetLinesModified();
2936 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
2939 // Make sure that the size is still correct
2940 m_game_ui->updateChatSize();
2943 void Game::updateCamera(f32 dtime)
2945 LocalPlayer *player = client->getEnv().getLocalPlayer();
2948 For interaction purposes, get info about the held item
2950 - Is it a usable item?
2951 - Can it point to liquids?
2953 ItemStack playeritem;
2955 ItemStack selected, hand;
2956 playeritem = player->getWieldedItem(&selected, &hand);
2959 ToolCapabilities playeritem_toolcap =
2960 playeritem.getToolCapabilities(itemdef_manager);
2962 v3s16 old_camera_offset = camera->getOffset();
2964 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2965 GenericCAO *playercao = player->getCAO();
2967 // If playercao not loaded, don't change camera
2971 camera->toggleCameraMode();
2973 // Make the player visible depending on camera mode.
2974 playercao->updateMeshCulling();
2975 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2978 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2979 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2981 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2982 camera->update(player, dtime, tool_reload_ratio);
2983 camera->step(dtime);
2985 v3f camera_position = camera->getPosition();
2986 v3f camera_direction = camera->getDirection();
2987 f32 camera_fov = camera->getFovMax();
2988 v3s16 camera_offset = camera->getOffset();
2990 m_camera_offset_changed = (camera_offset != old_camera_offset);
2992 if (!m_flags.disable_camera_update) {
2993 client->getEnv().getClientMap().updateCamera(camera_position,
2994 camera_direction, camera_fov, camera_offset);
2996 if (m_camera_offset_changed) {
2997 client->updateCameraOffset(camera_offset);
2998 client->getEnv().updateCameraOffset(camera_offset);
3001 clouds->updateCameraOffset(camera_offset);
3007 void Game::updateSound(f32 dtime)
3009 // Update sound listener
3010 v3s16 camera_offset = camera->getOffset();
3011 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3012 v3f(0, 0, 0), // velocity
3013 camera->getDirection(),
3014 camera->getCameraNode()->getUpVector());
3016 bool mute_sound = g_settings->getBool("mute_sound");
3018 sound->setListenerGain(0.0f);
3020 // Check if volume is in the proper range, else fix it.
3021 float old_volume = g_settings->getFloat("sound_volume");
3022 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3023 sound->setListenerGain(new_volume);
3025 if (old_volume != new_volume) {
3026 g_settings->setFloat("sound_volume", new_volume);
3030 LocalPlayer *player = client->getEnv().getLocalPlayer();
3032 // Tell the sound maker whether to make footstep sounds
3033 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3035 // Update sound maker
3036 if (player->makes_footstep_sound)
3037 soundmaker->step(dtime);
3039 ClientMap &map = client->getEnv().getClientMap();
3040 MapNode n = map.getNode(player->getFootstepNodePos());
3041 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3045 void Game::processPlayerInteraction(f32 dtime, bool show_hud)
3047 LocalPlayer *player = client->getEnv().getLocalPlayer();
3049 const v3f camera_direction = camera->getDirection();
3050 const v3s16 camera_offset = camera->getOffset();
3053 Calculate what block is the crosshair pointing to
3056 ItemStack selected_item, hand_item;
3057 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3059 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3060 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3062 core::line3d<f32> shootline;
3064 switch (camera->getCameraMode()) {
3065 case CAMERA_MODE_FIRST:
3066 // Shoot from camera position, with bobbing
3067 shootline.start = camera->getPosition();
3069 case CAMERA_MODE_THIRD:
3070 // Shoot from player head, no bobbing
3071 shootline.start = camera->getHeadPosition();
3073 case CAMERA_MODE_THIRD_FRONT:
3074 shootline.start = camera->getHeadPosition();
3075 // prevent player pointing anything in front-view
3079 shootline.end = shootline.start + camera_direction * BS * d;
3081 #ifdef HAVE_TOUCHSCREENGUI
3083 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3084 shootline = g_touchscreengui->getShootline();
3085 // Scale shootline to the acual distance the player can reach
3086 shootline.end = shootline.start
3087 + shootline.getVector().normalize() * BS * d;
3088 shootline.start += intToFloat(camera_offset, BS);
3089 shootline.end += intToFloat(camera_offset, BS);
3094 PointedThing pointed = updatePointedThing(shootline,
3095 selected_def.liquids_pointable,
3096 !runData.btn_down_for_dig,
3099 if (pointed != runData.pointed_old) {
3100 infostream << "Pointing at " << pointed.dump() << std::endl;
3101 hud->updateSelectionMesh(camera_offset);
3104 // Allow digging again if button is not pressed
3105 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3106 runData.digging_blocked = false;
3110 - releasing dig button
3111 - pointing away from node
3113 if (runData.digging) {
3114 if (wasKeyReleased(KeyType::DIG)) {
3115 infostream << "Dig button released (stopped digging)" << std::endl;
3116 runData.digging = false;
3117 } else if (pointed != runData.pointed_old) {
3118 if (pointed.type == POINTEDTHING_NODE
3119 && runData.pointed_old.type == POINTEDTHING_NODE
3120 && pointed.node_undersurface
3121 == runData.pointed_old.node_undersurface) {
3122 // Still pointing to the same node, but a different face.
3125 infostream << "Pointing away from node (stopped digging)" << std::endl;
3126 runData.digging = false;
3127 hud->updateSelectionMesh(camera_offset);
3131 if (!runData.digging) {
3132 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3133 client->setCrack(-1, v3s16(0, 0, 0));
3134 runData.dig_time = 0.0;
3136 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3137 // Remove e.g. torches faster when clicking instead of holding dig button
3138 runData.nodig_delay_timer = 0;
3139 runData.dig_instantly = false;
3142 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3143 runData.btn_down_for_dig = false;
3145 runData.punching = false;
3147 soundmaker->m_player_leftpunch_sound.name = "";
3149 // Prepare for repeating, unless we're not supposed to
3150 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3151 runData.repeat_place_timer += dtime;
3153 runData.repeat_place_timer = 0;
3155 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3156 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3157 !client->getScript()->on_item_use(selected_item, pointed)))
3158 client->interact(INTERACT_USE, pointed);
3159 } else if (pointed.type == POINTEDTHING_NODE) {
3160 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3161 } else if (pointed.type == POINTEDTHING_OBJECT) {
3162 v3f player_position = player->getPosition();
3163 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
3164 handlePointingAtObject(pointed, tool_item, player_position,
3165 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
3166 } else if (isKeyDown(KeyType::DIG)) {
3167 // When button is held down in air, show continuous animation
3168 runData.punching = true;
3169 // Run callback even though item is not usable
3170 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3171 client->getScript()->on_item_use(selected_item, pointed);
3172 } else if (wasKeyPressed(KeyType::PLACE)) {
3173 handlePointingAtNothing(selected_item);
3176 runData.pointed_old = pointed;
3178 if (runData.punching || wasKeyPressed(KeyType::DIG))
3179 camera->setDigging(0); // dig animation
3181 input->clearWasKeyPressed();
3182 input->clearWasKeyReleased();
3183 // Ensure DIG & PLACE are marked as handled
3184 wasKeyDown(KeyType::DIG);
3185 wasKeyDown(KeyType::PLACE);
3187 input->joystick.clearWasKeyPressed(KeyType::DIG);
3188 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3190 input->joystick.clearWasKeyReleased(KeyType::DIG);
3191 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3195 PointedThing Game::updatePointedThing(
3196 const core::line3d<f32> &shootline,
3197 bool liquids_pointable,
3198 bool look_for_object,
3199 const v3s16 &camera_offset)
3201 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3202 selectionboxes->clear();
3203 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3204 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3205 "show_entity_selectionbox");
3207 ClientEnvironment &env = client->getEnv();
3208 ClientMap &map = env.getClientMap();
3209 const NodeDefManager *nodedef = map.getNodeDefManager();
3211 runData.selected_object = NULL;
3212 hud->pointing_at_object = false;
3214 RaycastState s(shootline, look_for_object, liquids_pointable);
3215 PointedThing result;
3216 env.continueRaycast(&s, &result);
3217 if (result.type == POINTEDTHING_OBJECT) {
3218 hud->pointing_at_object = true;
3220 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3221 aabb3f selection_box;
3222 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3223 runData.selected_object->getSelectionBox(&selection_box)) {
3224 v3f pos = runData.selected_object->getPosition();
3225 selectionboxes->push_back(aabb3f(selection_box));
3226 hud->setSelectionPos(pos, camera_offset);
3228 } else if (result.type == POINTEDTHING_NODE) {
3229 // Update selection boxes
3230 MapNode n = map.getNode(result.node_undersurface);
3231 std::vector<aabb3f> boxes;
3232 n.getSelectionBoxes(nodedef, &boxes,
3233 n.getNeighbors(result.node_undersurface, &map));
3236 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3237 i != boxes.end(); ++i) {
3239 box.MinEdge -= v3f(d, d, d);
3240 box.MaxEdge += v3f(d, d, d);
3241 selectionboxes->push_back(box);
3243 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3245 hud->setSelectedFaceNormal(v3f(
3246 result.intersection_normal.X,
3247 result.intersection_normal.Y,
3248 result.intersection_normal.Z));
3251 // Update selection mesh light level and vertex colors
3252 if (!selectionboxes->empty()) {
3253 v3f pf = hud->getSelectionPos();
3254 v3s16 p = floatToInt(pf, BS);
3256 // Get selection mesh light level
3257 MapNode n = map.getNode(p);
3258 u16 node_light = getInteriorLight(n, -1, nodedef);
3259 u16 light_level = node_light;
3261 for (const v3s16 &dir : g_6dirs) {
3262 n = map.getNode(p + dir);
3263 node_light = getInteriorLight(n, -1, nodedef);
3264 if (node_light > light_level)
3265 light_level = node_light;
3268 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3270 final_color_blend(&c, light_level, daynight_ratio);
3272 // Modify final color a bit with time
3273 u32 timer = porting::getTimeMs() % 5000;
3274 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3275 float sin_r = 0.08f * std::sin(timerf);
3276 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3277 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3278 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3279 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3280 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3282 // Set mesh final color
3283 hud->setSelectionMeshColor(c);
3289 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3291 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3292 PointedThing fauxPointed;
3293 fauxPointed.type = POINTEDTHING_NOTHING;
3294 client->interact(INTERACT_ACTIVATE, fauxPointed);
3298 void Game::handlePointingAtNode(const PointedThing &pointed,
3299 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3301 v3s16 nodepos = pointed.node_undersurface;
3302 v3s16 neighbourpos = pointed.node_abovesurface;
3305 Check information text of node
3308 ClientMap &map = client->getEnv().getClientMap();
3310 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3311 && !runData.digging_blocked
3312 && client->checkPrivilege("interact")) {
3313 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3316 // This should be done after digging handling
3317 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3320 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3321 meta->getString("infotext"))));
3323 MapNode n = map.getNode(nodepos);
3325 if (nodedef_manager->get(n).name == "unknown") {
3326 m_game_ui->setInfoText(L"Unknown node");
3330 if ((wasKeyPressed(KeyType::PLACE) ||
3331 runData.repeat_place_timer >= m_repeat_place_time) &&
3332 client->checkPrivilege("interact")) {
3333 runData.repeat_place_timer = 0;
3334 infostream << "Place button pressed while looking at ground" << std::endl;
3336 // Placing animation (always shown for feedback)
3337 camera->setDigging(1);
3339 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3341 // If the wielded item has node placement prediction,
3343 // And also set the sound and send the interact
3344 // But first check for meta formspec and rightclickable
3345 auto &def = selected_item.getDefinition(itemdef_manager);
3346 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3349 if (placed && client->modsLoaded())
3350 client->getScript()->on_placenode(pointed, def);
3354 bool Game::nodePlacement(const ItemDefinition &selected_def,
3355 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3356 const PointedThing &pointed, const NodeMetadata *meta)
3358 const auto &prediction = selected_def.node_placement_prediction;
3360 const NodeDefManager *nodedef = client->ndef();
3361 ClientMap &map = client->getEnv().getClientMap();
3363 bool is_valid_position;
3365 node = map.getNode(nodepos, &is_valid_position);
3366 if (!is_valid_position) {
3367 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3372 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3373 && !isKeyDown(KeyType::SNEAK)) {
3374 // on_rightclick callbacks are called anyway
3375 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3376 client->interact(INTERACT_PLACE, pointed);
3378 infostream << "Launching custom inventory view" << std::endl;
3380 InventoryLocation inventoryloc;
3381 inventoryloc.setNodeMeta(nodepos);
3383 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3384 &client->getEnv().getClientMap(), nodepos);
3385 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3387 auto *&formspec = m_game_ui->updateFormspec("");
3388 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3389 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3391 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3395 // on_rightclick callback
3396 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3397 !isKeyDown(KeyType::SNEAK))) {
3399 client->interact(INTERACT_PLACE, pointed);
3403 verbosestream << "Node placement prediction for "
3404 << selected_def.name << " is " << prediction << std::endl;
3405 v3s16 p = neighbourpos;
3407 // Place inside node itself if buildable_to
3408 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3409 if (is_valid_position) {
3410 if (nodedef->get(n_under).buildable_to) {
3413 node = map.getNode(p, &is_valid_position);
3414 if (is_valid_position && !nodedef->get(node).buildable_to) {
3415 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3417 client->interact(INTERACT_PLACE, pointed);
3423 // Find id of predicted node
3425 bool found = nodedef->getId(prediction, id);
3428 errorstream << "Node placement prediction failed for "
3429 << selected_def.name << " (places " << prediction
3430 << ") - Name not known" << std::endl;
3431 // Handle this as if prediction was empty
3433 client->interact(INTERACT_PLACE, pointed);
3437 const ContentFeatures &predicted_f = nodedef->get(id);
3439 // Predict param2 for facedir and wallmounted nodes
3440 // Compare core.item_place_node() for what the server does
3443 const u8 place_param2 = selected_def.place_param2;
3446 param2 = place_param2;
3447 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3448 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3449 v3s16 dir = nodepos - neighbourpos;
3451 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3452 param2 = dir.Y < 0 ? 1 : 0;
3453 } else if (abs(dir.X) > abs(dir.Z)) {
3454 param2 = dir.X < 0 ? 3 : 2;
3456 param2 = dir.Z < 0 ? 5 : 4;
3458 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3459 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3460 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3462 if (abs(dir.X) > abs(dir.Z)) {
3463 param2 = dir.X < 0 ? 3 : 1;
3465 param2 = dir.Z < 0 ? 2 : 0;
3469 // Check attachment if node is in group attached_node
3470 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3471 const static v3s16 wallmounted_dirs[8] = {
3481 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3482 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3483 pp = p + wallmounted_dirs[param2];
3485 pp = p + v3s16(0, -1, 0);
3487 if (!nodedef->get(map.getNode(pp)).walkable) {
3488 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3490 client->interact(INTERACT_PLACE, pointed);
3496 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3497 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3498 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3499 const auto &indexstr = selected_item.metadata.
3500 getString("palette_index", 0);
3501 if (!indexstr.empty()) {
3502 s32 index = mystoi(indexstr);
3503 if (predicted_f.param_type_2 == CPT2_COLOR) {
3505 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3506 // param2 = pure palette index + other
3507 param2 = (index & 0xf8) | (param2 & 0x07);
3508 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3509 // param2 = pure palette index + other
3510 param2 = (index & 0xe0) | (param2 & 0x1f);
3515 // Add node to client map
3516 MapNode n(id, 0, param2);
3519 LocalPlayer *player = client->getEnv().getLocalPlayer();
3521 // Dont place node when player would be inside new node
3522 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3523 if (!nodedef->get(n).walkable ||
3524 g_settings->getBool("enable_build_where_you_stand") ||
3525 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3526 (nodedef->get(n).walkable &&
3527 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3528 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3529 // This triggers the required mesh update too
3530 client->addNode(p, n);
3532 client->interact(INTERACT_PLACE, pointed);
3533 // A node is predicted, also play a sound
3534 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3537 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3540 } catch (const InvalidPositionException &e) {
3541 errorstream << "Node placement prediction failed for "
3542 << selected_def.name << " (places "
3543 << prediction << ") - Position not loaded" << std::endl;
3544 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3549 void Game::handlePointingAtObject(const PointedThing &pointed,
3550 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3552 std::wstring infotext = unescape_translate(
3553 utf8_to_wide(runData.selected_object->infoText()));
3556 if (!infotext.empty()) {
3559 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3562 m_game_ui->setInfoText(infotext);
3564 if (isKeyDown(KeyType::DIG)) {
3565 bool do_punch = false;
3566 bool do_punch_damage = false;
3568 if (runData.object_hit_delay_timer <= 0.0) {
3570 do_punch_damage = true;
3571 runData.object_hit_delay_timer = object_hit_delay;
3574 if (wasKeyPressed(KeyType::DIG))
3578 infostream << "Punched object" << std::endl;
3579 runData.punching = true;
3582 if (do_punch_damage) {
3583 // Report direct punch
3584 v3f objpos = runData.selected_object->getPosition();
3585 v3f dir = (objpos - player_position).normalize();
3587 bool disable_send = runData.selected_object->directReportPunch(
3588 dir, &tool_item, runData.time_from_last_punch);
3589 runData.time_from_last_punch = 0;
3592 client->interact(INTERACT_START_DIGGING, pointed);
3594 } else if (wasKeyDown(KeyType::PLACE)) {
3595 infostream << "Pressed place button while pointing at object" << std::endl;
3596 client->interact(INTERACT_PLACE, pointed); // place
3601 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3602 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3604 // See also: serverpackethandle.cpp, action == 2
3605 LocalPlayer *player = client->getEnv().getLocalPlayer();
3606 ClientMap &map = client->getEnv().getClientMap();
3607 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3609 // NOTE: Similar piece of code exists on the server side for
3611 // Get digging parameters
3612 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3613 &selected_item.getToolCapabilities(itemdef_manager),
3614 selected_item.wear);
3616 // If can't dig, try hand
3617 if (!params.diggable) {
3618 params = getDigParams(nodedef_manager->get(n).groups,
3619 &hand_item.getToolCapabilities(itemdef_manager));
3622 if (!params.diggable) {
3623 // I guess nobody will wait for this long
3624 runData.dig_time_complete = 10000000.0;
3626 runData.dig_time_complete = params.time;
3628 if (m_cache_enable_particles) {
3629 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3630 client->getParticleManager()->addNodeParticle(client,
3631 player, nodepos, n, features);
3635 if (!runData.digging) {
3636 infostream << "Started digging" << std::endl;
3637 runData.dig_instantly = runData.dig_time_complete == 0;
3638 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3640 client->interact(INTERACT_START_DIGGING, pointed);
3641 runData.digging = true;
3642 runData.btn_down_for_dig = true;
3645 if (!runData.dig_instantly) {
3646 runData.dig_index = (float)crack_animation_length
3648 / runData.dig_time_complete;
3650 // This is for e.g. torches
3651 runData.dig_index = crack_animation_length;
3654 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3656 if (sound_dig.exists() && params.diggable) {
3657 if (sound_dig.name == "__group") {
3658 if (!params.main_group.empty()) {
3659 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3660 soundmaker->m_player_leftpunch_sound.name =
3661 std::string("default_dig_") +
3665 soundmaker->m_player_leftpunch_sound = sound_dig;
3669 // Don't show cracks if not diggable
3670 if (runData.dig_time_complete >= 100000.0) {
3671 } else if (runData.dig_index < crack_animation_length) {
3672 //TimeTaker timer("client.setTempMod");
3673 //infostream<<"dig_index="<<dig_index<<std::endl;
3674 client->setCrack(runData.dig_index, nodepos);
3676 infostream << "Digging completed" << std::endl;
3677 client->setCrack(-1, v3s16(0, 0, 0));
3679 runData.dig_time = 0;
3680 runData.digging = false;
3681 // we successfully dug, now block it from repeating if we want to be safe
3682 if (g_settings->getBool("safe_dig_and_place"))
3683 runData.digging_blocked = true;
3685 runData.nodig_delay_timer =
3686 runData.dig_time_complete / (float)crack_animation_length;
3688 // We don't want a corresponding delay to very time consuming nodes
3689 // and nodes without digging time (e.g. torches) get a fixed delay.
3690 if (runData.nodig_delay_timer > 0.3)
3691 runData.nodig_delay_timer = 0.3;
3692 else if (runData.dig_instantly)
3693 runData.nodig_delay_timer = 0.15;
3695 bool is_valid_position;
3696 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3697 if (is_valid_position) {
3698 if (client->modsLoaded() &&
3699 client->getScript()->on_dignode(nodepos, wasnode)) {
3703 const ContentFeatures &f = client->ndef()->get(wasnode);
3704 if (f.node_dig_prediction == "air") {
3705 client->removeNode(nodepos);
3706 } else if (!f.node_dig_prediction.empty()) {
3708 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3710 client->addNode(nodepos, id, true);
3712 // implicit else: no prediction
3715 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3717 if (m_cache_enable_particles) {
3718 const ContentFeatures &features =
3719 client->getNodeDefManager()->get(wasnode);
3720 client->getParticleManager()->addDiggingParticles(client,
3721 player, nodepos, wasnode, features);
3725 // Send event to trigger sound
3726 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3729 if (runData.dig_time_complete < 100000.0) {
3730 runData.dig_time += dtime;
3732 runData.dig_time = 0;
3733 client->setCrack(-1, nodepos);
3736 camera->setDigging(0); // Dig animation
3739 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3740 const CameraOrientation &cam)
3742 TimeTaker tt_update("Game::updateFrame()");
3743 LocalPlayer *player = client->getEnv().getLocalPlayer();
3749 if (draw_control->range_all) {
3750 runData.fog_range = 100000 * BS;
3752 runData.fog_range = draw_control->wanted_range * BS;
3756 Calculate general brightness
3758 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3759 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3760 float direct_brightness;
3763 if (m_cache_enable_noclip && m_cache_enable_free_move) {
3764 direct_brightness = time_brightness;
3765 sunlight_seen = true;
3767 float old_brightness = sky->getBrightness();
3768 direct_brightness = client->getEnv().getClientMap()
3769 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3770 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3774 float time_of_day_smooth = runData.time_of_day_smooth;
3775 float time_of_day = client->getEnv().getTimeOfDayF();
3777 static const float maxsm = 0.05f;
3778 static const float todsm = 0.05f;
3780 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3781 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3782 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3783 time_of_day_smooth = time_of_day;
3785 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3786 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3787 + (time_of_day + 1.0) * todsm;
3789 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3790 + time_of_day * todsm;
3792 runData.time_of_day_smooth = time_of_day_smooth;
3794 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3795 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3796 player->getPitch());
3802 if (sky->getCloudsVisible()) {
3803 clouds->setVisible(true);
3804 clouds->step(dtime);
3805 // camera->getPosition is not enough for 3rd person views
3806 v3f camera_node_position = camera->getCameraNode()->getPosition();
3807 v3s16 camera_offset = camera->getOffset();
3808 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3809 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3810 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3811 clouds->update(camera_node_position,
3812 sky->getCloudColor());
3813 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3814 // if inside clouds, and fog enabled, use that as sky
3816 video::SColor clouds_dark = clouds->getColor()
3817 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3818 sky->overrideColors(clouds_dark, clouds->getColor());
3819 sky->setInClouds(true);
3820 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3821 // do not draw clouds after all
3822 clouds->setVisible(false);
3825 clouds->setVisible(false);
3832 client->getParticleManager()->step(dtime);
3838 if (m_cache_enable_fog) {
3841 video::EFT_FOG_LINEAR,
3842 runData.fog_range * m_cache_fog_start,
3843 runData.fog_range * 1.0,
3851 video::EFT_FOG_LINEAR,
3863 if (player->hurt_tilt_timer > 0.0f) {
3864 player->hurt_tilt_timer -= dtime * 6.0f;
3866 if (player->hurt_tilt_timer < 0.0f)
3867 player->hurt_tilt_strength = 0.0f;
3871 Update minimap pos and rotation
3873 if (mapper && m_game_ui->m_flags.show_hud) {
3874 mapper->setPos(floatToInt(player->getPosition(), BS));
3875 mapper->setAngle(player->getYaw());
3879 Get chat messages from client
3888 if (player->getWieldIndex() != runData.new_playeritem)
3889 client->setPlayerItem(runData.new_playeritem);
3891 if (client->updateWieldedItem()) {
3892 // Update wielded tool
3893 ItemStack selected_item, hand_item;
3894 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3895 camera->wield(tool_item);
3899 Update block draw list every 200ms or when camera direction has
3902 runData.update_draw_list_timer += dtime;
3904 float update_draw_list_delta = 0.2f;
3906 v3f camera_direction = camera->getDirection();
3907 if (runData.update_draw_list_timer >= update_draw_list_delta
3908 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3909 || m_camera_offset_changed
3910 || client->getEnv().getClientMap().needsUpdateDrawList()) {
3911 runData.update_draw_list_timer = 0;
3912 client->getEnv().getClientMap().updateDrawList();
3913 runData.update_draw_list_last_cam_dir = camera_direction;
3916 if (RenderingEngine::get_shadow_renderer()) {
3920 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3923 make sure menu is on top
3924 1. Delete formspec menu reference if menu was removed
3925 2. Else, make sure formspec menu is on top
3927 auto formspec = m_game_ui->getFormspecGUI();
3928 do { // breakable. only runs for one iteration
3932 if (formspec->getReferenceCount() == 1) {
3933 m_game_ui->deleteFormspec();
3937 auto &loc = formspec->getFormspecLocation();
3938 if (loc.type == InventoryLocation::NODEMETA) {
3939 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3940 if (!meta || meta->getString("formspec").empty()) {
3941 formspec->quitMenu();
3947 guiroot->bringToFront(formspec);
3951 ==================== Drawing begins ====================
3953 const video::SColor skycolor = sky->getSkyColor();
3955 TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
3956 driver->beginScene(true, true, skycolor);
3958 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3959 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3960 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3961 bool draw_crosshair = (
3962 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3963 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3964 #ifdef HAVE_TOUCHSCREENGUI
3966 draw_crosshair = !g_settings->getBool("touchtarget");
3967 } catch (SettingNotFoundException) {
3970 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3971 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3976 v2u32 screensize = driver->getScreenSize();
3978 if (m_game_ui->m_flags.show_profiler_graph)
3979 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3984 if (runData.damage_flash > 0.0f) {
3985 video::SColor color(runData.damage_flash, 180, 0, 0);
3986 driver->draw2DRectangle(color,
3987 core::rect<s32>(0, 0, screensize.X, screensize.Y),
3990 runData.damage_flash -= 384.0f * dtime;
3994 ==================== End scene ====================
3996 #if IRRLICHT_VERSION_MT_REVISION < 5
3997 if (++m_reset_HW_buffer_counter > 500) {
3999 Periodically remove all mesh HW buffers.
4001 Work around for a quirk in Irrlicht where a HW buffer is only
4002 released after 20000 iterations (triggered from endScene()).
4004 Without this, all loaded but unused meshes will retain their HW
4005 buffers for at least 5 minutes, at which point looking up the HW buffers
4006 becomes a bottleneck and the framerate drops (as much as 30%).
4008 Tests showed that numbers between 50 and 1000 are good, so picked 500.
4009 There are no other public Irrlicht APIs that allow interacting with the
4010 HW buffers without tracking the status of every individual mesh.
4012 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4014 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4015 driver->removeAllHardwareBuffers();
4016 m_reset_HW_buffer_counter = 0;
4022 stats->drawtime = tt_draw.stop(true);
4023 g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
4024 g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
4027 /* Log times and stuff for visualization */
4028 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4030 Profiler::GraphValues values;
4031 g_profiler->graphGet(values);
4035 /****************************************************************************
4037 *****************************************************************************/
4038 void Game::updateShadows()
4040 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4044 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4046 float timeoftheday = getWickedTimeOfDay(in_timeofday);
4047 bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
4048 bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
4049 shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
4051 timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
4052 const float offset_constant = 10000.0f;
4054 v3f light(0.0f, 0.0f, -1.0f);
4055 light.rotateXZBy(90);
4056 light.rotateXYBy(timeoftheday * 360 - 90);
4057 light.rotateYZBy(sky->getSkyBodyOrbitTilt());
4059 v3f sun_pos = light * offset_constant;
4061 if (shadow->getDirectionalLightCount() == 0)
4062 shadow->addDirectionalLight();
4063 shadow->getDirectionalLight().setDirection(sun_pos);
4064 shadow->setTimeOfDay(in_timeofday);
4066 shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4069 /****************************************************************************
4071 ****************************************************************************/
4073 void FpsControl::reset()
4075 last_time = porting::getTimeUs();
4079 * On some computers framerate doesn't seem to be automatically limited
4081 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
4083 const u64 frametime_min = 1000000.0f / (
4084 device->isWindowFocused() && !g_menumgr.pausesGame()
4085 ? g_settings->getFloat("fps_max")
4086 : g_settings->getFloat("fps_max_unfocused"));
4088 u64 time = porting::getTimeUs();
4090 if (time > last_time) // Make sure time hasn't overflowed
4091 busy_time = time - last_time;
4095 if (busy_time < frametime_min) {
4096 sleep_time = frametime_min - busy_time;
4097 if (sleep_time > 1000)
4098 sleep_ms(sleep_time / 1000);
4103 // Read the timer again to accurately determine how long we actually slept,
4104 // rather than calculating it by adding sleep_time to time.
4105 time = porting::getTimeUs();
4107 if (time > last_time) // Make sure last_time hasn't overflowed
4108 *dtime = (time - last_time) / 1000000.0f;
4115 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4117 const wchar_t *wmsg = wgettext(msg);
4118 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4123 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4125 ((Game *)data)->readSettings();
4128 void Game::readSettings()
4130 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4131 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4132 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4133 m_cache_enable_particles = g_settings->getBool("enable_particles");
4134 m_cache_enable_fog = g_settings->getBool("enable_fog");
4135 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
4136 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4137 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
4139 m_cache_enable_noclip = g_settings->getBool("noclip");
4140 m_cache_enable_free_move = g_settings->getBool("free_move");
4142 m_cache_fog_start = g_settings->getFloat("fog_start");
4144 m_cache_cam_smoothing = 0;
4145 if (g_settings->getBool("cinematic"))
4146 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4148 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4150 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4151 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4152 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4154 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4157 /****************************************************************************/
4158 /****************************************************************************
4160 ****************************************************************************/
4161 /****************************************************************************/
4163 void Game::showDeathFormspec()
4165 static std::string formspec_str =
4166 std::string("formspec_version[1]") +
4168 "bgcolor[#320000b4;true]"
4169 "label[4.85,1.35;" + gettext("You died") + "]"
4170 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4174 /* Note: FormspecFormSource and LocalFormspecHandler *
4175 * are deleted by guiFormSpecMenu */
4176 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4177 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4179 auto *&formspec = m_game_ui->getFormspecGUI();
4180 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4181 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4182 formspec->setFocus("btn_respawn");
4185 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4186 void Game::showPauseMenu()
4188 #ifdef HAVE_TOUCHSCREENGUI
4189 static const std::string control_text = strgettext("Default Controls:\n"
4190 "No menu visible:\n"
4191 "- single tap: button activate\n"
4192 "- double tap: place/use\n"
4193 "- slide finger: look around\n"
4194 "Menu/Inventory visible:\n"
4195 "- double tap (outside):\n"
4197 "- touch stack, touch slot:\n"
4199 "- touch&drag, tap 2nd finger\n"
4200 " --> place single item to slot\n"
4203 static const std::string control_text_template = strgettext("Controls:\n"
4204 "- %s: move forwards\n"
4205 "- %s: move backwards\n"
4207 "- %s: move right\n"
4208 "- %s: jump/climb up\n"
4211 "- %s: sneak/climb down\n"
4214 "- Mouse: turn/look\n"
4215 "- Mouse wheel: select item\n"
4219 char control_text_buf[600];
4221 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4222 GET_KEY_NAME(keymap_forward),
4223 GET_KEY_NAME(keymap_backward),
4224 GET_KEY_NAME(keymap_left),
4225 GET_KEY_NAME(keymap_right),
4226 GET_KEY_NAME(keymap_jump),
4227 GET_KEY_NAME(keymap_dig),
4228 GET_KEY_NAME(keymap_place),
4229 GET_KEY_NAME(keymap_sneak),
4230 GET_KEY_NAME(keymap_drop),
4231 GET_KEY_NAME(keymap_inventory),
4232 GET_KEY_NAME(keymap_chat)
4235 std::string control_text = std::string(control_text_buf);
4236 str_formspec_escape(control_text);
4239 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4240 std::ostringstream os;
4242 os << "formspec_version[1]" << SIZE_TAG
4243 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4244 << strgettext("Continue") << "]";
4246 if (!simple_singleplayer_mode) {
4247 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4248 << strgettext("Change Password") << "]";
4250 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4255 if (g_settings->getBool("enable_sound")) {
4256 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4257 << strgettext("Sound Volume") << "]";
4260 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4261 << strgettext("Change Keys") << "]";
4263 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4264 << strgettext("Exit to Menu") << "]";
4265 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4266 << strgettext("Exit to OS") << "]"
4267 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4268 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4270 << strgettext("Game info:") << "\n";
4271 const std::string &address = client->getAddressName();
4272 static const std::string mode = strgettext("- Mode: ");
4273 if (!simple_singleplayer_mode) {
4274 Address serverAddress = client->getServerAddress();
4275 if (!address.empty()) {
4276 os << mode << strgettext("Remote server") << "\n"
4277 << strgettext("- Address: ") << address;
4279 os << mode << strgettext("Hosting server");
4281 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4283 os << mode << strgettext("Singleplayer") << "\n";
4285 if (simple_singleplayer_mode || address.empty()) {
4286 static const std::string on = strgettext("On");
4287 static const std::string off = strgettext("Off");
4288 // Note: Status of enable_damage and creative_mode settings is intentionally
4289 // NOT shown here because the game might roll its own damage system and/or do
4290 // a per-player Creative Mode, in which case writing it here would mislead.
4291 bool damage = g_settings->getBool("enable_damage");
4292 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4293 if (!simple_singleplayer_mode) {
4295 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4296 //~ PvP = Player versus Player
4297 os << strgettext("- PvP: ") << pvp << "\n";
4299 os << strgettext("- Public: ") << announced << "\n";
4300 std::string server_name = g_settings->get("server_name");
4301 str_formspec_escape(server_name);
4302 if (announced == on && !server_name.empty())
4303 os << strgettext("- Server Name: ") << server_name;
4310 /* Note: FormspecFormSource and LocalFormspecHandler *
4311 * are deleted by guiFormSpecMenu */
4312 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4313 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4315 auto *&formspec = m_game_ui->getFormspecGUI();
4316 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4317 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4318 formspec->setFocus("btn_continue");
4319 formspec->doPause = true;
4321 if (simple_singleplayer_mode)
4325 /****************************************************************************/
4326 /****************************************************************************
4327 extern function for launching the game
4328 ****************************************************************************/
4329 /****************************************************************************/
4331 void the_game(bool *kill,
4332 InputHandler *input,
4333 RenderingEngine *rendering_engine,
4334 const GameStartData &start_data,
4335 std::string &error_message,
4336 ChatBackend &chat_backend,
4337 bool *reconnect_requested) // Used for local game
4341 /* Make a copy of the server address because if a local singleplayer server
4342 * is created then this is updated and we don't want to change the value
4343 * passed to us by the calling function
4348 if (game.startup(kill, input, rendering_engine, start_data,
4349 error_message, reconnect_requested, &chat_backend)) {
4353 } catch (SerializationError &e) {
4354 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
4355 error_message = strgettext("A serialization error occurred:") +"\n"
4356 + e.what() + "\n\n" + ver_err;
4357 errorstream << error_message << std::endl;
4358 } catch (ServerError &e) {
4359 error_message = e.what();
4360 errorstream << "ServerError: " << error_message << std::endl;
4361 } catch (ModError &e) {
4362 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
4363 error_message = std::string("ModError: ") + e.what() +
4364 strgettext("\nCheck debug.txt for details.");
4365 errorstream << error_message << std::endl;