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;
612 float update_shadows_timer;
616 v3f update_draw_list_last_cam_dir;
618 float time_of_day_smooth;
623 struct ClientEventHandler
625 void (Game::*handler)(ClientEvent *, CameraOrientation *);
628 /****************************************************************************
630 ****************************************************************************/
632 using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
634 /* This is not intended to be a public class. If a public class becomes
635 * desirable then it may be better to create another 'wrapper' class that
636 * hides most of the stuff in this class (nothing in this class is required
637 * by any other file) but exposes the public methods/data only.
644 bool startup(bool *kill,
646 RenderingEngine *rendering_engine,
647 const GameStartData &game_params,
648 std::string &error_message,
650 ChatBackend *chat_backend);
657 // Basic initialisation
658 bool init(const std::string &map_dir, const std::string &address,
659 u16 port, const SubgameSpec &gamespec);
661 bool createSingleplayerServer(const std::string &map_dir,
662 const SubgameSpec &gamespec, u16 port);
665 bool createClient(const GameStartData &start_data);
669 bool connectToServer(const GameStartData &start_data,
670 bool *connect_ok, bool *aborted);
671 bool getServerContent(bool *aborted);
675 void updateInteractTimers(f32 dtime);
676 bool checkConnection();
677 bool handleCallbacks();
678 void processQueues();
679 void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
680 void updateDebugState();
681 void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
682 void updateProfilerGraphs(ProfilerGraph *graph);
685 void processUserInput(f32 dtime);
686 void processKeyInput();
687 void processItemSelection(u16 *new_playeritem);
689 void dropSelectedItem(bool single_item = false);
690 void openInventory();
691 void openConsole(float scale, const wchar_t *line=NULL);
692 void toggleFreeMove();
693 void toggleFreeMoveAlt();
694 void togglePitchMove();
697 void toggleCinematic();
698 void toggleBlockBounds();
699 void toggleAutoforward();
701 void toggleMinimap(bool shift_pressed);
704 void toggleUpdateCamera();
706 void increaseViewRange();
707 void decreaseViewRange();
708 void toggleFullViewRange();
709 void checkZoomEnabled();
711 void updateCameraDirection(CameraOrientation *cam, float dtime);
712 void updateCameraOrientation(CameraOrientation *cam, float dtime);
713 void updatePlayerControl(const CameraOrientation &cam);
714 void step(f32 *dtime);
715 void processClientEvents(CameraOrientation *cam);
716 void updateCamera(u32 busy_time, f32 dtime);
717 void updateSound(f32 dtime);
718 void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug);
720 * Returns the object or node the player is pointing at.
721 * Also updates the selected thing in the Hud.
723 * @param[in] shootline the shootline, starting from
724 * the camera position. This also gives the maximal distance
726 * @param[in] liquids_pointable if false, liquids are ignored
727 * @param[in] look_for_object if false, objects are ignored
728 * @param[in] camera_offset offset of the camera
729 * @param[out] selected_object the selected object or
732 PointedThing updatePointedThing(
733 const core::line3d<f32> &shootline, bool liquids_pointable,
734 bool look_for_object, const v3s16 &camera_offset);
735 void handlePointingAtNothing(const ItemStack &playerItem);
736 void handlePointingAtNode(const PointedThing &pointed,
737 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
738 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
739 const v3f &player_position, bool show_debug);
740 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
741 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
742 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
743 const CameraOrientation &cam);
744 void updateShadows();
747 void limitFps(FpsControl *fps_timings, f32 *dtime);
749 void showOverlayMessage(const char *msg, float dtime, int percent,
750 bool draw_clouds = true);
752 static void settingChangedCallback(const std::string &setting_name, void *data);
755 inline bool isKeyDown(GameKeyType k)
757 return input->isKeyDown(k);
759 inline bool wasKeyDown(GameKeyType k)
761 return input->wasKeyDown(k);
763 inline bool wasKeyPressed(GameKeyType k)
765 return input->wasKeyPressed(k);
767 inline bool wasKeyReleased(GameKeyType k)
769 return input->wasKeyReleased(k);
773 void handleAndroidChatInput();
778 bool force_fog_off = false;
779 bool disable_camera_update = false;
782 void showDeathFormspec();
783 void showPauseMenu();
785 void pauseAnimation();
786 void resumeAnimation();
788 // ClientEvent handlers
789 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
790 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
791 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
792 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
793 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
794 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
795 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
796 CameraOrientation *cam);
797 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
798 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
799 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
800 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
801 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
802 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
803 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
804 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
805 CameraOrientation *cam);
806 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
808 void updateChat(f32 dtime, const v2u32 &screensize);
810 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
811 const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
812 const NodeMetadata *meta);
813 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
815 f32 getSensitivityScaleFactor() const;
817 InputHandler *input = nullptr;
819 Client *client = nullptr;
820 Server *server = nullptr;
822 IWritableTextureSource *texture_src = nullptr;
823 IWritableShaderSource *shader_src = nullptr;
825 // When created, these will be filled with data received from the server
826 IWritableItemDefManager *itemdef_manager = nullptr;
827 NodeDefManager *nodedef_manager = nullptr;
829 GameOnDemandSoundFetcher soundfetcher; // useful when testing
830 ISoundManager *sound = nullptr;
831 bool sound_is_dummy = false;
832 SoundMaker *soundmaker = nullptr;
834 ChatBackend *chat_backend = nullptr;
835 LogOutputBuffer m_chat_log_buf;
837 EventManager *eventmgr = nullptr;
838 QuicktuneShortcutter *quicktune = nullptr;
839 bool registration_confirmation_shown = false;
841 std::unique_ptr<GameUI> m_game_ui;
842 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
843 MapDrawControl *draw_control = nullptr;
844 Camera *camera = nullptr;
845 Clouds *clouds = nullptr; // Free using ->Drop()
846 Sky *sky = nullptr; // Free using ->Drop()
848 Minimap *mapper = nullptr;
850 // Map server hud ids to client hud ids
851 std::unordered_map<u32, u32> m_hud_server_to_client;
857 This class does take ownership/responsibily for cleaning up etc of any of
858 these items (e.g. device)
860 IrrlichtDevice *device;
861 RenderingEngine *m_rendering_engine;
862 video::IVideoDriver *driver;
863 scene::ISceneManager *smgr;
865 std::string *error_message;
866 bool *reconnect_requested;
867 scene::ISceneNode *skybox;
868 PausedNodesList paused_animated_nodes;
870 bool simple_singleplayer_mode;
873 /* Pre-calculated values
875 int crack_animation_length;
877 IntervalLimiter profiler_interval;
880 * TODO: Local caching of settings is not optimal and should at some stage
881 * be updated to use a global settings object for getting thse values
882 * (as opposed to the this local caching). This can be addressed in
885 bool m_cache_doubletap_jump;
886 bool m_cache_enable_clouds;
887 bool m_cache_enable_joysticks;
888 bool m_cache_enable_particles;
889 bool m_cache_enable_fog;
890 bool m_cache_enable_noclip;
891 bool m_cache_enable_free_move;
892 f32 m_cache_mouse_sensitivity;
893 f32 m_cache_joystick_frustum_sensitivity;
894 f32 m_repeat_place_time;
895 f32 m_cache_cam_smoothing;
896 f32 m_cache_fog_start;
898 bool m_invert_mouse = false;
899 bool m_first_loop_after_window_activation = false;
900 bool m_camera_offset_changed = false;
902 bool m_does_lost_focus_pause_game = false;
904 int m_reset_HW_buffer_counter = 0;
906 bool m_cache_hold_aux1;
907 bool m_android_chat_open;
912 m_chat_log_buf(g_logger),
913 m_game_ui(new GameUI())
915 g_settings->registerChangedCallback("doubletap_jump",
916 &settingChangedCallback, this);
917 g_settings->registerChangedCallback("enable_clouds",
918 &settingChangedCallback, this);
919 g_settings->registerChangedCallback("doubletap_joysticks",
920 &settingChangedCallback, this);
921 g_settings->registerChangedCallback("enable_particles",
922 &settingChangedCallback, this);
923 g_settings->registerChangedCallback("enable_fog",
924 &settingChangedCallback, this);
925 g_settings->registerChangedCallback("mouse_sensitivity",
926 &settingChangedCallback, this);
927 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
928 &settingChangedCallback, this);
929 g_settings->registerChangedCallback("repeat_place_time",
930 &settingChangedCallback, this);
931 g_settings->registerChangedCallback("noclip",
932 &settingChangedCallback, this);
933 g_settings->registerChangedCallback("free_move",
934 &settingChangedCallback, this);
935 g_settings->registerChangedCallback("cinematic",
936 &settingChangedCallback, this);
937 g_settings->registerChangedCallback("cinematic_camera_smoothing",
938 &settingChangedCallback, this);
939 g_settings->registerChangedCallback("camera_smoothing",
940 &settingChangedCallback, this);
945 m_cache_hold_aux1 = false; // This is initialised properly later
952 /****************************************************************************
954 ****************************************************************************/
963 delete server; // deleted first to stop all server threads
971 delete nodedef_manager;
972 delete itemdef_manager;
975 clearTextureNameCache();
977 g_settings->deregisterChangedCallback("doubletap_jump",
978 &settingChangedCallback, this);
979 g_settings->deregisterChangedCallback("enable_clouds",
980 &settingChangedCallback, this);
981 g_settings->deregisterChangedCallback("enable_particles",
982 &settingChangedCallback, this);
983 g_settings->deregisterChangedCallback("enable_fog",
984 &settingChangedCallback, this);
985 g_settings->deregisterChangedCallback("mouse_sensitivity",
986 &settingChangedCallback, this);
987 g_settings->deregisterChangedCallback("repeat_place_time",
988 &settingChangedCallback, this);
989 g_settings->deregisterChangedCallback("noclip",
990 &settingChangedCallback, this);
991 g_settings->deregisterChangedCallback("free_move",
992 &settingChangedCallback, this);
993 g_settings->deregisterChangedCallback("cinematic",
994 &settingChangedCallback, this);
995 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
996 &settingChangedCallback, this);
997 g_settings->deregisterChangedCallback("camera_smoothing",
998 &settingChangedCallback, this);
1001 bool Game::startup(bool *kill,
1002 InputHandler *input,
1003 RenderingEngine *rendering_engine,
1004 const GameStartData &start_data,
1005 std::string &error_message,
1007 ChatBackend *chat_backend)
1011 m_rendering_engine = rendering_engine;
1012 device = m_rendering_engine->get_raw_device();
1014 this->error_message = &error_message;
1015 reconnect_requested = reconnect;
1016 this->input = input;
1017 this->chat_backend = chat_backend;
1018 simple_singleplayer_mode = start_data.isSinglePlayer();
1020 input->keycache.populate();
1022 driver = device->getVideoDriver();
1023 smgr = m_rendering_engine->get_scene_manager();
1025 smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1028 runData = GameRunData();
1029 runData.time_from_last_punch = 10.0;
1031 m_game_ui->initFlags();
1033 m_invert_mouse = g_settings->getBool("invert_mouse");
1034 m_first_loop_after_window_activation = true;
1036 g_client_translations->clear();
1038 // address can change if simple_singleplayer_mode
1039 if (!init(start_data.world_spec.path, start_data.address,
1040 start_data.socket_port, start_data.game_spec))
1043 if (!createClient(start_data))
1046 m_rendering_engine->initialize(client, hud);
1054 ProfilerGraph graph;
1055 RunStats stats = { 0 };
1056 CameraOrientation cam_view_target = { 0 };
1057 CameraOrientation cam_view = { 0 };
1058 FpsControl draw_times = { 0 };
1059 f32 dtime; // in seconds
1061 /* Clear the profiler */
1062 Profiler::GraphValues dummyvalues;
1063 g_profiler->graphGet(dummyvalues);
1065 draw_times.last_time = m_rendering_engine->get_timer_time();
1067 set_light_table(g_settings->getFloat("display_gamma"));
1070 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1071 && client->checkPrivilege("fast");
1074 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1075 g_settings->getU16("screen_h"));
1077 while (m_rendering_engine->run()
1078 && !(*kill || g_gamecallback->shutdown_requested
1079 || (server && server->isShutdownRequested()))) {
1081 const irr::core::dimension2d<u32> ¤t_screen_size =
1082 m_rendering_engine->get_video_driver()->getScreenSize();
1083 // Verify if window size has changed and save it if it's the case
1084 // Ensure evaluating settings->getBool after verifying screensize
1085 // First condition is cheaper
1086 if (previous_screen_size != current_screen_size &&
1087 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1088 g_settings->getBool("autosave_screensize")) {
1089 g_settings->setU16("screen_w", current_screen_size.Width);
1090 g_settings->setU16("screen_h", current_screen_size.Height);
1091 previous_screen_size = current_screen_size;
1094 // Calculate dtime =
1095 // m_rendering_engine->run() from this iteration
1096 // + Sleep time until the wanted FPS are reached
1097 limitFps(&draw_times, &dtime);
1099 // Prepare render data for next iteration
1101 updateStats(&stats, draw_times, dtime);
1102 updateInteractTimers(dtime);
1104 if (!checkConnection())
1106 if (!handleCallbacks())
1111 m_game_ui->clearInfoText();
1112 hud->resizeHotbar();
1115 updateProfilers(stats, draw_times, dtime);
1116 processUserInput(dtime);
1117 // Update camera before player movement to avoid camera lag of one frame
1118 updateCameraDirection(&cam_view_target, dtime);
1119 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1120 cam_view.camera_yaw) * m_cache_cam_smoothing;
1121 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1122 cam_view.camera_pitch) * m_cache_cam_smoothing;
1123 updatePlayerControl(cam_view);
1125 processClientEvents(&cam_view_target);
1127 updateCamera(draw_times.busy_time, dtime);
1129 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
1130 m_game_ui->m_flags.show_basic_debug);
1131 updateFrame(&graph, &stats, dtime, cam_view);
1132 updateProfilerGraphs(&graph);
1134 // Update if minimap has been disabled by the server
1135 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1137 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1144 void Game::shutdown()
1146 m_rendering_engine->finalize();
1147 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1148 if (g_settings->get("3d_mode") == "pageflip") {
1149 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1152 auto formspec = m_game_ui->getFormspecGUI();
1154 formspec->quitMenu();
1156 #ifdef HAVE_TOUCHSCREENGUI
1157 g_touchscreengui->hide();
1160 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1165 if (gui_chat_console)
1166 gui_chat_console->drop();
1172 while (g_menumgr.menuCount() > 0) {
1173 g_menumgr.m_stack.front()->setVisible(false);
1174 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1177 m_game_ui->deleteFormspec();
1179 chat_backend->addMessage(L"", L"# Disconnected.");
1180 chat_backend->addMessage(L"", L"");
1181 m_chat_log_buf.clear();
1185 while (!client->isShutdown()) {
1186 assert(texture_src != NULL);
1187 assert(shader_src != NULL);
1188 texture_src->processQueue();
1189 shader_src->processQueue();
1196 /****************************************************************************/
1197 /****************************************************************************
1199 ****************************************************************************/
1200 /****************************************************************************/
1203 const std::string &map_dir,
1204 const std::string &address,
1206 const SubgameSpec &gamespec)
1208 texture_src = createTextureSource();
1210 showOverlayMessage(N_("Loading..."), 0, 0);
1212 shader_src = createShaderSource();
1214 itemdef_manager = createItemDefManager();
1215 nodedef_manager = createNodeDefManager();
1217 eventmgr = new EventManager();
1218 quicktune = new QuicktuneShortcutter();
1220 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1221 && eventmgr && quicktune))
1227 // Create a server if not connecting to an existing one
1228 if (address.empty()) {
1229 if (!createSingleplayerServer(map_dir, gamespec, port))
1236 bool Game::initSound()
1239 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1240 infostream << "Attempting to use OpenAL audio" << std::endl;
1241 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1243 infostream << "Failed to initialize OpenAL audio" << std::endl;
1245 infostream << "Sound disabled." << std::endl;
1249 infostream << "Using dummy audio." << std::endl;
1250 sound = &dummySoundManager;
1251 sound_is_dummy = true;
1254 soundmaker = new SoundMaker(sound, nodedef_manager);
1258 soundmaker->registerReceiver(eventmgr);
1263 bool Game::createSingleplayerServer(const std::string &map_dir,
1264 const SubgameSpec &gamespec, u16 port)
1266 showOverlayMessage(N_("Creating server..."), 0, 5);
1268 std::string bind_str = g_settings->get("bind_address");
1269 Address bind_addr(0, 0, 0, 0, port);
1271 if (g_settings->getBool("ipv6_server")) {
1272 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1276 bind_addr.Resolve(bind_str.c_str());
1277 } catch (ResolveError &e) {
1278 infostream << "Resolving bind address \"" << bind_str
1279 << "\" failed: " << e.what()
1280 << " -- Listening on all addresses." << std::endl;
1283 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1284 *error_message = "Unable to listen on " +
1285 bind_addr.serializeString() +
1286 " because IPv6 is disabled";
1287 errorstream << *error_message << std::endl;
1291 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1292 false, nullptr, error_message);
1298 bool Game::createClient(const GameStartData &start_data)
1300 showOverlayMessage(N_("Creating client..."), 0, 10);
1302 draw_control = new MapDrawControl();
1306 bool could_connect, connect_aborted;
1307 #ifdef HAVE_TOUCHSCREENGUI
1308 if (g_touchscreengui) {
1309 g_touchscreengui->init(texture_src);
1310 g_touchscreengui->hide();
1313 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1316 if (!could_connect) {
1317 if (error_message->empty() && !connect_aborted) {
1318 // Should not happen if error messages are set properly
1319 *error_message = "Connection failed for unknown reason";
1320 errorstream << *error_message << std::endl;
1325 if (!getServerContent(&connect_aborted)) {
1326 if (error_message->empty() && !connect_aborted) {
1327 // Should not happen if error messages are set properly
1328 *error_message = "Connection failed for unknown reason";
1329 errorstream << *error_message << std::endl;
1334 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1335 &m_flags.force_fog_off, &runData.fog_range, client);
1336 shader_src->addShaderConstantSetterFactory(scsf);
1338 // Update cached textures, meshes and materials
1339 client->afterContentReceived();
1343 camera = new Camera(*draw_control, client, m_rendering_engine);
1344 if (!camera->successfullyCreated(*error_message))
1346 client->setCamera(camera);
1350 if (m_cache_enable_clouds)
1351 clouds = new Clouds(smgr, -1, time(0));
1355 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
1357 skybox = NULL; // This is used/set later on in the main run loop
1359 /* Pre-calculated values
1361 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1363 v2u32 size = t->getOriginalSize();
1364 crack_animation_length = size.Y / size.X;
1366 crack_animation_length = 5;
1372 /* Set window caption
1374 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1376 str += utf8_to_wide(g_version_hash);
1378 const wchar_t *text = nullptr;
1379 if (simple_singleplayer_mode)
1380 text = wgettext("Singleplayer");
1382 text = wgettext("Multiplayer");
1389 str += driver->getName();
1392 device->setWindowCaption(str.c_str());
1394 LocalPlayer *player = client->getEnv().getLocalPlayer();
1395 player->hurt_tilt_timer = 0;
1396 player->hurt_tilt_strength = 0;
1398 hud = new Hud(client, player, &player->inventory);
1400 mapper = client->getMinimap();
1402 if (mapper && client->modsLoaded())
1403 client->getScript()->on_minimap_ready(mapper);
1408 bool Game::initGui()
1412 // Remove stale "recent" chat messages from previous connections
1413 chat_backend->clearRecentChat();
1415 // Make sure the size of the recent messages buffer is right
1416 chat_backend->applySettings();
1418 // Chat backend and console
1419 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1420 -1, chat_backend, client, &g_menumgr);
1422 #ifdef HAVE_TOUCHSCREENGUI
1424 if (g_touchscreengui)
1425 g_touchscreengui->show();
1432 bool Game::connectToServer(const GameStartData &start_data,
1433 bool *connect_ok, bool *connection_aborted)
1435 *connect_ok = false; // Let's not be overly optimistic
1436 *connection_aborted = false;
1437 bool local_server_mode = false;
1439 showOverlayMessage(N_("Resolving address..."), 0, 15);
1441 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1444 connect_address.Resolve(start_data.address.c_str());
1446 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1447 //connect_address.Resolve("localhost");
1448 if (connect_address.isIPv6()) {
1449 IPv6AddressBytes addr_bytes;
1450 addr_bytes.bytes[15] = 1;
1451 connect_address.setAddress(&addr_bytes);
1453 connect_address.setAddress(127, 0, 0, 1);
1455 local_server_mode = true;
1457 } catch (ResolveError &e) {
1458 *error_message = std::string("Couldn't resolve address: ") + e.what();
1459 errorstream << *error_message << std::endl;
1463 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1464 *error_message = "Unable to connect to " +
1465 connect_address.serializeString() +
1466 " because IPv6 is disabled";
1467 errorstream << *error_message << std::endl;
1471 client = new Client(start_data.name.c_str(),
1472 start_data.password, start_data.address,
1473 *draw_control, texture_src, shader_src,
1474 itemdef_manager, nodedef_manager, sound, eventmgr,
1475 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get());
1477 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1479 infostream << "Connecting to server at ";
1480 connect_address.print(&infostream);
1481 infostream << std::endl;
1483 client->connect(connect_address,
1484 simple_singleplayer_mode || local_server_mode);
1487 Wait for server to accept connection
1493 FpsControl fps_control = { 0 };
1495 f32 wait_time = 0; // in seconds
1497 fps_control.last_time = m_rendering_engine->get_timer_time();
1499 while (m_rendering_engine->run()) {
1501 limitFps(&fps_control, &dtime);
1503 // Update client and server
1504 client->step(dtime);
1507 server->step(dtime);
1510 if (client->getState() == LC_Init) {
1516 if (*connection_aborted)
1519 if (client->accessDenied()) {
1520 *error_message = "Access denied. Reason: "
1521 + client->accessDeniedReason();
1522 *reconnect_requested = client->reconnectRequested();
1523 errorstream << *error_message << std::endl;
1527 if (input->cancelPressed()) {
1528 *connection_aborted = true;
1529 infostream << "Connect aborted [Escape]" << std::endl;
1533 if (client->m_is_registration_confirmation_state) {
1534 if (registration_confirmation_shown) {
1535 // Keep drawing the GUI
1536 m_rendering_engine->draw_menu_scene(guienv, dtime, true);
1538 registration_confirmation_shown = true;
1539 (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
1540 &g_menumgr, client, start_data.name, start_data.password,
1541 connection_aborted, texture_src))->drop();
1545 // Only time out if we aren't waiting for the server we started
1546 if (!start_data.address.empty() && wait_time > 10) {
1547 *error_message = "Connection timed out.";
1548 errorstream << *error_message << std::endl;
1553 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1556 } catch (con::PeerNotFoundException &e) {
1557 // TODO: Should something be done here? At least an info/error
1565 bool Game::getServerContent(bool *aborted)
1569 FpsControl fps_control = { 0 };
1570 f32 dtime; // in seconds
1572 fps_control.last_time = m_rendering_engine->get_timer_time();
1574 while (m_rendering_engine->run()) {
1576 limitFps(&fps_control, &dtime);
1578 // Update client and server
1579 client->step(dtime);
1582 server->step(dtime);
1585 if (client->mediaReceived() && client->itemdefReceived() &&
1586 client->nodedefReceived()) {
1591 if (!checkConnection())
1594 if (client->getState() < LC_Init) {
1595 *error_message = "Client disconnected";
1596 errorstream << *error_message << std::endl;
1600 if (input->cancelPressed()) {
1602 infostream << "Connect aborted [Escape]" << std::endl;
1609 if (!client->itemdefReceived()) {
1610 const wchar_t *text = wgettext("Item definitions...");
1612 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1615 } else if (!client->nodedefReceived()) {
1616 const wchar_t *text = wgettext("Node definitions...");
1618 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1622 std::stringstream message;
1623 std::fixed(message);
1624 message.precision(0);
1625 float receive = client->mediaReceiveProgress() * 100;
1626 message << gettext("Media...");
1628 message << " " << receive << "%";
1629 message.precision(2);
1631 if ((USE_CURL == 0) ||
1632 (!g_settings->getBool("enable_remote_media_server"))) {
1633 float cur = client->getCurRate();
1634 std::string cur_unit = gettext("KiB/s");
1638 cur_unit = gettext("MiB/s");
1641 message << " (" << cur << ' ' << cur_unit << ")";
1644 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1645 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
1646 texture_src, dtime, progress);
1654 /****************************************************************************/
1655 /****************************************************************************
1657 ****************************************************************************/
1658 /****************************************************************************/
1660 inline void Game::updateInteractTimers(f32 dtime)
1662 if (runData.nodig_delay_timer >= 0)
1663 runData.nodig_delay_timer -= dtime;
1665 if (runData.object_hit_delay_timer >= 0)
1666 runData.object_hit_delay_timer -= dtime;
1668 runData.time_from_last_punch += dtime;
1672 /* returns false if game should exit, otherwise true
1674 inline bool Game::checkConnection()
1676 if (client->accessDenied()) {
1677 *error_message = "Access denied. Reason: "
1678 + client->accessDeniedReason();
1679 *reconnect_requested = client->reconnectRequested();
1680 errorstream << *error_message << std::endl;
1688 /* returns false if game should exit, otherwise true
1690 inline bool Game::handleCallbacks()
1692 if (g_gamecallback->disconnect_requested) {
1693 g_gamecallback->disconnect_requested = false;
1697 if (g_gamecallback->changepassword_requested) {
1698 (new GUIPasswordChange(guienv, guiroot, -1,
1699 &g_menumgr, client, texture_src))->drop();
1700 g_gamecallback->changepassword_requested = false;
1703 if (g_gamecallback->changevolume_requested) {
1704 (new GUIVolumeChange(guienv, guiroot, -1,
1705 &g_menumgr, texture_src))->drop();
1706 g_gamecallback->changevolume_requested = false;
1709 if (g_gamecallback->keyconfig_requested) {
1710 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1711 &g_menumgr, texture_src))->drop();
1712 g_gamecallback->keyconfig_requested = false;
1715 if (g_gamecallback->keyconfig_changed) {
1716 input->keycache.populate(); // update the cache with new settings
1717 g_gamecallback->keyconfig_changed = false;
1724 void Game::processQueues()
1726 texture_src->processQueue();
1727 itemdef_manager->processQueue(client);
1728 shader_src->processQueue();
1731 void Game::updateDebugState()
1733 bool has_basic_debug = client->checkPrivilege("basic_debug");
1734 bool has_debug = client->checkPrivilege("debug");
1736 if (m_game_ui->m_flags.show_basic_debug) {
1737 if (!has_basic_debug) {
1738 m_game_ui->m_flags.show_basic_debug = false;
1740 } else if (m_game_ui->m_flags.show_minimal_debug) {
1741 if (has_basic_debug) {
1742 m_game_ui->m_flags.show_basic_debug = true;
1745 if (!has_basic_debug)
1746 hud->disableBlockBounds();
1748 draw_control->show_wireframe = false;
1751 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1754 float profiler_print_interval =
1755 g_settings->getFloat("profiler_print_interval");
1756 bool print_to_log = true;
1758 if (profiler_print_interval == 0) {
1759 print_to_log = false;
1760 profiler_print_interval = 3;
1763 if (profiler_interval.step(dtime, profiler_print_interval)) {
1765 infostream << "Profiler:" << std::endl;
1766 g_profiler->print(infostream);
1769 m_game_ui->updateProfiler();
1770 g_profiler->clear();
1773 // Update update graphs
1774 g_profiler->graphAdd("Time non-rendering [ms]",
1775 draw_times.busy_time - stats.drawtime);
1777 g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time);
1778 g_profiler->graphAdd("FPS", 1.0f / dtime);
1781 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1788 /* Time average and jitter calculation
1790 jp = &stats->dtime_jitter;
1791 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1793 jitter = dtime - jp->avg;
1795 if (jitter > jp->max)
1798 jp->counter += dtime;
1800 if (jp->counter > 0.0) {
1802 jp->max_sample = jp->max;
1803 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1807 /* Busytime average and jitter calculation
1809 jp = &stats->busy_time_jitter;
1810 jp->avg = jp->avg + draw_times.busy_time * 0.02;
1812 jitter = draw_times.busy_time - jp->avg;
1814 if (jitter > jp->max)
1816 if (jitter < jp->min)
1819 jp->counter += dtime;
1821 if (jp->counter > 0.0) {
1823 jp->max_sample = jp->max;
1824 jp->min_sample = jp->min;
1832 /****************************************************************************
1834 ****************************************************************************/
1836 void Game::processUserInput(f32 dtime)
1838 // Reset input if window not active or some menu is active
1839 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1841 #ifdef HAVE_TOUCHSCREENGUI
1842 g_touchscreengui->hide();
1845 #ifdef HAVE_TOUCHSCREENGUI
1846 else if (g_touchscreengui) {
1847 /* on touchscreengui step may generate own input events which ain't
1848 * what we want in case we just did clear them */
1849 g_touchscreengui->step(dtime);
1853 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1854 gui_chat_console->closeConsoleAtOnce();
1857 // Input handler step() (used by the random input generator)
1861 auto formspec = m_game_ui->getFormspecGUI();
1863 formspec->getAndroidUIInput();
1865 handleAndroidChatInput();
1868 // Increase timer for double tap of "keymap_jump"
1869 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1870 runData.jump_timer += dtime;
1873 processItemSelection(&runData.new_playeritem);
1877 void Game::processKeyInput()
1879 if (wasKeyDown(KeyType::DROP)) {
1880 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1881 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1882 toggleAutoforward();
1883 } else if (wasKeyDown(KeyType::BACKWARD)) {
1884 if (g_settings->getBool("continuous_forward"))
1885 toggleAutoforward();
1886 } else if (wasKeyDown(KeyType::INVENTORY)) {
1888 } else if (input->cancelPressed()) {
1890 m_android_chat_open = false;
1892 if (!gui_chat_console->isOpenInhibited()) {
1895 } else if (wasKeyDown(KeyType::CHAT)) {
1896 openConsole(0.2, L"");
1897 } else if (wasKeyDown(KeyType::CMD)) {
1898 openConsole(0.2, L"/");
1899 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1900 if (client->modsLoaded())
1901 openConsole(0.2, L".");
1903 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1904 } else if (wasKeyDown(KeyType::CONSOLE)) {
1905 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1906 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1908 } else if (wasKeyDown(KeyType::JUMP)) {
1909 toggleFreeMoveAlt();
1910 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1912 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1914 } else if (wasKeyDown(KeyType::NOCLIP)) {
1917 } else if (wasKeyDown(KeyType::MUTE)) {
1918 if (g_settings->getBool("enable_sound")) {
1919 bool new_mute_sound = !g_settings->getBool("mute_sound");
1920 g_settings->setBool("mute_sound", new_mute_sound);
1922 m_game_ui->showTranslatedStatusText("Sound muted");
1924 m_game_ui->showTranslatedStatusText("Sound unmuted");
1926 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1928 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1929 if (g_settings->getBool("enable_sound")) {
1930 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1932 g_settings->setFloat("sound_volume", new_volume);
1933 const wchar_t *str = wgettext("Volume changed to %d%%");
1934 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1936 m_game_ui->showStatusText(buf);
1938 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1940 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1941 if (g_settings->getBool("enable_sound")) {
1942 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1944 g_settings->setFloat("sound_volume", new_volume);
1945 const wchar_t *str = wgettext("Volume changed to %d%%");
1946 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1948 m_game_ui->showStatusText(buf);
1950 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1953 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1954 || wasKeyDown(KeyType::DEC_VOLUME)) {
1955 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1957 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1959 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1960 client->makeScreenshot();
1961 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
1962 toggleBlockBounds();
1963 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1964 m_game_ui->toggleHud();
1965 } else if (wasKeyDown(KeyType::MINIMAP)) {
1966 toggleMinimap(isKeyDown(KeyType::SNEAK));
1967 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1968 m_game_ui->toggleChat();
1969 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1971 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1972 toggleUpdateCamera();
1973 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1975 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1976 m_game_ui->toggleProfiler();
1977 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1978 increaseViewRange();
1979 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1980 decreaseViewRange();
1981 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1982 toggleFullViewRange();
1983 } else if (wasKeyDown(KeyType::ZOOM)) {
1985 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1987 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1989 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1991 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1995 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1996 runData.reset_jump_timer = false;
1997 runData.jump_timer = 0.0f;
2000 if (quicktune->hasMessage()) {
2001 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2005 void Game::processItemSelection(u16 *new_playeritem)
2007 LocalPlayer *player = client->getEnv().getLocalPlayer();
2009 /* Item selection using mouse wheel
2011 *new_playeritem = player->getWieldIndex();
2013 s32 wheel = input->getMouseWheel();
2014 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2015 player->hud_hotbar_itemcount - 1);
2019 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2022 if (wasKeyDown(KeyType::HOTBAR_PREV))
2026 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2028 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2031 /* Item selection using hotbar slot keys
2033 for (u16 i = 0; i <= max_item; i++) {
2034 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2035 *new_playeritem = i;
2042 void Game::dropSelectedItem(bool single_item)
2044 IDropAction *a = new IDropAction();
2045 a->count = single_item ? 1 : 0;
2046 a->from_inv.setCurrentPlayer();
2047 a->from_list = "main";
2048 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2049 client->inventoryAction(a);
2053 void Game::openInventory()
2056 * Don't permit to open inventory is CAO or player doesn't exists.
2057 * This prevent showing an empty inventory at player load
2060 LocalPlayer *player = client->getEnv().getLocalPlayer();
2061 if (!player || !player->getCAO())
2064 infostream << "Game: Launching inventory" << std::endl;
2066 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2068 InventoryLocation inventoryloc;
2069 inventoryloc.setCurrentPlayer();
2071 if (!client->modsLoaded()
2072 || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2073 TextDest *txt_dst = new TextDestPlayerInventory(client);
2074 auto *&formspec = m_game_ui->updateFormspec("");
2075 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2076 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2078 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2083 void Game::openConsole(float scale, const wchar_t *line)
2085 assert(scale > 0.0f && scale <= 1.0f);
2088 porting::showInputDialog(gettext("ok"), "", "", 2);
2089 m_android_chat_open = true;
2091 if (gui_chat_console->isOpenInhibited())
2093 gui_chat_console->openConsole(scale);
2095 gui_chat_console->setCloseOnEnter(true);
2096 gui_chat_console->replaceAndAddToHistory(line);
2102 void Game::handleAndroidChatInput()
2104 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2105 std::string text = porting::getInputDialogValue();
2106 client->typeChatMessage(utf8_to_wide(text));
2107 m_android_chat_open = false;
2113 void Game::toggleFreeMove()
2115 bool free_move = !g_settings->getBool("free_move");
2116 g_settings->set("free_move", bool_to_cstr(free_move));
2119 if (client->checkPrivilege("fly")) {
2120 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2122 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2125 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2129 void Game::toggleFreeMoveAlt()
2131 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2134 runData.reset_jump_timer = true;
2138 void Game::togglePitchMove()
2140 bool pitch_move = !g_settings->getBool("pitch_move");
2141 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2144 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2146 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2151 void Game::toggleFast()
2153 bool fast_move = !g_settings->getBool("fast_move");
2154 bool has_fast_privs = client->checkPrivilege("fast");
2155 g_settings->set("fast_move", bool_to_cstr(fast_move));
2158 if (has_fast_privs) {
2159 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2161 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2164 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2168 m_cache_hold_aux1 = fast_move && has_fast_privs;
2173 void Game::toggleNoClip()
2175 bool noclip = !g_settings->getBool("noclip");
2176 g_settings->set("noclip", bool_to_cstr(noclip));
2179 if (client->checkPrivilege("noclip")) {
2180 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2182 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2185 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2189 void Game::toggleCinematic()
2191 bool cinematic = !g_settings->getBool("cinematic");
2192 g_settings->set("cinematic", bool_to_cstr(cinematic));
2195 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2197 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2200 void Game::toggleBlockBounds()
2202 if (client->checkPrivilege("basic_debug")) {
2203 hud->toggleBlockBounds();
2205 m_game_ui->showTranslatedStatusText("Can't show block bounds (need 'basic_debug' privilege)");
2209 // Autoforward by toggling continuous forward.
2210 void Game::toggleAutoforward()
2212 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2213 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2215 if (autorun_enabled)
2216 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2218 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2221 void Game::toggleMinimap(bool shift_pressed)
2223 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2227 mapper->toggleMinimapShape();
2231 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2233 // Not so satisying code to keep compatibility with old fixed mode system
2235 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2237 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2238 m_game_ui->m_flags.show_minimap = false;
2241 // If radar is disabled, try to find a non radar mode or fall back to 0
2242 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2243 while (mapper->getModeIndex() &&
2244 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2247 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2251 // End of 'not so satifying code'
2252 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2253 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2254 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2256 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2259 void Game::toggleFog()
2261 bool fog_enabled = g_settings->getBool("enable_fog");
2262 g_settings->setBool("enable_fog", !fog_enabled);
2264 m_game_ui->showTranslatedStatusText("Fog disabled");
2266 m_game_ui->showTranslatedStatusText("Fog enabled");
2270 void Game::toggleDebug()
2272 // Initial: No debug info
2273 // 1x toggle: Debug text
2274 // 2x toggle: Debug text with profiler graph
2275 // 3x toggle: Debug text and wireframe (needs "debug" priv)
2276 // Next toggle: Back to initial
2278 // The debug text can be in 2 modes: minimal and basic.
2279 // * Minimal: Only technical client info that not gameplay-relevant
2280 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
2281 // Basic mode is used when player has "basic_debug" priv,
2282 // otherwise the Minimal mode is used.
2283 if (!m_game_ui->m_flags.show_minimal_debug) {
2284 m_game_ui->m_flags.show_minimal_debug = true;
2285 if (client->checkPrivilege("basic_debug")) {
2286 m_game_ui->m_flags.show_basic_debug = true;
2288 m_game_ui->m_flags.show_profiler_graph = false;
2289 draw_control->show_wireframe = false;
2290 m_game_ui->showTranslatedStatusText("Debug info shown");
2291 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2292 if (client->checkPrivilege("basic_debug")) {
2293 m_game_ui->m_flags.show_basic_debug = true;
2295 m_game_ui->m_flags.show_profiler_graph = true;
2296 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2297 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2298 if (client->checkPrivilege("basic_debug")) {
2299 m_game_ui->m_flags.show_basic_debug = true;
2301 m_game_ui->m_flags.show_profiler_graph = false;
2302 draw_control->show_wireframe = true;
2303 m_game_ui->showTranslatedStatusText("Wireframe shown");
2305 m_game_ui->m_flags.show_minimal_debug = false;
2306 m_game_ui->m_flags.show_basic_debug = false;
2307 m_game_ui->m_flags.show_profiler_graph = false;
2308 draw_control->show_wireframe = false;
2309 if (client->checkPrivilege("debug")) {
2310 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2312 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2318 void Game::toggleUpdateCamera()
2320 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2321 if (m_flags.disable_camera_update)
2322 m_game_ui->showTranslatedStatusText("Camera update disabled");
2324 m_game_ui->showTranslatedStatusText("Camera update enabled");
2328 void Game::increaseViewRange()
2330 s16 range = g_settings->getS16("viewing_range");
2331 s16 range_new = range + 10;
2335 if (range_new > 4000) {
2337 str = wgettext("Viewing range is at maximum: %d");
2338 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2340 m_game_ui->showStatusText(buf);
2343 str = wgettext("Viewing range changed to %d");
2344 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2346 m_game_ui->showStatusText(buf);
2348 g_settings->set("viewing_range", itos(range_new));
2352 void Game::decreaseViewRange()
2354 s16 range = g_settings->getS16("viewing_range");
2355 s16 range_new = range - 10;
2359 if (range_new < 20) {
2361 str = wgettext("Viewing range is at minimum: %d");
2362 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2364 m_game_ui->showStatusText(buf);
2366 str = wgettext("Viewing range changed to %d");
2367 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2369 m_game_ui->showStatusText(buf);
2371 g_settings->set("viewing_range", itos(range_new));
2375 void Game::toggleFullViewRange()
2377 draw_control->range_all = !draw_control->range_all;
2378 if (draw_control->range_all)
2379 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2381 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2385 void Game::checkZoomEnabled()
2387 LocalPlayer *player = client->getEnv().getLocalPlayer();
2388 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2389 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2392 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2394 if ((device->isWindowActive() && device->isWindowFocused()
2395 && !isMenuActive()) || input->isRandom()) {
2398 if (!input->isRandom()) {
2399 // Mac OSX gets upset if this is set every frame
2400 if (device->getCursorControl()->isVisible())
2401 device->getCursorControl()->setVisible(false);
2405 if (m_first_loop_after_window_activation) {
2406 m_first_loop_after_window_activation = false;
2408 input->setMousePos(driver->getScreenSize().Width / 2,
2409 driver->getScreenSize().Height / 2);
2411 updateCameraOrientation(cam, dtime);
2417 // Mac OSX gets upset if this is set every frame
2418 if (!device->getCursorControl()->isVisible())
2419 device->getCursorControl()->setVisible(true);
2422 m_first_loop_after_window_activation = true;
2427 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2428 // responsiveness independently of FOV.
2429 f32 Game::getSensitivityScaleFactor() const
2431 f32 fov_y = client->getCamera()->getFovY();
2433 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2434 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2436 return tan(fov_y / 2.0f) * 1.3763818698f;
2439 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2441 #ifdef HAVE_TOUCHSCREENGUI
2442 if (g_touchscreengui) {
2443 cam->camera_yaw += g_touchscreengui->getYawChange();
2444 cam->camera_pitch = g_touchscreengui->getPitch();
2447 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2448 v2s32 dist = input->getMousePos() - center;
2450 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2454 f32 sens_scale = getSensitivityScaleFactor();
2455 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2456 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2458 if (dist.X != 0 || dist.Y != 0)
2459 input->setMousePos(center.X, center.Y);
2460 #ifdef HAVE_TOUCHSCREENGUI
2464 if (m_cache_enable_joysticks) {
2465 f32 sens_scale = getSensitivityScaleFactor();
2466 f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime * sens_scale;
2467 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2468 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2471 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2475 void Game::updatePlayerControl(const CameraOrientation &cam)
2477 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2479 // DO NOT use the isKeyDown method for the forward, backward, left, right
2480 // buttons, as the code that uses the controls needs to be able to
2481 // distinguish between the two in order to know when to use joysticks.
2483 PlayerControl control(
2484 input->isKeyDown(KeyType::FORWARD),
2485 input->isKeyDown(KeyType::BACKWARD),
2486 input->isKeyDown(KeyType::LEFT),
2487 input->isKeyDown(KeyType::RIGHT),
2488 isKeyDown(KeyType::JUMP),
2489 isKeyDown(KeyType::AUX1),
2490 isKeyDown(KeyType::SNEAK),
2491 isKeyDown(KeyType::ZOOM),
2492 isKeyDown(KeyType::DIG),
2493 isKeyDown(KeyType::PLACE),
2496 input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE),
2497 input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE)
2500 u32 keypress_bits = (
2501 ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) |
2502 ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) |
2503 ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) |
2504 ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) |
2505 ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) |
2506 ( (u32)(isKeyDown(KeyType::AUX1) & 0x1) << 5) |
2507 ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) |
2508 ( (u32)(isKeyDown(KeyType::DIG) & 0x1) << 7) |
2509 ( (u32)(isKeyDown(KeyType::PLACE) & 0x1) << 8) |
2510 ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9)
2514 /* For Android, simulate holding down AUX1 (fast move) if the user has
2515 * the fast_move setting toggled on. If there is an aux1 key defined for
2516 * Android then its meaning is inverted (i.e. holding aux1 means walk and
2519 if (m_cache_hold_aux1) {
2520 control.aux1 = control.aux1 ^ true;
2521 keypress_bits ^= ((u32)(1U << 5));
2525 LocalPlayer *player = client->getEnv().getLocalPlayer();
2527 // autojump if set: simulate "jump" key
2528 if (player->getAutojump()) {
2529 control.jump = true;
2530 keypress_bits |= 1U << 4;
2533 // autoforward if set: simulate "up" key
2534 if (player->getPlayerSettings().continuous_forward &&
2535 client->activeObjectsReceived() && !player->isDead()) {
2537 keypress_bits |= 1U << 0;
2540 client->setPlayerControl(control);
2541 player->keyPressed = keypress_bits;
2547 inline void Game::step(f32 *dtime)
2549 bool can_be_and_is_paused =
2550 (simple_singleplayer_mode && g_menumgr.pausesGame());
2552 if (can_be_and_is_paused) { // This is for a singleplayer server
2553 *dtime = 0; // No time passes
2555 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
2559 server->step(*dtime);
2561 client->step(*dtime);
2565 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2568 for (auto &&child: node->getChildren())
2569 pauseNodeAnimation(paused, child);
2570 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2572 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2573 float speed = animated_node->getAnimationSpeed();
2576 paused.push_back({grab(animated_node), speed});
2577 animated_node->setAnimationSpeed(0.0f);
2580 void Game::pauseAnimation()
2582 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2585 void Game::resumeAnimation()
2587 for (auto &&pair: paused_animated_nodes)
2588 pair.first->setAnimationSpeed(pair.second);
2589 paused_animated_nodes.clear();
2592 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2593 {&Game::handleClientEvent_None},
2594 {&Game::handleClientEvent_PlayerDamage},
2595 {&Game::handleClientEvent_PlayerForceMove},
2596 {&Game::handleClientEvent_Deathscreen},
2597 {&Game::handleClientEvent_ShowFormSpec},
2598 {&Game::handleClientEvent_ShowLocalFormSpec},
2599 {&Game::handleClientEvent_HandleParticleEvent},
2600 {&Game::handleClientEvent_HandleParticleEvent},
2601 {&Game::handleClientEvent_HandleParticleEvent},
2602 {&Game::handleClientEvent_HudAdd},
2603 {&Game::handleClientEvent_HudRemove},
2604 {&Game::handleClientEvent_HudChange},
2605 {&Game::handleClientEvent_SetSky},
2606 {&Game::handleClientEvent_SetSun},
2607 {&Game::handleClientEvent_SetMoon},
2608 {&Game::handleClientEvent_SetStars},
2609 {&Game::handleClientEvent_OverrideDayNigthRatio},
2610 {&Game::handleClientEvent_CloudParams},
2613 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2615 FATAL_ERROR("ClientEvent type None received");
2618 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2620 if (client->modsLoaded())
2621 client->getScript()->on_damage_taken(event->player_damage.amount);
2623 // Damage flash and hurt tilt are not used at death
2624 if (client->getHP() > 0) {
2625 LocalPlayer *player = client->getEnv().getLocalPlayer();
2627 f32 hp_max = player->getCAO() ?
2628 player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2629 f32 damage_ratio = event->player_damage.amount / hp_max;
2631 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2632 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2634 player->hurt_tilt_timer = 1.5f;
2635 player->hurt_tilt_strength =
2636 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2639 // Play damage sound
2640 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2643 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2645 cam->camera_yaw = event->player_force_move.yaw;
2646 cam->camera_pitch = event->player_force_move.pitch;
2649 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2651 // If client scripting is enabled, deathscreen is handled by CSM code in
2652 // builtin/client/init.lua
2653 if (client->modsLoaded())
2654 client->getScript()->on_death();
2656 showDeathFormspec();
2658 /* Handle visualization */
2659 LocalPlayer *player = client->getEnv().getLocalPlayer();
2660 runData.damage_flash = 0;
2661 player->hurt_tilt_timer = 0;
2662 player->hurt_tilt_strength = 0;
2665 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2667 if (event->show_formspec.formspec->empty()) {
2668 auto formspec = m_game_ui->getFormspecGUI();
2669 if (formspec && (event->show_formspec.formname->empty()
2670 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2671 formspec->quitMenu();
2674 FormspecFormSource *fs_src =
2675 new FormspecFormSource(*(event->show_formspec.formspec));
2676 TextDestPlayerInventory *txt_dst =
2677 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2679 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2680 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2681 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2684 delete event->show_formspec.formspec;
2685 delete event->show_formspec.formname;
2688 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2690 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2691 LocalFormspecHandler *txt_dst =
2692 new LocalFormspecHandler(*event->show_formspec.formname, client);
2693 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2694 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2696 delete event->show_formspec.formspec;
2697 delete event->show_formspec.formname;
2700 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2701 CameraOrientation *cam)
2703 LocalPlayer *player = client->getEnv().getLocalPlayer();
2704 client->getParticleManager()->handleParticleEvent(event, client, player);
2707 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2709 LocalPlayer *player = client->getEnv().getLocalPlayer();
2711 u32 server_id = event->hudadd->server_id;
2712 // ignore if we already have a HUD with that ID
2713 auto i = m_hud_server_to_client.find(server_id);
2714 if (i != m_hud_server_to_client.end()) {
2715 delete event->hudadd;
2719 HudElement *e = new HudElement;
2720 e->type = static_cast<HudElementType>(event->hudadd->type);
2721 e->pos = event->hudadd->pos;
2722 e->name = event->hudadd->name;
2723 e->scale = event->hudadd->scale;
2724 e->text = event->hudadd->text;
2725 e->number = event->hudadd->number;
2726 e->item = event->hudadd->item;
2727 e->dir = event->hudadd->dir;
2728 e->align = event->hudadd->align;
2729 e->offset = event->hudadd->offset;
2730 e->world_pos = event->hudadd->world_pos;
2731 e->size = event->hudadd->size;
2732 e->z_index = event->hudadd->z_index;
2733 e->text2 = event->hudadd->text2;
2734 m_hud_server_to_client[server_id] = player->addHud(e);
2736 delete event->hudadd;
2739 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2741 LocalPlayer *player = client->getEnv().getLocalPlayer();
2743 auto i = m_hud_server_to_client.find(event->hudrm.id);
2744 if (i != m_hud_server_to_client.end()) {
2745 HudElement *e = player->removeHud(i->second);
2747 m_hud_server_to_client.erase(i);
2752 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2754 LocalPlayer *player = client->getEnv().getLocalPlayer();
2756 HudElement *e = nullptr;
2758 auto i = m_hud_server_to_client.find(event->hudchange->id);
2759 if (i != m_hud_server_to_client.end()) {
2760 e = player->getHud(i->second);
2764 delete event->hudchange;
2768 #define CASE_SET(statval, prop, dataprop) \
2770 e->prop = event->hudchange->dataprop; \
2773 switch (event->hudchange->stat) {
2774 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2776 CASE_SET(HUD_STAT_NAME, name, sdata);
2778 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2780 CASE_SET(HUD_STAT_TEXT, text, sdata);
2782 CASE_SET(HUD_STAT_NUMBER, number, data);
2784 CASE_SET(HUD_STAT_ITEM, item, data);
2786 CASE_SET(HUD_STAT_DIR, dir, data);
2788 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2790 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2792 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2794 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2796 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2798 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2803 delete event->hudchange;
2806 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2808 sky->setVisible(false);
2809 // Whether clouds are visible in front of a custom skybox.
2810 sky->setCloudsEnabled(event->set_sky->clouds);
2816 // Clear the old textures out in case we switch rendering type.
2817 sky->clearSkyboxTextures();
2818 // Handle according to type
2819 if (event->set_sky->type == "regular") {
2820 // Shows the mesh skybox
2821 sky->setVisible(true);
2822 // Update mesh based skybox colours if applicable.
2823 sky->setSkyColors(event->set_sky->sky_color);
2824 sky->setHorizonTint(
2825 event->set_sky->fog_sun_tint,
2826 event->set_sky->fog_moon_tint,
2827 event->set_sky->fog_tint_type
2829 } else if (event->set_sky->type == "skybox" &&
2830 event->set_sky->textures.size() == 6) {
2831 // Disable the dyanmic mesh skybox:
2832 sky->setVisible(false);
2834 sky->setFallbackBgColor(event->set_sky->bgcolor);
2835 // Set sunrise and sunset fog tinting:
2836 sky->setHorizonTint(
2837 event->set_sky->fog_sun_tint,
2838 event->set_sky->fog_moon_tint,
2839 event->set_sky->fog_tint_type
2841 // Add textures to skybox.
2842 for (int i = 0; i < 6; i++)
2843 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2845 // Handle everything else as plain color.
2846 if (event->set_sky->type != "plain")
2847 infostream << "Unknown sky type: "
2848 << (event->set_sky->type) << std::endl;
2849 sky->setVisible(false);
2850 sky->setFallbackBgColor(event->set_sky->bgcolor);
2851 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2852 sky->setHorizonTint(
2853 event->set_sky->bgcolor,
2854 event->set_sky->bgcolor,
2859 delete event->set_sky;
2862 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2864 sky->setSunVisible(event->sun_params->visible);
2865 sky->setSunTexture(event->sun_params->texture,
2866 event->sun_params->tonemap, texture_src);
2867 sky->setSunScale(event->sun_params->scale);
2868 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2869 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2870 delete event->sun_params;
2873 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2875 sky->setMoonVisible(event->moon_params->visible);
2876 sky->setMoonTexture(event->moon_params->texture,
2877 event->moon_params->tonemap, texture_src);
2878 sky->setMoonScale(event->moon_params->scale);
2879 delete event->moon_params;
2882 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2884 sky->setStarsVisible(event->star_params->visible);
2885 sky->setStarCount(event->star_params->count, false);
2886 sky->setStarColor(event->star_params->starcolor);
2887 sky->setStarScale(event->star_params->scale);
2888 delete event->star_params;
2891 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2892 CameraOrientation *cam)
2894 client->getEnv().setDayNightRatioOverride(
2895 event->override_day_night_ratio.do_override,
2896 event->override_day_night_ratio.ratio_f * 1000.0f);
2899 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2904 clouds->setDensity(event->cloud_params.density);
2905 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2906 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2907 clouds->setHeight(event->cloud_params.height);
2908 clouds->setThickness(event->cloud_params.thickness);
2909 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2912 void Game::processClientEvents(CameraOrientation *cam)
2914 while (client->hasClientEvents()) {
2915 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2916 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2917 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2918 (this->*evHandler.handler)(event.get(), cam);
2922 void Game::updateChat(f32 dtime, const v2u32 &screensize)
2924 // Get new messages from error log buffer
2925 while (!m_chat_log_buf.empty())
2926 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2928 // Get new messages from client
2929 std::wstring message;
2930 while (client->getChatMessage(message)) {
2931 chat_backend->addUnparsedMessage(message);
2934 // Remove old messages
2935 chat_backend->step(dtime);
2937 // Display all messages in a static text element
2938 m_game_ui->setChatText(chat_backend->getRecentChat(),
2939 chat_backend->getRecentBuffer().getLineCount());
2942 void Game::updateCamera(u32 busy_time, f32 dtime)
2944 LocalPlayer *player = client->getEnv().getLocalPlayer();
2947 For interaction purposes, get info about the held item
2949 - Is it a usable item?
2950 - Can it point to liquids?
2952 ItemStack playeritem;
2954 ItemStack selected, hand;
2955 playeritem = player->getWieldedItem(&selected, &hand);
2958 ToolCapabilities playeritem_toolcap =
2959 playeritem.getToolCapabilities(itemdef_manager);
2961 v3s16 old_camera_offset = camera->getOffset();
2963 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2964 GenericCAO *playercao = player->getCAO();
2966 // If playercao not loaded, don't change camera
2970 camera->toggleCameraMode();
2972 // Make the player visible depending on camera mode.
2973 playercao->updateMeshCulling();
2974 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2977 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2978 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2980 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2981 camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
2982 camera->step(dtime);
2984 v3f camera_position = camera->getPosition();
2985 v3f camera_direction = camera->getDirection();
2986 f32 camera_fov = camera->getFovMax();
2987 v3s16 camera_offset = camera->getOffset();
2989 m_camera_offset_changed = (camera_offset != old_camera_offset);
2991 if (!m_flags.disable_camera_update) {
2992 client->getEnv().getClientMap().updateCamera(camera_position,
2993 camera_direction, camera_fov, camera_offset);
2995 if (m_camera_offset_changed) {
2996 client->updateCameraOffset(camera_offset);
2997 client->getEnv().updateCameraOffset(camera_offset);
3000 clouds->updateCameraOffset(camera_offset);
3006 void Game::updateSound(f32 dtime)
3008 // Update sound listener
3009 v3s16 camera_offset = camera->getOffset();
3010 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3011 v3f(0, 0, 0), // velocity
3012 camera->getDirection(),
3013 camera->getCameraNode()->getUpVector());
3015 bool mute_sound = g_settings->getBool("mute_sound");
3017 sound->setListenerGain(0.0f);
3019 // Check if volume is in the proper range, else fix it.
3020 float old_volume = g_settings->getFloat("sound_volume");
3021 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3022 sound->setListenerGain(new_volume);
3024 if (old_volume != new_volume) {
3025 g_settings->setFloat("sound_volume", new_volume);
3029 LocalPlayer *player = client->getEnv().getLocalPlayer();
3031 // Tell the sound maker whether to make footstep sounds
3032 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3034 // Update sound maker
3035 if (player->makes_footstep_sound)
3036 soundmaker->step(dtime);
3038 ClientMap &map = client->getEnv().getClientMap();
3039 MapNode n = map.getNode(player->getFootstepNodePos());
3040 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3044 void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
3046 LocalPlayer *player = client->getEnv().getLocalPlayer();
3048 const v3f camera_direction = camera->getDirection();
3049 const v3s16 camera_offset = camera->getOffset();
3052 Calculate what block is the crosshair pointing to
3055 ItemStack selected_item, hand_item;
3056 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3058 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3059 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3061 core::line3d<f32> shootline;
3063 switch (camera->getCameraMode()) {
3064 case CAMERA_MODE_FIRST:
3065 // Shoot from camera position, with bobbing
3066 shootline.start = camera->getPosition();
3068 case CAMERA_MODE_THIRD:
3069 // Shoot from player head, no bobbing
3070 shootline.start = camera->getHeadPosition();
3072 case CAMERA_MODE_THIRD_FRONT:
3073 shootline.start = camera->getHeadPosition();
3074 // prevent player pointing anything in front-view
3078 shootline.end = shootline.start + camera_direction * BS * d;
3080 #ifdef HAVE_TOUCHSCREENGUI
3082 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3083 shootline = g_touchscreengui->getShootline();
3084 // Scale shootline to the acual distance the player can reach
3085 shootline.end = shootline.start
3086 + shootline.getVector().normalize() * BS * d;
3087 shootline.start += intToFloat(camera_offset, BS);
3088 shootline.end += intToFloat(camera_offset, BS);
3093 PointedThing pointed = updatePointedThing(shootline,
3094 selected_def.liquids_pointable,
3095 !runData.btn_down_for_dig,
3098 if (pointed != runData.pointed_old) {
3099 infostream << "Pointing at " << pointed.dump() << std::endl;
3100 hud->updateSelectionMesh(camera_offset);
3103 // Allow digging again if button is not pressed
3104 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3105 runData.digging_blocked = false;
3109 - releasing dig button
3110 - pointing away from node
3112 if (runData.digging) {
3113 if (wasKeyReleased(KeyType::DIG)) {
3114 infostream << "Dig button released (stopped digging)" << std::endl;
3115 runData.digging = false;
3116 } else if (pointed != runData.pointed_old) {
3117 if (pointed.type == POINTEDTHING_NODE
3118 && runData.pointed_old.type == POINTEDTHING_NODE
3119 && pointed.node_undersurface
3120 == runData.pointed_old.node_undersurface) {
3121 // Still pointing to the same node, but a different face.
3124 infostream << "Pointing away from node (stopped digging)" << std::endl;
3125 runData.digging = false;
3126 hud->updateSelectionMesh(camera_offset);
3130 if (!runData.digging) {
3131 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3132 client->setCrack(-1, v3s16(0, 0, 0));
3133 runData.dig_time = 0.0;
3135 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3136 // Remove e.g. torches faster when clicking instead of holding dig button
3137 runData.nodig_delay_timer = 0;
3138 runData.dig_instantly = false;
3141 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3142 runData.btn_down_for_dig = false;
3144 runData.punching = false;
3146 soundmaker->m_player_leftpunch_sound.name = "";
3148 // Prepare for repeating, unless we're not supposed to
3149 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3150 runData.repeat_place_timer += dtime;
3152 runData.repeat_place_timer = 0;
3154 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3155 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3156 !client->getScript()->on_item_use(selected_item, pointed)))
3157 client->interact(INTERACT_USE, pointed);
3158 } else if (pointed.type == POINTEDTHING_NODE) {
3159 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3160 } else if (pointed.type == POINTEDTHING_OBJECT) {
3161 v3f player_position = player->getPosition();
3162 handlePointingAtObject(pointed, tool_item, player_position, show_debug);
3163 } else if (isKeyDown(KeyType::DIG)) {
3164 // When button is held down in air, show continuous animation
3165 runData.punching = true;
3166 // Run callback even though item is not usable
3167 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3168 client->getScript()->on_item_use(selected_item, pointed);
3169 } else if (wasKeyPressed(KeyType::PLACE)) {
3170 handlePointingAtNothing(selected_item);
3173 runData.pointed_old = pointed;
3175 if (runData.punching || wasKeyPressed(KeyType::DIG))
3176 camera->setDigging(0); // dig animation
3178 input->clearWasKeyPressed();
3179 input->clearWasKeyReleased();
3180 // Ensure DIG & PLACE are marked as handled
3181 wasKeyDown(KeyType::DIG);
3182 wasKeyDown(KeyType::PLACE);
3184 input->joystick.clearWasKeyPressed(KeyType::DIG);
3185 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3187 input->joystick.clearWasKeyReleased(KeyType::DIG);
3188 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3192 PointedThing Game::updatePointedThing(
3193 const core::line3d<f32> &shootline,
3194 bool liquids_pointable,
3195 bool look_for_object,
3196 const v3s16 &camera_offset)
3198 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3199 selectionboxes->clear();
3200 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3201 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3202 "show_entity_selectionbox");
3204 ClientEnvironment &env = client->getEnv();
3205 ClientMap &map = env.getClientMap();
3206 const NodeDefManager *nodedef = map.getNodeDefManager();
3208 runData.selected_object = NULL;
3209 hud->pointing_at_object = false;
3211 RaycastState s(shootline, look_for_object, liquids_pointable);
3212 PointedThing result;
3213 env.continueRaycast(&s, &result);
3214 if (result.type == POINTEDTHING_OBJECT) {
3215 hud->pointing_at_object = true;
3217 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3218 aabb3f selection_box;
3219 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3220 runData.selected_object->getSelectionBox(&selection_box)) {
3221 v3f pos = runData.selected_object->getPosition();
3222 selectionboxes->push_back(aabb3f(selection_box));
3223 hud->setSelectionPos(pos, camera_offset);
3225 } else if (result.type == POINTEDTHING_NODE) {
3226 // Update selection boxes
3227 MapNode n = map.getNode(result.node_undersurface);
3228 std::vector<aabb3f> boxes;
3229 n.getSelectionBoxes(nodedef, &boxes,
3230 n.getNeighbors(result.node_undersurface, &map));
3233 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3234 i != boxes.end(); ++i) {
3236 box.MinEdge -= v3f(d, d, d);
3237 box.MaxEdge += v3f(d, d, d);
3238 selectionboxes->push_back(box);
3240 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3242 hud->setSelectedFaceNormal(v3f(
3243 result.intersection_normal.X,
3244 result.intersection_normal.Y,
3245 result.intersection_normal.Z));
3248 // Update selection mesh light level and vertex colors
3249 if (!selectionboxes->empty()) {
3250 v3f pf = hud->getSelectionPos();
3251 v3s16 p = floatToInt(pf, BS);
3253 // Get selection mesh light level
3254 MapNode n = map.getNode(p);
3255 u16 node_light = getInteriorLight(n, -1, nodedef);
3256 u16 light_level = node_light;
3258 for (const v3s16 &dir : g_6dirs) {
3259 n = map.getNode(p + dir);
3260 node_light = getInteriorLight(n, -1, nodedef);
3261 if (node_light > light_level)
3262 light_level = node_light;
3265 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3267 final_color_blend(&c, light_level, daynight_ratio);
3269 // Modify final color a bit with time
3270 u32 timer = porting::getTimeMs() % 5000;
3271 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3272 float sin_r = 0.08f * std::sin(timerf);
3273 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3274 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3275 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3276 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3277 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3279 // Set mesh final color
3280 hud->setSelectionMeshColor(c);
3286 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3288 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3289 PointedThing fauxPointed;
3290 fauxPointed.type = POINTEDTHING_NOTHING;
3291 client->interact(INTERACT_ACTIVATE, fauxPointed);
3295 void Game::handlePointingAtNode(const PointedThing &pointed,
3296 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3298 v3s16 nodepos = pointed.node_undersurface;
3299 v3s16 neighbourpos = pointed.node_abovesurface;
3302 Check information text of node
3305 ClientMap &map = client->getEnv().getClientMap();
3307 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3308 && !runData.digging_blocked
3309 && client->checkPrivilege("interact")) {
3310 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3313 // This should be done after digging handling
3314 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3317 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3318 meta->getString("infotext"))));
3320 MapNode n = map.getNode(nodepos);
3322 if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
3323 m_game_ui->setInfoText(L"Unknown node: " +
3324 utf8_to_wide(nodedef_manager->get(n).name));
3328 if ((wasKeyPressed(KeyType::PLACE) ||
3329 runData.repeat_place_timer >= m_repeat_place_time) &&
3330 client->checkPrivilege("interact")) {
3331 runData.repeat_place_timer = 0;
3332 infostream << "Place button pressed while looking at ground" << std::endl;
3334 // Placing animation (always shown for feedback)
3335 camera->setDigging(1);
3337 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3339 // If the wielded item has node placement prediction,
3341 // And also set the sound and send the interact
3342 // But first check for meta formspec and rightclickable
3343 auto &def = selected_item.getDefinition(itemdef_manager);
3344 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3347 if (placed && client->modsLoaded())
3348 client->getScript()->on_placenode(pointed, def);
3352 bool Game::nodePlacement(const ItemDefinition &selected_def,
3353 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3354 const PointedThing &pointed, const NodeMetadata *meta)
3356 const auto &prediction = selected_def.node_placement_prediction;
3358 const NodeDefManager *nodedef = client->ndef();
3359 ClientMap &map = client->getEnv().getClientMap();
3361 bool is_valid_position;
3363 node = map.getNode(nodepos, &is_valid_position);
3364 if (!is_valid_position) {
3365 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3370 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3371 && !isKeyDown(KeyType::SNEAK)) {
3372 // on_rightclick callbacks are called anyway
3373 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3374 client->interact(INTERACT_PLACE, pointed);
3376 infostream << "Launching custom inventory view" << std::endl;
3378 InventoryLocation inventoryloc;
3379 inventoryloc.setNodeMeta(nodepos);
3381 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3382 &client->getEnv().getClientMap(), nodepos);
3383 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3385 auto *&formspec = m_game_ui->updateFormspec("");
3386 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3387 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3389 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3393 // on_rightclick callback
3394 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3395 !isKeyDown(KeyType::SNEAK))) {
3397 client->interact(INTERACT_PLACE, pointed);
3401 verbosestream << "Node placement prediction for "
3402 << selected_def.name << " is " << prediction << std::endl;
3403 v3s16 p = neighbourpos;
3405 // Place inside node itself if buildable_to
3406 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3407 if (is_valid_position) {
3408 if (nodedef->get(n_under).buildable_to) {
3411 node = map.getNode(p, &is_valid_position);
3412 if (is_valid_position && !nodedef->get(node).buildable_to) {
3413 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3415 client->interact(INTERACT_PLACE, pointed);
3421 // Find id of predicted node
3423 bool found = nodedef->getId(prediction, id);
3426 errorstream << "Node placement prediction failed for "
3427 << selected_def.name << " (places " << prediction
3428 << ") - Name not known" << std::endl;
3429 // Handle this as if prediction was empty
3431 client->interact(INTERACT_PLACE, pointed);
3435 const ContentFeatures &predicted_f = nodedef->get(id);
3437 // Predict param2 for facedir and wallmounted nodes
3438 // Compare core.item_place_node() for what the server does
3441 const u8 place_param2 = selected_def.place_param2;
3444 param2 = place_param2;
3445 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3446 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3447 v3s16 dir = nodepos - neighbourpos;
3449 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3450 param2 = dir.Y < 0 ? 1 : 0;
3451 } else if (abs(dir.X) > abs(dir.Z)) {
3452 param2 = dir.X < 0 ? 3 : 2;
3454 param2 = dir.Z < 0 ? 5 : 4;
3456 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3457 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3458 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3460 if (abs(dir.X) > abs(dir.Z)) {
3461 param2 = dir.X < 0 ? 3 : 1;
3463 param2 = dir.Z < 0 ? 2 : 0;
3467 // Check attachment if node is in group attached_node
3468 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3469 const static v3s16 wallmounted_dirs[8] = {
3479 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3480 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3481 pp = p + wallmounted_dirs[param2];
3483 pp = p + v3s16(0, -1, 0);
3485 if (!nodedef->get(map.getNode(pp)).walkable) {
3486 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3488 client->interact(INTERACT_PLACE, pointed);
3494 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3495 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3496 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3497 const auto &indexstr = selected_item.metadata.
3498 getString("palette_index", 0);
3499 if (!indexstr.empty()) {
3500 s32 index = mystoi(indexstr);
3501 if (predicted_f.param_type_2 == CPT2_COLOR) {
3503 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3504 // param2 = pure palette index + other
3505 param2 = (index & 0xf8) | (param2 & 0x07);
3506 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3507 // param2 = pure palette index + other
3508 param2 = (index & 0xe0) | (param2 & 0x1f);
3513 // Add node to client map
3514 MapNode n(id, 0, param2);
3517 LocalPlayer *player = client->getEnv().getLocalPlayer();
3519 // Dont place node when player would be inside new node
3520 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3521 if (!nodedef->get(n).walkable ||
3522 g_settings->getBool("enable_build_where_you_stand") ||
3523 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3524 (nodedef->get(n).walkable &&
3525 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3526 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3527 // This triggers the required mesh update too
3528 client->addNode(p, n);
3530 client->interact(INTERACT_PLACE, pointed);
3531 // A node is predicted, also play a sound
3532 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3535 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3538 } catch (const InvalidPositionException &e) {
3539 errorstream << "Node placement prediction failed for "
3540 << selected_def.name << " (places "
3541 << prediction << ") - Position not loaded" << std::endl;
3542 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3547 void Game::handlePointingAtObject(const PointedThing &pointed,
3548 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3550 std::wstring infotext = unescape_translate(
3551 utf8_to_wide(runData.selected_object->infoText()));
3554 if (!infotext.empty()) {
3557 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3560 m_game_ui->setInfoText(infotext);
3562 if (isKeyDown(KeyType::DIG)) {
3563 bool do_punch = false;
3564 bool do_punch_damage = false;
3566 if (runData.object_hit_delay_timer <= 0.0) {
3568 do_punch_damage = true;
3569 runData.object_hit_delay_timer = object_hit_delay;
3572 if (wasKeyPressed(KeyType::DIG))
3576 infostream << "Punched object" << std::endl;
3577 runData.punching = true;
3580 if (do_punch_damage) {
3581 // Report direct punch
3582 v3f objpos = runData.selected_object->getPosition();
3583 v3f dir = (objpos - player_position).normalize();
3585 bool disable_send = runData.selected_object->directReportPunch(
3586 dir, &tool_item, runData.time_from_last_punch);
3587 runData.time_from_last_punch = 0;
3590 client->interact(INTERACT_START_DIGGING, pointed);
3592 } else if (wasKeyDown(KeyType::PLACE)) {
3593 infostream << "Pressed place button while pointing at object" << std::endl;
3594 client->interact(INTERACT_PLACE, pointed); // place
3599 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3600 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3602 // See also: serverpackethandle.cpp, action == 2
3603 LocalPlayer *player = client->getEnv().getLocalPlayer();
3604 ClientMap &map = client->getEnv().getClientMap();
3605 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3607 // NOTE: Similar piece of code exists on the server side for
3609 // Get digging parameters
3610 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3611 &selected_item.getToolCapabilities(itemdef_manager));
3613 // If can't dig, try hand
3614 if (!params.diggable) {
3615 params = getDigParams(nodedef_manager->get(n).groups,
3616 &hand_item.getToolCapabilities(itemdef_manager));
3619 if (!params.diggable) {
3620 // I guess nobody will wait for this long
3621 runData.dig_time_complete = 10000000.0;
3623 runData.dig_time_complete = params.time;
3625 if (m_cache_enable_particles) {
3626 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3627 client->getParticleManager()->addNodeParticle(client,
3628 player, nodepos, n, features);
3632 if (!runData.digging) {
3633 infostream << "Started digging" << std::endl;
3634 runData.dig_instantly = runData.dig_time_complete == 0;
3635 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3637 client->interact(INTERACT_START_DIGGING, pointed);
3638 runData.digging = true;
3639 runData.btn_down_for_dig = true;
3642 if (!runData.dig_instantly) {
3643 runData.dig_index = (float)crack_animation_length
3645 / runData.dig_time_complete;
3647 // This is for e.g. torches
3648 runData.dig_index = crack_animation_length;
3651 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3653 if (sound_dig.exists() && params.diggable) {
3654 if (sound_dig.name == "__group") {
3655 if (!params.main_group.empty()) {
3656 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3657 soundmaker->m_player_leftpunch_sound.name =
3658 std::string("default_dig_") +
3662 soundmaker->m_player_leftpunch_sound = sound_dig;
3666 // Don't show cracks if not diggable
3667 if (runData.dig_time_complete >= 100000.0) {
3668 } else if (runData.dig_index < crack_animation_length) {
3669 //TimeTaker timer("client.setTempMod");
3670 //infostream<<"dig_index="<<dig_index<<std::endl;
3671 client->setCrack(runData.dig_index, nodepos);
3673 infostream << "Digging completed" << std::endl;
3674 client->setCrack(-1, v3s16(0, 0, 0));
3676 runData.dig_time = 0;
3677 runData.digging = false;
3678 // we successfully dug, now block it from repeating if we want to be safe
3679 if (g_settings->getBool("safe_dig_and_place"))
3680 runData.digging_blocked = true;
3682 runData.nodig_delay_timer =
3683 runData.dig_time_complete / (float)crack_animation_length;
3685 // We don't want a corresponding delay to very time consuming nodes
3686 // and nodes without digging time (e.g. torches) get a fixed delay.
3687 if (runData.nodig_delay_timer > 0.3)
3688 runData.nodig_delay_timer = 0.3;
3689 else if (runData.dig_instantly)
3690 runData.nodig_delay_timer = 0.15;
3692 bool is_valid_position;
3693 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3694 if (is_valid_position) {
3695 if (client->modsLoaded() &&
3696 client->getScript()->on_dignode(nodepos, wasnode)) {
3700 const ContentFeatures &f = client->ndef()->get(wasnode);
3701 if (f.node_dig_prediction == "air") {
3702 client->removeNode(nodepos);
3703 } else if (!f.node_dig_prediction.empty()) {
3705 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3707 client->addNode(nodepos, id, true);
3709 // implicit else: no prediction
3712 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3714 if (m_cache_enable_particles) {
3715 const ContentFeatures &features =
3716 client->getNodeDefManager()->get(wasnode);
3717 client->getParticleManager()->addDiggingParticles(client,
3718 player, nodepos, wasnode, features);
3722 // Send event to trigger sound
3723 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3726 if (runData.dig_time_complete < 100000.0) {
3727 runData.dig_time += dtime;
3729 runData.dig_time = 0;
3730 client->setCrack(-1, nodepos);
3733 camera->setDigging(0); // Dig animation
3736 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3737 const CameraOrientation &cam)
3739 TimeTaker tt_update("Game::updateFrame()");
3740 LocalPlayer *player = client->getEnv().getLocalPlayer();
3746 if (draw_control->range_all) {
3747 runData.fog_range = 100000 * BS;
3749 runData.fog_range = draw_control->wanted_range * BS;
3753 Calculate general brightness
3755 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3756 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3757 float direct_brightness;
3760 if (m_cache_enable_noclip && m_cache_enable_free_move) {
3761 direct_brightness = time_brightness;
3762 sunlight_seen = true;
3764 float old_brightness = sky->getBrightness();
3765 direct_brightness = client->getEnv().getClientMap()
3766 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3767 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3771 float time_of_day_smooth = runData.time_of_day_smooth;
3772 float time_of_day = client->getEnv().getTimeOfDayF();
3774 static const float maxsm = 0.05f;
3775 static const float todsm = 0.05f;
3777 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3778 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3779 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3780 time_of_day_smooth = time_of_day;
3782 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3783 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3784 + (time_of_day + 1.0) * todsm;
3786 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3787 + time_of_day * todsm;
3789 runData.time_of_day_smooth = time_of_day_smooth;
3791 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3792 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3793 player->getPitch());
3799 if (sky->getCloudsVisible()) {
3800 clouds->setVisible(true);
3801 clouds->step(dtime);
3802 // camera->getPosition is not enough for 3rd person views
3803 v3f camera_node_position = camera->getCameraNode()->getPosition();
3804 v3s16 camera_offset = camera->getOffset();
3805 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3806 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3807 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3808 clouds->update(camera_node_position,
3809 sky->getCloudColor());
3810 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3811 // if inside clouds, and fog enabled, use that as sky
3813 video::SColor clouds_dark = clouds->getColor()
3814 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3815 sky->overrideColors(clouds_dark, clouds->getColor());
3816 sky->setInClouds(true);
3817 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3818 // do not draw clouds after all
3819 clouds->setVisible(false);
3822 clouds->setVisible(false);
3829 client->getParticleManager()->step(dtime);
3835 if (m_cache_enable_fog) {
3838 video::EFT_FOG_LINEAR,
3839 runData.fog_range * m_cache_fog_start,
3840 runData.fog_range * 1.0,
3848 video::EFT_FOG_LINEAR,
3858 Get chat messages from client
3861 v2u32 screensize = driver->getScreenSize();
3863 updateChat(dtime, screensize);
3869 if (player->getWieldIndex() != runData.new_playeritem)
3870 client->setPlayerItem(runData.new_playeritem);
3872 if (client->updateWieldedItem()) {
3873 // Update wielded tool
3874 ItemStack selected_item, hand_item;
3875 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3876 camera->wield(tool_item);
3880 Update block draw list every 200ms or when camera direction has
3883 runData.update_draw_list_timer += dtime;
3884 runData.update_shadows_timer += dtime;
3886 float update_draw_list_delta = 0.2f;
3887 bool draw_list_updated = false;
3889 v3f camera_direction = camera->getDirection();
3890 if (runData.update_draw_list_timer >= update_draw_list_delta
3891 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3892 || m_camera_offset_changed) {
3894 runData.update_draw_list_timer = 0;
3895 client->getEnv().getClientMap().updateDrawList();
3896 runData.update_draw_list_last_cam_dir = camera_direction;
3897 draw_list_updated = true;
3900 if (ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer()) {
3901 update_draw_list_delta = shadow->getUpdateDelta();
3903 if (m_camera_offset_changed ||
3904 (runData.update_shadows_timer > update_draw_list_delta &&
3905 (!draw_list_updated || shadow->getDirectionalLightCount() == 0))) {
3906 runData.update_shadows_timer = 0;
3911 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3914 make sure menu is on top
3915 1. Delete formspec menu reference if menu was removed
3916 2. Else, make sure formspec menu is on top
3918 auto formspec = m_game_ui->getFormspecGUI();
3919 do { // breakable. only runs for one iteration
3923 if (formspec->getReferenceCount() == 1) {
3924 m_game_ui->deleteFormspec();
3928 auto &loc = formspec->getFormspecLocation();
3929 if (loc.type == InventoryLocation::NODEMETA) {
3930 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3931 if (!meta || meta->getString("formspec").empty()) {
3932 formspec->quitMenu();
3938 guiroot->bringToFront(formspec);
3944 const video::SColor &skycolor = sky->getSkyColor();
3946 TimeTaker tt_draw("Draw scene");
3947 driver->beginScene(true, true, skycolor);
3949 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3950 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3951 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3952 bool draw_crosshair = (
3953 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3954 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3955 #ifdef HAVE_TOUCHSCREENGUI
3957 draw_crosshair = !g_settings->getBool("touchtarget");
3958 } catch (SettingNotFoundException) {
3961 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3962 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3967 if (m_game_ui->m_flags.show_profiler_graph)
3968 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3973 if (runData.damage_flash > 0.0f) {
3974 video::SColor color(runData.damage_flash, 180, 0, 0);
3975 driver->draw2DRectangle(color,
3976 core::rect<s32>(0, 0, screensize.X, screensize.Y),
3979 runData.damage_flash -= 384.0f * dtime;
3985 if (player->hurt_tilt_timer > 0.0f) {
3986 player->hurt_tilt_timer -= dtime * 6.0f;
3988 if (player->hurt_tilt_timer < 0.0f)
3989 player->hurt_tilt_strength = 0.0f;
3993 Update minimap pos and rotation
3995 if (mapper && m_game_ui->m_flags.show_hud) {
3996 mapper->setPos(floatToInt(player->getPosition(), BS));
3997 mapper->setAngle(player->getYaw());
4003 if (++m_reset_HW_buffer_counter > 500) {
4005 Periodically remove all mesh HW buffers.
4007 Work around for a quirk in Irrlicht where a HW buffer is only
4008 released after 20000 iterations (triggered from endScene()).
4010 Without this, all loaded but unused meshes will retain their HW
4011 buffers for at least 5 minutes, at which point looking up the HW buffers
4012 becomes a bottleneck and the framerate drops (as much as 30%).
4014 Tests showed that numbers between 50 and 1000 are good, so picked 500.
4015 There are no other public Irrlicht APIs that allow interacting with the
4016 HW buffers without tracking the status of every individual mesh.
4018 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4020 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4021 driver->removeAllHardwareBuffers();
4022 m_reset_HW_buffer_counter = 0;
4026 stats->drawtime = tt_draw.stop(true);
4027 g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
4028 g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
4031 /* Log times and stuff for visualization */
4032 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4034 Profiler::GraphValues values;
4035 g_profiler->graphGet(values);
4039 /****************************************************************************
4041 *****************************************************************************/
4042 void Game::updateShadows()
4044 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4048 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4050 float timeoftheday = fmod(getWickedTimeOfDay(in_timeofday) + 0.75f, 0.5f) + 0.25f;
4051 const float offset_constant = 10000.0f;
4053 v3f light(0.0f, 0.0f, -1.0f);
4054 light.rotateXZBy(90);
4055 light.rotateXYBy(timeoftheday * 360 - 90);
4056 light.rotateYZBy(sky->getSkyBodyOrbitTilt());
4058 v3f sun_pos = light * offset_constant;
4060 if (shadow->getDirectionalLightCount() == 0)
4061 shadow->addDirectionalLight();
4062 shadow->getDirectionalLight().setDirection(sun_pos);
4063 shadow->setTimeOfDay(in_timeofday);
4065 shadow->getDirectionalLight().update_frustum(camera, client);
4068 /****************************************************************************
4070 ****************************************************************************/
4072 /* On some computers framerate doesn't seem to be automatically limited
4074 inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
4076 // not using getRealTime is necessary for wine
4077 device->getTimer()->tick(); // Maker sure device time is up-to-date
4078 u32 time = device->getTimer()->getTime();
4079 u32 last_time = fps_timings->last_time;
4081 if (time > last_time) // Make sure time hasn't overflowed
4082 fps_timings->busy_time = time - last_time;
4084 fps_timings->busy_time = 0;
4086 u32 frametime_min = 1000 / (
4087 device->isWindowFocused() && !g_menumgr.pausesGame()
4088 ? g_settings->getFloat("fps_max")
4089 : g_settings->getFloat("fps_max_unfocused"));
4091 if (fps_timings->busy_time < frametime_min) {
4092 fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
4093 device->sleep(fps_timings->sleep_time);
4095 fps_timings->sleep_time = 0;
4098 /* Get the new value of the device timer. Note that device->sleep() may
4099 * not sleep for the entire requested time as sleep may be interrupted and
4100 * therefore it is arguably more accurate to get the new time from the
4101 * device rather than calculating it by adding sleep_time to time.
4104 device->getTimer()->tick(); // Update device timer
4105 time = device->getTimer()->getTime();
4107 if (time > last_time) // Make sure last_time hasn't overflowed
4108 *dtime = (time - last_time) / 1000.0;
4112 fps_timings->last_time = time;
4115 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4117 const wchar_t *wmsg = wgettext(msg);
4118 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4123 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4125 ((Game *)data)->readSettings();
4128 void Game::readSettings()
4130 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4131 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4132 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4133 m_cache_enable_particles = g_settings->getBool("enable_particles");
4134 m_cache_enable_fog = g_settings->getBool("enable_fog");
4135 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
4136 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4137 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
4139 m_cache_enable_noclip = g_settings->getBool("noclip");
4140 m_cache_enable_free_move = g_settings->getBool("free_move");
4142 m_cache_fog_start = g_settings->getFloat("fog_start");
4144 m_cache_cam_smoothing = 0;
4145 if (g_settings->getBool("cinematic"))
4146 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4148 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4150 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4151 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4152 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4154 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4157 /****************************************************************************/
4158 /****************************************************************************
4160 ****************************************************************************/
4161 /****************************************************************************/
4163 void Game::showDeathFormspec()
4165 static std::string formspec_str =
4166 std::string("formspec_version[1]") +
4168 "bgcolor[#320000b4;true]"
4169 "label[4.85,1.35;" + gettext("You died") + "]"
4170 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4174 /* Note: FormspecFormSource and LocalFormspecHandler *
4175 * are deleted by guiFormSpecMenu */
4176 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4177 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4179 auto *&formspec = m_game_ui->getFormspecGUI();
4180 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4181 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4182 formspec->setFocus("btn_respawn");
4185 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4186 void Game::showPauseMenu()
4189 static const std::string control_text = strgettext("Default Controls:\n"
4190 "No menu visible:\n"
4191 "- single tap: button activate\n"
4192 "- double tap: place/use\n"
4193 "- slide finger: look around\n"
4194 "Menu/Inventory visible:\n"
4195 "- double tap (outside):\n"
4197 "- touch stack, touch slot:\n"
4199 "- touch&drag, tap 2nd finger\n"
4200 " --> place single item to slot\n"
4203 static const std::string control_text_template = strgettext("Controls:\n"
4204 "- %s: move forwards\n"
4205 "- %s: move backwards\n"
4207 "- %s: move right\n"
4208 "- %s: jump/climb up\n"
4211 "- %s: sneak/climb down\n"
4214 "- Mouse: turn/look\n"
4215 "- Mouse wheel: select item\n"
4219 char control_text_buf[600];
4221 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4222 GET_KEY_NAME(keymap_forward),
4223 GET_KEY_NAME(keymap_backward),
4224 GET_KEY_NAME(keymap_left),
4225 GET_KEY_NAME(keymap_right),
4226 GET_KEY_NAME(keymap_jump),
4227 GET_KEY_NAME(keymap_dig),
4228 GET_KEY_NAME(keymap_place),
4229 GET_KEY_NAME(keymap_sneak),
4230 GET_KEY_NAME(keymap_drop),
4231 GET_KEY_NAME(keymap_inventory),
4232 GET_KEY_NAME(keymap_chat)
4235 std::string control_text = std::string(control_text_buf);
4236 str_formspec_escape(control_text);
4239 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4240 std::ostringstream os;
4242 os << "formspec_version[1]" << SIZE_TAG
4243 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4244 << strgettext("Continue") << "]";
4246 if (!simple_singleplayer_mode) {
4247 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4248 << strgettext("Change Password") << "]";
4250 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4255 if (g_settings->getBool("enable_sound")) {
4256 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4257 << strgettext("Sound Volume") << "]";
4260 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4261 << strgettext("Change Keys") << "]";
4263 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4264 << strgettext("Exit to Menu") << "]";
4265 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4266 << strgettext("Exit to OS") << "]"
4267 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4268 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4270 << strgettext("Game info:") << "\n";
4271 const std::string &address = client->getAddressName();
4272 static const std::string mode = strgettext("- Mode: ");
4273 if (!simple_singleplayer_mode) {
4274 Address serverAddress = client->getServerAddress();
4275 if (!address.empty()) {
4276 os << mode << strgettext("Remote server") << "\n"
4277 << strgettext("- Address: ") << address;
4279 os << mode << strgettext("Hosting server");
4281 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4283 os << mode << strgettext("Singleplayer") << "\n";
4285 if (simple_singleplayer_mode || address.empty()) {
4286 static const std::string on = strgettext("On");
4287 static const std::string off = strgettext("Off");
4288 const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
4289 const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
4290 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4291 os << strgettext("- Damage: ") << damage << "\n"
4292 << strgettext("- Creative Mode: ") << creative << "\n";
4293 if (!simple_singleplayer_mode) {
4294 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4295 //~ PvP = Player versus Player
4296 os << strgettext("- PvP: ") << pvp << "\n"
4297 << strgettext("- Public: ") << announced << "\n";
4298 std::string server_name = g_settings->get("server_name");
4299 str_formspec_escape(server_name);
4300 if (announced == on && !server_name.empty())
4301 os << strgettext("- Server Name: ") << server_name;
4308 /* Note: FormspecFormSource and LocalFormspecHandler *
4309 * are deleted by guiFormSpecMenu */
4310 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4311 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4313 auto *&formspec = m_game_ui->getFormspecGUI();
4314 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4315 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4316 formspec->setFocus("btn_continue");
4317 formspec->doPause = true;
4319 if (simple_singleplayer_mode)
4323 /****************************************************************************/
4324 /****************************************************************************
4325 extern function for launching the game
4326 ****************************************************************************/
4327 /****************************************************************************/
4329 void the_game(bool *kill,
4330 InputHandler *input,
4331 RenderingEngine *rendering_engine,
4332 const GameStartData &start_data,
4333 std::string &error_message,
4334 ChatBackend &chat_backend,
4335 bool *reconnect_requested) // Used for local game
4339 /* Make a copy of the server address because if a local singleplayer server
4340 * is created then this is updated and we don't want to change the value
4341 * passed to us by the calling function
4346 if (game.startup(kill, input, rendering_engine, start_data,
4347 error_message, reconnect_requested, &chat_backend)) {
4351 } catch (SerializationError &e) {
4352 error_message = std::string("A serialization error occurred:\n")
4353 + e.what() + "\n\nThe server is probably "
4354 " running a different version of " PROJECT_NAME_C ".";
4355 errorstream << error_message << std::endl;
4356 } catch (ServerError &e) {
4357 error_message = e.what();
4358 errorstream << "ServerError: " << error_message << std::endl;
4359 } catch (ModError &e) {
4360 error_message = std::string("ModError: ") + e.what() +
4361 strgettext("\nCheck debug.txt for details.");
4362 errorstream << error_message << std::endl;