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 float object_hit_delay = 0.2;
581 u32 last_time, busy_time, sleep_time;
585 /* The reason the following structs are not anonymous structs within the
586 * class is that they are not used by the majority of member functions and
587 * many functions that do require objects of thse types do not modify them
588 * (so they can be passed as a const qualified parameter)
594 PointedThing pointed_old;
597 bool btn_down_for_dig;
599 bool digging_blocked;
600 bool reset_jump_timer;
601 float nodig_delay_timer;
603 float dig_time_complete;
604 float repeat_place_timer;
605 float object_hit_delay_timer;
606 float time_from_last_punch;
607 ClientActiveObject *selected_object;
611 float update_draw_list_timer;
615 v3f update_draw_list_last_cam_dir;
617 float time_of_day_smooth;
622 struct ClientEventHandler
624 void (Game::*handler)(ClientEvent *, CameraOrientation *);
627 /****************************************************************************
629 ****************************************************************************/
631 using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
633 /* This is not intended to be a public class. If a public class becomes
634 * desirable then it may be better to create another 'wrapper' class that
635 * hides most of the stuff in this class (nothing in this class is required
636 * by any other file) but exposes the public methods/data only.
643 bool startup(bool *kill,
645 RenderingEngine *rendering_engine,
646 const GameStartData &game_params,
647 std::string &error_message,
649 ChatBackend *chat_backend);
656 // Basic initialisation
657 bool init(const std::string &map_dir, const std::string &address,
658 u16 port, const SubgameSpec &gamespec);
660 bool createSingleplayerServer(const std::string &map_dir,
661 const SubgameSpec &gamespec, u16 port);
664 bool createClient(const GameStartData &start_data);
668 bool connectToServer(const GameStartData &start_data,
669 bool *connect_ok, bool *aborted);
670 bool getServerContent(bool *aborted);
674 void updateInteractTimers(f32 dtime);
675 bool checkConnection();
676 bool handleCallbacks();
677 void processQueues();
678 void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
679 void updateDebugState();
680 void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
681 void updateProfilerGraphs(ProfilerGraph *graph);
684 void processUserInput(f32 dtime);
685 void processKeyInput();
686 void processItemSelection(u16 *new_playeritem);
688 void dropSelectedItem(bool single_item = false);
689 void openInventory();
690 void openConsole(float scale, const wchar_t *line=NULL);
691 void toggleFreeMove();
692 void toggleFreeMoveAlt();
693 void togglePitchMove();
696 void toggleCinematic();
697 void toggleBlockBounds();
698 void toggleAutoforward();
700 void toggleMinimap(bool shift_pressed);
703 void toggleUpdateCamera();
705 void increaseViewRange();
706 void decreaseViewRange();
707 void toggleFullViewRange();
708 void checkZoomEnabled();
710 void updateCameraDirection(CameraOrientation *cam, float dtime);
711 void updateCameraOrientation(CameraOrientation *cam, float dtime);
712 void updatePlayerControl(const CameraOrientation &cam);
713 void step(f32 *dtime);
714 void processClientEvents(CameraOrientation *cam);
715 void updateCamera(u32 busy_time, f32 dtime);
716 void updateSound(f32 dtime);
717 void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug);
719 * Returns the object or node the player is pointing at.
720 * Also updates the selected thing in the Hud.
722 * @param[in] shootline the shootline, starting from
723 * the camera position. This also gives the maximal distance
725 * @param[in] liquids_pointable if false, liquids are ignored
726 * @param[in] look_for_object if false, objects are ignored
727 * @param[in] camera_offset offset of the camera
728 * @param[out] selected_object the selected object or
731 PointedThing updatePointedThing(
732 const core::line3d<f32> &shootline, bool liquids_pointable,
733 bool look_for_object, const v3s16 &camera_offset);
734 void handlePointingAtNothing(const ItemStack &playerItem);
735 void handlePointingAtNode(const PointedThing &pointed,
736 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
737 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
738 const v3f &player_position, bool show_debug);
739 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
740 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
741 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
742 const CameraOrientation &cam);
743 void updateShadows();
746 void limitFps(FpsControl *fps_timings, f32 *dtime);
748 void showOverlayMessage(const char *msg, float dtime, int percent,
749 bool draw_clouds = true);
751 static void settingChangedCallback(const std::string &setting_name, void *data);
754 inline bool isKeyDown(GameKeyType k)
756 return input->isKeyDown(k);
758 inline bool wasKeyDown(GameKeyType k)
760 return input->wasKeyDown(k);
762 inline bool wasKeyPressed(GameKeyType k)
764 return input->wasKeyPressed(k);
766 inline bool wasKeyReleased(GameKeyType k)
768 return input->wasKeyReleased(k);
772 void handleAndroidChatInput();
777 bool force_fog_off = false;
778 bool disable_camera_update = false;
781 void showDeathFormspec();
782 void showPauseMenu();
784 void pauseAnimation();
785 void resumeAnimation();
787 // ClientEvent handlers
788 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
789 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
790 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
791 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
792 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
793 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
794 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
795 CameraOrientation *cam);
796 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
797 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
798 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
799 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
800 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
801 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
802 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
803 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
804 CameraOrientation *cam);
805 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
807 void updateChat(f32 dtime);
809 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
810 const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
811 const NodeMetadata *meta);
812 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
814 f32 getSensitivityScaleFactor() const;
816 InputHandler *input = nullptr;
818 Client *client = nullptr;
819 Server *server = nullptr;
821 IWritableTextureSource *texture_src = nullptr;
822 IWritableShaderSource *shader_src = nullptr;
824 // When created, these will be filled with data received from the server
825 IWritableItemDefManager *itemdef_manager = nullptr;
826 NodeDefManager *nodedef_manager = nullptr;
828 GameOnDemandSoundFetcher soundfetcher; // useful when testing
829 ISoundManager *sound = nullptr;
830 bool sound_is_dummy = false;
831 SoundMaker *soundmaker = nullptr;
833 ChatBackend *chat_backend = nullptr;
834 LogOutputBuffer m_chat_log_buf;
836 EventManager *eventmgr = nullptr;
837 QuicktuneShortcutter *quicktune = nullptr;
838 bool registration_confirmation_shown = false;
840 std::unique_ptr<GameUI> m_game_ui;
841 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
842 MapDrawControl *draw_control = nullptr;
843 Camera *camera = nullptr;
844 Clouds *clouds = nullptr; // Free using ->Drop()
845 Sky *sky = nullptr; // Free using ->Drop()
847 Minimap *mapper = nullptr;
849 // Map server hud ids to client hud ids
850 std::unordered_map<u32, u32> m_hud_server_to_client;
856 This class does take ownership/responsibily for cleaning up etc of any of
857 these items (e.g. device)
859 IrrlichtDevice *device;
860 RenderingEngine *m_rendering_engine;
861 video::IVideoDriver *driver;
862 scene::ISceneManager *smgr;
864 std::string *error_message;
865 bool *reconnect_requested;
866 scene::ISceneNode *skybox;
867 PausedNodesList paused_animated_nodes;
869 bool simple_singleplayer_mode;
872 /* Pre-calculated values
874 int crack_animation_length;
876 IntervalLimiter profiler_interval;
879 * TODO: Local caching of settings is not optimal and should at some stage
880 * be updated to use a global settings object for getting thse values
881 * (as opposed to the this local caching). This can be addressed in
884 bool m_cache_doubletap_jump;
885 bool m_cache_enable_clouds;
886 bool m_cache_enable_joysticks;
887 bool m_cache_enable_particles;
888 bool m_cache_enable_fog;
889 bool m_cache_enable_noclip;
890 bool m_cache_enable_free_move;
891 f32 m_cache_mouse_sensitivity;
892 f32 m_cache_joystick_frustum_sensitivity;
893 f32 m_repeat_place_time;
894 f32 m_cache_cam_smoothing;
895 f32 m_cache_fog_start;
897 bool m_invert_mouse = false;
898 bool m_first_loop_after_window_activation = false;
899 bool m_camera_offset_changed = false;
901 bool m_does_lost_focus_pause_game = false;
903 int m_reset_HW_buffer_counter = 0;
904 #ifdef HAVE_TOUCHSCREENGUI
905 bool m_cache_hold_aux1;
908 bool m_android_chat_open;
913 m_chat_log_buf(g_logger),
914 m_game_ui(new GameUI())
916 g_settings->registerChangedCallback("doubletap_jump",
917 &settingChangedCallback, this);
918 g_settings->registerChangedCallback("enable_clouds",
919 &settingChangedCallback, this);
920 g_settings->registerChangedCallback("doubletap_joysticks",
921 &settingChangedCallback, this);
922 g_settings->registerChangedCallback("enable_particles",
923 &settingChangedCallback, this);
924 g_settings->registerChangedCallback("enable_fog",
925 &settingChangedCallback, this);
926 g_settings->registerChangedCallback("mouse_sensitivity",
927 &settingChangedCallback, this);
928 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
929 &settingChangedCallback, this);
930 g_settings->registerChangedCallback("repeat_place_time",
931 &settingChangedCallback, this);
932 g_settings->registerChangedCallback("noclip",
933 &settingChangedCallback, this);
934 g_settings->registerChangedCallback("free_move",
935 &settingChangedCallback, this);
936 g_settings->registerChangedCallback("cinematic",
937 &settingChangedCallback, this);
938 g_settings->registerChangedCallback("cinematic_camera_smoothing",
939 &settingChangedCallback, this);
940 g_settings->registerChangedCallback("camera_smoothing",
941 &settingChangedCallback, this);
945 #ifdef HAVE_TOUCHSCREENGUI
946 m_cache_hold_aux1 = false; // This is initialised properly later
953 /****************************************************************************
955 ****************************************************************************/
964 delete server; // deleted first to stop all server threads
972 delete nodedef_manager;
973 delete itemdef_manager;
976 clearTextureNameCache();
978 g_settings->deregisterChangedCallback("doubletap_jump",
979 &settingChangedCallback, this);
980 g_settings->deregisterChangedCallback("enable_clouds",
981 &settingChangedCallback, this);
982 g_settings->deregisterChangedCallback("enable_particles",
983 &settingChangedCallback, this);
984 g_settings->deregisterChangedCallback("enable_fog",
985 &settingChangedCallback, this);
986 g_settings->deregisterChangedCallback("mouse_sensitivity",
987 &settingChangedCallback, this);
988 g_settings->deregisterChangedCallback("repeat_place_time",
989 &settingChangedCallback, this);
990 g_settings->deregisterChangedCallback("noclip",
991 &settingChangedCallback, this);
992 g_settings->deregisterChangedCallback("free_move",
993 &settingChangedCallback, this);
994 g_settings->deregisterChangedCallback("cinematic",
995 &settingChangedCallback, this);
996 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
997 &settingChangedCallback, this);
998 g_settings->deregisterChangedCallback("camera_smoothing",
999 &settingChangedCallback, this);
1002 bool Game::startup(bool *kill,
1003 InputHandler *input,
1004 RenderingEngine *rendering_engine,
1005 const GameStartData &start_data,
1006 std::string &error_message,
1008 ChatBackend *chat_backend)
1012 m_rendering_engine = rendering_engine;
1013 device = m_rendering_engine->get_raw_device();
1015 this->error_message = &error_message;
1016 reconnect_requested = reconnect;
1017 this->input = input;
1018 this->chat_backend = chat_backend;
1019 simple_singleplayer_mode = start_data.isSinglePlayer();
1021 input->keycache.populate();
1023 driver = device->getVideoDriver();
1024 smgr = m_rendering_engine->get_scene_manager();
1026 smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1029 runData = GameRunData();
1030 runData.time_from_last_punch = 10.0;
1032 m_game_ui->initFlags();
1034 m_invert_mouse = g_settings->getBool("invert_mouse");
1035 m_first_loop_after_window_activation = true;
1037 g_client_translations->clear();
1039 // address can change if simple_singleplayer_mode
1040 if (!init(start_data.world_spec.path, start_data.address,
1041 start_data.socket_port, start_data.game_spec))
1044 if (!createClient(start_data))
1047 m_rendering_engine->initialize(client, hud);
1055 ProfilerGraph graph;
1056 RunStats stats = { 0 };
1057 CameraOrientation cam_view_target = { 0 };
1058 CameraOrientation cam_view = { 0 };
1059 FpsControl draw_times = { 0 };
1060 f32 dtime; // in seconds
1062 /* Clear the profiler */
1063 Profiler::GraphValues dummyvalues;
1064 g_profiler->graphGet(dummyvalues);
1066 draw_times.last_time = m_rendering_engine->get_timer_time();
1068 set_light_table(g_settings->getFloat("display_gamma"));
1070 #ifdef HAVE_TOUCHSCREENGUI
1071 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1072 && client->checkPrivilege("fast");
1075 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1076 g_settings->getU16("screen_h"));
1078 while (m_rendering_engine->run()
1079 && !(*kill || g_gamecallback->shutdown_requested
1080 || (server && server->isShutdownRequested()))) {
1082 const irr::core::dimension2d<u32> ¤t_screen_size =
1083 m_rendering_engine->get_video_driver()->getScreenSize();
1084 // Verify if window size has changed and save it if it's the case
1085 // Ensure evaluating settings->getBool after verifying screensize
1086 // First condition is cheaper
1087 if (previous_screen_size != current_screen_size &&
1088 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1089 g_settings->getBool("autosave_screensize")) {
1090 g_settings->setU16("screen_w", current_screen_size.Width);
1091 g_settings->setU16("screen_h", current_screen_size.Height);
1092 previous_screen_size = current_screen_size;
1095 // Calculate dtime =
1096 // m_rendering_engine->run() from this iteration
1097 // + Sleep time until the wanted FPS are reached
1098 limitFps(&draw_times, &dtime);
1100 // Prepare render data for next iteration
1102 updateStats(&stats, draw_times, dtime);
1103 updateInteractTimers(dtime);
1105 if (!checkConnection())
1107 if (!handleCallbacks())
1112 m_game_ui->clearInfoText();
1113 hud->resizeHotbar();
1116 updateProfilers(stats, draw_times, dtime);
1117 processUserInput(dtime);
1118 // Update camera before player movement to avoid camera lag of one frame
1119 updateCameraDirection(&cam_view_target, dtime);
1120 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1121 cam_view.camera_yaw) * m_cache_cam_smoothing;
1122 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1123 cam_view.camera_pitch) * m_cache_cam_smoothing;
1124 updatePlayerControl(cam_view);
1126 processClientEvents(&cam_view_target);
1128 updateCamera(draw_times.busy_time, dtime);
1130 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
1131 m_game_ui->m_flags.show_basic_debug);
1132 updateFrame(&graph, &stats, dtime, cam_view);
1133 updateProfilerGraphs(&graph);
1135 // Update if minimap has been disabled by the server
1136 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1138 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1145 void Game::shutdown()
1147 m_rendering_engine->finalize();
1148 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1149 if (g_settings->get("3d_mode") == "pageflip") {
1150 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1153 auto formspec = m_game_ui->getFormspecGUI();
1155 formspec->quitMenu();
1157 #ifdef HAVE_TOUCHSCREENGUI
1158 g_touchscreengui->hide();
1161 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1166 if (gui_chat_console)
1167 gui_chat_console->drop();
1173 while (g_menumgr.menuCount() > 0) {
1174 g_menumgr.m_stack.front()->setVisible(false);
1175 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1178 m_game_ui->deleteFormspec();
1180 chat_backend->addMessage(L"", L"# Disconnected.");
1181 chat_backend->addMessage(L"", L"");
1182 m_chat_log_buf.clear();
1186 while (!client->isShutdown()) {
1187 assert(texture_src != NULL);
1188 assert(shader_src != NULL);
1189 texture_src->processQueue();
1190 shader_src->processQueue();
1197 /****************************************************************************/
1198 /****************************************************************************
1200 ****************************************************************************/
1201 /****************************************************************************/
1204 const std::string &map_dir,
1205 const std::string &address,
1207 const SubgameSpec &gamespec)
1209 texture_src = createTextureSource();
1211 showOverlayMessage(N_("Loading..."), 0, 0);
1213 shader_src = createShaderSource();
1215 itemdef_manager = createItemDefManager();
1216 nodedef_manager = createNodeDefManager();
1218 eventmgr = new EventManager();
1219 quicktune = new QuicktuneShortcutter();
1221 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1222 && eventmgr && quicktune))
1228 // Create a server if not connecting to an existing one
1229 if (address.empty()) {
1230 if (!createSingleplayerServer(map_dir, gamespec, port))
1237 bool Game::initSound()
1240 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1241 infostream << "Attempting to use OpenAL audio" << std::endl;
1242 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1244 infostream << "Failed to initialize OpenAL audio" << std::endl;
1246 infostream << "Sound disabled." << std::endl;
1250 infostream << "Using dummy audio." << std::endl;
1251 sound = &dummySoundManager;
1252 sound_is_dummy = true;
1255 soundmaker = new SoundMaker(sound, nodedef_manager);
1259 soundmaker->registerReceiver(eventmgr);
1264 bool Game::createSingleplayerServer(const std::string &map_dir,
1265 const SubgameSpec &gamespec, u16 port)
1267 showOverlayMessage(N_("Creating server..."), 0, 5);
1269 std::string bind_str = g_settings->get("bind_address");
1270 Address bind_addr(0, 0, 0, 0, port);
1272 if (g_settings->getBool("ipv6_server")) {
1273 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1277 bind_addr.Resolve(bind_str.c_str());
1278 } catch (ResolveError &e) {
1279 infostream << "Resolving bind address \"" << bind_str
1280 << "\" failed: " << e.what()
1281 << " -- Listening on all addresses." << std::endl;
1284 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1285 *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
1286 bind_addr.serializeString().c_str());
1287 errorstream << *error_message << std::endl;
1291 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1292 false, nullptr, error_message);
1298 bool Game::createClient(const GameStartData &start_data)
1300 showOverlayMessage(N_("Creating client..."), 0, 10);
1302 draw_control = new MapDrawControl();
1306 bool could_connect, connect_aborted;
1307 #ifdef HAVE_TOUCHSCREENGUI
1308 if (g_touchscreengui) {
1309 g_touchscreengui->init(texture_src);
1310 g_touchscreengui->hide();
1313 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1316 if (!could_connect) {
1317 if (error_message->empty() && !connect_aborted) {
1318 // Should not happen if error messages are set properly
1319 *error_message = gettext("Connection failed for unknown reason");
1320 errorstream << *error_message << std::endl;
1325 if (!getServerContent(&connect_aborted)) {
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 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1335 &m_flags.force_fog_off, &runData.fog_range, client);
1336 shader_src->addShaderConstantSetterFactory(scsf);
1338 // Update cached textures, meshes and materials
1339 client->afterContentReceived();
1343 camera = new Camera(*draw_control, client, m_rendering_engine);
1344 if (client->modsLoaded())
1345 client->getScript()->on_camera_ready(camera);
1346 client->setCamera(camera);
1350 if (m_cache_enable_clouds)
1351 clouds = new Clouds(smgr, -1, time(0));
1355 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
1357 skybox = NULL; // This is used/set later on in the main run loop
1359 /* Pre-calculated values
1361 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1363 v2u32 size = t->getOriginalSize();
1364 crack_animation_length = size.Y / size.X;
1366 crack_animation_length = 5;
1372 /* Set window caption
1374 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1376 str += utf8_to_wide(g_version_hash);
1378 const wchar_t *text = nullptr;
1379 if (simple_singleplayer_mode)
1380 text = wgettext("Singleplayer");
1382 text = wgettext("Multiplayer");
1389 str += driver->getName();
1392 device->setWindowCaption(str.c_str());
1394 LocalPlayer *player = client->getEnv().getLocalPlayer();
1395 player->hurt_tilt_timer = 0;
1396 player->hurt_tilt_strength = 0;
1398 hud = new Hud(client, player, &player->inventory);
1400 mapper = client->getMinimap();
1402 if (mapper && client->modsLoaded())
1403 client->getScript()->on_minimap_ready(mapper);
1408 bool Game::initGui()
1412 // Remove stale "recent" chat messages from previous connections
1413 chat_backend->clearRecentChat();
1415 // Make sure the size of the recent messages buffer is right
1416 chat_backend->applySettings();
1418 // Chat backend and console
1419 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1420 -1, chat_backend, client, &g_menumgr);
1422 #ifdef HAVE_TOUCHSCREENGUI
1424 if (g_touchscreengui)
1425 g_touchscreengui->show();
1432 bool Game::connectToServer(const GameStartData &start_data,
1433 bool *connect_ok, bool *connection_aborted)
1435 *connect_ok = false; // Let's not be overly optimistic
1436 *connection_aborted = false;
1437 bool local_server_mode = false;
1439 showOverlayMessage(N_("Resolving address..."), 0, 15);
1441 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1444 connect_address.Resolve(start_data.address.c_str());
1446 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1447 //connect_address.Resolve("localhost");
1448 if (connect_address.isIPv6()) {
1449 IPv6AddressBytes addr_bytes;
1450 addr_bytes.bytes[15] = 1;
1451 connect_address.setAddress(&addr_bytes);
1453 connect_address.setAddress(127, 0, 0, 1);
1455 local_server_mode = true;
1457 } catch (ResolveError &e) {
1458 *error_message = fmtgettext("Couldn't resolve address: %s", e.what());
1460 errorstream << *error_message << std::endl;
1464 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1465 *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
1466 errorstream << *error_message << std::endl;
1470 client = new Client(start_data.name.c_str(),
1471 start_data.password, start_data.address,
1472 *draw_control, texture_src, shader_src,
1473 itemdef_manager, nodedef_manager, sound, eventmgr,
1474 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get());
1476 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1478 infostream << "Connecting to server at ";
1479 connect_address.print(&infostream);
1480 infostream << std::endl;
1482 client->connect(connect_address,
1483 simple_singleplayer_mode || local_server_mode);
1486 Wait for server to accept connection
1492 FpsControl fps_control = { 0 };
1494 f32 wait_time = 0; // in seconds
1496 fps_control.last_time = m_rendering_engine->get_timer_time();
1498 while (m_rendering_engine->run()) {
1500 limitFps(&fps_control, &dtime);
1502 // Update client and server
1503 client->step(dtime);
1506 server->step(dtime);
1509 if (client->getState() == LC_Init) {
1515 if (*connection_aborted)
1518 if (client->accessDenied()) {
1519 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1520 *reconnect_requested = client->reconnectRequested();
1521 errorstream << *error_message << std::endl;
1525 if (input->cancelPressed()) {
1526 *connection_aborted = true;
1527 infostream << "Connect aborted [Escape]" << std::endl;
1531 if (client->m_is_registration_confirmation_state) {
1532 if (registration_confirmation_shown) {
1533 // Keep drawing the GUI
1534 m_rendering_engine->draw_menu_scene(guienv, dtime, true);
1536 registration_confirmation_shown = true;
1537 (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
1538 &g_menumgr, client, start_data.name, start_data.password,
1539 connection_aborted, texture_src))->drop();
1543 // Only time out if we aren't waiting for the server we started
1544 if (!start_data.address.empty() && wait_time > 10) {
1545 *error_message = gettext("Connection timed out.");
1546 errorstream << *error_message << std::endl;
1551 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1554 } catch (con::PeerNotFoundException &e) {
1555 // TODO: Should something be done here? At least an info/error
1563 bool Game::getServerContent(bool *aborted)
1567 FpsControl fps_control = { 0 };
1568 f32 dtime; // in seconds
1570 fps_control.last_time = m_rendering_engine->get_timer_time();
1572 while (m_rendering_engine->run()) {
1574 limitFps(&fps_control, &dtime);
1576 // Update client and server
1577 client->step(dtime);
1580 server->step(dtime);
1583 if (client->mediaReceived() && client->itemdefReceived() &&
1584 client->nodedefReceived()) {
1589 if (!checkConnection())
1592 if (client->getState() < LC_Init) {
1593 *error_message = gettext("Client disconnected");
1594 errorstream << *error_message << std::endl;
1598 if (input->cancelPressed()) {
1600 infostream << "Connect aborted [Escape]" << std::endl;
1607 if (!client->itemdefReceived()) {
1608 const wchar_t *text = wgettext("Item definitions...");
1610 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1613 } else if (!client->nodedefReceived()) {
1614 const wchar_t *text = wgettext("Node definitions...");
1616 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1620 std::ostringstream message;
1621 std::fixed(message);
1622 message.precision(0);
1623 float receive = client->mediaReceiveProgress() * 100;
1624 message << gettext("Media...");
1626 message << " " << receive << "%";
1627 message.precision(2);
1629 if ((USE_CURL == 0) ||
1630 (!g_settings->getBool("enable_remote_media_server"))) {
1631 float cur = client->getCurRate();
1632 std::string cur_unit = gettext("KiB/s");
1636 cur_unit = gettext("MiB/s");
1639 message << " (" << cur << ' ' << cur_unit << ")";
1642 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1643 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
1644 texture_src, dtime, progress);
1652 /****************************************************************************/
1653 /****************************************************************************
1655 ****************************************************************************/
1656 /****************************************************************************/
1658 inline void Game::updateInteractTimers(f32 dtime)
1660 if (runData.nodig_delay_timer >= 0)
1661 runData.nodig_delay_timer -= dtime;
1663 if (runData.object_hit_delay_timer >= 0)
1664 runData.object_hit_delay_timer -= dtime;
1666 runData.time_from_last_punch += dtime;
1670 /* returns false if game should exit, otherwise true
1672 inline bool Game::checkConnection()
1674 if (client->accessDenied()) {
1675 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1676 *reconnect_requested = client->reconnectRequested();
1677 errorstream << *error_message << std::endl;
1685 /* returns false if game should exit, otherwise true
1687 inline bool Game::handleCallbacks()
1689 if (g_gamecallback->disconnect_requested) {
1690 g_gamecallback->disconnect_requested = false;
1694 if (g_gamecallback->changepassword_requested) {
1695 (new GUIPasswordChange(guienv, guiroot, -1,
1696 &g_menumgr, client, texture_src))->drop();
1697 g_gamecallback->changepassword_requested = false;
1700 if (g_gamecallback->changevolume_requested) {
1701 (new GUIVolumeChange(guienv, guiroot, -1,
1702 &g_menumgr, texture_src))->drop();
1703 g_gamecallback->changevolume_requested = false;
1706 if (g_gamecallback->keyconfig_requested) {
1707 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1708 &g_menumgr, texture_src))->drop();
1709 g_gamecallback->keyconfig_requested = false;
1712 if (g_gamecallback->keyconfig_changed) {
1713 input->keycache.populate(); // update the cache with new settings
1714 g_gamecallback->keyconfig_changed = false;
1721 void Game::processQueues()
1723 texture_src->processQueue();
1724 itemdef_manager->processQueue(client);
1725 shader_src->processQueue();
1728 void Game::updateDebugState()
1730 bool has_basic_debug = client->checkPrivilege("basic_debug");
1731 bool has_debug = client->checkPrivilege("debug");
1733 if (m_game_ui->m_flags.show_basic_debug) {
1734 if (!has_basic_debug) {
1735 m_game_ui->m_flags.show_basic_debug = false;
1737 } else if (m_game_ui->m_flags.show_minimal_debug) {
1738 if (has_basic_debug) {
1739 m_game_ui->m_flags.show_basic_debug = true;
1742 if (!has_basic_debug)
1743 hud->disableBlockBounds();
1745 draw_control->show_wireframe = false;
1748 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1751 float profiler_print_interval =
1752 g_settings->getFloat("profiler_print_interval");
1753 bool print_to_log = true;
1755 if (profiler_print_interval == 0) {
1756 print_to_log = false;
1757 profiler_print_interval = 3;
1760 if (profiler_interval.step(dtime, profiler_print_interval)) {
1762 infostream << "Profiler:" << std::endl;
1763 g_profiler->print(infostream);
1766 m_game_ui->updateProfiler();
1767 g_profiler->clear();
1770 // Update update graphs
1771 g_profiler->graphAdd("Time non-rendering [ms]",
1772 draw_times.busy_time - stats.drawtime);
1774 g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time);
1775 g_profiler->graphAdd("FPS", 1.0f / dtime);
1778 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1785 /* Time average and jitter calculation
1787 jp = &stats->dtime_jitter;
1788 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1790 jitter = dtime - jp->avg;
1792 if (jitter > jp->max)
1795 jp->counter += dtime;
1797 if (jp->counter > 0.0) {
1799 jp->max_sample = jp->max;
1800 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1804 /* Busytime average and jitter calculation
1806 jp = &stats->busy_time_jitter;
1807 jp->avg = jp->avg + draw_times.busy_time * 0.02;
1809 jitter = draw_times.busy_time - jp->avg;
1811 if (jitter > jp->max)
1813 if (jitter < jp->min)
1816 jp->counter += dtime;
1818 if (jp->counter > 0.0) {
1820 jp->max_sample = jp->max;
1821 jp->min_sample = jp->min;
1829 /****************************************************************************
1831 ****************************************************************************/
1833 void Game::processUserInput(f32 dtime)
1835 // Reset input if window not active or some menu is active
1836 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1838 #ifdef HAVE_TOUCHSCREENGUI
1839 g_touchscreengui->hide();
1842 #ifdef HAVE_TOUCHSCREENGUI
1843 else if (g_touchscreengui) {
1844 /* on touchscreengui step may generate own input events which ain't
1845 * what we want in case we just did clear them */
1846 g_touchscreengui->show();
1847 g_touchscreengui->step(dtime);
1851 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1852 gui_chat_console->closeConsoleAtOnce();
1855 // Input handler step() (used by the random input generator)
1859 auto formspec = m_game_ui->getFormspecGUI();
1861 formspec->getAndroidUIInput();
1863 handleAndroidChatInput();
1866 // Increase timer for double tap of "keymap_jump"
1867 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1868 runData.jump_timer += dtime;
1871 processItemSelection(&runData.new_playeritem);
1875 void Game::processKeyInput()
1877 if (wasKeyDown(KeyType::DROP)) {
1878 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1879 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1880 toggleAutoforward();
1881 } else if (wasKeyDown(KeyType::BACKWARD)) {
1882 if (g_settings->getBool("continuous_forward"))
1883 toggleAutoforward();
1884 } else if (wasKeyDown(KeyType::INVENTORY)) {
1886 } else if (input->cancelPressed()) {
1888 m_android_chat_open = false;
1890 if (!gui_chat_console->isOpenInhibited()) {
1893 } else if (wasKeyDown(KeyType::CHAT)) {
1894 openConsole(0.2, L"");
1895 } else if (wasKeyDown(KeyType::CMD)) {
1896 openConsole(0.2, L"/");
1897 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1898 if (client->modsLoaded())
1899 openConsole(0.2, L".");
1901 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1902 } else if (wasKeyDown(KeyType::CONSOLE)) {
1903 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1904 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1906 } else if (wasKeyDown(KeyType::JUMP)) {
1907 toggleFreeMoveAlt();
1908 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1910 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1912 } else if (wasKeyDown(KeyType::NOCLIP)) {
1915 } else if (wasKeyDown(KeyType::MUTE)) {
1916 if (g_settings->getBool("enable_sound")) {
1917 bool new_mute_sound = !g_settings->getBool("mute_sound");
1918 g_settings->setBool("mute_sound", new_mute_sound);
1920 m_game_ui->showTranslatedStatusText("Sound muted");
1922 m_game_ui->showTranslatedStatusText("Sound unmuted");
1924 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1926 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1927 if (g_settings->getBool("enable_sound")) {
1928 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1929 g_settings->setFloat("sound_volume", new_volume);
1930 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1931 m_game_ui->showStatusText(msg);
1933 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1935 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1936 if (g_settings->getBool("enable_sound")) {
1937 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1938 g_settings->setFloat("sound_volume", new_volume);
1939 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1940 m_game_ui->showStatusText(msg);
1942 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1945 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1946 || wasKeyDown(KeyType::DEC_VOLUME)) {
1947 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1949 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1951 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1952 client->makeScreenshot();
1953 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
1954 toggleBlockBounds();
1955 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1956 m_game_ui->toggleHud();
1957 } else if (wasKeyDown(KeyType::MINIMAP)) {
1958 toggleMinimap(isKeyDown(KeyType::SNEAK));
1959 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1960 m_game_ui->toggleChat();
1961 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1963 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1964 toggleUpdateCamera();
1965 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1967 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1968 m_game_ui->toggleProfiler();
1969 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1970 increaseViewRange();
1971 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1972 decreaseViewRange();
1973 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1974 toggleFullViewRange();
1975 } else if (wasKeyDown(KeyType::ZOOM)) {
1977 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1979 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1981 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1983 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1987 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1988 runData.reset_jump_timer = false;
1989 runData.jump_timer = 0.0f;
1992 if (quicktune->hasMessage()) {
1993 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
1997 void Game::processItemSelection(u16 *new_playeritem)
1999 LocalPlayer *player = client->getEnv().getLocalPlayer();
2001 /* Item selection using mouse wheel
2003 *new_playeritem = player->getWieldIndex();
2005 s32 wheel = input->getMouseWheel();
2006 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2007 player->hud_hotbar_itemcount - 1);
2011 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2014 if (wasKeyDown(KeyType::HOTBAR_PREV))
2018 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2020 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2023 /* Item selection using hotbar slot keys
2025 for (u16 i = 0; i <= max_item; i++) {
2026 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2027 *new_playeritem = i;
2034 void Game::dropSelectedItem(bool single_item)
2036 IDropAction *a = new IDropAction();
2037 a->count = single_item ? 1 : 0;
2038 a->from_inv.setCurrentPlayer();
2039 a->from_list = "main";
2040 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2041 client->inventoryAction(a);
2045 void Game::openInventory()
2048 * Don't permit to open inventory is CAO or player doesn't exists.
2049 * This prevent showing an empty inventory at player load
2052 LocalPlayer *player = client->getEnv().getLocalPlayer();
2053 if (!player || !player->getCAO())
2056 infostream << "Game: Launching inventory" << std::endl;
2058 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2060 InventoryLocation inventoryloc;
2061 inventoryloc.setCurrentPlayer();
2063 if (!client->modsLoaded()
2064 || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2065 TextDest *txt_dst = new TextDestPlayerInventory(client);
2066 auto *&formspec = m_game_ui->updateFormspec("");
2067 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2068 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2070 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2075 void Game::openConsole(float scale, const wchar_t *line)
2077 assert(scale > 0.0f && scale <= 1.0f);
2080 porting::showInputDialog(gettext("ok"), "", "", 2);
2081 m_android_chat_open = true;
2083 if (gui_chat_console->isOpenInhibited())
2085 gui_chat_console->openConsole(scale);
2087 gui_chat_console->setCloseOnEnter(true);
2088 gui_chat_console->replaceAndAddToHistory(line);
2094 void Game::handleAndroidChatInput()
2096 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2097 std::string text = porting::getInputDialogValue();
2098 client->typeChatMessage(utf8_to_wide(text));
2099 m_android_chat_open = false;
2105 void Game::toggleFreeMove()
2107 bool free_move = !g_settings->getBool("free_move");
2108 g_settings->set("free_move", bool_to_cstr(free_move));
2111 if (client->checkPrivilege("fly")) {
2112 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2114 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2117 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2121 void Game::toggleFreeMoveAlt()
2123 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2126 runData.reset_jump_timer = true;
2130 void Game::togglePitchMove()
2132 bool pitch_move = !g_settings->getBool("pitch_move");
2133 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2136 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2138 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2143 void Game::toggleFast()
2145 bool fast_move = !g_settings->getBool("fast_move");
2146 bool has_fast_privs = client->checkPrivilege("fast");
2147 g_settings->set("fast_move", bool_to_cstr(fast_move));
2150 if (has_fast_privs) {
2151 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2153 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2156 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2159 #ifdef HAVE_TOUCHSCREENGUI
2160 m_cache_hold_aux1 = fast_move && has_fast_privs;
2165 void Game::toggleNoClip()
2167 bool noclip = !g_settings->getBool("noclip");
2168 g_settings->set("noclip", bool_to_cstr(noclip));
2171 if (client->checkPrivilege("noclip")) {
2172 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2174 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2177 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2181 void Game::toggleCinematic()
2183 bool cinematic = !g_settings->getBool("cinematic");
2184 g_settings->set("cinematic", bool_to_cstr(cinematic));
2187 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2189 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2192 void Game::toggleBlockBounds()
2194 if (client->checkPrivilege("basic_debug")) {
2195 enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
2197 case Hud::BLOCK_BOUNDS_OFF:
2198 m_game_ui->showTranslatedStatusText("Block bounds hidden");
2200 case Hud::BLOCK_BOUNDS_CURRENT:
2201 m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
2203 case Hud::BLOCK_BOUNDS_NEAR:
2204 m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
2206 case Hud::BLOCK_BOUNDS_MAX:
2207 m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
2214 m_game_ui->showTranslatedStatusText("Can't show block bounds (need 'basic_debug' privilege)");
2218 // Autoforward by toggling continuous forward.
2219 void Game::toggleAutoforward()
2221 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2222 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2224 if (autorun_enabled)
2225 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2227 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2230 void Game::toggleMinimap(bool shift_pressed)
2232 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2236 mapper->toggleMinimapShape();
2240 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2242 // Not so satisying code to keep compatibility with old fixed mode system
2244 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2246 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2247 m_game_ui->m_flags.show_minimap = false;
2250 // If radar is disabled, try to find a non radar mode or fall back to 0
2251 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2252 while (mapper->getModeIndex() &&
2253 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2256 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2260 // End of 'not so satifying code'
2261 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2262 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2263 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2265 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2268 void Game::toggleFog()
2270 bool fog_enabled = g_settings->getBool("enable_fog");
2271 g_settings->setBool("enable_fog", !fog_enabled);
2273 m_game_ui->showTranslatedStatusText("Fog disabled");
2275 m_game_ui->showTranslatedStatusText("Fog enabled");
2279 void Game::toggleDebug()
2281 // Initial: No debug info
2282 // 1x toggle: Debug text
2283 // 2x toggle: Debug text with profiler graph
2284 // 3x toggle: Debug text and wireframe (needs "debug" priv)
2285 // Next toggle: Back to initial
2287 // The debug text can be in 2 modes: minimal and basic.
2288 // * Minimal: Only technical client info that not gameplay-relevant
2289 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
2290 // Basic mode is used when player has "basic_debug" priv,
2291 // otherwise the Minimal mode is used.
2292 if (!m_game_ui->m_flags.show_minimal_debug) {
2293 m_game_ui->m_flags.show_minimal_debug = true;
2294 if (client->checkPrivilege("basic_debug")) {
2295 m_game_ui->m_flags.show_basic_debug = true;
2297 m_game_ui->m_flags.show_profiler_graph = false;
2298 draw_control->show_wireframe = false;
2299 m_game_ui->showTranslatedStatusText("Debug info shown");
2300 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2301 if (client->checkPrivilege("basic_debug")) {
2302 m_game_ui->m_flags.show_basic_debug = true;
2304 m_game_ui->m_flags.show_profiler_graph = true;
2305 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2306 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2307 if (client->checkPrivilege("basic_debug")) {
2308 m_game_ui->m_flags.show_basic_debug = true;
2310 m_game_ui->m_flags.show_profiler_graph = false;
2311 draw_control->show_wireframe = true;
2312 m_game_ui->showTranslatedStatusText("Wireframe shown");
2314 m_game_ui->m_flags.show_minimal_debug = false;
2315 m_game_ui->m_flags.show_basic_debug = false;
2316 m_game_ui->m_flags.show_profiler_graph = false;
2317 draw_control->show_wireframe = false;
2318 if (client->checkPrivilege("debug")) {
2319 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2321 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2327 void Game::toggleUpdateCamera()
2329 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2330 if (m_flags.disable_camera_update)
2331 m_game_ui->showTranslatedStatusText("Camera update disabled");
2333 m_game_ui->showTranslatedStatusText("Camera update enabled");
2337 void Game::increaseViewRange()
2339 s16 range = g_settings->getS16("viewing_range");
2340 s16 range_new = range + 10;
2342 if (range_new > 4000) {
2344 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
2345 m_game_ui->showStatusText(msg);
2347 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2348 m_game_ui->showStatusText(msg);
2350 g_settings->set("viewing_range", itos(range_new));
2354 void Game::decreaseViewRange()
2356 s16 range = g_settings->getS16("viewing_range");
2357 s16 range_new = range - 10;
2359 if (range_new < 20) {
2361 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
2362 m_game_ui->showStatusText(msg);
2364 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2365 m_game_ui->showStatusText(msg);
2367 g_settings->set("viewing_range", itos(range_new));
2371 void Game::toggleFullViewRange()
2373 draw_control->range_all = !draw_control->range_all;
2374 if (draw_control->range_all)
2375 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2377 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2381 void Game::checkZoomEnabled()
2383 LocalPlayer *player = client->getEnv().getLocalPlayer();
2384 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2385 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2388 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2390 if ((device->isWindowActive() && device->isWindowFocused()
2391 && !isMenuActive()) || input->isRandom()) {
2394 if (!input->isRandom()) {
2395 // Mac OSX gets upset if this is set every frame
2396 if (device->getCursorControl()->isVisible())
2397 device->getCursorControl()->setVisible(false);
2401 if (m_first_loop_after_window_activation) {
2402 m_first_loop_after_window_activation = false;
2404 input->setMousePos(driver->getScreenSize().Width / 2,
2405 driver->getScreenSize().Height / 2);
2407 updateCameraOrientation(cam, dtime);
2413 // Mac OSX gets upset if this is set every frame
2414 if (!device->getCursorControl()->isVisible())
2415 device->getCursorControl()->setVisible(true);
2418 m_first_loop_after_window_activation = true;
2423 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2424 // responsiveness independently of FOV.
2425 f32 Game::getSensitivityScaleFactor() const
2427 f32 fov_y = client->getCamera()->getFovY();
2429 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2430 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2432 return tan(fov_y / 2.0f) * 1.3763818698f;
2435 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2437 #ifdef HAVE_TOUCHSCREENGUI
2438 if (g_touchscreengui) {
2439 cam->camera_yaw += g_touchscreengui->getYawChange();
2440 cam->camera_pitch = g_touchscreengui->getPitch();
2443 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2444 v2s32 dist = input->getMousePos() - center;
2446 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2450 f32 sens_scale = getSensitivityScaleFactor();
2451 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2452 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2454 if (dist.X != 0 || dist.Y != 0)
2455 input->setMousePos(center.X, center.Y);
2456 #ifdef HAVE_TOUCHSCREENGUI
2460 if (m_cache_enable_joysticks) {
2461 f32 sens_scale = getSensitivityScaleFactor();
2462 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
2463 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2464 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2467 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2471 void Game::updatePlayerControl(const CameraOrientation &cam)
2473 LocalPlayer *player = client->getEnv().getLocalPlayer();
2475 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2477 PlayerControl control(
2478 isKeyDown(KeyType::JUMP) || player->getAutojump(),
2479 isKeyDown(KeyType::AUX1),
2480 isKeyDown(KeyType::SNEAK),
2481 isKeyDown(KeyType::ZOOM),
2482 isKeyDown(KeyType::DIG),
2483 isKeyDown(KeyType::PLACE),
2486 input->getMovementSpeed(),
2487 input->getMovementDirection()
2490 // autoforward if set: move towards pointed position at maximum speed
2491 if (player->getPlayerSettings().continuous_forward &&
2492 client->activeObjectsReceived() && !player->isDead()) {
2493 control.movement_speed = 1.0f;
2494 control.movement_direction = 0.0f;
2497 #ifdef HAVE_TOUCHSCREENGUI
2498 /* For touch, simulate holding down AUX1 (fast move) if the user has
2499 * the fast_move setting toggled on. If there is an aux1 key defined for
2500 * touch then its meaning is inverted (i.e. holding aux1 means walk and
2503 if (m_cache_hold_aux1) {
2504 control.aux1 = control.aux1 ^ true;
2508 u32 keypress_bits = (
2509 ( (u32)(control.jump & 0x1) << 4) |
2510 ( (u32)(control.aux1 & 0x1) << 5) |
2511 ( (u32)(control.sneak & 0x1) << 6) |
2512 ( (u32)(control.dig & 0x1) << 7) |
2513 ( (u32)(control.place & 0x1) << 8) |
2514 ( (u32)(control.zoom & 0x1) << 9)
2517 // Set direction keys to ensure mod compatibility
2518 if (control.movement_speed > 0.001f) {
2519 float absolute_direction;
2521 // Check in original orientation (absolute value indicates forward / backward)
2522 absolute_direction = abs(control.movement_direction);
2523 if (absolute_direction < (3.0f / 8.0f * M_PI))
2524 keypress_bits |= (u32)(0x1 << 0); // Forward
2525 if (absolute_direction > (5.0f / 8.0f * M_PI))
2526 keypress_bits |= (u32)(0x1 << 1); // Backward
2528 // Rotate entire coordinate system by 90 degrees (absolute value indicates left / right)
2529 absolute_direction = control.movement_direction + M_PI_2;
2530 if (absolute_direction >= M_PI)
2531 absolute_direction -= 2 * M_PI;
2532 absolute_direction = abs(absolute_direction);
2533 if (absolute_direction < (3.0f / 8.0f * M_PI))
2534 keypress_bits |= (u32)(0x1 << 2); // Left
2535 if (absolute_direction > (5.0f / 8.0f * M_PI))
2536 keypress_bits |= (u32)(0x1 << 3); // Right
2539 client->setPlayerControl(control);
2540 player->keyPressed = keypress_bits;
2546 inline void Game::step(f32 *dtime)
2548 bool can_be_and_is_paused =
2549 (simple_singleplayer_mode && g_menumgr.pausesGame());
2551 if (can_be_and_is_paused) { // This is for a singleplayer server
2552 *dtime = 0; // No time passes
2554 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
2558 server->step(*dtime);
2560 client->step(*dtime);
2564 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2567 for (auto &&child: node->getChildren())
2568 pauseNodeAnimation(paused, child);
2569 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2571 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2572 float speed = animated_node->getAnimationSpeed();
2575 paused.push_back({grab(animated_node), speed});
2576 animated_node->setAnimationSpeed(0.0f);
2579 void Game::pauseAnimation()
2581 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2584 void Game::resumeAnimation()
2586 for (auto &&pair: paused_animated_nodes)
2587 pair.first->setAnimationSpeed(pair.second);
2588 paused_animated_nodes.clear();
2591 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2592 {&Game::handleClientEvent_None},
2593 {&Game::handleClientEvent_PlayerDamage},
2594 {&Game::handleClientEvent_PlayerForceMove},
2595 {&Game::handleClientEvent_Deathscreen},
2596 {&Game::handleClientEvent_ShowFormSpec},
2597 {&Game::handleClientEvent_ShowLocalFormSpec},
2598 {&Game::handleClientEvent_HandleParticleEvent},
2599 {&Game::handleClientEvent_HandleParticleEvent},
2600 {&Game::handleClientEvent_HandleParticleEvent},
2601 {&Game::handleClientEvent_HudAdd},
2602 {&Game::handleClientEvent_HudRemove},
2603 {&Game::handleClientEvent_HudChange},
2604 {&Game::handleClientEvent_SetSky},
2605 {&Game::handleClientEvent_SetSun},
2606 {&Game::handleClientEvent_SetMoon},
2607 {&Game::handleClientEvent_SetStars},
2608 {&Game::handleClientEvent_OverrideDayNigthRatio},
2609 {&Game::handleClientEvent_CloudParams},
2612 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2614 FATAL_ERROR("ClientEvent type None received");
2617 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2619 if (client->modsLoaded())
2620 client->getScript()->on_damage_taken(event->player_damage.amount);
2622 // Damage flash and hurt tilt are not used at death
2623 if (client->getHP() > 0) {
2624 LocalPlayer *player = client->getEnv().getLocalPlayer();
2626 f32 hp_max = player->getCAO() ?
2627 player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2628 f32 damage_ratio = event->player_damage.amount / hp_max;
2630 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2631 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2633 player->hurt_tilt_timer = 1.5f;
2634 player->hurt_tilt_strength =
2635 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2638 // Play damage sound
2639 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2642 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2644 cam->camera_yaw = event->player_force_move.yaw;
2645 cam->camera_pitch = event->player_force_move.pitch;
2648 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2650 // If client scripting is enabled, deathscreen is handled by CSM code in
2651 // builtin/client/init.lua
2652 if (client->modsLoaded())
2653 client->getScript()->on_death();
2655 showDeathFormspec();
2657 /* Handle visualization */
2658 LocalPlayer *player = client->getEnv().getLocalPlayer();
2659 runData.damage_flash = 0;
2660 player->hurt_tilt_timer = 0;
2661 player->hurt_tilt_strength = 0;
2664 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2666 if (event->show_formspec.formspec->empty()) {
2667 auto formspec = m_game_ui->getFormspecGUI();
2668 if (formspec && (event->show_formspec.formname->empty()
2669 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2670 formspec->quitMenu();
2673 FormspecFormSource *fs_src =
2674 new FormspecFormSource(*(event->show_formspec.formspec));
2675 TextDestPlayerInventory *txt_dst =
2676 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2678 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2679 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2680 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2683 delete event->show_formspec.formspec;
2684 delete event->show_formspec.formname;
2687 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2689 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2690 LocalFormspecHandler *txt_dst =
2691 new LocalFormspecHandler(*event->show_formspec.formname, client);
2692 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2693 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2695 delete event->show_formspec.formspec;
2696 delete event->show_formspec.formname;
2699 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2700 CameraOrientation *cam)
2702 LocalPlayer *player = client->getEnv().getLocalPlayer();
2703 client->getParticleManager()->handleParticleEvent(event, client, player);
2706 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2708 LocalPlayer *player = client->getEnv().getLocalPlayer();
2710 u32 server_id = event->hudadd->server_id;
2711 // ignore if we already have a HUD with that ID
2712 auto i = m_hud_server_to_client.find(server_id);
2713 if (i != m_hud_server_to_client.end()) {
2714 delete event->hudadd;
2718 HudElement *e = new HudElement;
2719 e->type = static_cast<HudElementType>(event->hudadd->type);
2720 e->pos = event->hudadd->pos;
2721 e->name = event->hudadd->name;
2722 e->scale = event->hudadd->scale;
2723 e->text = event->hudadd->text;
2724 e->number = event->hudadd->number;
2725 e->item = event->hudadd->item;
2726 e->dir = event->hudadd->dir;
2727 e->align = event->hudadd->align;
2728 e->offset = event->hudadd->offset;
2729 e->world_pos = event->hudadd->world_pos;
2730 e->size = event->hudadd->size;
2731 e->z_index = event->hudadd->z_index;
2732 e->text2 = event->hudadd->text2;
2733 e->style = event->hudadd->style;
2734 m_hud_server_to_client[server_id] = player->addHud(e);
2736 delete event->hudadd;
2739 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2741 LocalPlayer *player = client->getEnv().getLocalPlayer();
2743 auto i = m_hud_server_to_client.find(event->hudrm.id);
2744 if (i != m_hud_server_to_client.end()) {
2745 HudElement *e = player->removeHud(i->second);
2747 m_hud_server_to_client.erase(i);
2752 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2754 LocalPlayer *player = client->getEnv().getLocalPlayer();
2756 HudElement *e = nullptr;
2758 auto i = m_hud_server_to_client.find(event->hudchange->id);
2759 if (i != m_hud_server_to_client.end()) {
2760 e = player->getHud(i->second);
2764 delete event->hudchange;
2768 #define CASE_SET(statval, prop, dataprop) \
2770 e->prop = event->hudchange->dataprop; \
2773 switch (event->hudchange->stat) {
2774 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2776 CASE_SET(HUD_STAT_NAME, name, sdata);
2778 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2780 CASE_SET(HUD_STAT_TEXT, text, sdata);
2782 CASE_SET(HUD_STAT_NUMBER, number, data);
2784 CASE_SET(HUD_STAT_ITEM, item, data);
2786 CASE_SET(HUD_STAT_DIR, dir, data);
2788 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2790 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2792 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2794 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2796 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2798 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2800 CASE_SET(HUD_STAT_STYLE, style, data);
2805 delete event->hudchange;
2808 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2810 sky->setVisible(false);
2811 // Whether clouds are visible in front of a custom skybox.
2812 sky->setCloudsEnabled(event->set_sky->clouds);
2818 // Clear the old textures out in case we switch rendering type.
2819 sky->clearSkyboxTextures();
2820 // Handle according to type
2821 if (event->set_sky->type == "regular") {
2822 // Shows the mesh skybox
2823 sky->setVisible(true);
2824 // Update mesh based skybox colours if applicable.
2825 sky->setSkyColors(event->set_sky->sky_color);
2826 sky->setHorizonTint(
2827 event->set_sky->fog_sun_tint,
2828 event->set_sky->fog_moon_tint,
2829 event->set_sky->fog_tint_type
2831 } else if (event->set_sky->type == "skybox" &&
2832 event->set_sky->textures.size() == 6) {
2833 // Disable the dyanmic mesh skybox:
2834 sky->setVisible(false);
2836 sky->setFallbackBgColor(event->set_sky->bgcolor);
2837 // Set sunrise and sunset fog tinting:
2838 sky->setHorizonTint(
2839 event->set_sky->fog_sun_tint,
2840 event->set_sky->fog_moon_tint,
2841 event->set_sky->fog_tint_type
2843 // Add textures to skybox.
2844 for (int i = 0; i < 6; i++)
2845 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2847 // Handle everything else as plain color.
2848 if (event->set_sky->type != "plain")
2849 infostream << "Unknown sky type: "
2850 << (event->set_sky->type) << std::endl;
2851 sky->setVisible(false);
2852 sky->setFallbackBgColor(event->set_sky->bgcolor);
2853 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2854 sky->setHorizonTint(
2855 event->set_sky->bgcolor,
2856 event->set_sky->bgcolor,
2861 delete event->set_sky;
2864 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2866 sky->setSunVisible(event->sun_params->visible);
2867 sky->setSunTexture(event->sun_params->texture,
2868 event->sun_params->tonemap, texture_src);
2869 sky->setSunScale(event->sun_params->scale);
2870 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2871 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2872 delete event->sun_params;
2875 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2877 sky->setMoonVisible(event->moon_params->visible);
2878 sky->setMoonTexture(event->moon_params->texture,
2879 event->moon_params->tonemap, texture_src);
2880 sky->setMoonScale(event->moon_params->scale);
2881 delete event->moon_params;
2884 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2886 sky->setStarsVisible(event->star_params->visible);
2887 sky->setStarCount(event->star_params->count, false);
2888 sky->setStarColor(event->star_params->starcolor);
2889 sky->setStarScale(event->star_params->scale);
2890 delete event->star_params;
2893 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2894 CameraOrientation *cam)
2896 client->getEnv().setDayNightRatioOverride(
2897 event->override_day_night_ratio.do_override,
2898 event->override_day_night_ratio.ratio_f * 1000.0f);
2901 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2906 clouds->setDensity(event->cloud_params.density);
2907 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2908 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2909 clouds->setHeight(event->cloud_params.height);
2910 clouds->setThickness(event->cloud_params.thickness);
2911 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2914 void Game::processClientEvents(CameraOrientation *cam)
2916 while (client->hasClientEvents()) {
2917 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2918 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2919 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2920 (this->*evHandler.handler)(event.get(), cam);
2924 void Game::updateChat(f32 dtime)
2926 // Get new messages from error log buffer
2927 while (!m_chat_log_buf.empty())
2928 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2930 // Get new messages from client
2931 std::wstring message;
2932 while (client->getChatMessage(message)) {
2933 chat_backend->addUnparsedMessage(message);
2936 // Remove old messages
2937 chat_backend->step(dtime);
2939 // Display all messages in a static text element
2940 auto &buf = chat_backend->getRecentBuffer();
2941 if (buf.getLinesModified()) {
2942 buf.resetLinesModified();
2943 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
2946 // Make sure that the size is still correct
2947 m_game_ui->updateChatSize();
2950 void Game::updateCamera(u32 busy_time, f32 dtime)
2952 LocalPlayer *player = client->getEnv().getLocalPlayer();
2955 For interaction purposes, get info about the held item
2957 - Is it a usable item?
2958 - Can it point to liquids?
2960 ItemStack playeritem;
2962 ItemStack selected, hand;
2963 playeritem = player->getWieldedItem(&selected, &hand);
2966 ToolCapabilities playeritem_toolcap =
2967 playeritem.getToolCapabilities(itemdef_manager);
2969 v3s16 old_camera_offset = camera->getOffset();
2971 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2972 GenericCAO *playercao = player->getCAO();
2974 // If playercao not loaded, don't change camera
2978 camera->toggleCameraMode();
2980 // Make the player visible depending on camera mode.
2981 playercao->updateMeshCulling();
2982 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2985 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2986 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2988 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2989 camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
2990 camera->step(dtime);
2992 v3f camera_position = camera->getPosition();
2993 v3f camera_direction = camera->getDirection();
2994 f32 camera_fov = camera->getFovMax();
2995 v3s16 camera_offset = camera->getOffset();
2997 m_camera_offset_changed = (camera_offset != old_camera_offset);
2999 if (!m_flags.disable_camera_update) {
3000 client->getEnv().getClientMap().updateCamera(camera_position,
3001 camera_direction, camera_fov, camera_offset);
3003 if (m_camera_offset_changed) {
3004 client->updateCameraOffset(camera_offset);
3005 client->getEnv().updateCameraOffset(camera_offset);
3008 clouds->updateCameraOffset(camera_offset);
3014 void Game::updateSound(f32 dtime)
3016 // Update sound listener
3017 v3s16 camera_offset = camera->getOffset();
3018 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3019 v3f(0, 0, 0), // velocity
3020 camera->getDirection(),
3021 camera->getCameraNode()->getUpVector());
3023 bool mute_sound = g_settings->getBool("mute_sound");
3025 sound->setListenerGain(0.0f);
3027 // Check if volume is in the proper range, else fix it.
3028 float old_volume = g_settings->getFloat("sound_volume");
3029 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3030 sound->setListenerGain(new_volume);
3032 if (old_volume != new_volume) {
3033 g_settings->setFloat("sound_volume", new_volume);
3037 LocalPlayer *player = client->getEnv().getLocalPlayer();
3039 // Tell the sound maker whether to make footstep sounds
3040 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3042 // Update sound maker
3043 if (player->makes_footstep_sound)
3044 soundmaker->step(dtime);
3046 ClientMap &map = client->getEnv().getClientMap();
3047 MapNode n = map.getNode(player->getFootstepNodePos());
3048 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3052 void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
3054 LocalPlayer *player = client->getEnv().getLocalPlayer();
3056 const v3f camera_direction = camera->getDirection();
3057 const v3s16 camera_offset = camera->getOffset();
3060 Calculate what block is the crosshair pointing to
3063 ItemStack selected_item, hand_item;
3064 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3066 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3067 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3069 core::line3d<f32> shootline;
3071 switch (camera->getCameraMode()) {
3072 case CAMERA_MODE_FIRST:
3073 // Shoot from camera position, with bobbing
3074 shootline.start = camera->getPosition();
3076 case CAMERA_MODE_THIRD:
3077 // Shoot from player head, no bobbing
3078 shootline.start = camera->getHeadPosition();
3080 case CAMERA_MODE_THIRD_FRONT:
3081 shootline.start = camera->getHeadPosition();
3082 // prevent player pointing anything in front-view
3086 shootline.end = shootline.start + camera_direction * BS * d;
3088 #ifdef HAVE_TOUCHSCREENGUI
3090 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3091 shootline = g_touchscreengui->getShootline();
3092 // Scale shootline to the acual distance the player can reach
3093 shootline.end = shootline.start
3094 + shootline.getVector().normalize() * BS * d;
3095 shootline.start += intToFloat(camera_offset, BS);
3096 shootline.end += intToFloat(camera_offset, BS);
3101 PointedThing pointed = updatePointedThing(shootline,
3102 selected_def.liquids_pointable,
3103 !runData.btn_down_for_dig,
3106 if (pointed != runData.pointed_old) {
3107 infostream << "Pointing at " << pointed.dump() << std::endl;
3108 hud->updateSelectionMesh(camera_offset);
3111 // Allow digging again if button is not pressed
3112 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3113 runData.digging_blocked = false;
3117 - releasing dig button
3118 - pointing away from node
3120 if (runData.digging) {
3121 if (wasKeyReleased(KeyType::DIG)) {
3122 infostream << "Dig button released (stopped digging)" << std::endl;
3123 runData.digging = false;
3124 } else if (pointed != runData.pointed_old) {
3125 if (pointed.type == POINTEDTHING_NODE
3126 && runData.pointed_old.type == POINTEDTHING_NODE
3127 && pointed.node_undersurface
3128 == runData.pointed_old.node_undersurface) {
3129 // Still pointing to the same node, but a different face.
3132 infostream << "Pointing away from node (stopped digging)" << std::endl;
3133 runData.digging = false;
3134 hud->updateSelectionMesh(camera_offset);
3138 if (!runData.digging) {
3139 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3140 client->setCrack(-1, v3s16(0, 0, 0));
3141 runData.dig_time = 0.0;
3143 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3144 // Remove e.g. torches faster when clicking instead of holding dig button
3145 runData.nodig_delay_timer = 0;
3146 runData.dig_instantly = false;
3149 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3150 runData.btn_down_for_dig = false;
3152 runData.punching = false;
3154 soundmaker->m_player_leftpunch_sound.name = "";
3156 // Prepare for repeating, unless we're not supposed to
3157 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3158 runData.repeat_place_timer += dtime;
3160 runData.repeat_place_timer = 0;
3162 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3163 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3164 !client->getScript()->on_item_use(selected_item, pointed)))
3165 client->interact(INTERACT_USE, pointed);
3166 } else if (pointed.type == POINTEDTHING_NODE) {
3167 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3168 } else if (pointed.type == POINTEDTHING_OBJECT) {
3169 v3f player_position = player->getPosition();
3170 handlePointingAtObject(pointed, tool_item, player_position, show_debug);
3171 } else if (isKeyDown(KeyType::DIG)) {
3172 // When button is held down in air, show continuous animation
3173 runData.punching = true;
3174 // Run callback even though item is not usable
3175 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3176 client->getScript()->on_item_use(selected_item, pointed);
3177 } else if (wasKeyPressed(KeyType::PLACE)) {
3178 handlePointingAtNothing(selected_item);
3181 runData.pointed_old = pointed;
3183 if (runData.punching || wasKeyPressed(KeyType::DIG))
3184 camera->setDigging(0); // dig animation
3186 input->clearWasKeyPressed();
3187 input->clearWasKeyReleased();
3188 // Ensure DIG & PLACE are marked as handled
3189 wasKeyDown(KeyType::DIG);
3190 wasKeyDown(KeyType::PLACE);
3192 input->joystick.clearWasKeyPressed(KeyType::DIG);
3193 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3195 input->joystick.clearWasKeyReleased(KeyType::DIG);
3196 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3200 PointedThing Game::updatePointedThing(
3201 const core::line3d<f32> &shootline,
3202 bool liquids_pointable,
3203 bool look_for_object,
3204 const v3s16 &camera_offset)
3206 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3207 selectionboxes->clear();
3208 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3209 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3210 "show_entity_selectionbox");
3212 ClientEnvironment &env = client->getEnv();
3213 ClientMap &map = env.getClientMap();
3214 const NodeDefManager *nodedef = map.getNodeDefManager();
3216 runData.selected_object = NULL;
3217 hud->pointing_at_object = false;
3219 RaycastState s(shootline, look_for_object, liquids_pointable);
3220 PointedThing result;
3221 env.continueRaycast(&s, &result);
3222 if (result.type == POINTEDTHING_OBJECT) {
3223 hud->pointing_at_object = true;
3225 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3226 aabb3f selection_box;
3227 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3228 runData.selected_object->getSelectionBox(&selection_box)) {
3229 v3f pos = runData.selected_object->getPosition();
3230 selectionboxes->push_back(aabb3f(selection_box));
3231 hud->setSelectionPos(pos, camera_offset);
3233 } else if (result.type == POINTEDTHING_NODE) {
3234 // Update selection boxes
3235 MapNode n = map.getNode(result.node_undersurface);
3236 std::vector<aabb3f> boxes;
3237 n.getSelectionBoxes(nodedef, &boxes,
3238 n.getNeighbors(result.node_undersurface, &map));
3241 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3242 i != boxes.end(); ++i) {
3244 box.MinEdge -= v3f(d, d, d);
3245 box.MaxEdge += v3f(d, d, d);
3246 selectionboxes->push_back(box);
3248 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3250 hud->setSelectedFaceNormal(v3f(
3251 result.intersection_normal.X,
3252 result.intersection_normal.Y,
3253 result.intersection_normal.Z));
3256 // Update selection mesh light level and vertex colors
3257 if (!selectionboxes->empty()) {
3258 v3f pf = hud->getSelectionPos();
3259 v3s16 p = floatToInt(pf, BS);
3261 // Get selection mesh light level
3262 MapNode n = map.getNode(p);
3263 u16 node_light = getInteriorLight(n, -1, nodedef);
3264 u16 light_level = node_light;
3266 for (const v3s16 &dir : g_6dirs) {
3267 n = map.getNode(p + dir);
3268 node_light = getInteriorLight(n, -1, nodedef);
3269 if (node_light > light_level)
3270 light_level = node_light;
3273 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3275 final_color_blend(&c, light_level, daynight_ratio);
3277 // Modify final color a bit with time
3278 u32 timer = porting::getTimeMs() % 5000;
3279 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3280 float sin_r = 0.08f * std::sin(timerf);
3281 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3282 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3283 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3284 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3285 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3287 // Set mesh final color
3288 hud->setSelectionMeshColor(c);
3294 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3296 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3297 PointedThing fauxPointed;
3298 fauxPointed.type = POINTEDTHING_NOTHING;
3299 client->interact(INTERACT_ACTIVATE, fauxPointed);
3303 void Game::handlePointingAtNode(const PointedThing &pointed,
3304 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3306 v3s16 nodepos = pointed.node_undersurface;
3307 v3s16 neighbourpos = pointed.node_abovesurface;
3310 Check information text of node
3313 ClientMap &map = client->getEnv().getClientMap();
3315 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3316 && !runData.digging_blocked
3317 && client->checkPrivilege("interact")) {
3318 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3321 // This should be done after digging handling
3322 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3325 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3326 meta->getString("infotext"))));
3328 MapNode n = map.getNode(nodepos);
3330 if (nodedef_manager->get(n).name == "unknown") {
3331 m_game_ui->setInfoText(L"Unknown node");
3335 if ((wasKeyPressed(KeyType::PLACE) ||
3336 runData.repeat_place_timer >= m_repeat_place_time) &&
3337 client->checkPrivilege("interact")) {
3338 runData.repeat_place_timer = 0;
3339 infostream << "Place button pressed while looking at ground" << std::endl;
3341 // Placing animation (always shown for feedback)
3342 camera->setDigging(1);
3344 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3346 // If the wielded item has node placement prediction,
3348 // And also set the sound and send the interact
3349 // But first check for meta formspec and rightclickable
3350 auto &def = selected_item.getDefinition(itemdef_manager);
3351 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3354 if (placed && client->modsLoaded())
3355 client->getScript()->on_placenode(pointed, def);
3359 bool Game::nodePlacement(const ItemDefinition &selected_def,
3360 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3361 const PointedThing &pointed, const NodeMetadata *meta)
3363 const auto &prediction = selected_def.node_placement_prediction;
3365 const NodeDefManager *nodedef = client->ndef();
3366 ClientMap &map = client->getEnv().getClientMap();
3368 bool is_valid_position;
3370 node = map.getNode(nodepos, &is_valid_position);
3371 if (!is_valid_position) {
3372 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3377 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3378 && !isKeyDown(KeyType::SNEAK)) {
3379 // on_rightclick callbacks are called anyway
3380 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3381 client->interact(INTERACT_PLACE, pointed);
3383 infostream << "Launching custom inventory view" << std::endl;
3385 InventoryLocation inventoryloc;
3386 inventoryloc.setNodeMeta(nodepos);
3388 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3389 &client->getEnv().getClientMap(), nodepos);
3390 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3392 auto *&formspec = m_game_ui->updateFormspec("");
3393 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3394 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3396 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3400 // on_rightclick callback
3401 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3402 !isKeyDown(KeyType::SNEAK))) {
3404 client->interact(INTERACT_PLACE, pointed);
3408 verbosestream << "Node placement prediction for "
3409 << selected_def.name << " is " << prediction << std::endl;
3410 v3s16 p = neighbourpos;
3412 // Place inside node itself if buildable_to
3413 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3414 if (is_valid_position) {
3415 if (nodedef->get(n_under).buildable_to) {
3418 node = map.getNode(p, &is_valid_position);
3419 if (is_valid_position && !nodedef->get(node).buildable_to) {
3420 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3422 client->interact(INTERACT_PLACE, pointed);
3428 // Find id of predicted node
3430 bool found = nodedef->getId(prediction, id);
3433 errorstream << "Node placement prediction failed for "
3434 << selected_def.name << " (places " << prediction
3435 << ") - Name not known" << std::endl;
3436 // Handle this as if prediction was empty
3438 client->interact(INTERACT_PLACE, pointed);
3442 const ContentFeatures &predicted_f = nodedef->get(id);
3444 // Predict param2 for facedir and wallmounted nodes
3445 // Compare core.item_place_node() for what the server does
3448 const u8 place_param2 = selected_def.place_param2;
3451 param2 = place_param2;
3452 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3453 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3454 v3s16 dir = nodepos - neighbourpos;
3456 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3457 param2 = dir.Y < 0 ? 1 : 0;
3458 } else if (abs(dir.X) > abs(dir.Z)) {
3459 param2 = dir.X < 0 ? 3 : 2;
3461 param2 = dir.Z < 0 ? 5 : 4;
3463 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3464 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3465 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3467 if (abs(dir.X) > abs(dir.Z)) {
3468 param2 = dir.X < 0 ? 3 : 1;
3470 param2 = dir.Z < 0 ? 2 : 0;
3474 // Check attachment if node is in group attached_node
3475 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3476 const static v3s16 wallmounted_dirs[8] = {
3486 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3487 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3488 pp = p + wallmounted_dirs[param2];
3490 pp = p + v3s16(0, -1, 0);
3492 if (!nodedef->get(map.getNode(pp)).walkable) {
3493 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3495 client->interact(INTERACT_PLACE, pointed);
3501 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3502 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3503 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3504 const auto &indexstr = selected_item.metadata.
3505 getString("palette_index", 0);
3506 if (!indexstr.empty()) {
3507 s32 index = mystoi(indexstr);
3508 if (predicted_f.param_type_2 == CPT2_COLOR) {
3510 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3511 // param2 = pure palette index + other
3512 param2 = (index & 0xf8) | (param2 & 0x07);
3513 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3514 // param2 = pure palette index + other
3515 param2 = (index & 0xe0) | (param2 & 0x1f);
3520 // Add node to client map
3521 MapNode n(id, 0, param2);
3524 LocalPlayer *player = client->getEnv().getLocalPlayer();
3526 // Dont place node when player would be inside new node
3527 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3528 if (!nodedef->get(n).walkable ||
3529 g_settings->getBool("enable_build_where_you_stand") ||
3530 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3531 (nodedef->get(n).walkable &&
3532 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3533 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3534 // This triggers the required mesh update too
3535 client->addNode(p, n);
3537 client->interact(INTERACT_PLACE, pointed);
3538 // A node is predicted, also play a sound
3539 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3542 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3545 } catch (const InvalidPositionException &e) {
3546 errorstream << "Node placement prediction failed for "
3547 << selected_def.name << " (places "
3548 << prediction << ") - Position not loaded" << std::endl;
3549 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3554 void Game::handlePointingAtObject(const PointedThing &pointed,
3555 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3557 std::wstring infotext = unescape_translate(
3558 utf8_to_wide(runData.selected_object->infoText()));
3561 if (!infotext.empty()) {
3564 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3567 m_game_ui->setInfoText(infotext);
3569 if (isKeyDown(KeyType::DIG)) {
3570 bool do_punch = false;
3571 bool do_punch_damage = false;
3573 if (runData.object_hit_delay_timer <= 0.0) {
3575 do_punch_damage = true;
3576 runData.object_hit_delay_timer = object_hit_delay;
3579 if (wasKeyPressed(KeyType::DIG))
3583 infostream << "Punched object" << std::endl;
3584 runData.punching = true;
3587 if (do_punch_damage) {
3588 // Report direct punch
3589 v3f objpos = runData.selected_object->getPosition();
3590 v3f dir = (objpos - player_position).normalize();
3592 bool disable_send = runData.selected_object->directReportPunch(
3593 dir, &tool_item, runData.time_from_last_punch);
3594 runData.time_from_last_punch = 0;
3597 client->interact(INTERACT_START_DIGGING, pointed);
3599 } else if (wasKeyDown(KeyType::PLACE)) {
3600 infostream << "Pressed place button while pointing at object" << std::endl;
3601 client->interact(INTERACT_PLACE, pointed); // place
3606 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3607 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3609 // See also: serverpackethandle.cpp, action == 2
3610 LocalPlayer *player = client->getEnv().getLocalPlayer();
3611 ClientMap &map = client->getEnv().getClientMap();
3612 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3614 // NOTE: Similar piece of code exists on the server side for
3616 // Get digging parameters
3617 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3618 &selected_item.getToolCapabilities(itemdef_manager),
3619 selected_item.wear);
3621 // If can't dig, try hand
3622 if (!params.diggable) {
3623 params = getDigParams(nodedef_manager->get(n).groups,
3624 &hand_item.getToolCapabilities(itemdef_manager));
3627 if (!params.diggable) {
3628 // I guess nobody will wait for this long
3629 runData.dig_time_complete = 10000000.0;
3631 runData.dig_time_complete = params.time;
3633 if (m_cache_enable_particles) {
3634 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3635 client->getParticleManager()->addNodeParticle(client,
3636 player, nodepos, n, features);
3640 if (!runData.digging) {
3641 infostream << "Started digging" << std::endl;
3642 runData.dig_instantly = runData.dig_time_complete == 0;
3643 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3645 client->interact(INTERACT_START_DIGGING, pointed);
3646 runData.digging = true;
3647 runData.btn_down_for_dig = true;
3650 if (!runData.dig_instantly) {
3651 runData.dig_index = (float)crack_animation_length
3653 / runData.dig_time_complete;
3655 // This is for e.g. torches
3656 runData.dig_index = crack_animation_length;
3659 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3661 if (sound_dig.exists() && params.diggable) {
3662 if (sound_dig.name == "__group") {
3663 if (!params.main_group.empty()) {
3664 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3665 soundmaker->m_player_leftpunch_sound.name =
3666 std::string("default_dig_") +
3670 soundmaker->m_player_leftpunch_sound = sound_dig;
3674 // Don't show cracks if not diggable
3675 if (runData.dig_time_complete >= 100000.0) {
3676 } else if (runData.dig_index < crack_animation_length) {
3677 //TimeTaker timer("client.setTempMod");
3678 //infostream<<"dig_index="<<dig_index<<std::endl;
3679 client->setCrack(runData.dig_index, nodepos);
3681 infostream << "Digging completed" << std::endl;
3682 client->setCrack(-1, v3s16(0, 0, 0));
3684 runData.dig_time = 0;
3685 runData.digging = false;
3686 // we successfully dug, now block it from repeating if we want to be safe
3687 if (g_settings->getBool("safe_dig_and_place"))
3688 runData.digging_blocked = true;
3690 runData.nodig_delay_timer =
3691 runData.dig_time_complete / (float)crack_animation_length;
3693 // We don't want a corresponding delay to very time consuming nodes
3694 // and nodes without digging time (e.g. torches) get a fixed delay.
3695 if (runData.nodig_delay_timer > 0.3)
3696 runData.nodig_delay_timer = 0.3;
3697 else if (runData.dig_instantly)
3698 runData.nodig_delay_timer = 0.15;
3700 bool is_valid_position;
3701 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3702 if (is_valid_position) {
3703 if (client->modsLoaded() &&
3704 client->getScript()->on_dignode(nodepos, wasnode)) {
3708 const ContentFeatures &f = client->ndef()->get(wasnode);
3709 if (f.node_dig_prediction == "air") {
3710 client->removeNode(nodepos);
3711 } else if (!f.node_dig_prediction.empty()) {
3713 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3715 client->addNode(nodepos, id, true);
3717 // implicit else: no prediction
3720 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3722 if (m_cache_enable_particles) {
3723 const ContentFeatures &features =
3724 client->getNodeDefManager()->get(wasnode);
3725 client->getParticleManager()->addDiggingParticles(client,
3726 player, nodepos, wasnode, features);
3730 // Send event to trigger sound
3731 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3734 if (runData.dig_time_complete < 100000.0) {
3735 runData.dig_time += dtime;
3737 runData.dig_time = 0;
3738 client->setCrack(-1, nodepos);
3741 camera->setDigging(0); // Dig animation
3744 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3745 const CameraOrientation &cam)
3747 TimeTaker tt_update("Game::updateFrame()");
3748 LocalPlayer *player = client->getEnv().getLocalPlayer();
3754 if (draw_control->range_all) {
3755 runData.fog_range = 100000 * BS;
3757 runData.fog_range = draw_control->wanted_range * BS;
3761 Calculate general brightness
3763 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3764 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3765 float direct_brightness;
3768 if (m_cache_enable_noclip && m_cache_enable_free_move) {
3769 direct_brightness = time_brightness;
3770 sunlight_seen = true;
3772 float old_brightness = sky->getBrightness();
3773 direct_brightness = client->getEnv().getClientMap()
3774 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3775 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3779 float time_of_day_smooth = runData.time_of_day_smooth;
3780 float time_of_day = client->getEnv().getTimeOfDayF();
3782 static const float maxsm = 0.05f;
3783 static const float todsm = 0.05f;
3785 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3786 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3787 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3788 time_of_day_smooth = time_of_day;
3790 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3791 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3792 + (time_of_day + 1.0) * todsm;
3794 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3795 + time_of_day * todsm;
3797 runData.time_of_day_smooth = time_of_day_smooth;
3799 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3800 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3801 player->getPitch());
3807 if (sky->getCloudsVisible()) {
3808 clouds->setVisible(true);
3809 clouds->step(dtime);
3810 // camera->getPosition is not enough for 3rd person views
3811 v3f camera_node_position = camera->getCameraNode()->getPosition();
3812 v3s16 camera_offset = camera->getOffset();
3813 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3814 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3815 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3816 clouds->update(camera_node_position,
3817 sky->getCloudColor());
3818 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3819 // if inside clouds, and fog enabled, use that as sky
3821 video::SColor clouds_dark = clouds->getColor()
3822 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3823 sky->overrideColors(clouds_dark, clouds->getColor());
3824 sky->setInClouds(true);
3825 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3826 // do not draw clouds after all
3827 clouds->setVisible(false);
3830 clouds->setVisible(false);
3837 client->getParticleManager()->step(dtime);
3843 if (m_cache_enable_fog) {
3846 video::EFT_FOG_LINEAR,
3847 runData.fog_range * m_cache_fog_start,
3848 runData.fog_range * 1.0,
3856 video::EFT_FOG_LINEAR,
3866 Get chat messages from client
3875 if (player->getWieldIndex() != runData.new_playeritem)
3876 client->setPlayerItem(runData.new_playeritem);
3878 if (client->updateWieldedItem()) {
3879 // Update wielded tool
3880 ItemStack selected_item, hand_item;
3881 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3882 camera->wield(tool_item);
3886 Update block draw list every 200ms or when camera direction has
3889 runData.update_draw_list_timer += dtime;
3891 float update_draw_list_delta = 0.2f;
3893 v3f camera_direction = camera->getDirection();
3894 if (runData.update_draw_list_timer >= update_draw_list_delta
3895 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3896 || m_camera_offset_changed
3897 || client->getEnv().getClientMap().needsUpdateDrawList()) {
3898 runData.update_draw_list_timer = 0;
3899 client->getEnv().getClientMap().updateDrawList();
3900 runData.update_draw_list_last_cam_dir = camera_direction;
3903 if (RenderingEngine::get_shadow_renderer()) {
3907 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3910 make sure menu is on top
3911 1. Delete formspec menu reference if menu was removed
3912 2. Else, make sure formspec menu is on top
3914 auto formspec = m_game_ui->getFormspecGUI();
3915 do { // breakable. only runs for one iteration
3919 if (formspec->getReferenceCount() == 1) {
3920 m_game_ui->deleteFormspec();
3924 auto &loc = formspec->getFormspecLocation();
3925 if (loc.type == InventoryLocation::NODEMETA) {
3926 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3927 if (!meta || meta->getString("formspec").empty()) {
3928 formspec->quitMenu();
3934 guiroot->bringToFront(formspec);
3940 const video::SColor &skycolor = sky->getSkyColor();
3942 TimeTaker tt_draw("Draw scene");
3943 driver->beginScene(true, true, skycolor);
3945 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3946 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3947 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3948 bool draw_crosshair = (
3949 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3950 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3951 #ifdef HAVE_TOUCHSCREENGUI
3953 draw_crosshair = !g_settings->getBool("touchtarget");
3954 } catch (SettingNotFoundException) {
3957 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3958 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3963 v2u32 screensize = driver->getScreenSize();
3965 if (m_game_ui->m_flags.show_profiler_graph)
3966 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3971 if (runData.damage_flash > 0.0f) {
3972 video::SColor color(runData.damage_flash, 180, 0, 0);
3973 driver->draw2DRectangle(color,
3974 core::rect<s32>(0, 0, screensize.X, screensize.Y),
3977 runData.damage_flash -= 384.0f * dtime;
3983 if (player->hurt_tilt_timer > 0.0f) {
3984 player->hurt_tilt_timer -= dtime * 6.0f;
3986 if (player->hurt_tilt_timer < 0.0f)
3987 player->hurt_tilt_strength = 0.0f;
3991 Update minimap pos and rotation
3993 if (mapper && m_game_ui->m_flags.show_hud) {
3994 mapper->setPos(floatToInt(player->getPosition(), BS));
3995 mapper->setAngle(player->getYaw());
4001 if (++m_reset_HW_buffer_counter > 500) {
4003 Periodically remove all mesh HW buffers.
4005 Work around for a quirk in Irrlicht where a HW buffer is only
4006 released after 20000 iterations (triggered from endScene()).
4008 Without this, all loaded but unused meshes will retain their HW
4009 buffers for at least 5 minutes, at which point looking up the HW buffers
4010 becomes a bottleneck and the framerate drops (as much as 30%).
4012 Tests showed that numbers between 50 and 1000 are good, so picked 500.
4013 There are no other public Irrlicht APIs that allow interacting with the
4014 HW buffers without tracking the status of every individual mesh.
4016 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4018 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4019 driver->removeAllHardwareBuffers();
4020 m_reset_HW_buffer_counter = 0;
4024 stats->drawtime = tt_draw.stop(true);
4025 g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
4026 g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
4029 /* Log times and stuff for visualization */
4030 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4032 Profiler::GraphValues values;
4033 g_profiler->graphGet(values);
4037 /****************************************************************************
4039 *****************************************************************************/
4040 void Game::updateShadows()
4042 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4046 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4048 float timeoftheday = fmod(getWickedTimeOfDay(in_timeofday) + 0.75f, 0.5f) + 0.25f;
4049 const float offset_constant = 10000.0f;
4051 v3f light(0.0f, 0.0f, -1.0f);
4052 light.rotateXZBy(90);
4053 light.rotateXYBy(timeoftheday * 360 - 90);
4054 light.rotateYZBy(sky->getSkyBodyOrbitTilt());
4056 v3f sun_pos = light * offset_constant;
4058 if (shadow->getDirectionalLightCount() == 0)
4059 shadow->addDirectionalLight();
4060 shadow->getDirectionalLight().setDirection(sun_pos);
4061 shadow->setTimeOfDay(in_timeofday);
4063 shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4066 /****************************************************************************
4068 ****************************************************************************/
4070 /* On some computers framerate doesn't seem to be automatically limited
4072 inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
4074 // not using getRealTime is necessary for wine
4075 device->getTimer()->tick(); // Maker sure device time is up-to-date
4076 u32 time = device->getTimer()->getTime();
4077 u32 last_time = fps_timings->last_time;
4079 if (time > last_time) // Make sure time hasn't overflowed
4080 fps_timings->busy_time = time - last_time;
4082 fps_timings->busy_time = 0;
4084 u32 frametime_min = 1000 / (
4085 device->isWindowFocused() && !g_menumgr.pausesGame()
4086 ? g_settings->getFloat("fps_max")
4087 : g_settings->getFloat("fps_max_unfocused"));
4089 if (fps_timings->busy_time < frametime_min) {
4090 fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
4091 device->sleep(fps_timings->sleep_time);
4093 fps_timings->sleep_time = 0;
4096 /* Get the new value of the device timer. Note that device->sleep() may
4097 * not sleep for the entire requested time as sleep may be interrupted and
4098 * therefore it is arguably more accurate to get the new time from the
4099 * device rather than calculating it by adding sleep_time to time.
4102 device->getTimer()->tick(); // Update device timer
4103 time = device->getTimer()->getTime();
4105 if (time > last_time) // Make sure last_time hasn't overflowed
4106 *dtime = (time - last_time) / 1000.0;
4110 fps_timings->last_time = time;
4113 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4115 const wchar_t *wmsg = wgettext(msg);
4116 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4121 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4123 ((Game *)data)->readSettings();
4126 void Game::readSettings()
4128 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4129 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4130 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4131 m_cache_enable_particles = g_settings->getBool("enable_particles");
4132 m_cache_enable_fog = g_settings->getBool("enable_fog");
4133 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
4134 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4135 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
4137 m_cache_enable_noclip = g_settings->getBool("noclip");
4138 m_cache_enable_free_move = g_settings->getBool("free_move");
4140 m_cache_fog_start = g_settings->getFloat("fog_start");
4142 m_cache_cam_smoothing = 0;
4143 if (g_settings->getBool("cinematic"))
4144 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4146 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4148 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4149 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4150 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4152 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4155 /****************************************************************************/
4156 /****************************************************************************
4158 ****************************************************************************/
4159 /****************************************************************************/
4161 void Game::showDeathFormspec()
4163 static std::string formspec_str =
4164 std::string("formspec_version[1]") +
4166 "bgcolor[#320000b4;true]"
4167 "label[4.85,1.35;" + gettext("You died") + "]"
4168 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4172 /* Note: FormspecFormSource and LocalFormspecHandler *
4173 * are deleted by guiFormSpecMenu */
4174 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4175 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4177 auto *&formspec = m_game_ui->getFormspecGUI();
4178 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4179 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4180 formspec->setFocus("btn_respawn");
4183 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4184 void Game::showPauseMenu()
4186 #ifdef HAVE_TOUCHSCREENGUI
4187 static const std::string control_text = strgettext("Default Controls:\n"
4188 "No menu visible:\n"
4189 "- single tap: button activate\n"
4190 "- double tap: place/use\n"
4191 "- slide finger: look around\n"
4192 "Menu/Inventory visible:\n"
4193 "- double tap (outside):\n"
4195 "- touch stack, touch slot:\n"
4197 "- touch&drag, tap 2nd finger\n"
4198 " --> place single item to slot\n"
4201 static const std::string control_text_template = strgettext("Controls:\n"
4202 "- %s: move forwards\n"
4203 "- %s: move backwards\n"
4205 "- %s: move right\n"
4206 "- %s: jump/climb up\n"
4209 "- %s: sneak/climb down\n"
4212 "- Mouse: turn/look\n"
4213 "- Mouse wheel: select item\n"
4217 char control_text_buf[600];
4219 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4220 GET_KEY_NAME(keymap_forward),
4221 GET_KEY_NAME(keymap_backward),
4222 GET_KEY_NAME(keymap_left),
4223 GET_KEY_NAME(keymap_right),
4224 GET_KEY_NAME(keymap_jump),
4225 GET_KEY_NAME(keymap_dig),
4226 GET_KEY_NAME(keymap_place),
4227 GET_KEY_NAME(keymap_sneak),
4228 GET_KEY_NAME(keymap_drop),
4229 GET_KEY_NAME(keymap_inventory),
4230 GET_KEY_NAME(keymap_chat)
4233 std::string control_text = std::string(control_text_buf);
4234 str_formspec_escape(control_text);
4237 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4238 std::ostringstream os;
4240 os << "formspec_version[1]" << SIZE_TAG
4241 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4242 << strgettext("Continue") << "]";
4244 if (!simple_singleplayer_mode) {
4245 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4246 << strgettext("Change Password") << "]";
4248 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4253 if (g_settings->getBool("enable_sound")) {
4254 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4255 << strgettext("Sound Volume") << "]";
4258 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4259 << strgettext("Change Keys") << "]";
4261 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4262 << strgettext("Exit to Menu") << "]";
4263 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4264 << strgettext("Exit to OS") << "]"
4265 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4266 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4268 << strgettext("Game info:") << "\n";
4269 const std::string &address = client->getAddressName();
4270 static const std::string mode = strgettext("- Mode: ");
4271 if (!simple_singleplayer_mode) {
4272 Address serverAddress = client->getServerAddress();
4273 if (!address.empty()) {
4274 os << mode << strgettext("Remote server") << "\n"
4275 << strgettext("- Address: ") << address;
4277 os << mode << strgettext("Hosting server");
4279 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4281 os << mode << strgettext("Singleplayer") << "\n";
4283 if (simple_singleplayer_mode || address.empty()) {
4284 static const std::string on = strgettext("On");
4285 static const std::string off = strgettext("Off");
4286 const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
4287 const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
4288 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4289 os << strgettext("- Damage: ") << damage << "\n"
4290 << strgettext("- Creative Mode: ") << creative << "\n";
4291 if (!simple_singleplayer_mode) {
4292 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4293 //~ PvP = Player versus Player
4294 os << strgettext("- PvP: ") << pvp << "\n"
4295 << strgettext("- Public: ") << announced << "\n";
4296 std::string server_name = g_settings->get("server_name");
4297 str_formspec_escape(server_name);
4298 if (announced == on && !server_name.empty())
4299 os << strgettext("- Server Name: ") << server_name;
4306 /* Note: FormspecFormSource and LocalFormspecHandler *
4307 * are deleted by guiFormSpecMenu */
4308 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4309 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4311 auto *&formspec = m_game_ui->getFormspecGUI();
4312 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4313 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4314 formspec->setFocus("btn_continue");
4315 formspec->doPause = true;
4317 if (simple_singleplayer_mode)
4321 /****************************************************************************/
4322 /****************************************************************************
4323 extern function for launching the game
4324 ****************************************************************************/
4325 /****************************************************************************/
4327 void the_game(bool *kill,
4328 InputHandler *input,
4329 RenderingEngine *rendering_engine,
4330 const GameStartData &start_data,
4331 std::string &error_message,
4332 ChatBackend &chat_backend,
4333 bool *reconnect_requested) // Used for local game
4337 /* Make a copy of the server address because if a local singleplayer server
4338 * is created then this is updated and we don't want to change the value
4339 * passed to us by the calling function
4344 if (game.startup(kill, input, rendering_engine, start_data,
4345 error_message, reconnect_requested, &chat_backend)) {
4349 } catch (SerializationError &e) {
4350 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
4351 error_message = strgettext("A serialization error occurred:") +"\n"
4352 + e.what() + "\n\n" + ver_err;
4353 errorstream << error_message << std::endl;
4354 } catch (ServerError &e) {
4355 error_message = e.what();
4356 errorstream << "ServerError: " << error_message << std::endl;
4357 } catch (ModError &e) {
4358 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
4359 error_message = std::string("ModError: ") + e.what() +
4360 strgettext("\nCheck debug.txt for details.");
4361 errorstream << error_message << std::endl;