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);
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 updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
680 void updateProfilerGraphs(ProfilerGraph *graph);
683 void processUserInput(f32 dtime);
684 void processKeyInput();
685 void processItemSelection(u16 *new_playeritem);
687 void dropSelectedItem(bool single_item = false);
688 void openInventory();
689 void openConsole(float scale, const wchar_t *line=NULL);
690 void toggleFreeMove();
691 void toggleFreeMoveAlt();
692 void togglePitchMove();
695 void toggleCinematic();
696 void toggleAutoforward();
698 void toggleMinimap(bool shift_pressed);
701 void toggleUpdateCamera();
703 void increaseViewRange();
704 void decreaseViewRange();
705 void toggleFullViewRange();
706 void checkZoomEnabled();
708 void updateCameraDirection(CameraOrientation *cam, float dtime);
709 void updateCameraOrientation(CameraOrientation *cam, float dtime);
710 void updatePlayerControl(const CameraOrientation &cam);
711 void step(f32 *dtime);
712 void processClientEvents(CameraOrientation *cam);
713 void updateCamera(u32 busy_time, f32 dtime);
714 void updateSound(f32 dtime);
715 void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug);
717 * Returns the object or node the player is pointing at.
718 * Also updates the selected thing in the Hud.
720 * @param[in] shootline the shootline, starting from
721 * the camera position. This also gives the maximal distance
723 * @param[in] liquids_pointable if false, liquids are ignored
724 * @param[in] look_for_object if false, objects are ignored
725 * @param[in] camera_offset offset of the camera
726 * @param[out] selected_object the selected object or
729 PointedThing updatePointedThing(
730 const core::line3d<f32> &shootline, bool liquids_pointable,
731 bool look_for_object, const v3s16 &camera_offset);
732 void handlePointingAtNothing(const ItemStack &playerItem);
733 void handlePointingAtNode(const PointedThing &pointed,
734 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
735 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
736 const v3f &player_position, bool show_debug);
737 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
738 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
739 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
740 const CameraOrientation &cam);
743 void limitFps(FpsControl *fps_timings, f32 *dtime);
745 void showOverlayMessage(const char *msg, float dtime, int percent,
746 bool draw_clouds = true);
748 static void settingChangedCallback(const std::string &setting_name, void *data);
751 inline bool isKeyDown(GameKeyType k)
753 return input->isKeyDown(k);
755 inline bool wasKeyDown(GameKeyType k)
757 return input->wasKeyDown(k);
759 inline bool wasKeyPressed(GameKeyType k)
761 return input->wasKeyPressed(k);
763 inline bool wasKeyReleased(GameKeyType k)
765 return input->wasKeyReleased(k);
769 void handleAndroidChatInput();
774 bool force_fog_off = false;
775 bool disable_camera_update = false;
778 void showDeathFormspec();
779 void showPauseMenu();
781 void pauseAnimation();
782 void resumeAnimation();
784 // ClientEvent handlers
785 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
786 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
787 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
788 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
789 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
790 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
791 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
792 CameraOrientation *cam);
793 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
794 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
795 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
796 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
797 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
798 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
799 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
800 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
801 CameraOrientation *cam);
802 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
804 void updateChat(f32 dtime, const v2u32 &screensize);
806 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
807 const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
808 const NodeMetadata *meta);
809 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
811 f32 getSensitivityScaleFactor() const;
813 InputHandler *input = nullptr;
815 Client *client = nullptr;
816 Server *server = nullptr;
818 IWritableTextureSource *texture_src = nullptr;
819 IWritableShaderSource *shader_src = nullptr;
821 // When created, these will be filled with data received from the server
822 IWritableItemDefManager *itemdef_manager = nullptr;
823 NodeDefManager *nodedef_manager = nullptr;
825 GameOnDemandSoundFetcher soundfetcher; // useful when testing
826 ISoundManager *sound = nullptr;
827 bool sound_is_dummy = false;
828 SoundMaker *soundmaker = nullptr;
830 ChatBackend *chat_backend = nullptr;
831 LogOutputBuffer m_chat_log_buf;
833 EventManager *eventmgr = nullptr;
834 QuicktuneShortcutter *quicktune = nullptr;
835 bool registration_confirmation_shown = false;
837 std::unique_ptr<GameUI> m_game_ui;
838 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
839 MapDrawControl *draw_control = nullptr;
840 Camera *camera = nullptr;
841 Clouds *clouds = nullptr; // Free using ->Drop()
842 Sky *sky = nullptr; // Free using ->Drop()
844 Minimap *mapper = nullptr;
846 // Map server hud ids to client hud ids
847 std::unordered_map<u32, u32> m_hud_server_to_client;
853 This class does take ownership/responsibily for cleaning up etc of any of
854 these items (e.g. device)
856 IrrlichtDevice *device;
857 RenderingEngine *m_rendering_engine;
858 video::IVideoDriver *driver;
859 scene::ISceneManager *smgr;
861 std::string *error_message;
862 bool *reconnect_requested;
863 scene::ISceneNode *skybox;
864 PausedNodesList paused_animated_nodes;
866 bool simple_singleplayer_mode;
869 /* Pre-calculated values
871 int crack_animation_length;
873 IntervalLimiter profiler_interval;
876 * TODO: Local caching of settings is not optimal and should at some stage
877 * be updated to use a global settings object for getting thse values
878 * (as opposed to the this local caching). This can be addressed in
881 bool m_cache_doubletap_jump;
882 bool m_cache_enable_clouds;
883 bool m_cache_enable_joysticks;
884 bool m_cache_enable_particles;
885 bool m_cache_enable_fog;
886 bool m_cache_enable_noclip;
887 bool m_cache_enable_free_move;
888 f32 m_cache_mouse_sensitivity;
889 f32 m_cache_joystick_frustum_sensitivity;
890 f32 m_repeat_place_time;
891 f32 m_cache_cam_smoothing;
892 f32 m_cache_fog_start;
894 bool m_invert_mouse = false;
895 bool m_first_loop_after_window_activation = false;
896 bool m_camera_offset_changed = false;
898 bool m_does_lost_focus_pause_game = false;
900 int m_reset_HW_buffer_counter = 0;
902 bool m_cache_hold_aux1;
903 bool m_android_chat_open;
908 m_chat_log_buf(g_logger),
909 m_game_ui(new GameUI())
911 g_settings->registerChangedCallback("doubletap_jump",
912 &settingChangedCallback, this);
913 g_settings->registerChangedCallback("enable_clouds",
914 &settingChangedCallback, this);
915 g_settings->registerChangedCallback("doubletap_joysticks",
916 &settingChangedCallback, this);
917 g_settings->registerChangedCallback("enable_particles",
918 &settingChangedCallback, this);
919 g_settings->registerChangedCallback("enable_fog",
920 &settingChangedCallback, this);
921 g_settings->registerChangedCallback("mouse_sensitivity",
922 &settingChangedCallback, this);
923 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
924 &settingChangedCallback, this);
925 g_settings->registerChangedCallback("repeat_place_time",
926 &settingChangedCallback, this);
927 g_settings->registerChangedCallback("noclip",
928 &settingChangedCallback, this);
929 g_settings->registerChangedCallback("free_move",
930 &settingChangedCallback, this);
931 g_settings->registerChangedCallback("cinematic",
932 &settingChangedCallback, this);
933 g_settings->registerChangedCallback("cinematic_camera_smoothing",
934 &settingChangedCallback, this);
935 g_settings->registerChangedCallback("camera_smoothing",
936 &settingChangedCallback, this);
941 m_cache_hold_aux1 = false; // This is initialised properly later
948 /****************************************************************************
950 ****************************************************************************/
959 delete server; // deleted first to stop all server threads
967 delete nodedef_manager;
968 delete itemdef_manager;
971 clearTextureNameCache();
973 g_settings->deregisterChangedCallback("doubletap_jump",
974 &settingChangedCallback, this);
975 g_settings->deregisterChangedCallback("enable_clouds",
976 &settingChangedCallback, this);
977 g_settings->deregisterChangedCallback("enable_particles",
978 &settingChangedCallback, this);
979 g_settings->deregisterChangedCallback("enable_fog",
980 &settingChangedCallback, this);
981 g_settings->deregisterChangedCallback("mouse_sensitivity",
982 &settingChangedCallback, this);
983 g_settings->deregisterChangedCallback("repeat_place_time",
984 &settingChangedCallback, this);
985 g_settings->deregisterChangedCallback("noclip",
986 &settingChangedCallback, this);
987 g_settings->deregisterChangedCallback("free_move",
988 &settingChangedCallback, this);
989 g_settings->deregisterChangedCallback("cinematic",
990 &settingChangedCallback, this);
991 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
992 &settingChangedCallback, this);
993 g_settings->deregisterChangedCallback("camera_smoothing",
994 &settingChangedCallback, this);
997 bool Game::startup(bool *kill,
999 RenderingEngine *rendering_engine,
1000 const GameStartData &start_data,
1001 std::string &error_message,
1003 ChatBackend *chat_backend)
1007 m_rendering_engine = rendering_engine;
1008 device = m_rendering_engine->get_raw_device();
1010 this->error_message = &error_message;
1011 reconnect_requested = reconnect;
1012 this->input = input;
1013 this->chat_backend = chat_backend;
1014 simple_singleplayer_mode = start_data.isSinglePlayer();
1016 input->keycache.populate();
1018 driver = device->getVideoDriver();
1019 smgr = m_rendering_engine->get_scene_manager();
1021 smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1024 runData = GameRunData();
1025 runData.time_from_last_punch = 10.0;
1027 m_game_ui->initFlags();
1029 m_invert_mouse = g_settings->getBool("invert_mouse");
1030 m_first_loop_after_window_activation = true;
1032 g_client_translations->clear();
1034 // address can change if simple_singleplayer_mode
1035 if (!init(start_data.world_spec.path, start_data.address,
1036 start_data.socket_port, start_data.game_spec))
1039 if (!createClient(start_data))
1042 m_rendering_engine->initialize(client, hud);
1050 ProfilerGraph graph;
1051 RunStats stats = { 0 };
1052 CameraOrientation cam_view_target = { 0 };
1053 CameraOrientation cam_view = { 0 };
1054 FpsControl draw_times = { 0 };
1055 f32 dtime; // in seconds
1057 /* Clear the profiler */
1058 Profiler::GraphValues dummyvalues;
1059 g_profiler->graphGet(dummyvalues);
1061 draw_times.last_time = m_rendering_engine->get_timer_time();
1063 set_light_table(g_settings->getFloat("display_gamma"));
1066 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1067 && client->checkPrivilege("fast");
1070 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1071 g_settings->getU16("screen_h"));
1073 while (m_rendering_engine->run()
1074 && !(*kill || g_gamecallback->shutdown_requested
1075 || (server && server->isShutdownRequested()))) {
1077 const irr::core::dimension2d<u32> ¤t_screen_size =
1078 m_rendering_engine->get_video_driver()->getScreenSize();
1079 // Verify if window size has changed and save it if it's the case
1080 // Ensure evaluating settings->getBool after verifying screensize
1081 // First condition is cheaper
1082 if (previous_screen_size != current_screen_size &&
1083 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1084 g_settings->getBool("autosave_screensize")) {
1085 g_settings->setU16("screen_w", current_screen_size.Width);
1086 g_settings->setU16("screen_h", current_screen_size.Height);
1087 previous_screen_size = current_screen_size;
1090 // Calculate dtime =
1091 // m_rendering_engine->run() from this iteration
1092 // + Sleep time until the wanted FPS are reached
1093 limitFps(&draw_times, &dtime);
1095 // Prepare render data for next iteration
1097 updateStats(&stats, draw_times, dtime);
1098 updateInteractTimers(dtime);
1100 if (!checkConnection())
1102 if (!handleCallbacks())
1107 m_game_ui->clearInfoText();
1108 hud->resizeHotbar();
1110 updateProfilers(stats, draw_times, dtime);
1111 processUserInput(dtime);
1112 // Update camera before player movement to avoid camera lag of one frame
1113 updateCameraDirection(&cam_view_target, dtime);
1114 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1115 cam_view.camera_yaw) * m_cache_cam_smoothing;
1116 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1117 cam_view.camera_pitch) * m_cache_cam_smoothing;
1118 updatePlayerControl(cam_view);
1120 processClientEvents(&cam_view_target);
1121 updateCamera(draw_times.busy_time, dtime);
1123 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
1124 m_game_ui->m_flags.show_debug);
1125 updateFrame(&graph, &stats, dtime, cam_view);
1126 updateProfilerGraphs(&graph);
1128 // Update if minimap has been disabled by the server
1129 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1131 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1138 void Game::shutdown()
1140 m_rendering_engine->finalize();
1141 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1142 if (g_settings->get("3d_mode") == "pageflip") {
1143 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1146 auto formspec = m_game_ui->getFormspecGUI();
1148 formspec->quitMenu();
1150 #ifdef HAVE_TOUCHSCREENGUI
1151 g_touchscreengui->hide();
1154 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1159 if (gui_chat_console)
1160 gui_chat_console->drop();
1166 while (g_menumgr.menuCount() > 0) {
1167 g_menumgr.m_stack.front()->setVisible(false);
1168 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1171 m_game_ui->deleteFormspec();
1173 chat_backend->addMessage(L"", L"# Disconnected.");
1174 chat_backend->addMessage(L"", L"");
1175 m_chat_log_buf.clear();
1179 while (!client->isShutdown()) {
1180 assert(texture_src != NULL);
1181 assert(shader_src != NULL);
1182 texture_src->processQueue();
1183 shader_src->processQueue();
1190 /****************************************************************************/
1191 /****************************************************************************
1193 ****************************************************************************/
1194 /****************************************************************************/
1197 const std::string &map_dir,
1198 const std::string &address,
1200 const SubgameSpec &gamespec)
1202 texture_src = createTextureSource();
1204 showOverlayMessage(N_("Loading..."), 0, 0);
1206 shader_src = createShaderSource();
1208 itemdef_manager = createItemDefManager();
1209 nodedef_manager = createNodeDefManager();
1211 eventmgr = new EventManager();
1212 quicktune = new QuicktuneShortcutter();
1214 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1215 && eventmgr && quicktune))
1221 // Create a server if not connecting to an existing one
1222 if (address.empty()) {
1223 if (!createSingleplayerServer(map_dir, gamespec, port))
1230 bool Game::initSound()
1233 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1234 infostream << "Attempting to use OpenAL audio" << std::endl;
1235 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1237 infostream << "Failed to initialize OpenAL audio" << std::endl;
1239 infostream << "Sound disabled." << std::endl;
1243 infostream << "Using dummy audio." << std::endl;
1244 sound = &dummySoundManager;
1245 sound_is_dummy = true;
1248 soundmaker = new SoundMaker(sound, nodedef_manager);
1252 soundmaker->registerReceiver(eventmgr);
1257 bool Game::createSingleplayerServer(const std::string &map_dir,
1258 const SubgameSpec &gamespec, u16 port)
1260 showOverlayMessage(N_("Creating server..."), 0, 5);
1262 std::string bind_str = g_settings->get("bind_address");
1263 Address bind_addr(0, 0, 0, 0, port);
1265 if (g_settings->getBool("ipv6_server")) {
1266 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1270 bind_addr.Resolve(bind_str.c_str());
1271 } catch (ResolveError &e) {
1272 infostream << "Resolving bind address \"" << bind_str
1273 << "\" failed: " << e.what()
1274 << " -- Listening on all addresses." << std::endl;
1277 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1278 *error_message = "Unable to listen on " +
1279 bind_addr.serializeString() +
1280 " because IPv6 is disabled";
1281 errorstream << *error_message << std::endl;
1285 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1286 false, nullptr, error_message);
1292 bool Game::createClient(const GameStartData &start_data)
1294 showOverlayMessage(N_("Creating client..."), 0, 10);
1296 draw_control = new MapDrawControl();
1300 bool could_connect, connect_aborted;
1301 #ifdef HAVE_TOUCHSCREENGUI
1302 if (g_touchscreengui) {
1303 g_touchscreengui->init(texture_src);
1304 g_touchscreengui->hide();
1307 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1310 if (!could_connect) {
1311 if (error_message->empty() && !connect_aborted) {
1312 // Should not happen if error messages are set properly
1313 *error_message = "Connection failed for unknown reason";
1314 errorstream << *error_message << std::endl;
1319 if (!getServerContent(&connect_aborted)) {
1320 if (error_message->empty() && !connect_aborted) {
1321 // Should not happen if error messages are set properly
1322 *error_message = "Connection failed for unknown reason";
1323 errorstream << *error_message << std::endl;
1328 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1329 &m_flags.force_fog_off, &runData.fog_range, client);
1330 shader_src->addShaderConstantSetterFactory(scsf);
1332 // Update cached textures, meshes and materials
1333 client->afterContentReceived();
1337 camera = new Camera(*draw_control, client, m_rendering_engine);
1338 if (!camera->successfullyCreated(*error_message))
1340 client->setCamera(camera);
1344 if (m_cache_enable_clouds)
1345 clouds = new Clouds(smgr, -1, time(0));
1349 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
1351 skybox = NULL; // This is used/set later on in the main run loop
1353 /* Pre-calculated values
1355 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1357 v2u32 size = t->getOriginalSize();
1358 crack_animation_length = size.Y / size.X;
1360 crack_animation_length = 5;
1366 /* Set window caption
1368 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1370 str += utf8_to_wide(g_version_hash);
1372 const wchar_t *text = nullptr;
1373 if (simple_singleplayer_mode)
1374 text = wgettext("Singleplayer");
1376 text = wgettext("Multiplayer");
1383 str += driver->getName();
1386 device->setWindowCaption(str.c_str());
1388 LocalPlayer *player = client->getEnv().getLocalPlayer();
1389 player->hurt_tilt_timer = 0;
1390 player->hurt_tilt_strength = 0;
1392 hud = new Hud(client, player, &player->inventory);
1394 mapper = client->getMinimap();
1396 if (mapper && client->modsLoaded())
1397 client->getScript()->on_minimap_ready(mapper);
1402 bool Game::initGui()
1406 // Remove stale "recent" chat messages from previous connections
1407 chat_backend->clearRecentChat();
1409 // Make sure the size of the recent messages buffer is right
1410 chat_backend->applySettings();
1412 // Chat backend and console
1413 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1414 -1, chat_backend, client, &g_menumgr);
1416 #ifdef HAVE_TOUCHSCREENGUI
1418 if (g_touchscreengui)
1419 g_touchscreengui->show();
1426 bool Game::connectToServer(const GameStartData &start_data,
1427 bool *connect_ok, bool *connection_aborted)
1429 *connect_ok = false; // Let's not be overly optimistic
1430 *connection_aborted = false;
1431 bool local_server_mode = false;
1433 showOverlayMessage(N_("Resolving address..."), 0, 15);
1435 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1438 connect_address.Resolve(start_data.address.c_str());
1440 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1441 //connect_address.Resolve("localhost");
1442 if (connect_address.isIPv6()) {
1443 IPv6AddressBytes addr_bytes;
1444 addr_bytes.bytes[15] = 1;
1445 connect_address.setAddress(&addr_bytes);
1447 connect_address.setAddress(127, 0, 0, 1);
1449 local_server_mode = true;
1451 } catch (ResolveError &e) {
1452 *error_message = std::string("Couldn't resolve address: ") + e.what();
1453 errorstream << *error_message << std::endl;
1457 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1458 *error_message = "Unable to connect to " +
1459 connect_address.serializeString() +
1460 " because IPv6 is disabled";
1461 errorstream << *error_message << std::endl;
1465 client = new Client(start_data.name.c_str(),
1466 start_data.password, start_data.address,
1467 *draw_control, texture_src, shader_src,
1468 itemdef_manager, nodedef_manager, sound, eventmgr,
1469 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get());
1471 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1473 infostream << "Connecting to server at ";
1474 connect_address.print(&infostream);
1475 infostream << std::endl;
1477 client->connect(connect_address,
1478 simple_singleplayer_mode || local_server_mode);
1481 Wait for server to accept connection
1487 FpsControl fps_control = { 0 };
1489 f32 wait_time = 0; // in seconds
1491 fps_control.last_time = m_rendering_engine->get_timer_time();
1493 while (m_rendering_engine->run()) {
1495 limitFps(&fps_control, &dtime);
1497 // Update client and server
1498 client->step(dtime);
1501 server->step(dtime);
1504 if (client->getState() == LC_Init) {
1510 if (*connection_aborted)
1513 if (client->accessDenied()) {
1514 *error_message = "Access denied. Reason: "
1515 + client->accessDeniedReason();
1516 *reconnect_requested = client->reconnectRequested();
1517 errorstream << *error_message << std::endl;
1521 if (input->cancelPressed()) {
1522 *connection_aborted = true;
1523 infostream << "Connect aborted [Escape]" << std::endl;
1527 if (client->m_is_registration_confirmation_state) {
1528 if (registration_confirmation_shown) {
1529 // Keep drawing the GUI
1530 m_rendering_engine->draw_menu_scene(guienv, dtime, true);
1532 registration_confirmation_shown = true;
1533 (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
1534 &g_menumgr, client, start_data.name, start_data.password,
1535 connection_aborted, texture_src))->drop();
1539 // Only time out if we aren't waiting for the server we started
1540 if (!start_data.address.empty() && wait_time > 10) {
1541 *error_message = "Connection timed out.";
1542 errorstream << *error_message << std::endl;
1547 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1550 } catch (con::PeerNotFoundException &e) {
1551 // TODO: Should something be done here? At least an info/error
1559 bool Game::getServerContent(bool *aborted)
1563 FpsControl fps_control = { 0 };
1564 f32 dtime; // in seconds
1566 fps_control.last_time = m_rendering_engine->get_timer_time();
1568 while (m_rendering_engine->run()) {
1570 limitFps(&fps_control, &dtime);
1572 // Update client and server
1573 client->step(dtime);
1576 server->step(dtime);
1579 if (client->mediaReceived() && client->itemdefReceived() &&
1580 client->nodedefReceived()) {
1585 if (!checkConnection())
1588 if (client->getState() < LC_Init) {
1589 *error_message = "Client disconnected";
1590 errorstream << *error_message << std::endl;
1594 if (input->cancelPressed()) {
1596 infostream << "Connect aborted [Escape]" << std::endl;
1603 if (!client->itemdefReceived()) {
1604 const wchar_t *text = wgettext("Item definitions...");
1606 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1609 } else if (!client->nodedefReceived()) {
1610 const wchar_t *text = wgettext("Node definitions...");
1612 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1616 std::stringstream message;
1617 std::fixed(message);
1618 message.precision(0);
1619 float receive = client->mediaReceiveProgress() * 100;
1620 message << gettext("Media...");
1622 message << " " << receive << "%";
1623 message.precision(2);
1625 if ((USE_CURL == 0) ||
1626 (!g_settings->getBool("enable_remote_media_server"))) {
1627 float cur = client->getCurRate();
1628 std::string cur_unit = gettext("KiB/s");
1632 cur_unit = gettext("MiB/s");
1635 message << " (" << cur << ' ' << cur_unit << ")";
1638 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1639 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
1640 texture_src, dtime, progress);
1648 /****************************************************************************/
1649 /****************************************************************************
1651 ****************************************************************************/
1652 /****************************************************************************/
1654 inline void Game::updateInteractTimers(f32 dtime)
1656 if (runData.nodig_delay_timer >= 0)
1657 runData.nodig_delay_timer -= dtime;
1659 if (runData.object_hit_delay_timer >= 0)
1660 runData.object_hit_delay_timer -= dtime;
1662 runData.time_from_last_punch += dtime;
1666 /* returns false if game should exit, otherwise true
1668 inline bool Game::checkConnection()
1670 if (client->accessDenied()) {
1671 *error_message = "Access denied. Reason: "
1672 + client->accessDeniedReason();
1673 *reconnect_requested = client->reconnectRequested();
1674 errorstream << *error_message << std::endl;
1682 /* returns false if game should exit, otherwise true
1684 inline bool Game::handleCallbacks()
1686 if (g_gamecallback->disconnect_requested) {
1687 g_gamecallback->disconnect_requested = false;
1691 if (g_gamecallback->changepassword_requested) {
1692 (new GUIPasswordChange(guienv, guiroot, -1,
1693 &g_menumgr, client, texture_src))->drop();
1694 g_gamecallback->changepassword_requested = false;
1697 if (g_gamecallback->changevolume_requested) {
1698 (new GUIVolumeChange(guienv, guiroot, -1,
1699 &g_menumgr, texture_src))->drop();
1700 g_gamecallback->changevolume_requested = false;
1703 if (g_gamecallback->keyconfig_requested) {
1704 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1705 &g_menumgr, texture_src))->drop();
1706 g_gamecallback->keyconfig_requested = false;
1709 if (g_gamecallback->keyconfig_changed) {
1710 input->keycache.populate(); // update the cache with new settings
1711 g_gamecallback->keyconfig_changed = false;
1718 void Game::processQueues()
1720 texture_src->processQueue();
1721 itemdef_manager->processQueue(client);
1722 shader_src->processQueue();
1726 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1729 float profiler_print_interval =
1730 g_settings->getFloat("profiler_print_interval");
1731 bool print_to_log = true;
1733 if (profiler_print_interval == 0) {
1734 print_to_log = false;
1735 profiler_print_interval = 3;
1738 if (profiler_interval.step(dtime, profiler_print_interval)) {
1740 infostream << "Profiler:" << std::endl;
1741 g_profiler->print(infostream);
1744 m_game_ui->updateProfiler();
1745 g_profiler->clear();
1748 // Update update graphs
1749 g_profiler->graphAdd("Time non-rendering [ms]",
1750 draw_times.busy_time - stats.drawtime);
1752 g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time);
1753 g_profiler->graphAdd("FPS", 1.0f / dtime);
1756 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1763 /* Time average and jitter calculation
1765 jp = &stats->dtime_jitter;
1766 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1768 jitter = dtime - jp->avg;
1770 if (jitter > jp->max)
1773 jp->counter += dtime;
1775 if (jp->counter > 0.0) {
1777 jp->max_sample = jp->max;
1778 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1782 /* Busytime average and jitter calculation
1784 jp = &stats->busy_time_jitter;
1785 jp->avg = jp->avg + draw_times.busy_time * 0.02;
1787 jitter = draw_times.busy_time - jp->avg;
1789 if (jitter > jp->max)
1791 if (jitter < jp->min)
1794 jp->counter += dtime;
1796 if (jp->counter > 0.0) {
1798 jp->max_sample = jp->max;
1799 jp->min_sample = jp->min;
1807 /****************************************************************************
1809 ****************************************************************************/
1811 void Game::processUserInput(f32 dtime)
1813 // Reset input if window not active or some menu is active
1814 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1816 #ifdef HAVE_TOUCHSCREENGUI
1817 g_touchscreengui->hide();
1820 #ifdef HAVE_TOUCHSCREENGUI
1821 else if (g_touchscreengui) {
1822 /* on touchscreengui step may generate own input events which ain't
1823 * what we want in case we just did clear them */
1824 g_touchscreengui->step(dtime);
1828 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1829 gui_chat_console->closeConsoleAtOnce();
1832 // Input handler step() (used by the random input generator)
1836 auto formspec = m_game_ui->getFormspecGUI();
1838 formspec->getAndroidUIInput();
1840 handleAndroidChatInput();
1843 // Increase timer for double tap of "keymap_jump"
1844 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1845 runData.jump_timer += dtime;
1848 processItemSelection(&runData.new_playeritem);
1852 void Game::processKeyInput()
1854 if (wasKeyDown(KeyType::DROP)) {
1855 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1856 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1857 toggleAutoforward();
1858 } else if (wasKeyDown(KeyType::BACKWARD)) {
1859 if (g_settings->getBool("continuous_forward"))
1860 toggleAutoforward();
1861 } else if (wasKeyDown(KeyType::INVENTORY)) {
1863 } else if (input->cancelPressed()) {
1865 m_android_chat_open = false;
1867 if (!gui_chat_console->isOpenInhibited()) {
1870 } else if (wasKeyDown(KeyType::CHAT)) {
1871 openConsole(0.2, L"");
1872 } else if (wasKeyDown(KeyType::CMD)) {
1873 openConsole(0.2, L"/");
1874 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1875 if (client->modsLoaded())
1876 openConsole(0.2, L".");
1878 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1879 } else if (wasKeyDown(KeyType::CONSOLE)) {
1880 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1881 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1883 } else if (wasKeyDown(KeyType::JUMP)) {
1884 toggleFreeMoveAlt();
1885 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1887 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1889 } else if (wasKeyDown(KeyType::NOCLIP)) {
1892 } else if (wasKeyDown(KeyType::MUTE)) {
1893 if (g_settings->getBool("enable_sound")) {
1894 bool new_mute_sound = !g_settings->getBool("mute_sound");
1895 g_settings->setBool("mute_sound", new_mute_sound);
1897 m_game_ui->showTranslatedStatusText("Sound muted");
1899 m_game_ui->showTranslatedStatusText("Sound unmuted");
1901 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1903 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1904 if (g_settings->getBool("enable_sound")) {
1905 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1907 g_settings->setFloat("sound_volume", new_volume);
1908 const wchar_t *str = wgettext("Volume changed to %d%%");
1909 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1911 m_game_ui->showStatusText(buf);
1913 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1915 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1916 if (g_settings->getBool("enable_sound")) {
1917 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1919 g_settings->setFloat("sound_volume", new_volume);
1920 const wchar_t *str = wgettext("Volume changed to %d%%");
1921 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1923 m_game_ui->showStatusText(buf);
1925 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1928 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1929 || wasKeyDown(KeyType::DEC_VOLUME)) {
1930 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1932 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1934 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1935 client->makeScreenshot();
1936 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
1937 hud->toggleBlockBounds();
1938 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1939 m_game_ui->toggleHud();
1940 } else if (wasKeyDown(KeyType::MINIMAP)) {
1941 toggleMinimap(isKeyDown(KeyType::SNEAK));
1942 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1943 m_game_ui->toggleChat();
1944 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1946 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1947 toggleUpdateCamera();
1948 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1950 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1951 m_game_ui->toggleProfiler();
1952 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1953 increaseViewRange();
1954 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1955 decreaseViewRange();
1956 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1957 toggleFullViewRange();
1958 } else if (wasKeyDown(KeyType::ZOOM)) {
1960 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1962 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1964 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1966 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1970 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1971 runData.reset_jump_timer = false;
1972 runData.jump_timer = 0.0f;
1975 if (quicktune->hasMessage()) {
1976 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
1980 void Game::processItemSelection(u16 *new_playeritem)
1982 LocalPlayer *player = client->getEnv().getLocalPlayer();
1984 /* Item selection using mouse wheel
1986 *new_playeritem = player->getWieldIndex();
1988 s32 wheel = input->getMouseWheel();
1989 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
1990 player->hud_hotbar_itemcount - 1);
1994 if (wasKeyDown(KeyType::HOTBAR_NEXT))
1997 if (wasKeyDown(KeyType::HOTBAR_PREV))
2001 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2003 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2006 /* Item selection using hotbar slot keys
2008 for (u16 i = 0; i <= max_item; i++) {
2009 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2010 *new_playeritem = i;
2017 void Game::dropSelectedItem(bool single_item)
2019 IDropAction *a = new IDropAction();
2020 a->count = single_item ? 1 : 0;
2021 a->from_inv.setCurrentPlayer();
2022 a->from_list = "main";
2023 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2024 client->inventoryAction(a);
2028 void Game::openInventory()
2031 * Don't permit to open inventory is CAO or player doesn't exists.
2032 * This prevent showing an empty inventory at player load
2035 LocalPlayer *player = client->getEnv().getLocalPlayer();
2036 if (!player || !player->getCAO())
2039 infostream << "Game: Launching inventory" << std::endl;
2041 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2043 InventoryLocation inventoryloc;
2044 inventoryloc.setCurrentPlayer();
2046 if (!client->modsLoaded()
2047 || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2048 TextDest *txt_dst = new TextDestPlayerInventory(client);
2049 auto *&formspec = m_game_ui->updateFormspec("");
2050 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2051 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2053 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2058 void Game::openConsole(float scale, const wchar_t *line)
2060 assert(scale > 0.0f && scale <= 1.0f);
2063 porting::showInputDialog(gettext("ok"), "", "", 2);
2064 m_android_chat_open = true;
2066 if (gui_chat_console->isOpenInhibited())
2068 gui_chat_console->openConsole(scale);
2070 gui_chat_console->setCloseOnEnter(true);
2071 gui_chat_console->replaceAndAddToHistory(line);
2077 void Game::handleAndroidChatInput()
2079 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2080 std::string text = porting::getInputDialogValue();
2081 client->typeChatMessage(utf8_to_wide(text));
2082 m_android_chat_open = false;
2088 void Game::toggleFreeMove()
2090 bool free_move = !g_settings->getBool("free_move");
2091 g_settings->set("free_move", bool_to_cstr(free_move));
2094 if (client->checkPrivilege("fly")) {
2095 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2097 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2100 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2104 void Game::toggleFreeMoveAlt()
2106 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2109 runData.reset_jump_timer = true;
2113 void Game::togglePitchMove()
2115 bool pitch_move = !g_settings->getBool("pitch_move");
2116 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2119 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2121 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2126 void Game::toggleFast()
2128 bool fast_move = !g_settings->getBool("fast_move");
2129 bool has_fast_privs = client->checkPrivilege("fast");
2130 g_settings->set("fast_move", bool_to_cstr(fast_move));
2133 if (has_fast_privs) {
2134 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2136 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2139 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2143 m_cache_hold_aux1 = fast_move && has_fast_privs;
2148 void Game::toggleNoClip()
2150 bool noclip = !g_settings->getBool("noclip");
2151 g_settings->set("noclip", bool_to_cstr(noclip));
2154 if (client->checkPrivilege("noclip")) {
2155 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2157 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2160 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2164 void Game::toggleCinematic()
2166 bool cinematic = !g_settings->getBool("cinematic");
2167 g_settings->set("cinematic", bool_to_cstr(cinematic));
2170 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2172 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2175 // Autoforward by toggling continuous forward.
2176 void Game::toggleAutoforward()
2178 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2179 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2181 if (autorun_enabled)
2182 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2184 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2187 void Game::toggleMinimap(bool shift_pressed)
2189 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2193 mapper->toggleMinimapShape();
2197 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2199 // Not so satisying code to keep compatibility with old fixed mode system
2201 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2203 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2204 m_game_ui->m_flags.show_minimap = false;
2207 // If radar is disabled, try to find a non radar mode or fall back to 0
2208 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2209 while (mapper->getModeIndex() &&
2210 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2213 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2217 // End of 'not so satifying code'
2218 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2219 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2220 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2222 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2225 void Game::toggleFog()
2227 bool fog_enabled = g_settings->getBool("enable_fog");
2228 g_settings->setBool("enable_fog", !fog_enabled);
2230 m_game_ui->showTranslatedStatusText("Fog disabled");
2232 m_game_ui->showTranslatedStatusText("Fog enabled");
2236 void Game::toggleDebug()
2238 // Initial / 4x toggle: Chat only
2239 // 1x toggle: Debug text with chat
2240 // 2x toggle: Debug text with profiler graph
2241 // 3x toggle: Debug text and wireframe
2242 if (!m_game_ui->m_flags.show_debug) {
2243 m_game_ui->m_flags.show_debug = true;
2244 m_game_ui->m_flags.show_profiler_graph = false;
2245 draw_control->show_wireframe = false;
2246 m_game_ui->showTranslatedStatusText("Debug info shown");
2247 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2248 m_game_ui->m_flags.show_profiler_graph = true;
2249 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2250 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2251 m_game_ui->m_flags.show_profiler_graph = false;
2252 draw_control->show_wireframe = true;
2253 m_game_ui->showTranslatedStatusText("Wireframe shown");
2255 m_game_ui->m_flags.show_debug = false;
2256 m_game_ui->m_flags.show_profiler_graph = false;
2257 draw_control->show_wireframe = false;
2258 if (client->checkPrivilege("debug")) {
2259 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2261 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2267 void Game::toggleUpdateCamera()
2269 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2270 if (m_flags.disable_camera_update)
2271 m_game_ui->showTranslatedStatusText("Camera update disabled");
2273 m_game_ui->showTranslatedStatusText("Camera update enabled");
2277 void Game::increaseViewRange()
2279 s16 range = g_settings->getS16("viewing_range");
2280 s16 range_new = range + 10;
2284 if (range_new > 4000) {
2286 str = wgettext("Viewing range is at maximum: %d");
2287 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2289 m_game_ui->showStatusText(buf);
2292 str = wgettext("Viewing range changed to %d");
2293 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2295 m_game_ui->showStatusText(buf);
2297 g_settings->set("viewing_range", itos(range_new));
2301 void Game::decreaseViewRange()
2303 s16 range = g_settings->getS16("viewing_range");
2304 s16 range_new = range - 10;
2308 if (range_new < 20) {
2310 str = wgettext("Viewing range is at minimum: %d");
2311 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2313 m_game_ui->showStatusText(buf);
2315 str = wgettext("Viewing range changed to %d");
2316 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2318 m_game_ui->showStatusText(buf);
2320 g_settings->set("viewing_range", itos(range_new));
2324 void Game::toggleFullViewRange()
2326 draw_control->range_all = !draw_control->range_all;
2327 if (draw_control->range_all)
2328 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2330 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2334 void Game::checkZoomEnabled()
2336 LocalPlayer *player = client->getEnv().getLocalPlayer();
2337 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2338 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2341 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2343 if ((device->isWindowActive() && device->isWindowFocused()
2344 && !isMenuActive()) || input->isRandom()) {
2347 if (!input->isRandom()) {
2348 // Mac OSX gets upset if this is set every frame
2349 if (device->getCursorControl()->isVisible())
2350 device->getCursorControl()->setVisible(false);
2354 if (m_first_loop_after_window_activation) {
2355 m_first_loop_after_window_activation = false;
2357 input->setMousePos(driver->getScreenSize().Width / 2,
2358 driver->getScreenSize().Height / 2);
2360 updateCameraOrientation(cam, dtime);
2366 // Mac OSX gets upset if this is set every frame
2367 if (!device->getCursorControl()->isVisible())
2368 device->getCursorControl()->setVisible(true);
2371 m_first_loop_after_window_activation = true;
2376 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2377 // responsiveness independently of FOV.
2378 f32 Game::getSensitivityScaleFactor() const
2380 f32 fov_y = client->getCamera()->getFovY();
2382 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2383 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2385 return tan(fov_y / 2.0f) * 1.3763818698f;
2388 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2390 #ifdef HAVE_TOUCHSCREENGUI
2391 if (g_touchscreengui) {
2392 cam->camera_yaw += g_touchscreengui->getYawChange();
2393 cam->camera_pitch = g_touchscreengui->getPitch();
2396 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2397 v2s32 dist = input->getMousePos() - center;
2399 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2403 f32 sens_scale = getSensitivityScaleFactor();
2404 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2405 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2407 if (dist.X != 0 || dist.Y != 0)
2408 input->setMousePos(center.X, center.Y);
2409 #ifdef HAVE_TOUCHSCREENGUI
2413 if (m_cache_enable_joysticks) {
2414 f32 sens_scale = getSensitivityScaleFactor();
2415 f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime * sens_scale;
2416 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2417 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2420 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2424 void Game::updatePlayerControl(const CameraOrientation &cam)
2426 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2428 // DO NOT use the isKeyDown method for the forward, backward, left, right
2429 // buttons, as the code that uses the controls needs to be able to
2430 // distinguish between the two in order to know when to use joysticks.
2432 PlayerControl control(
2433 input->isKeyDown(KeyType::FORWARD),
2434 input->isKeyDown(KeyType::BACKWARD),
2435 input->isKeyDown(KeyType::LEFT),
2436 input->isKeyDown(KeyType::RIGHT),
2437 isKeyDown(KeyType::JUMP),
2438 isKeyDown(KeyType::AUX1),
2439 isKeyDown(KeyType::SNEAK),
2440 isKeyDown(KeyType::ZOOM),
2441 isKeyDown(KeyType::DIG),
2442 isKeyDown(KeyType::PLACE),
2445 input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE),
2446 input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE)
2449 u32 keypress_bits = (
2450 ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) |
2451 ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) |
2452 ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) |
2453 ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) |
2454 ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) |
2455 ( (u32)(isKeyDown(KeyType::AUX1) & 0x1) << 5) |
2456 ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) |
2457 ( (u32)(isKeyDown(KeyType::DIG) & 0x1) << 7) |
2458 ( (u32)(isKeyDown(KeyType::PLACE) & 0x1) << 8) |
2459 ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9)
2463 /* For Android, simulate holding down AUX1 (fast move) if the user has
2464 * the fast_move setting toggled on. If there is an aux1 key defined for
2465 * Android then its meaning is inverted (i.e. holding aux1 means walk and
2468 if (m_cache_hold_aux1) {
2469 control.aux1 = control.aux1 ^ true;
2470 keypress_bits ^= ((u32)(1U << 5));
2474 LocalPlayer *player = client->getEnv().getLocalPlayer();
2476 // autojump if set: simulate "jump" key
2477 if (player->getAutojump()) {
2478 control.jump = true;
2479 keypress_bits |= 1U << 4;
2482 // autoforward if set: simulate "up" key
2483 if (player->getPlayerSettings().continuous_forward &&
2484 client->activeObjectsReceived() && !player->isDead()) {
2486 keypress_bits |= 1U << 0;
2489 client->setPlayerControl(control);
2490 player->keyPressed = keypress_bits;
2496 inline void Game::step(f32 *dtime)
2498 bool can_be_and_is_paused =
2499 (simple_singleplayer_mode && g_menumgr.pausesGame());
2501 if (can_be_and_is_paused) { // This is for a singleplayer server
2502 *dtime = 0; // No time passes
2504 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
2508 server->step(*dtime);
2510 client->step(*dtime);
2514 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2517 for (auto &&child: node->getChildren())
2518 pauseNodeAnimation(paused, child);
2519 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2521 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2522 float speed = animated_node->getAnimationSpeed();
2525 paused.push_back({grab(animated_node), speed});
2526 animated_node->setAnimationSpeed(0.0f);
2529 void Game::pauseAnimation()
2531 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2534 void Game::resumeAnimation()
2536 for (auto &&pair: paused_animated_nodes)
2537 pair.first->setAnimationSpeed(pair.second);
2538 paused_animated_nodes.clear();
2541 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2542 {&Game::handleClientEvent_None},
2543 {&Game::handleClientEvent_PlayerDamage},
2544 {&Game::handleClientEvent_PlayerForceMove},
2545 {&Game::handleClientEvent_Deathscreen},
2546 {&Game::handleClientEvent_ShowFormSpec},
2547 {&Game::handleClientEvent_ShowLocalFormSpec},
2548 {&Game::handleClientEvent_HandleParticleEvent},
2549 {&Game::handleClientEvent_HandleParticleEvent},
2550 {&Game::handleClientEvent_HandleParticleEvent},
2551 {&Game::handleClientEvent_HudAdd},
2552 {&Game::handleClientEvent_HudRemove},
2553 {&Game::handleClientEvent_HudChange},
2554 {&Game::handleClientEvent_SetSky},
2555 {&Game::handleClientEvent_SetSun},
2556 {&Game::handleClientEvent_SetMoon},
2557 {&Game::handleClientEvent_SetStars},
2558 {&Game::handleClientEvent_OverrideDayNigthRatio},
2559 {&Game::handleClientEvent_CloudParams},
2562 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2564 FATAL_ERROR("ClientEvent type None received");
2567 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2569 if (client->modsLoaded())
2570 client->getScript()->on_damage_taken(event->player_damage.amount);
2572 // Damage flash and hurt tilt are not used at death
2573 if (client->getHP() > 0) {
2574 LocalPlayer *player = client->getEnv().getLocalPlayer();
2576 f32 hp_max = player->getCAO() ?
2577 player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2578 f32 damage_ratio = event->player_damage.amount / hp_max;
2580 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2581 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2583 player->hurt_tilt_timer = 1.5f;
2584 player->hurt_tilt_strength =
2585 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2588 // Play damage sound
2589 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2592 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2594 cam->camera_yaw = event->player_force_move.yaw;
2595 cam->camera_pitch = event->player_force_move.pitch;
2598 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2600 // If client scripting is enabled, deathscreen is handled by CSM code in
2601 // builtin/client/init.lua
2602 if (client->modsLoaded())
2603 client->getScript()->on_death();
2605 showDeathFormspec();
2607 /* Handle visualization */
2608 LocalPlayer *player = client->getEnv().getLocalPlayer();
2609 runData.damage_flash = 0;
2610 player->hurt_tilt_timer = 0;
2611 player->hurt_tilt_strength = 0;
2614 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2616 if (event->show_formspec.formspec->empty()) {
2617 auto formspec = m_game_ui->getFormspecGUI();
2618 if (formspec && (event->show_formspec.formname->empty()
2619 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2620 formspec->quitMenu();
2623 FormspecFormSource *fs_src =
2624 new FormspecFormSource(*(event->show_formspec.formspec));
2625 TextDestPlayerInventory *txt_dst =
2626 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2628 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2629 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2630 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2633 delete event->show_formspec.formspec;
2634 delete event->show_formspec.formname;
2637 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2639 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2640 LocalFormspecHandler *txt_dst =
2641 new LocalFormspecHandler(*event->show_formspec.formname, client);
2642 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2643 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2645 delete event->show_formspec.formspec;
2646 delete event->show_formspec.formname;
2649 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2650 CameraOrientation *cam)
2652 LocalPlayer *player = client->getEnv().getLocalPlayer();
2653 client->getParticleManager()->handleParticleEvent(event, client, player);
2656 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2658 LocalPlayer *player = client->getEnv().getLocalPlayer();
2660 u32 server_id = event->hudadd->server_id;
2661 // ignore if we already have a HUD with that ID
2662 auto i = m_hud_server_to_client.find(server_id);
2663 if (i != m_hud_server_to_client.end()) {
2664 delete event->hudadd;
2668 HudElement *e = new HudElement;
2669 e->type = static_cast<HudElementType>(event->hudadd->type);
2670 e->pos = event->hudadd->pos;
2671 e->name = event->hudadd->name;
2672 e->scale = event->hudadd->scale;
2673 e->text = event->hudadd->text;
2674 e->number = event->hudadd->number;
2675 e->item = event->hudadd->item;
2676 e->dir = event->hudadd->dir;
2677 e->align = event->hudadd->align;
2678 e->offset = event->hudadd->offset;
2679 e->world_pos = event->hudadd->world_pos;
2680 e->size = event->hudadd->size;
2681 e->z_index = event->hudadd->z_index;
2682 e->text2 = event->hudadd->text2;
2683 m_hud_server_to_client[server_id] = player->addHud(e);
2685 delete event->hudadd;
2688 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2690 LocalPlayer *player = client->getEnv().getLocalPlayer();
2692 auto i = m_hud_server_to_client.find(event->hudrm.id);
2693 if (i != m_hud_server_to_client.end()) {
2694 HudElement *e = player->removeHud(i->second);
2696 m_hud_server_to_client.erase(i);
2701 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2703 LocalPlayer *player = client->getEnv().getLocalPlayer();
2705 HudElement *e = nullptr;
2707 auto i = m_hud_server_to_client.find(event->hudchange->id);
2708 if (i != m_hud_server_to_client.end()) {
2709 e = player->getHud(i->second);
2713 delete event->hudchange;
2717 #define CASE_SET(statval, prop, dataprop) \
2719 e->prop = event->hudchange->dataprop; \
2722 switch (event->hudchange->stat) {
2723 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2725 CASE_SET(HUD_STAT_NAME, name, sdata);
2727 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2729 CASE_SET(HUD_STAT_TEXT, text, sdata);
2731 CASE_SET(HUD_STAT_NUMBER, number, data);
2733 CASE_SET(HUD_STAT_ITEM, item, data);
2735 CASE_SET(HUD_STAT_DIR, dir, data);
2737 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2739 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2741 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2743 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2745 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2747 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2752 delete event->hudchange;
2755 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2757 sky->setVisible(false);
2758 // Whether clouds are visible in front of a custom skybox.
2759 sky->setCloudsEnabled(event->set_sky->clouds);
2765 // Clear the old textures out in case we switch rendering type.
2766 sky->clearSkyboxTextures();
2767 // Handle according to type
2768 if (event->set_sky->type == "regular") {
2769 // Shows the mesh skybox
2770 sky->setVisible(true);
2771 // Update mesh based skybox colours if applicable.
2772 sky->setSkyColors(event->set_sky->sky_color);
2773 sky->setHorizonTint(
2774 event->set_sky->fog_sun_tint,
2775 event->set_sky->fog_moon_tint,
2776 event->set_sky->fog_tint_type
2778 } else if (event->set_sky->type == "skybox" &&
2779 event->set_sky->textures.size() == 6) {
2780 // Disable the dyanmic mesh skybox:
2781 sky->setVisible(false);
2783 sky->setFallbackBgColor(event->set_sky->bgcolor);
2784 // Set sunrise and sunset fog tinting:
2785 sky->setHorizonTint(
2786 event->set_sky->fog_sun_tint,
2787 event->set_sky->fog_moon_tint,
2788 event->set_sky->fog_tint_type
2790 // Add textures to skybox.
2791 for (int i = 0; i < 6; i++)
2792 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2794 // Handle everything else as plain color.
2795 if (event->set_sky->type != "plain")
2796 infostream << "Unknown sky type: "
2797 << (event->set_sky->type) << std::endl;
2798 sky->setVisible(false);
2799 sky->setFallbackBgColor(event->set_sky->bgcolor);
2800 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2801 sky->setHorizonTint(
2802 event->set_sky->bgcolor,
2803 event->set_sky->bgcolor,
2808 delete event->set_sky;
2811 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2813 sky->setSunVisible(event->sun_params->visible);
2814 sky->setSunTexture(event->sun_params->texture,
2815 event->sun_params->tonemap, texture_src);
2816 sky->setSunScale(event->sun_params->scale);
2817 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2818 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2819 delete event->sun_params;
2822 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2824 sky->setMoonVisible(event->moon_params->visible);
2825 sky->setMoonTexture(event->moon_params->texture,
2826 event->moon_params->tonemap, texture_src);
2827 sky->setMoonScale(event->moon_params->scale);
2828 delete event->moon_params;
2831 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2833 sky->setStarsVisible(event->star_params->visible);
2834 sky->setStarCount(event->star_params->count, false);
2835 sky->setStarColor(event->star_params->starcolor);
2836 sky->setStarScale(event->star_params->scale);
2837 delete event->star_params;
2840 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2841 CameraOrientation *cam)
2843 client->getEnv().setDayNightRatioOverride(
2844 event->override_day_night_ratio.do_override,
2845 event->override_day_night_ratio.ratio_f * 1000.0f);
2848 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2853 clouds->setDensity(event->cloud_params.density);
2854 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2855 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2856 clouds->setHeight(event->cloud_params.height);
2857 clouds->setThickness(event->cloud_params.thickness);
2858 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2861 void Game::processClientEvents(CameraOrientation *cam)
2863 while (client->hasClientEvents()) {
2864 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2865 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2866 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2867 (this->*evHandler.handler)(event.get(), cam);
2871 void Game::updateChat(f32 dtime, const v2u32 &screensize)
2873 // Get new messages from error log buffer
2874 while (!m_chat_log_buf.empty())
2875 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2877 // Get new messages from client
2878 std::wstring message;
2879 while (client->getChatMessage(message)) {
2880 chat_backend->addUnparsedMessage(message);
2883 // Remove old messages
2884 chat_backend->step(dtime);
2886 // Display all messages in a static text element
2887 m_game_ui->setChatText(chat_backend->getRecentChat(),
2888 chat_backend->getRecentBuffer().getLineCount());
2891 void Game::updateCamera(u32 busy_time, f32 dtime)
2893 LocalPlayer *player = client->getEnv().getLocalPlayer();
2896 For interaction purposes, get info about the held item
2898 - Is it a usable item?
2899 - Can it point to liquids?
2901 ItemStack playeritem;
2903 ItemStack selected, hand;
2904 playeritem = player->getWieldedItem(&selected, &hand);
2907 ToolCapabilities playeritem_toolcap =
2908 playeritem.getToolCapabilities(itemdef_manager);
2910 v3s16 old_camera_offset = camera->getOffset();
2912 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2913 GenericCAO *playercao = player->getCAO();
2915 // If playercao not loaded, don't change camera
2919 camera->toggleCameraMode();
2921 // Make the player visible depending on camera mode.
2922 playercao->updateMeshCulling();
2923 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2926 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2927 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2929 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2930 camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
2931 camera->step(dtime);
2933 v3f camera_position = camera->getPosition();
2934 v3f camera_direction = camera->getDirection();
2935 f32 camera_fov = camera->getFovMax();
2936 v3s16 camera_offset = camera->getOffset();
2938 m_camera_offset_changed = (camera_offset != old_camera_offset);
2940 if (!m_flags.disable_camera_update) {
2941 client->getEnv().getClientMap().updateCamera(camera_position,
2942 camera_direction, camera_fov, camera_offset);
2944 if (m_camera_offset_changed) {
2945 client->updateCameraOffset(camera_offset);
2946 client->getEnv().updateCameraOffset(camera_offset);
2949 clouds->updateCameraOffset(camera_offset);
2955 void Game::updateSound(f32 dtime)
2957 // Update sound listener
2958 v3s16 camera_offset = camera->getOffset();
2959 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
2960 v3f(0, 0, 0), // velocity
2961 camera->getDirection(),
2962 camera->getCameraNode()->getUpVector());
2964 bool mute_sound = g_settings->getBool("mute_sound");
2966 sound->setListenerGain(0.0f);
2968 // Check if volume is in the proper range, else fix it.
2969 float old_volume = g_settings->getFloat("sound_volume");
2970 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
2971 sound->setListenerGain(new_volume);
2973 if (old_volume != new_volume) {
2974 g_settings->setFloat("sound_volume", new_volume);
2978 LocalPlayer *player = client->getEnv().getLocalPlayer();
2980 // Tell the sound maker whether to make footstep sounds
2981 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
2983 // Update sound maker
2984 if (player->makes_footstep_sound)
2985 soundmaker->step(dtime);
2987 ClientMap &map = client->getEnv().getClientMap();
2988 MapNode n = map.getNode(player->getFootstepNodePos());
2989 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
2993 void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
2995 LocalPlayer *player = client->getEnv().getLocalPlayer();
2997 const v3f camera_direction = camera->getDirection();
2998 const v3s16 camera_offset = camera->getOffset();
3001 Calculate what block is the crosshair pointing to
3004 ItemStack selected_item, hand_item;
3005 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3007 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3008 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3010 core::line3d<f32> shootline;
3012 switch (camera->getCameraMode()) {
3013 case CAMERA_MODE_FIRST:
3014 // Shoot from camera position, with bobbing
3015 shootline.start = camera->getPosition();
3017 case CAMERA_MODE_THIRD:
3018 // Shoot from player head, no bobbing
3019 shootline.start = camera->getHeadPosition();
3021 case CAMERA_MODE_THIRD_FRONT:
3022 shootline.start = camera->getHeadPosition();
3023 // prevent player pointing anything in front-view
3027 shootline.end = shootline.start + camera_direction * BS * d;
3029 #ifdef HAVE_TOUCHSCREENGUI
3031 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3032 shootline = g_touchscreengui->getShootline();
3033 // Scale shootline to the acual distance the player can reach
3034 shootline.end = shootline.start
3035 + shootline.getVector().normalize() * BS * d;
3036 shootline.start += intToFloat(camera_offset, BS);
3037 shootline.end += intToFloat(camera_offset, BS);
3042 PointedThing pointed = updatePointedThing(shootline,
3043 selected_def.liquids_pointable,
3044 !runData.btn_down_for_dig,
3047 if (pointed != runData.pointed_old) {
3048 infostream << "Pointing at " << pointed.dump() << std::endl;
3049 hud->updateSelectionMesh(camera_offset);
3052 // Allow digging again if button is not pressed
3053 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3054 runData.digging_blocked = false;
3058 - releasing dig button
3059 - pointing away from node
3061 if (runData.digging) {
3062 if (wasKeyReleased(KeyType::DIG)) {
3063 infostream << "Dig button released (stopped digging)" << std::endl;
3064 runData.digging = false;
3065 } else if (pointed != runData.pointed_old) {
3066 if (pointed.type == POINTEDTHING_NODE
3067 && runData.pointed_old.type == POINTEDTHING_NODE
3068 && pointed.node_undersurface
3069 == runData.pointed_old.node_undersurface) {
3070 // Still pointing to the same node, but a different face.
3073 infostream << "Pointing away from node (stopped digging)" << std::endl;
3074 runData.digging = false;
3075 hud->updateSelectionMesh(camera_offset);
3079 if (!runData.digging) {
3080 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3081 client->setCrack(-1, v3s16(0, 0, 0));
3082 runData.dig_time = 0.0;
3084 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3085 // Remove e.g. torches faster when clicking instead of holding dig button
3086 runData.nodig_delay_timer = 0;
3087 runData.dig_instantly = false;
3090 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3091 runData.btn_down_for_dig = false;
3093 runData.punching = false;
3095 soundmaker->m_player_leftpunch_sound.name = "";
3097 // Prepare for repeating, unless we're not supposed to
3098 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3099 runData.repeat_place_timer += dtime;
3101 runData.repeat_place_timer = 0;
3103 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3104 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3105 !client->getScript()->on_item_use(selected_item, pointed)))
3106 client->interact(INTERACT_USE, pointed);
3107 } else if (pointed.type == POINTEDTHING_NODE) {
3108 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3109 } else if (pointed.type == POINTEDTHING_OBJECT) {
3110 v3f player_position = player->getPosition();
3111 handlePointingAtObject(pointed, tool_item, player_position, show_debug);
3112 } else if (isKeyDown(KeyType::DIG)) {
3113 // When button is held down in air, show continuous animation
3114 runData.punching = true;
3115 // Run callback even though item is not usable
3116 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3117 client->getScript()->on_item_use(selected_item, pointed);
3118 } else if (wasKeyPressed(KeyType::PLACE)) {
3119 handlePointingAtNothing(selected_item);
3122 runData.pointed_old = pointed;
3124 if (runData.punching || wasKeyPressed(KeyType::DIG))
3125 camera->setDigging(0); // dig animation
3127 input->clearWasKeyPressed();
3128 input->clearWasKeyReleased();
3129 // Ensure DIG & PLACE are marked as handled
3130 wasKeyDown(KeyType::DIG);
3131 wasKeyDown(KeyType::PLACE);
3133 input->joystick.clearWasKeyPressed(KeyType::DIG);
3134 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3136 input->joystick.clearWasKeyReleased(KeyType::DIG);
3137 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3141 PointedThing Game::updatePointedThing(
3142 const core::line3d<f32> &shootline,
3143 bool liquids_pointable,
3144 bool look_for_object,
3145 const v3s16 &camera_offset)
3147 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3148 selectionboxes->clear();
3149 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3150 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3151 "show_entity_selectionbox");
3153 ClientEnvironment &env = client->getEnv();
3154 ClientMap &map = env.getClientMap();
3155 const NodeDefManager *nodedef = map.getNodeDefManager();
3157 runData.selected_object = NULL;
3158 hud->pointing_at_object = false;
3160 RaycastState s(shootline, look_for_object, liquids_pointable);
3161 PointedThing result;
3162 env.continueRaycast(&s, &result);
3163 if (result.type == POINTEDTHING_OBJECT) {
3164 hud->pointing_at_object = true;
3166 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3167 aabb3f selection_box;
3168 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3169 runData.selected_object->getSelectionBox(&selection_box)) {
3170 v3f pos = runData.selected_object->getPosition();
3171 selectionboxes->push_back(aabb3f(selection_box));
3172 hud->setSelectionPos(pos, camera_offset);
3174 } else if (result.type == POINTEDTHING_NODE) {
3175 // Update selection boxes
3176 MapNode n = map.getNode(result.node_undersurface);
3177 std::vector<aabb3f> boxes;
3178 n.getSelectionBoxes(nodedef, &boxes,
3179 n.getNeighbors(result.node_undersurface, &map));
3182 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3183 i != boxes.end(); ++i) {
3185 box.MinEdge -= v3f(d, d, d);
3186 box.MaxEdge += v3f(d, d, d);
3187 selectionboxes->push_back(box);
3189 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3191 hud->setSelectedFaceNormal(v3f(
3192 result.intersection_normal.X,
3193 result.intersection_normal.Y,
3194 result.intersection_normal.Z));
3197 // Update selection mesh light level and vertex colors
3198 if (!selectionboxes->empty()) {
3199 v3f pf = hud->getSelectionPos();
3200 v3s16 p = floatToInt(pf, BS);
3202 // Get selection mesh light level
3203 MapNode n = map.getNode(p);
3204 u16 node_light = getInteriorLight(n, -1, nodedef);
3205 u16 light_level = node_light;
3207 for (const v3s16 &dir : g_6dirs) {
3208 n = map.getNode(p + dir);
3209 node_light = getInteriorLight(n, -1, nodedef);
3210 if (node_light > light_level)
3211 light_level = node_light;
3214 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3216 final_color_blend(&c, light_level, daynight_ratio);
3218 // Modify final color a bit with time
3219 u32 timer = porting::getTimeMs() % 5000;
3220 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3221 float sin_r = 0.08f * std::sin(timerf);
3222 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3223 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3224 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3225 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3226 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3228 // Set mesh final color
3229 hud->setSelectionMeshColor(c);
3235 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3237 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3238 PointedThing fauxPointed;
3239 fauxPointed.type = POINTEDTHING_NOTHING;
3240 client->interact(INTERACT_ACTIVATE, fauxPointed);
3244 void Game::handlePointingAtNode(const PointedThing &pointed,
3245 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3247 v3s16 nodepos = pointed.node_undersurface;
3248 v3s16 neighbourpos = pointed.node_abovesurface;
3251 Check information text of node
3254 ClientMap &map = client->getEnv().getClientMap();
3256 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3257 && !runData.digging_blocked
3258 && client->checkPrivilege("interact")) {
3259 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3262 // This should be done after digging handling
3263 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3266 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3267 meta->getString("infotext"))));
3269 MapNode n = map.getNode(nodepos);
3271 if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
3272 m_game_ui->setInfoText(L"Unknown node: " +
3273 utf8_to_wide(nodedef_manager->get(n).name));
3277 if ((wasKeyPressed(KeyType::PLACE) ||
3278 runData.repeat_place_timer >= m_repeat_place_time) &&
3279 client->checkPrivilege("interact")) {
3280 runData.repeat_place_timer = 0;
3281 infostream << "Place button pressed while looking at ground" << std::endl;
3283 // Placing animation (always shown for feedback)
3284 camera->setDigging(1);
3286 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3288 // If the wielded item has node placement prediction,
3290 // And also set the sound and send the interact
3291 // But first check for meta formspec and rightclickable
3292 auto &def = selected_item.getDefinition(itemdef_manager);
3293 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3296 if (placed && client->modsLoaded())
3297 client->getScript()->on_placenode(pointed, def);
3301 bool Game::nodePlacement(const ItemDefinition &selected_def,
3302 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3303 const PointedThing &pointed, const NodeMetadata *meta)
3305 const auto &prediction = selected_def.node_placement_prediction;
3307 const NodeDefManager *nodedef = client->ndef();
3308 ClientMap &map = client->getEnv().getClientMap();
3310 bool is_valid_position;
3312 node = map.getNode(nodepos, &is_valid_position);
3313 if (!is_valid_position) {
3314 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3319 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3320 && !isKeyDown(KeyType::SNEAK)) {
3321 // on_rightclick callbacks are called anyway
3322 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3323 client->interact(INTERACT_PLACE, pointed);
3325 infostream << "Launching custom inventory view" << std::endl;
3327 InventoryLocation inventoryloc;
3328 inventoryloc.setNodeMeta(nodepos);
3330 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3331 &client->getEnv().getClientMap(), nodepos);
3332 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3334 auto *&formspec = m_game_ui->updateFormspec("");
3335 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3336 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3338 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3342 // on_rightclick callback
3343 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3344 !isKeyDown(KeyType::SNEAK))) {
3346 client->interact(INTERACT_PLACE, pointed);
3350 verbosestream << "Node placement prediction for "
3351 << selected_def.name << " is " << prediction << std::endl;
3352 v3s16 p = neighbourpos;
3354 // Place inside node itself if buildable_to
3355 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3356 if (is_valid_position) {
3357 if (nodedef->get(n_under).buildable_to) {
3360 node = map.getNode(p, &is_valid_position);
3361 if (is_valid_position && !nodedef->get(node).buildable_to) {
3362 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3364 client->interact(INTERACT_PLACE, pointed);
3370 // Find id of predicted node
3372 bool found = nodedef->getId(prediction, id);
3375 errorstream << "Node placement prediction failed for "
3376 << selected_def.name << " (places " << prediction
3377 << ") - Name not known" << std::endl;
3378 // Handle this as if prediction was empty
3380 client->interact(INTERACT_PLACE, pointed);
3384 const ContentFeatures &predicted_f = nodedef->get(id);
3386 // Predict param2 for facedir and wallmounted nodes
3387 // Compare core.item_place_node() for what the server does
3390 const u8 place_param2 = selected_def.place_param2;
3393 param2 = place_param2;
3394 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3395 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3396 v3s16 dir = nodepos - neighbourpos;
3398 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3399 param2 = dir.Y < 0 ? 1 : 0;
3400 } else if (abs(dir.X) > abs(dir.Z)) {
3401 param2 = dir.X < 0 ? 3 : 2;
3403 param2 = dir.Z < 0 ? 5 : 4;
3405 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3406 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3407 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3409 if (abs(dir.X) > abs(dir.Z)) {
3410 param2 = dir.X < 0 ? 3 : 1;
3412 param2 = dir.Z < 0 ? 2 : 0;
3416 // Check attachment if node is in group attached_node
3417 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3418 const static v3s16 wallmounted_dirs[8] = {
3428 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3429 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3430 pp = p + wallmounted_dirs[param2];
3432 pp = p + v3s16(0, -1, 0);
3434 if (!nodedef->get(map.getNode(pp)).walkable) {
3435 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3437 client->interact(INTERACT_PLACE, pointed);
3443 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3444 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3445 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3446 const auto &indexstr = selected_item.metadata.
3447 getString("palette_index", 0);
3448 if (!indexstr.empty()) {
3449 s32 index = mystoi(indexstr);
3450 if (predicted_f.param_type_2 == CPT2_COLOR) {
3452 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3453 // param2 = pure palette index + other
3454 param2 = (index & 0xf8) | (param2 & 0x07);
3455 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3456 // param2 = pure palette index + other
3457 param2 = (index & 0xe0) | (param2 & 0x1f);
3462 // Add node to client map
3463 MapNode n(id, 0, param2);
3466 LocalPlayer *player = client->getEnv().getLocalPlayer();
3468 // Dont place node when player would be inside new node
3469 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3470 if (!nodedef->get(n).walkable ||
3471 g_settings->getBool("enable_build_where_you_stand") ||
3472 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3473 (nodedef->get(n).walkable &&
3474 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3475 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3476 // This triggers the required mesh update too
3477 client->addNode(p, n);
3479 client->interact(INTERACT_PLACE, pointed);
3480 // A node is predicted, also play a sound
3481 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3484 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3487 } catch (const InvalidPositionException &e) {
3488 errorstream << "Node placement prediction failed for "
3489 << selected_def.name << " (places "
3490 << prediction << ") - Position not loaded" << std::endl;
3491 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3496 void Game::handlePointingAtObject(const PointedThing &pointed,
3497 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3499 std::wstring infotext = unescape_translate(
3500 utf8_to_wide(runData.selected_object->infoText()));
3503 if (!infotext.empty()) {
3506 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3509 m_game_ui->setInfoText(infotext);
3511 if (isKeyDown(KeyType::DIG)) {
3512 bool do_punch = false;
3513 bool do_punch_damage = false;
3515 if (runData.object_hit_delay_timer <= 0.0) {
3517 do_punch_damage = true;
3518 runData.object_hit_delay_timer = object_hit_delay;
3521 if (wasKeyPressed(KeyType::DIG))
3525 infostream << "Punched object" << std::endl;
3526 runData.punching = true;
3529 if (do_punch_damage) {
3530 // Report direct punch
3531 v3f objpos = runData.selected_object->getPosition();
3532 v3f dir = (objpos - player_position).normalize();
3534 bool disable_send = runData.selected_object->directReportPunch(
3535 dir, &tool_item, runData.time_from_last_punch);
3536 runData.time_from_last_punch = 0;
3539 client->interact(INTERACT_START_DIGGING, pointed);
3541 } else if (wasKeyDown(KeyType::PLACE)) {
3542 infostream << "Pressed place button while pointing at object" << std::endl;
3543 client->interact(INTERACT_PLACE, pointed); // place
3548 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3549 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3551 // See also: serverpackethandle.cpp, action == 2
3552 LocalPlayer *player = client->getEnv().getLocalPlayer();
3553 ClientMap &map = client->getEnv().getClientMap();
3554 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3556 // NOTE: Similar piece of code exists on the server side for
3558 // Get digging parameters
3559 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3560 &selected_item.getToolCapabilities(itemdef_manager));
3562 // If can't dig, try hand
3563 if (!params.diggable) {
3564 params = getDigParams(nodedef_manager->get(n).groups,
3565 &hand_item.getToolCapabilities(itemdef_manager));
3568 if (!params.diggable) {
3569 // I guess nobody will wait for this long
3570 runData.dig_time_complete = 10000000.0;
3572 runData.dig_time_complete = params.time;
3574 if (m_cache_enable_particles) {
3575 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3576 client->getParticleManager()->addNodeParticle(client,
3577 player, nodepos, n, features);
3581 if (!runData.digging) {
3582 infostream << "Started digging" << std::endl;
3583 runData.dig_instantly = runData.dig_time_complete == 0;
3584 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3586 client->interact(INTERACT_START_DIGGING, pointed);
3587 runData.digging = true;
3588 runData.btn_down_for_dig = true;
3591 if (!runData.dig_instantly) {
3592 runData.dig_index = (float)crack_animation_length
3594 / runData.dig_time_complete;
3596 // This is for e.g. torches
3597 runData.dig_index = crack_animation_length;
3600 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3602 if (sound_dig.exists() && params.diggable) {
3603 if (sound_dig.name == "__group") {
3604 if (!params.main_group.empty()) {
3605 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3606 soundmaker->m_player_leftpunch_sound.name =
3607 std::string("default_dig_") +
3611 soundmaker->m_player_leftpunch_sound = sound_dig;
3615 // Don't show cracks if not diggable
3616 if (runData.dig_time_complete >= 100000.0) {
3617 } else if (runData.dig_index < crack_animation_length) {
3618 //TimeTaker timer("client.setTempMod");
3619 //infostream<<"dig_index="<<dig_index<<std::endl;
3620 client->setCrack(runData.dig_index, nodepos);
3622 infostream << "Digging completed" << std::endl;
3623 client->setCrack(-1, v3s16(0, 0, 0));
3625 runData.dig_time = 0;
3626 runData.digging = false;
3627 // we successfully dug, now block it from repeating if we want to be safe
3628 if (g_settings->getBool("safe_dig_and_place"))
3629 runData.digging_blocked = true;
3631 runData.nodig_delay_timer =
3632 runData.dig_time_complete / (float)crack_animation_length;
3634 // We don't want a corresponding delay to very time consuming nodes
3635 // and nodes without digging time (e.g. torches) get a fixed delay.
3636 if (runData.nodig_delay_timer > 0.3)
3637 runData.nodig_delay_timer = 0.3;
3638 else if (runData.dig_instantly)
3639 runData.nodig_delay_timer = 0.15;
3641 bool is_valid_position;
3642 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3643 if (is_valid_position) {
3644 if (client->modsLoaded() &&
3645 client->getScript()->on_dignode(nodepos, wasnode)) {
3649 const ContentFeatures &f = client->ndef()->get(wasnode);
3650 if (f.node_dig_prediction == "air") {
3651 client->removeNode(nodepos);
3652 } else if (!f.node_dig_prediction.empty()) {
3654 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3656 client->addNode(nodepos, id, true);
3658 // implicit else: no prediction
3661 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3663 if (m_cache_enable_particles) {
3664 const ContentFeatures &features =
3665 client->getNodeDefManager()->get(wasnode);
3666 client->getParticleManager()->addDiggingParticles(client,
3667 player, nodepos, wasnode, features);
3671 // Send event to trigger sound
3672 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3675 if (runData.dig_time_complete < 100000.0) {
3676 runData.dig_time += dtime;
3678 runData.dig_time = 0;
3679 client->setCrack(-1, nodepos);
3682 camera->setDigging(0); // Dig animation
3685 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3686 const CameraOrientation &cam)
3688 TimeTaker tt_update("Game::updateFrame()");
3689 LocalPlayer *player = client->getEnv().getLocalPlayer();
3695 if (draw_control->range_all) {
3696 runData.fog_range = 100000 * BS;
3698 runData.fog_range = draw_control->wanted_range * BS;
3702 Calculate general brightness
3704 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3705 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3706 float direct_brightness;
3709 if (m_cache_enable_noclip && m_cache_enable_free_move) {
3710 direct_brightness = time_brightness;
3711 sunlight_seen = true;
3713 float old_brightness = sky->getBrightness();
3714 direct_brightness = client->getEnv().getClientMap()
3715 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3716 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3720 float time_of_day_smooth = runData.time_of_day_smooth;
3721 float time_of_day = client->getEnv().getTimeOfDayF();
3723 static const float maxsm = 0.05f;
3724 static const float todsm = 0.05f;
3726 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3727 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3728 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3729 time_of_day_smooth = time_of_day;
3731 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3732 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3733 + (time_of_day + 1.0) * todsm;
3735 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3736 + time_of_day * todsm;
3738 runData.time_of_day_smooth = time_of_day_smooth;
3740 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3741 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3742 player->getPitch());
3748 if (sky->getCloudsVisible()) {
3749 clouds->setVisible(true);
3750 clouds->step(dtime);
3751 // camera->getPosition is not enough for 3rd person views
3752 v3f camera_node_position = camera->getCameraNode()->getPosition();
3753 v3s16 camera_offset = camera->getOffset();
3754 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3755 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3756 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3757 clouds->update(camera_node_position,
3758 sky->getCloudColor());
3759 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3760 // if inside clouds, and fog enabled, use that as sky
3762 video::SColor clouds_dark = clouds->getColor()
3763 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3764 sky->overrideColors(clouds_dark, clouds->getColor());
3765 sky->setInClouds(true);
3766 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3767 // do not draw clouds after all
3768 clouds->setVisible(false);
3771 clouds->setVisible(false);
3778 client->getParticleManager()->step(dtime);
3784 if (m_cache_enable_fog) {
3787 video::EFT_FOG_LINEAR,
3788 runData.fog_range * m_cache_fog_start,
3789 runData.fog_range * 1.0,
3797 video::EFT_FOG_LINEAR,
3807 Get chat messages from client
3810 v2u32 screensize = driver->getScreenSize();
3812 updateChat(dtime, screensize);
3818 if (player->getWieldIndex() != runData.new_playeritem)
3819 client->setPlayerItem(runData.new_playeritem);
3821 if (client->updateWieldedItem()) {
3822 // Update wielded tool
3823 ItemStack selected_item, hand_item;
3824 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3825 camera->wield(tool_item);
3829 Update block draw list every 200ms or when camera direction has
3832 runData.update_draw_list_timer += dtime;
3834 v3f camera_direction = camera->getDirection();
3835 if (runData.update_draw_list_timer >= 0.2
3836 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3837 || m_camera_offset_changed) {
3838 runData.update_draw_list_timer = 0;
3839 client->getEnv().getClientMap().updateDrawList();
3840 runData.update_draw_list_last_cam_dir = camera_direction;
3843 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3846 make sure menu is on top
3847 1. Delete formspec menu reference if menu was removed
3848 2. Else, make sure formspec menu is on top
3850 auto formspec = m_game_ui->getFormspecGUI();
3851 do { // breakable. only runs for one iteration
3855 if (formspec->getReferenceCount() == 1) {
3856 m_game_ui->deleteFormspec();
3860 auto &loc = formspec->getFormspecLocation();
3861 if (loc.type == InventoryLocation::NODEMETA) {
3862 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3863 if (!meta || meta->getString("formspec").empty()) {
3864 formspec->quitMenu();
3870 guiroot->bringToFront(formspec);
3876 const video::SColor &skycolor = sky->getSkyColor();
3878 TimeTaker tt_draw("Draw scene");
3879 driver->beginScene(true, true, skycolor);
3881 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3882 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3883 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3884 bool draw_crosshair = (
3885 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3886 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3887 #ifdef HAVE_TOUCHSCREENGUI
3889 draw_crosshair = !g_settings->getBool("touchtarget");
3890 } catch (SettingNotFoundException) {
3893 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3894 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3899 if (m_game_ui->m_flags.show_profiler_graph)
3900 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3905 if (runData.damage_flash > 0.0f) {
3906 video::SColor color(runData.damage_flash, 180, 0, 0);
3907 driver->draw2DRectangle(color,
3908 core::rect<s32>(0, 0, screensize.X, screensize.Y),
3911 runData.damage_flash -= 384.0f * dtime;
3917 if (player->hurt_tilt_timer > 0.0f) {
3918 player->hurt_tilt_timer -= dtime * 6.0f;
3920 if (player->hurt_tilt_timer < 0.0f)
3921 player->hurt_tilt_strength = 0.0f;
3925 Update minimap pos and rotation
3927 if (mapper && m_game_ui->m_flags.show_hud) {
3928 mapper->setPos(floatToInt(player->getPosition(), BS));
3929 mapper->setAngle(player->getYaw());
3935 if (++m_reset_HW_buffer_counter > 500) {
3937 Periodically remove all mesh HW buffers.
3939 Work around for a quirk in Irrlicht where a HW buffer is only
3940 released after 20000 iterations (triggered from endScene()).
3942 Without this, all loaded but unused meshes will retain their HW
3943 buffers for at least 5 minutes, at which point looking up the HW buffers
3944 becomes a bottleneck and the framerate drops (as much as 30%).
3946 Tests showed that numbers between 50 and 1000 are good, so picked 500.
3947 There are no other public Irrlicht APIs that allow interacting with the
3948 HW buffers without tracking the status of every individual mesh.
3950 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
3952 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
3953 driver->removeAllHardwareBuffers();
3954 m_reset_HW_buffer_counter = 0;
3958 stats->drawtime = tt_draw.stop(true);
3959 g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
3960 g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
3963 /* Log times and stuff for visualization */
3964 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
3966 Profiler::GraphValues values;
3967 g_profiler->graphGet(values);
3973 /****************************************************************************
3975 ****************************************************************************/
3977 /* On some computers framerate doesn't seem to be automatically limited
3979 inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
3981 // not using getRealTime is necessary for wine
3982 device->getTimer()->tick(); // Maker sure device time is up-to-date
3983 u32 time = device->getTimer()->getTime();
3984 u32 last_time = fps_timings->last_time;
3986 if (time > last_time) // Make sure time hasn't overflowed
3987 fps_timings->busy_time = time - last_time;
3989 fps_timings->busy_time = 0;
3991 u32 frametime_min = 1000 / (
3992 device->isWindowFocused() && !g_menumgr.pausesGame()
3993 ? g_settings->getFloat("fps_max")
3994 : g_settings->getFloat("fps_max_unfocused"));
3996 if (fps_timings->busy_time < frametime_min) {
3997 fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
3998 device->sleep(fps_timings->sleep_time);
4000 fps_timings->sleep_time = 0;
4003 /* Get the new value of the device timer. Note that device->sleep() may
4004 * not sleep for the entire requested time as sleep may be interrupted and
4005 * therefore it is arguably more accurate to get the new time from the
4006 * device rather than calculating it by adding sleep_time to time.
4009 device->getTimer()->tick(); // Update device timer
4010 time = device->getTimer()->getTime();
4012 if (time > last_time) // Make sure last_time hasn't overflowed
4013 *dtime = (time - last_time) / 1000.0;
4017 fps_timings->last_time = time;
4020 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4022 const wchar_t *wmsg = wgettext(msg);
4023 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4028 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4030 ((Game *)data)->readSettings();
4033 void Game::readSettings()
4035 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4036 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4037 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4038 m_cache_enable_particles = g_settings->getBool("enable_particles");
4039 m_cache_enable_fog = g_settings->getBool("enable_fog");
4040 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
4041 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4042 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
4044 m_cache_enable_noclip = g_settings->getBool("noclip");
4045 m_cache_enable_free_move = g_settings->getBool("free_move");
4047 m_cache_fog_start = g_settings->getFloat("fog_start");
4049 m_cache_cam_smoothing = 0;
4050 if (g_settings->getBool("cinematic"))
4051 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4053 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4055 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4056 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4057 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4059 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4062 /****************************************************************************/
4063 /****************************************************************************
4065 ****************************************************************************/
4066 /****************************************************************************/
4068 void Game::showDeathFormspec()
4070 static std::string formspec_str =
4071 std::string("formspec_version[1]") +
4073 "bgcolor[#320000b4;true]"
4074 "label[4.85,1.35;" + gettext("You died") + "]"
4075 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4079 /* Note: FormspecFormSource and LocalFormspecHandler *
4080 * are deleted by guiFormSpecMenu */
4081 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4082 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4084 auto *&formspec = m_game_ui->getFormspecGUI();
4085 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4086 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4087 formspec->setFocus("btn_respawn");
4090 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4091 void Game::showPauseMenu()
4094 static const std::string control_text = strgettext("Default Controls:\n"
4095 "No menu visible:\n"
4096 "- single tap: button activate\n"
4097 "- double tap: place/use\n"
4098 "- slide finger: look around\n"
4099 "Menu/Inventory visible:\n"
4100 "- double tap (outside):\n"
4102 "- touch stack, touch slot:\n"
4104 "- touch&drag, tap 2nd finger\n"
4105 " --> place single item to slot\n"
4108 static const std::string control_text_template = strgettext("Controls:\n"
4109 "- %s: move forwards\n"
4110 "- %s: move backwards\n"
4112 "- %s: move right\n"
4113 "- %s: jump/climb up\n"
4116 "- %s: sneak/climb down\n"
4119 "- Mouse: turn/look\n"
4120 "- Mouse wheel: select item\n"
4124 char control_text_buf[600];
4126 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4127 GET_KEY_NAME(keymap_forward),
4128 GET_KEY_NAME(keymap_backward),
4129 GET_KEY_NAME(keymap_left),
4130 GET_KEY_NAME(keymap_right),
4131 GET_KEY_NAME(keymap_jump),
4132 GET_KEY_NAME(keymap_dig),
4133 GET_KEY_NAME(keymap_place),
4134 GET_KEY_NAME(keymap_sneak),
4135 GET_KEY_NAME(keymap_drop),
4136 GET_KEY_NAME(keymap_inventory),
4137 GET_KEY_NAME(keymap_chat)
4140 std::string control_text = std::string(control_text_buf);
4141 str_formspec_escape(control_text);
4144 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4145 std::ostringstream os;
4147 os << "formspec_version[1]" << SIZE_TAG
4148 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4149 << strgettext("Continue") << "]";
4151 if (!simple_singleplayer_mode) {
4152 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4153 << strgettext("Change Password") << "]";
4155 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4160 if (g_settings->getBool("enable_sound")) {
4161 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4162 << strgettext("Sound Volume") << "]";
4165 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4166 << strgettext("Change Keys") << "]";
4168 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4169 << strgettext("Exit to Menu") << "]";
4170 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4171 << strgettext("Exit to OS") << "]"
4172 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4173 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4175 << strgettext("Game info:") << "\n";
4176 const std::string &address = client->getAddressName();
4177 static const std::string mode = strgettext("- Mode: ");
4178 if (!simple_singleplayer_mode) {
4179 Address serverAddress = client->getServerAddress();
4180 if (!address.empty()) {
4181 os << mode << strgettext("Remote server") << "\n"
4182 << strgettext("- Address: ") << address;
4184 os << mode << strgettext("Hosting server");
4186 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4188 os << mode << strgettext("Singleplayer") << "\n";
4190 if (simple_singleplayer_mode || address.empty()) {
4191 static const std::string on = strgettext("On");
4192 static const std::string off = strgettext("Off");
4193 const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
4194 const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
4195 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4196 os << strgettext("- Damage: ") << damage << "\n"
4197 << strgettext("- Creative Mode: ") << creative << "\n";
4198 if (!simple_singleplayer_mode) {
4199 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4200 //~ PvP = Player versus Player
4201 os << strgettext("- PvP: ") << pvp << "\n"
4202 << strgettext("- Public: ") << announced << "\n";
4203 std::string server_name = g_settings->get("server_name");
4204 str_formspec_escape(server_name);
4205 if (announced == on && !server_name.empty())
4206 os << strgettext("- Server Name: ") << server_name;
4213 /* Note: FormspecFormSource and LocalFormspecHandler *
4214 * are deleted by guiFormSpecMenu */
4215 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4216 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4218 auto *&formspec = m_game_ui->getFormspecGUI();
4219 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4220 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4221 formspec->setFocus("btn_continue");
4222 formspec->doPause = true;
4224 if (simple_singleplayer_mode)
4228 /****************************************************************************/
4229 /****************************************************************************
4230 extern function for launching the game
4231 ****************************************************************************/
4232 /****************************************************************************/
4234 void the_game(bool *kill,
4235 InputHandler *input,
4236 RenderingEngine *rendering_engine,
4237 const GameStartData &start_data,
4238 std::string &error_message,
4239 ChatBackend &chat_backend,
4240 bool *reconnect_requested) // Used for local game
4244 /* Make a copy of the server address because if a local singleplayer server
4245 * is created then this is updated and we don't want to change the value
4246 * passed to us by the calling function
4251 if (game.startup(kill, input, rendering_engine, start_data,
4252 error_message, reconnect_requested, &chat_backend)) {
4256 } catch (SerializationError &e) {
4257 error_message = std::string("A serialization error occurred:\n")
4258 + e.what() + "\n\nThe server is probably "
4259 " running a different version of " PROJECT_NAME_C ".";
4260 errorstream << error_message << std::endl;
4261 } catch (ServerError &e) {
4262 error_message = e.what();
4263 errorstream << "ServerError: " << error_message << std::endl;
4264 } catch (ModError &e) {
4265 error_message = std::string("ModError: ") + e.what() +
4266 strgettext("\nCheck debug.txt for details.");
4267 errorstream << error_message << std::endl;