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"
72 #include "script/scripting_client.h"
76 #include "client/sound_openal.h"
78 #include "client/sound.h"
84 struct TextDestNodeMetadata : public TextDest
86 TextDestNodeMetadata(v3s16 p, Client *client)
91 // This is deprecated I guess? -celeron55
92 void gotText(const std::wstring &text)
94 std::string ntext = wide_to_utf8(text);
95 infostream << "Submitting 'text' field of node at (" << m_p.X << ","
96 << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
98 fields["text"] = ntext;
99 m_client->sendNodemetaFields(m_p, "", fields);
101 void gotText(const StringMap &fields)
103 m_client->sendNodemetaFields(m_p, "", fields);
110 struct TextDestPlayerInventory : public TextDest
112 TextDestPlayerInventory(Client *client)
117 TextDestPlayerInventory(Client *client, const std::string &formname)
120 m_formname = formname;
122 void gotText(const StringMap &fields)
124 m_client->sendInventoryFields(m_formname, fields);
130 struct LocalFormspecHandler : public TextDest
132 LocalFormspecHandler(const std::string &formname)
134 m_formname = formname;
137 LocalFormspecHandler(const std::string &formname, Client *client):
140 m_formname = formname;
143 void gotText(const StringMap &fields)
145 if (m_formname == "MT_PAUSE_MENU") {
146 if (fields.find("btn_sound") != fields.end()) {
147 g_gamecallback->changeVolume();
151 if (fields.find("btn_key_config") != fields.end()) {
152 g_gamecallback->keyConfig();
156 if (fields.find("btn_exit_menu") != fields.end()) {
157 g_gamecallback->disconnect();
161 if (fields.find("btn_exit_os") != fields.end()) {
162 g_gamecallback->exitToOS();
164 RenderingEngine::get_raw_device()->closeDevice();
169 if (fields.find("btn_change_password") != fields.end()) {
170 g_gamecallback->changePassword();
174 if (fields.find("quit") != fields.end()) {
178 if (fields.find("btn_continue") != fields.end()) {
183 if (m_formname == "MT_DEATH_SCREEN") {
184 assert(m_client != 0);
185 m_client->sendRespawn();
189 if (m_client && m_client->modsLoaded())
190 m_client->getScript()->on_formspec_input(m_formname, fields);
193 Client *m_client = nullptr;
196 /* Form update callback */
198 class NodeMetadataFormSource: public IFormSource
201 NodeMetadataFormSource(ClientMap *map, v3s16 p):
206 const std::string &getForm() const
208 static const std::string empty_string = "";
209 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
214 return meta->getString("formspec");
217 virtual std::string resolveText(const std::string &str)
219 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
224 return meta->resolveString(str);
231 class PlayerInventoryFormSource: public IFormSource
234 PlayerInventoryFormSource(Client *client):
239 const std::string &getForm() const
241 LocalPlayer *player = m_client->getEnv().getLocalPlayer();
242 return player->inventory_formspec;
248 class NodeDugEvent: public MtEvent
254 NodeDugEvent(v3s16 p, MapNode n):
258 MtEvent::Type getType() const
260 return MtEvent::NODE_DUG;
266 ISoundManager *m_sound;
267 const NodeDefManager *m_ndef;
269 bool makes_footstep_sound;
270 float m_player_step_timer;
271 float m_player_jump_timer;
273 SimpleSoundSpec m_player_step_sound;
274 SimpleSoundSpec m_player_leftpunch_sound;
275 SimpleSoundSpec m_player_rightpunch_sound;
277 SoundMaker(ISoundManager *sound, const NodeDefManager *ndef):
280 makes_footstep_sound(true),
281 m_player_step_timer(0.0f),
282 m_player_jump_timer(0.0f)
286 void playPlayerStep()
288 if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
289 m_player_step_timer = 0.03;
290 if (makes_footstep_sound)
291 m_sound->playSound(m_player_step_sound, false);
295 void playPlayerJump()
297 if (m_player_jump_timer <= 0.0f) {
298 m_player_jump_timer = 0.2f;
299 m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f), false);
303 static void viewBobbingStep(MtEvent *e, void *data)
305 SoundMaker *sm = (SoundMaker *)data;
306 sm->playPlayerStep();
309 static void playerRegainGround(MtEvent *e, void *data)
311 SoundMaker *sm = (SoundMaker *)data;
312 sm->playPlayerStep();
315 static void playerJump(MtEvent *e, void *data)
317 SoundMaker *sm = (SoundMaker *)data;
318 sm->playPlayerJump();
321 static void cameraPunchLeft(MtEvent *e, void *data)
323 SoundMaker *sm = (SoundMaker *)data;
324 sm->m_sound->playSound(sm->m_player_leftpunch_sound, false);
327 static void cameraPunchRight(MtEvent *e, void *data)
329 SoundMaker *sm = (SoundMaker *)data;
330 sm->m_sound->playSound(sm->m_player_rightpunch_sound, false);
333 static void nodeDug(MtEvent *e, void *data)
335 SoundMaker *sm = (SoundMaker *)data;
336 NodeDugEvent *nde = (NodeDugEvent *)e;
337 sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug, false);
340 static void playerDamage(MtEvent *e, void *data)
342 SoundMaker *sm = (SoundMaker *)data;
343 sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false);
346 static void playerFallingDamage(MtEvent *e, void *data)
348 SoundMaker *sm = (SoundMaker *)data;
349 sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false);
352 void registerReceiver(MtEventManager *mgr)
354 mgr->reg(MtEvent::VIEW_BOBBING_STEP, SoundMaker::viewBobbingStep, this);
355 mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, this);
356 mgr->reg(MtEvent::PLAYER_JUMP, SoundMaker::playerJump, this);
357 mgr->reg(MtEvent::CAMERA_PUNCH_LEFT, SoundMaker::cameraPunchLeft, this);
358 mgr->reg(MtEvent::CAMERA_PUNCH_RIGHT, SoundMaker::cameraPunchRight, this);
359 mgr->reg(MtEvent::NODE_DUG, SoundMaker::nodeDug, this);
360 mgr->reg(MtEvent::PLAYER_DAMAGE, SoundMaker::playerDamage, this);
361 mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, this);
364 void step(float dtime)
366 m_player_step_timer -= dtime;
367 m_player_jump_timer -= dtime;
371 // Locally stored sounds don't need to be preloaded because of this
372 class GameOnDemandSoundFetcher: public OnDemandSoundFetcher
374 std::set<std::string> m_fetched;
376 void paths_insert(std::set<std::string> &dst_paths,
377 const std::string &base,
378 const std::string &name)
380 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg");
381 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg");
382 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg");
383 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg");
384 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg");
385 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg");
386 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg");
387 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg");
388 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg");
389 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg");
390 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg");
393 void fetchSounds(const std::string &name,
394 std::set<std::string> &dst_paths,
395 std::set<std::string> &dst_datas)
397 if (m_fetched.count(name))
400 m_fetched.insert(name);
402 paths_insert(dst_paths, porting::path_share, name);
403 paths_insert(dst_paths, porting::path_user, name);
408 // before 1.8 there isn't a "integer interface", only float
409 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
410 typedef f32 SamplerLayer_t;
412 typedef s32 SamplerLayer_t;
416 class GameGlobalShaderConstantSetter : public IShaderConstantSetter
419 bool *m_force_fog_off;
422 CachedPixelShaderSetting<float, 4> m_sky_bg_color;
423 CachedPixelShaderSetting<float> m_fog_distance;
424 CachedVertexShaderSetting<float> m_animation_timer_vertex;
425 CachedPixelShaderSetting<float> m_animation_timer_pixel;
426 CachedPixelShaderSetting<float, 3> m_day_light;
427 CachedPixelShaderSetting<float, 4> m_star_color;
428 CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
429 CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
430 CachedPixelShaderSetting<float, 3> m_minimap_yaw;
431 CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
432 CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
433 CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
437 void onSettingsChange(const std::string &name)
439 if (name == "enable_fog")
440 m_fog_enabled = g_settings->getBool("enable_fog");
443 static void settingsCallback(const std::string &name, void *userdata)
445 reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
448 void setSky(Sky *sky) { m_sky = sky; }
450 GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
451 f32 *fog_range, Client *client) :
453 m_force_fog_off(force_fog_off),
454 m_fog_range(fog_range),
455 m_sky_bg_color("skyBgColor"),
456 m_fog_distance("fogDistance"),
457 m_animation_timer_vertex("animationTimer"),
458 m_animation_timer_pixel("animationTimer"),
459 m_day_light("dayLight"),
460 m_star_color("starColor"),
461 m_eye_position_pixel("eyePosition"),
462 m_eye_position_vertex("eyePosition"),
463 m_minimap_yaw("yawVec"),
464 m_camera_offset_pixel("cameraOffset"),
465 m_camera_offset_vertex("cameraOffset"),
466 m_base_texture("baseTexture"),
469 g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
470 m_fog_enabled = g_settings->getBool("enable_fog");
473 ~GameGlobalShaderConstantSetter()
475 g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
478 void onSetConstants(video::IMaterialRendererServices *services) override
481 video::SColor bgcolor = m_sky->getBgColor();
482 video::SColorf bgcolorf(bgcolor);
483 float bgcolorfa[4] = {
489 m_sky_bg_color.set(bgcolorfa, services);
492 float fog_distance = 10000 * BS;
494 if (m_fog_enabled && !*m_force_fog_off)
495 fog_distance = *m_fog_range;
497 m_fog_distance.set(&fog_distance, services);
499 u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
500 video::SColorf sunlight;
501 get_sunlight_color(&sunlight, daynight_ratio);
506 m_day_light.set(dnc, services);
508 video::SColorf star_color = m_sky->getCurrentStarColor();
509 float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a};
510 m_star_color.set(clr, services);
512 u32 animation_timer = porting::getTimeMs() % 1000000;
513 float animation_timer_f = (float)animation_timer / 100000.f;
514 m_animation_timer_vertex.set(&animation_timer_f, services);
515 m_animation_timer_pixel.set(&animation_timer_f, services);
517 float eye_position_array[3];
518 v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
519 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
520 eye_position_array[0] = epos.X;
521 eye_position_array[1] = epos.Y;
522 eye_position_array[2] = epos.Z;
524 epos.getAs3Values(eye_position_array);
526 m_eye_position_pixel.set(eye_position_array, services);
527 m_eye_position_vertex.set(eye_position_array, services);
529 if (m_client->getMinimap()) {
530 float minimap_yaw_array[3];
531 v3f minimap_yaw = m_client->getMinimap()->getYawVec();
532 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
533 minimap_yaw_array[0] = minimap_yaw.X;
534 minimap_yaw_array[1] = minimap_yaw.Y;
535 minimap_yaw_array[2] = minimap_yaw.Z;
537 minimap_yaw.getAs3Values(minimap_yaw_array);
539 m_minimap_yaw.set(minimap_yaw_array, services);
542 float camera_offset_array[3];
543 v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
544 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
545 camera_offset_array[0] = offset.X;
546 camera_offset_array[1] = offset.Y;
547 camera_offset_array[2] = offset.Z;
549 offset.getAs3Values(camera_offset_array);
551 m_camera_offset_pixel.set(camera_offset_array, services);
552 m_camera_offset_vertex.set(camera_offset_array, services);
554 SamplerLayer_t base_tex = 0;
555 m_base_texture.set(&base_tex, services);
560 class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
563 bool *m_force_fog_off;
566 std::vector<GameGlobalShaderConstantSetter *> created_nosky;
568 GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
569 f32 *fog_range, Client *client) :
571 m_force_fog_off(force_fog_off),
572 m_fog_range(fog_range),
576 void setSky(Sky *sky) {
578 for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
579 ggscs->setSky(m_sky);
581 created_nosky.clear();
584 virtual IShaderConstantSetter* create()
586 GameGlobalShaderConstantSetter *scs = new GameGlobalShaderConstantSetter(
587 m_sky, m_force_fog_off, m_fog_range, m_client);
589 created_nosky.push_back(scs);
595 #define SIZE_TAG "size[11,5.5]"
597 #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
600 /****************************************************************************
601 ****************************************************************************/
603 const float object_hit_delay = 0.2;
606 u32 last_time, busy_time, sleep_time;
610 /* The reason the following structs are not anonymous structs within the
611 * class is that they are not used by the majority of member functions and
612 * many functions that do require objects of thse types do not modify them
613 * (so they can be passed as a const qualified parameter)
619 PointedThing pointed_old;
622 bool btn_down_for_dig;
624 bool digging_blocked;
625 bool reset_jump_timer;
626 float nodig_delay_timer;
628 float dig_time_complete;
629 float repeat_place_timer;
630 float object_hit_delay_timer;
631 float time_from_last_punch;
632 ClientActiveObject *selected_object;
636 float update_draw_list_timer;
640 v3f update_draw_list_last_cam_dir;
642 float time_of_day_smooth;
647 struct ClientEventHandler
649 void (Game::*handler)(ClientEvent *, CameraOrientation *);
652 /****************************************************************************
654 ****************************************************************************/
656 /* This is not intended to be a public class. If a public class becomes
657 * desirable then it may be better to create another 'wrapper' class that
658 * hides most of the stuff in this class (nothing in this class is required
659 * by any other file) but exposes the public methods/data only.
666 bool startup(bool *kill,
668 const GameStartData &game_params,
669 std::string &error_message,
671 ChatBackend *chat_backend);
678 void extendedResourceCleanup();
680 // Basic initialisation
681 bool init(const std::string &map_dir, const std::string &address,
682 u16 port, const SubgameSpec &gamespec);
684 bool createSingleplayerServer(const std::string &map_dir,
685 const SubgameSpec &gamespec, u16 port);
688 bool createClient(const GameStartData &start_data);
692 bool connectToServer(const GameStartData &start_data,
693 bool *connect_ok, bool *aborted);
694 bool getServerContent(bool *aborted);
698 void updateInteractTimers(f32 dtime);
699 bool checkConnection();
700 bool handleCallbacks();
701 void processQueues();
702 void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
703 void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
704 void updateProfilerGraphs(ProfilerGraph *graph);
707 void processUserInput(f32 dtime);
708 void processKeyInput();
709 void processItemSelection(u16 *new_playeritem);
711 void dropSelectedItem(bool single_item = false);
712 void openInventory();
713 void openConsole(float scale, const wchar_t *line=NULL);
714 void toggleFreeMove();
715 void toggleFreeMoveAlt();
716 void togglePitchMove();
719 void toggleCinematic();
720 void toggleAutoforward();
722 void toggleMinimap(bool shift_pressed);
725 void toggleUpdateCamera();
727 void increaseViewRange();
728 void decreaseViewRange();
729 void toggleFullViewRange();
730 void checkZoomEnabled();
732 void updateCameraDirection(CameraOrientation *cam, float dtime);
733 void updateCameraOrientation(CameraOrientation *cam, float dtime);
734 void updatePlayerControl(const CameraOrientation &cam);
735 void step(f32 *dtime);
736 void processClientEvents(CameraOrientation *cam);
737 void updateCamera(u32 busy_time, f32 dtime);
738 void updateSound(f32 dtime);
739 void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug);
741 * Returns the object or node the player is pointing at.
742 * Also updates the selected thing in the Hud.
744 * @param[in] shootline the shootline, starting from
745 * the camera position. This also gives the maximal distance
747 * @param[in] liquids_pointable if false, liquids are ignored
748 * @param[in] look_for_object if false, objects are ignored
749 * @param[in] camera_offset offset of the camera
750 * @param[out] selected_object the selected object or
753 PointedThing updatePointedThing(
754 const core::line3d<f32> &shootline, bool liquids_pointable,
755 bool look_for_object, const v3s16 &camera_offset);
756 void handlePointingAtNothing(const ItemStack &playerItem);
757 void handlePointingAtNode(const PointedThing &pointed,
758 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
759 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
760 const v3f &player_position, bool show_debug);
761 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
762 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
763 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
764 const CameraOrientation &cam);
767 void limitFps(FpsControl *fps_timings, f32 *dtime);
769 void showOverlayMessage(const char *msg, float dtime, int percent,
770 bool draw_clouds = true);
772 static void settingChangedCallback(const std::string &setting_name, void *data);
775 inline bool isKeyDown(GameKeyType k)
777 return input->isKeyDown(k);
779 inline bool wasKeyDown(GameKeyType k)
781 return input->wasKeyDown(k);
783 inline bool wasKeyPressed(GameKeyType k)
785 return input->wasKeyPressed(k);
787 inline bool wasKeyReleased(GameKeyType k)
789 return input->wasKeyReleased(k);
793 void handleAndroidChatInput();
798 bool force_fog_off = false;
799 bool disable_camera_update = false;
802 void showDeathFormspec();
803 void showPauseMenu();
805 // ClientEvent handlers
806 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
807 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
808 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
809 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
810 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
811 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
812 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
813 CameraOrientation *cam);
814 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
815 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
816 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
817 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
818 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
819 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
820 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
821 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
822 CameraOrientation *cam);
823 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
825 void updateChat(f32 dtime, const v2u32 &screensize);
827 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
828 const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
829 const NodeMetadata *meta);
830 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
832 InputHandler *input = nullptr;
834 Client *client = nullptr;
835 Server *server = nullptr;
837 IWritableTextureSource *texture_src = nullptr;
838 IWritableShaderSource *shader_src = nullptr;
840 // When created, these will be filled with data received from the server
841 IWritableItemDefManager *itemdef_manager = nullptr;
842 NodeDefManager *nodedef_manager = nullptr;
844 GameOnDemandSoundFetcher soundfetcher; // useful when testing
845 ISoundManager *sound = nullptr;
846 bool sound_is_dummy = false;
847 SoundMaker *soundmaker = nullptr;
849 ChatBackend *chat_backend = nullptr;
850 LogOutputBuffer m_chat_log_buf;
852 EventManager *eventmgr = nullptr;
853 QuicktuneShortcutter *quicktune = nullptr;
854 bool registration_confirmation_shown = false;
856 std::unique_ptr<GameUI> m_game_ui;
857 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
858 MapDrawControl *draw_control = nullptr;
859 Camera *camera = nullptr;
860 Clouds *clouds = nullptr; // Free using ->Drop()
861 Sky *sky = nullptr; // Free using ->Drop()
863 Minimap *mapper = nullptr;
869 This class does take ownership/responsibily for cleaning up etc of any of
870 these items (e.g. device)
872 IrrlichtDevice *device;
873 video::IVideoDriver *driver;
874 scene::ISceneManager *smgr;
876 std::string *error_message;
877 bool *reconnect_requested;
878 scene::ISceneNode *skybox;
880 bool simple_singleplayer_mode;
883 /* Pre-calculated values
885 int crack_animation_length;
887 IntervalLimiter profiler_interval;
890 * TODO: Local caching of settings is not optimal and should at some stage
891 * be updated to use a global settings object for getting thse values
892 * (as opposed to the this local caching). This can be addressed in
895 bool m_cache_doubletap_jump;
896 bool m_cache_enable_clouds;
897 bool m_cache_enable_joysticks;
898 bool m_cache_enable_particles;
899 bool m_cache_enable_fog;
900 bool m_cache_enable_noclip;
901 bool m_cache_enable_free_move;
902 f32 m_cache_mouse_sensitivity;
903 f32 m_cache_joystick_frustum_sensitivity;
904 f32 m_repeat_place_time;
905 f32 m_cache_cam_smoothing;
906 f32 m_cache_fog_start;
908 bool m_invert_mouse = false;
909 bool m_first_loop_after_window_activation = false;
910 bool m_camera_offset_changed = false;
912 bool m_does_lost_focus_pause_game = false;
914 int m_reset_HW_buffer_counter = 0;
916 bool m_cache_hold_aux1;
917 bool m_android_chat_open;
922 m_chat_log_buf(g_logger),
923 m_game_ui(new GameUI())
925 g_settings->registerChangedCallback("doubletap_jump",
926 &settingChangedCallback, this);
927 g_settings->registerChangedCallback("enable_clouds",
928 &settingChangedCallback, this);
929 g_settings->registerChangedCallback("doubletap_joysticks",
930 &settingChangedCallback, this);
931 g_settings->registerChangedCallback("enable_particles",
932 &settingChangedCallback, this);
933 g_settings->registerChangedCallback("enable_fog",
934 &settingChangedCallback, this);
935 g_settings->registerChangedCallback("mouse_sensitivity",
936 &settingChangedCallback, this);
937 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
938 &settingChangedCallback, this);
939 g_settings->registerChangedCallback("repeat_place_time",
940 &settingChangedCallback, this);
941 g_settings->registerChangedCallback("noclip",
942 &settingChangedCallback, this);
943 g_settings->registerChangedCallback("free_move",
944 &settingChangedCallback, this);
945 g_settings->registerChangedCallback("cinematic",
946 &settingChangedCallback, this);
947 g_settings->registerChangedCallback("cinematic_camera_smoothing",
948 &settingChangedCallback, this);
949 g_settings->registerChangedCallback("camera_smoothing",
950 &settingChangedCallback, this);
955 m_cache_hold_aux1 = false; // This is initialised properly later
962 /****************************************************************************
964 ****************************************************************************/
973 delete server; // deleted first to stop all server threads
981 delete nodedef_manager;
982 delete itemdef_manager;
985 extendedResourceCleanup();
987 g_settings->deregisterChangedCallback("doubletap_jump",
988 &settingChangedCallback, this);
989 g_settings->deregisterChangedCallback("enable_clouds",
990 &settingChangedCallback, this);
991 g_settings->deregisterChangedCallback("enable_particles",
992 &settingChangedCallback, this);
993 g_settings->deregisterChangedCallback("enable_fog",
994 &settingChangedCallback, this);
995 g_settings->deregisterChangedCallback("mouse_sensitivity",
996 &settingChangedCallback, this);
997 g_settings->deregisterChangedCallback("repeat_place_time",
998 &settingChangedCallback, this);
999 g_settings->deregisterChangedCallback("noclip",
1000 &settingChangedCallback, this);
1001 g_settings->deregisterChangedCallback("free_move",
1002 &settingChangedCallback, this);
1003 g_settings->deregisterChangedCallback("cinematic",
1004 &settingChangedCallback, this);
1005 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
1006 &settingChangedCallback, this);
1007 g_settings->deregisterChangedCallback("camera_smoothing",
1008 &settingChangedCallback, this);
1011 bool Game::startup(bool *kill,
1012 InputHandler *input,
1013 const GameStartData &start_data,
1014 std::string &error_message,
1016 ChatBackend *chat_backend)
1020 this->device = RenderingEngine::get_raw_device();
1022 this->error_message = &error_message;
1023 this->reconnect_requested = reconnect;
1024 this->input = input;
1025 this->chat_backend = chat_backend;
1026 this->simple_singleplayer_mode = start_data.isSinglePlayer();
1028 input->keycache.populate();
1030 driver = device->getVideoDriver();
1031 smgr = RenderingEngine::get_scene_manager();
1033 RenderingEngine::get_scene_manager()->getParameters()->
1034 setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1037 runData = GameRunData();
1038 runData.time_from_last_punch = 10.0;
1040 m_game_ui->initFlags();
1042 m_invert_mouse = g_settings->getBool("invert_mouse");
1043 m_first_loop_after_window_activation = true;
1045 g_client_translations->clear();
1047 // address can change if simple_singleplayer_mode
1048 if (!init(start_data.world_spec.path, start_data.address,
1049 start_data.socket_port, start_data.game_spec))
1052 if (!createClient(start_data))
1055 RenderingEngine::initialize(client, hud);
1063 ProfilerGraph graph;
1064 RunStats stats = { 0 };
1065 CameraOrientation cam_view_target = { 0 };
1066 CameraOrientation cam_view = { 0 };
1067 FpsControl draw_times = { 0 };
1068 f32 dtime; // in seconds
1070 /* Clear the profiler */
1071 Profiler::GraphValues dummyvalues;
1072 g_profiler->graphGet(dummyvalues);
1074 draw_times.last_time = RenderingEngine::get_timer_time();
1076 set_light_table(g_settings->getFloat("display_gamma"));
1079 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1080 && client->checkPrivilege("fast");
1083 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1084 g_settings->getU16("screen_h"));
1086 while (RenderingEngine::run()
1087 && !(*kill || g_gamecallback->shutdown_requested
1088 || (server && server->isShutdownRequested()))) {
1090 const irr::core::dimension2d<u32> ¤t_screen_size =
1091 RenderingEngine::get_video_driver()->getScreenSize();
1092 // Verify if window size has changed and save it if it's the case
1093 // Ensure evaluating settings->getBool after verifying screensize
1094 // First condition is cheaper
1095 if (previous_screen_size != current_screen_size &&
1096 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1097 g_settings->getBool("autosave_screensize")) {
1098 g_settings->setU16("screen_w", current_screen_size.Width);
1099 g_settings->setU16("screen_h", current_screen_size.Height);
1100 previous_screen_size = current_screen_size;
1103 // Calculate dtime =
1104 // RenderingEngine::run() from this iteration
1105 // + Sleep time until the wanted FPS are reached
1106 limitFps(&draw_times, &dtime);
1108 // Prepare render data for next iteration
1110 updateStats(&stats, draw_times, dtime);
1111 updateInteractTimers(dtime);
1113 if (!checkConnection())
1115 if (!handleCallbacks())
1120 m_game_ui->clearInfoText();
1121 hud->resizeHotbar();
1123 updateProfilers(stats, draw_times, dtime);
1124 processUserInput(dtime);
1125 // Update camera before player movement to avoid camera lag of one frame
1126 updateCameraDirection(&cam_view_target, dtime);
1127 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1128 cam_view.camera_yaw) * m_cache_cam_smoothing;
1129 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1130 cam_view.camera_pitch) * m_cache_cam_smoothing;
1131 updatePlayerControl(cam_view);
1133 processClientEvents(&cam_view_target);
1134 updateCamera(draw_times.busy_time, dtime);
1136 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
1137 m_game_ui->m_flags.show_debug);
1138 updateFrame(&graph, &stats, dtime, cam_view);
1139 updateProfilerGraphs(&graph);
1141 // Update if minimap has been disabled by the server
1142 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1144 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1151 void Game::shutdown()
1153 RenderingEngine::finalize();
1154 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1155 if (g_settings->get("3d_mode") == "pageflip") {
1156 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1159 auto formspec = m_game_ui->getFormspecGUI();
1161 formspec->quitMenu();
1163 #ifdef HAVE_TOUCHSCREENGUI
1164 g_touchscreengui->hide();
1167 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1172 if (gui_chat_console)
1173 gui_chat_console->drop();
1179 while (g_menumgr.menuCount() > 0) {
1180 g_menumgr.m_stack.front()->setVisible(false);
1181 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1184 m_game_ui->deleteFormspec();
1186 chat_backend->addMessage(L"", L"# Disconnected.");
1187 chat_backend->addMessage(L"", L"");
1188 m_chat_log_buf.clear();
1192 while (!client->isShutdown()) {
1193 assert(texture_src != NULL);
1194 assert(shader_src != NULL);
1195 texture_src->processQueue();
1196 shader_src->processQueue();
1203 /****************************************************************************/
1204 /****************************************************************************
1206 ****************************************************************************/
1207 /****************************************************************************/
1210 const std::string &map_dir,
1211 const std::string &address,
1213 const SubgameSpec &gamespec)
1215 texture_src = createTextureSource();
1217 showOverlayMessage(N_("Loading..."), 0, 0);
1219 shader_src = createShaderSource();
1221 itemdef_manager = createItemDefManager();
1222 nodedef_manager = createNodeDefManager();
1224 eventmgr = new EventManager();
1225 quicktune = new QuicktuneShortcutter();
1227 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1228 && eventmgr && quicktune))
1234 // Create a server if not connecting to an existing one
1235 if (address.empty()) {
1236 if (!createSingleplayerServer(map_dir, gamespec, port))
1243 bool Game::initSound()
1246 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1247 infostream << "Attempting to use OpenAL audio" << std::endl;
1248 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1250 infostream << "Failed to initialize OpenAL audio" << std::endl;
1252 infostream << "Sound disabled." << std::endl;
1256 infostream << "Using dummy audio." << std::endl;
1257 sound = &dummySoundManager;
1258 sound_is_dummy = true;
1261 soundmaker = new SoundMaker(sound, nodedef_manager);
1265 soundmaker->registerReceiver(eventmgr);
1270 bool Game::createSingleplayerServer(const std::string &map_dir,
1271 const SubgameSpec &gamespec, u16 port)
1273 showOverlayMessage(N_("Creating server..."), 0, 5);
1275 std::string bind_str = g_settings->get("bind_address");
1276 Address bind_addr(0, 0, 0, 0, port);
1278 if (g_settings->getBool("ipv6_server")) {
1279 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1283 bind_addr.Resolve(bind_str.c_str());
1284 } catch (ResolveError &e) {
1285 infostream << "Resolving bind address \"" << bind_str
1286 << "\" failed: " << e.what()
1287 << " -- Listening on all addresses." << std::endl;
1290 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1291 *error_message = "Unable to listen on " +
1292 bind_addr.serializeString() +
1293 " because IPv6 is disabled";
1294 errorstream << *error_message << std::endl;
1298 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1299 false, nullptr, error_message);
1305 bool Game::createClient(const GameStartData &start_data)
1307 showOverlayMessage(N_("Creating client..."), 0, 10);
1309 draw_control = new MapDrawControl;
1313 bool could_connect, connect_aborted;
1314 #ifdef HAVE_TOUCHSCREENGUI
1315 if (g_touchscreengui) {
1316 g_touchscreengui->init(texture_src);
1317 g_touchscreengui->hide();
1320 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1323 if (!could_connect) {
1324 if (error_message->empty() && !connect_aborted) {
1325 // Should not happen if error messages are set properly
1326 *error_message = "Connection failed for unknown reason";
1327 errorstream << *error_message << std::endl;
1332 if (!getServerContent(&connect_aborted)) {
1333 if (error_message->empty() && !connect_aborted) {
1334 // Should not happen if error messages are set properly
1335 *error_message = "Connection failed for unknown reason";
1336 errorstream << *error_message << std::endl;
1341 GameGlobalShaderConstantSetterFactory *scsf = new GameGlobalShaderConstantSetterFactory(
1342 &m_flags.force_fog_off, &runData.fog_range, client);
1343 shader_src->addShaderConstantSetterFactory(scsf);
1345 // Update cached textures, meshes and materials
1346 client->afterContentReceived();
1350 camera = new Camera(*draw_control, client);
1351 if (!camera || !camera->successfullyCreated(*error_message))
1353 client->setCamera(camera);
1357 if (m_cache_enable_clouds) {
1358 clouds = new Clouds(smgr, -1, time(0));
1360 *error_message = "Memory allocation error (clouds)";
1361 errorstream << *error_message << std::endl;
1368 sky = new Sky(-1, texture_src, shader_src);
1370 skybox = NULL; // This is used/set later on in the main run loop
1373 *error_message = "Memory allocation error sky";
1374 errorstream << *error_message << std::endl;
1378 /* Pre-calculated values
1380 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1382 v2u32 size = t->getOriginalSize();
1383 crack_animation_length = size.Y / size.X;
1385 crack_animation_length = 5;
1391 /* Set window caption
1393 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1395 str += utf8_to_wide(g_version_hash);
1397 str += driver->getName();
1399 device->setWindowCaption(str.c_str());
1401 LocalPlayer *player = client->getEnv().getLocalPlayer();
1402 player->hurt_tilt_timer = 0;
1403 player->hurt_tilt_strength = 0;
1405 hud = new Hud(guienv, client, player, &player->inventory);
1408 *error_message = "Memory error: could not create HUD";
1409 errorstream << *error_message << std::endl;
1413 mapper = client->getMinimap();
1415 if (mapper && client->modsLoaded())
1416 client->getScript()->on_minimap_ready(mapper);
1421 bool Game::initGui()
1425 // Remove stale "recent" chat messages from previous connections
1426 chat_backend->clearRecentChat();
1428 // Make sure the size of the recent messages buffer is right
1429 chat_backend->applySettings();
1431 // Chat backend and console
1432 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1433 -1, chat_backend, client, &g_menumgr);
1434 if (!gui_chat_console) {
1435 *error_message = "Could not allocate memory for chat console";
1436 errorstream << *error_message << std::endl;
1440 #ifdef HAVE_TOUCHSCREENGUI
1442 if (g_touchscreengui)
1443 g_touchscreengui->show();
1450 bool Game::connectToServer(const GameStartData &start_data,
1451 bool *connect_ok, bool *connection_aborted)
1453 *connect_ok = false; // Let's not be overly optimistic
1454 *connection_aborted = false;
1455 bool local_server_mode = false;
1457 showOverlayMessage(N_("Resolving address..."), 0, 15);
1459 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1462 connect_address.Resolve(start_data.address.c_str());
1464 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1465 //connect_address.Resolve("localhost");
1466 if (connect_address.isIPv6()) {
1467 IPv6AddressBytes addr_bytes;
1468 addr_bytes.bytes[15] = 1;
1469 connect_address.setAddress(&addr_bytes);
1471 connect_address.setAddress(127, 0, 0, 1);
1473 local_server_mode = true;
1475 } catch (ResolveError &e) {
1476 *error_message = std::string("Couldn't resolve address: ") + e.what();
1477 errorstream << *error_message << std::endl;
1481 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1482 *error_message = "Unable to connect to " +
1483 connect_address.serializeString() +
1484 " because IPv6 is disabled";
1485 errorstream << *error_message << std::endl;
1489 client = new Client(start_data.name.c_str(),
1490 start_data.password, start_data.address,
1491 *draw_control, texture_src, shader_src,
1492 itemdef_manager, nodedef_manager, sound, eventmgr,
1493 connect_address.isIPv6(), m_game_ui.get());
1498 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1500 infostream << "Connecting to server at ";
1501 connect_address.print(&infostream);
1502 infostream << std::endl;
1504 client->connect(connect_address,
1505 simple_singleplayer_mode || local_server_mode);
1508 Wait for server to accept connection
1514 FpsControl fps_control = { 0 };
1516 f32 wait_time = 0; // in seconds
1518 fps_control.last_time = RenderingEngine::get_timer_time();
1520 while (RenderingEngine::run()) {
1522 limitFps(&fps_control, &dtime);
1524 // Update client and server
1525 client->step(dtime);
1528 server->step(dtime);
1531 if (client->getState() == LC_Init) {
1537 if (*connection_aborted)
1540 if (client->accessDenied()) {
1541 *error_message = "Access denied. Reason: "
1542 + client->accessDeniedReason();
1543 *reconnect_requested = client->reconnectRequested();
1544 errorstream << *error_message << std::endl;
1548 if (input->cancelPressed()) {
1549 *connection_aborted = true;
1550 infostream << "Connect aborted [Escape]" << std::endl;
1554 if (client->m_is_registration_confirmation_state) {
1555 if (registration_confirmation_shown) {
1556 // Keep drawing the GUI
1557 RenderingEngine::draw_menu_scene(guienv, dtime, true);
1559 registration_confirmation_shown = true;
1560 (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
1561 &g_menumgr, client, start_data.name, start_data.password,
1562 connection_aborted, texture_src))->drop();
1566 // Only time out if we aren't waiting for the server we started
1567 if (!start_data.isSinglePlayer() && wait_time > 10) {
1568 *error_message = "Connection timed out.";
1569 errorstream << *error_message << std::endl;
1574 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1577 } catch (con::PeerNotFoundException &e) {
1578 // TODO: Should something be done here? At least an info/error
1586 bool Game::getServerContent(bool *aborted)
1590 FpsControl fps_control = { 0 };
1591 f32 dtime; // in seconds
1593 fps_control.last_time = RenderingEngine::get_timer_time();
1595 while (RenderingEngine::run()) {
1597 limitFps(&fps_control, &dtime);
1599 // Update client and server
1600 client->step(dtime);
1603 server->step(dtime);
1606 if (client->mediaReceived() && client->itemdefReceived() &&
1607 client->nodedefReceived()) {
1612 if (!checkConnection())
1615 if (client->getState() < LC_Init) {
1616 *error_message = "Client disconnected";
1617 errorstream << *error_message << std::endl;
1621 if (input->cancelPressed()) {
1623 infostream << "Connect aborted [Escape]" << std::endl;
1630 if (!client->itemdefReceived()) {
1631 const wchar_t *text = wgettext("Item definitions...");
1633 RenderingEngine::draw_load_screen(text, guienv, texture_src,
1636 } else if (!client->nodedefReceived()) {
1637 const wchar_t *text = wgettext("Node definitions...");
1639 RenderingEngine::draw_load_screen(text, guienv, texture_src,
1643 std::stringstream message;
1644 std::fixed(message);
1645 message.precision(0);
1646 float receive = client->mediaReceiveProgress() * 100;
1647 message << gettext("Media...");
1649 message << " " << receive << "%";
1650 message.precision(2);
1652 if ((USE_CURL == 0) ||
1653 (!g_settings->getBool("enable_remote_media_server"))) {
1654 float cur = client->getCurRate();
1655 std::string cur_unit = gettext("KiB/s");
1659 cur_unit = gettext("MiB/s");
1662 message << " (" << cur << ' ' << cur_unit << ")";
1665 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1666 RenderingEngine::draw_load_screen(utf8_to_wide(message.str()), guienv,
1667 texture_src, dtime, progress);
1675 /****************************************************************************/
1676 /****************************************************************************
1678 ****************************************************************************/
1679 /****************************************************************************/
1681 inline void Game::updateInteractTimers(f32 dtime)
1683 if (runData.nodig_delay_timer >= 0)
1684 runData.nodig_delay_timer -= dtime;
1686 if (runData.object_hit_delay_timer >= 0)
1687 runData.object_hit_delay_timer -= dtime;
1689 runData.time_from_last_punch += dtime;
1693 /* returns false if game should exit, otherwise true
1695 inline bool Game::checkConnection()
1697 if (client->accessDenied()) {
1698 *error_message = "Access denied. Reason: "
1699 + client->accessDeniedReason();
1700 *reconnect_requested = client->reconnectRequested();
1701 errorstream << *error_message << std::endl;
1709 /* returns false if game should exit, otherwise true
1711 inline bool Game::handleCallbacks()
1713 if (g_gamecallback->disconnect_requested) {
1714 g_gamecallback->disconnect_requested = false;
1718 if (g_gamecallback->changepassword_requested) {
1719 (new GUIPasswordChange(guienv, guiroot, -1,
1720 &g_menumgr, client, texture_src))->drop();
1721 g_gamecallback->changepassword_requested = false;
1724 if (g_gamecallback->changevolume_requested) {
1725 (new GUIVolumeChange(guienv, guiroot, -1,
1726 &g_menumgr, texture_src))->drop();
1727 g_gamecallback->changevolume_requested = false;
1730 if (g_gamecallback->keyconfig_requested) {
1731 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1732 &g_menumgr, texture_src))->drop();
1733 g_gamecallback->keyconfig_requested = false;
1736 if (g_gamecallback->keyconfig_changed) {
1737 input->keycache.populate(); // update the cache with new settings
1738 g_gamecallback->keyconfig_changed = false;
1745 void Game::processQueues()
1747 texture_src->processQueue();
1748 itemdef_manager->processQueue(client);
1749 shader_src->processQueue();
1753 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1756 float profiler_print_interval =
1757 g_settings->getFloat("profiler_print_interval");
1758 bool print_to_log = true;
1760 if (profiler_print_interval == 0) {
1761 print_to_log = false;
1762 profiler_print_interval = 3;
1765 if (profiler_interval.step(dtime, profiler_print_interval)) {
1767 infostream << "Profiler:" << std::endl;
1768 g_profiler->print(infostream);
1771 m_game_ui->updateProfiler();
1772 g_profiler->clear();
1775 // Update update graphs
1776 g_profiler->graphAdd("Time non-rendering [ms]",
1777 draw_times.busy_time - stats.drawtime);
1779 g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time);
1780 g_profiler->graphAdd("FPS", 1.0f / dtime);
1783 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1790 /* Time average and jitter calculation
1792 jp = &stats->dtime_jitter;
1793 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1795 jitter = dtime - jp->avg;
1797 if (jitter > jp->max)
1800 jp->counter += dtime;
1802 if (jp->counter > 0.0) {
1804 jp->max_sample = jp->max;
1805 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1809 /* Busytime average and jitter calculation
1811 jp = &stats->busy_time_jitter;
1812 jp->avg = jp->avg + draw_times.busy_time * 0.02;
1814 jitter = draw_times.busy_time - jp->avg;
1816 if (jitter > jp->max)
1818 if (jitter < jp->min)
1821 jp->counter += dtime;
1823 if (jp->counter > 0.0) {
1825 jp->max_sample = jp->max;
1826 jp->min_sample = jp->min;
1834 /****************************************************************************
1836 ****************************************************************************/
1838 void Game::processUserInput(f32 dtime)
1840 // Reset input if window not active or some menu is active
1841 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1843 #ifdef HAVE_TOUCHSCREENGUI
1844 g_touchscreengui->hide();
1847 #ifdef HAVE_TOUCHSCREENGUI
1848 else if (g_touchscreengui) {
1849 /* on touchscreengui step may generate own input events which ain't
1850 * what we want in case we just did clear them */
1851 g_touchscreengui->step(dtime);
1855 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1856 gui_chat_console->closeConsoleAtOnce();
1859 // Input handler step() (used by the random input generator)
1863 auto formspec = m_game_ui->getFormspecGUI();
1865 formspec->getAndroidUIInput();
1867 handleAndroidChatInput();
1870 // Increase timer for double tap of "keymap_jump"
1871 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1872 runData.jump_timer += dtime;
1875 processItemSelection(&runData.new_playeritem);
1879 void Game::processKeyInput()
1881 if (wasKeyDown(KeyType::DROP)) {
1882 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1883 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1884 toggleAutoforward();
1885 } else if (wasKeyDown(KeyType::BACKWARD)) {
1886 if (g_settings->getBool("continuous_forward"))
1887 toggleAutoforward();
1888 } else if (wasKeyDown(KeyType::INVENTORY)) {
1890 } else if (input->cancelPressed()) {
1892 m_android_chat_open = false;
1894 if (!gui_chat_console->isOpenInhibited()) {
1897 } else if (wasKeyDown(KeyType::CHAT)) {
1898 openConsole(0.2, L"");
1899 } else if (wasKeyDown(KeyType::CMD)) {
1900 openConsole(0.2, L"/");
1901 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1902 if (client->modsLoaded())
1903 openConsole(0.2, L".");
1905 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1906 } else if (wasKeyDown(KeyType::CONSOLE)) {
1907 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1908 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1910 } else if (wasKeyDown(KeyType::JUMP)) {
1911 toggleFreeMoveAlt();
1912 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1914 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1916 } else if (wasKeyDown(KeyType::NOCLIP)) {
1919 } else if (wasKeyDown(KeyType::MUTE)) {
1920 if (g_settings->getBool("enable_sound")) {
1921 bool new_mute_sound = !g_settings->getBool("mute_sound");
1922 g_settings->setBool("mute_sound", new_mute_sound);
1924 m_game_ui->showTranslatedStatusText("Sound muted");
1926 m_game_ui->showTranslatedStatusText("Sound unmuted");
1928 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1930 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1931 if (g_settings->getBool("enable_sound")) {
1932 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1934 g_settings->setFloat("sound_volume", new_volume);
1935 const wchar_t *str = wgettext("Volume changed to %d%%");
1936 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1938 m_game_ui->showStatusText(buf);
1940 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1942 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1943 if (g_settings->getBool("enable_sound")) {
1944 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1946 g_settings->setFloat("sound_volume", new_volume);
1947 const wchar_t *str = wgettext("Volume changed to %d%%");
1948 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1950 m_game_ui->showStatusText(buf);
1952 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1955 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1956 || wasKeyDown(KeyType::DEC_VOLUME)) {
1957 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1959 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1961 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1962 client->makeScreenshot();
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, &input->joystick, fs_src,
2076 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 // Autoforward by toggling continuous forward.
2201 void Game::toggleAutoforward()
2203 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2204 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2206 if (autorun_enabled)
2207 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2209 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2212 void Game::toggleMinimap(bool shift_pressed)
2214 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2218 mapper->toggleMinimapShape();
2222 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2224 // Not so satisying code to keep compatibility with old fixed mode system
2226 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2228 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2229 m_game_ui->m_flags.show_minimap = false;
2232 // If radar is disabled, try to find a non radar mode or fall back to 0
2233 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2234 while (mapper->getModeIndex() &&
2235 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2238 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2242 // End of 'not so satifying code'
2243 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2244 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2245 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2247 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2250 void Game::toggleFog()
2252 bool fog_enabled = g_settings->getBool("enable_fog");
2253 g_settings->setBool("enable_fog", !fog_enabled);
2255 m_game_ui->showTranslatedStatusText("Fog disabled");
2257 m_game_ui->showTranslatedStatusText("Fog enabled");
2261 void Game::toggleDebug()
2263 // Initial / 4x toggle: Chat only
2264 // 1x toggle: Debug text with chat
2265 // 2x toggle: Debug text with profiler graph
2266 // 3x toggle: Debug text and wireframe
2267 if (!m_game_ui->m_flags.show_debug) {
2268 m_game_ui->m_flags.show_debug = true;
2269 m_game_ui->m_flags.show_profiler_graph = false;
2270 draw_control->show_wireframe = false;
2271 m_game_ui->showTranslatedStatusText("Debug info shown");
2272 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2273 m_game_ui->m_flags.show_profiler_graph = true;
2274 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2275 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2276 m_game_ui->m_flags.show_profiler_graph = false;
2277 draw_control->show_wireframe = true;
2278 m_game_ui->showTranslatedStatusText("Wireframe shown");
2280 m_game_ui->m_flags.show_debug = false;
2281 m_game_ui->m_flags.show_profiler_graph = false;
2282 draw_control->show_wireframe = false;
2283 if (client->checkPrivilege("debug")) {
2284 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2286 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2292 void Game::toggleUpdateCamera()
2294 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2295 if (m_flags.disable_camera_update)
2296 m_game_ui->showTranslatedStatusText("Camera update disabled");
2298 m_game_ui->showTranslatedStatusText("Camera update enabled");
2302 void Game::increaseViewRange()
2304 s16 range = g_settings->getS16("viewing_range");
2305 s16 range_new = range + 10;
2309 if (range_new > 4000) {
2311 str = wgettext("Viewing range is at maximum: %d");
2312 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2314 m_game_ui->showStatusText(buf);
2317 str = wgettext("Viewing range changed to %d");
2318 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2320 m_game_ui->showStatusText(buf);
2322 g_settings->set("viewing_range", itos(range_new));
2326 void Game::decreaseViewRange()
2328 s16 range = g_settings->getS16("viewing_range");
2329 s16 range_new = range - 10;
2333 if (range_new < 20) {
2335 str = wgettext("Viewing range is at minimum: %d");
2336 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2338 m_game_ui->showStatusText(buf);
2340 str = wgettext("Viewing range changed to %d");
2341 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2343 m_game_ui->showStatusText(buf);
2345 g_settings->set("viewing_range", itos(range_new));
2349 void Game::toggleFullViewRange()
2351 draw_control->range_all = !draw_control->range_all;
2352 if (draw_control->range_all)
2353 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2355 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2359 void Game::checkZoomEnabled()
2361 LocalPlayer *player = client->getEnv().getLocalPlayer();
2362 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2363 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2367 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2369 if ((device->isWindowActive() && device->isWindowFocused()
2370 && !isMenuActive()) || input->isRandom()) {
2373 if (!input->isRandom()) {
2374 // Mac OSX gets upset if this is set every frame
2375 if (device->getCursorControl()->isVisible())
2376 device->getCursorControl()->setVisible(false);
2380 if (m_first_loop_after_window_activation) {
2381 m_first_loop_after_window_activation = false;
2383 input->setMousePos(driver->getScreenSize().Width / 2,
2384 driver->getScreenSize().Height / 2);
2386 updateCameraOrientation(cam, dtime);
2392 // Mac OSX gets upset if this is set every frame
2393 if (!device->getCursorControl()->isVisible())
2394 device->getCursorControl()->setVisible(true);
2397 m_first_loop_after_window_activation = true;
2402 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2404 #ifdef HAVE_TOUCHSCREENGUI
2405 if (g_touchscreengui) {
2406 cam->camera_yaw += g_touchscreengui->getYawChange();
2407 cam->camera_pitch = g_touchscreengui->getPitch();
2410 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2411 v2s32 dist = input->getMousePos() - center;
2413 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2417 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity;
2418 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity;
2420 if (dist.X != 0 || dist.Y != 0)
2421 input->setMousePos(center.X, center.Y);
2422 #ifdef HAVE_TOUCHSCREENGUI
2426 if (m_cache_enable_joysticks) {
2427 f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime;
2428 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2429 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2432 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2436 void Game::updatePlayerControl(const CameraOrientation &cam)
2438 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2440 // DO NOT use the isKeyDown method for the forward, backward, left, right
2441 // buttons, as the code that uses the controls needs to be able to
2442 // distinguish between the two in order to know when to use joysticks.
2444 PlayerControl control(
2445 input->isKeyDown(KeyType::FORWARD),
2446 input->isKeyDown(KeyType::BACKWARD),
2447 input->isKeyDown(KeyType::LEFT),
2448 input->isKeyDown(KeyType::RIGHT),
2449 isKeyDown(KeyType::JUMP),
2450 isKeyDown(KeyType::SPECIAL1),
2451 isKeyDown(KeyType::SNEAK),
2452 isKeyDown(KeyType::ZOOM),
2453 isKeyDown(KeyType::DIG),
2454 isKeyDown(KeyType::PLACE),
2457 input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE),
2458 input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE)
2461 u32 keypress_bits = (
2462 ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) |
2463 ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) |
2464 ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) |
2465 ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) |
2466 ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) |
2467 ( (u32)(isKeyDown(KeyType::SPECIAL1) & 0x1) << 5) |
2468 ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) |
2469 ( (u32)(isKeyDown(KeyType::DIG) & 0x1) << 7) |
2470 ( (u32)(isKeyDown(KeyType::PLACE) & 0x1) << 8) |
2471 ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9)
2475 /* For Android, simulate holding down AUX1 (fast move) if the user has
2476 * the fast_move setting toggled on. If there is an aux1 key defined for
2477 * Android then its meaning is inverted (i.e. holding aux1 means walk and
2480 if (m_cache_hold_aux1) {
2481 control.aux1 = control.aux1 ^ true;
2482 keypress_bits ^= ((u32)(1U << 5));
2486 LocalPlayer *player = client->getEnv().getLocalPlayer();
2488 // autojump if set: simulate "jump" key
2489 if (player->getAutojump()) {
2490 control.jump = true;
2491 keypress_bits |= 1U << 4;
2494 // autoforward if set: simulate "up" key
2495 if (player->getPlayerSettings().continuous_forward &&
2496 client->activeObjectsReceived() && !player->isDead()) {
2498 keypress_bits |= 1U << 0;
2501 client->setPlayerControl(control);
2502 player->keyPressed = keypress_bits;
2508 inline void Game::step(f32 *dtime)
2510 bool can_be_and_is_paused =
2511 (simple_singleplayer_mode && g_menumgr.pausesGame());
2513 if (can_be_and_is_paused) { // This is for a singleplayer server
2514 *dtime = 0; // No time passes
2517 server->step(*dtime);
2519 client->step(*dtime);
2523 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2524 {&Game::handleClientEvent_None},
2525 {&Game::handleClientEvent_PlayerDamage},
2526 {&Game::handleClientEvent_PlayerForceMove},
2527 {&Game::handleClientEvent_Deathscreen},
2528 {&Game::handleClientEvent_ShowFormSpec},
2529 {&Game::handleClientEvent_ShowLocalFormSpec},
2530 {&Game::handleClientEvent_HandleParticleEvent},
2531 {&Game::handleClientEvent_HandleParticleEvent},
2532 {&Game::handleClientEvent_HandleParticleEvent},
2533 {&Game::handleClientEvent_HudAdd},
2534 {&Game::handleClientEvent_HudRemove},
2535 {&Game::handleClientEvent_HudChange},
2536 {&Game::handleClientEvent_SetSky},
2537 {&Game::handleClientEvent_SetSun},
2538 {&Game::handleClientEvent_SetMoon},
2539 {&Game::handleClientEvent_SetStars},
2540 {&Game::handleClientEvent_OverrideDayNigthRatio},
2541 {&Game::handleClientEvent_CloudParams},
2544 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2546 FATAL_ERROR("ClientEvent type None received");
2549 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2551 if (client->modsLoaded())
2552 client->getScript()->on_damage_taken(event->player_damage.amount);
2554 // Damage flash and hurt tilt are not used at death
2555 if (client->getHP() > 0) {
2556 runData.damage_flash += 95.0f + 3.2f * event->player_damage.amount;
2557 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2559 LocalPlayer *player = client->getEnv().getLocalPlayer();
2561 player->hurt_tilt_timer = 1.5f;
2562 player->hurt_tilt_strength =
2563 rangelim(event->player_damage.amount / 4.0f, 1.0f, 4.0f);
2566 // Play damage sound
2567 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2570 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2572 cam->camera_yaw = event->player_force_move.yaw;
2573 cam->camera_pitch = event->player_force_move.pitch;
2576 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2578 // If client scripting is enabled, deathscreen is handled by CSM code in
2579 // builtin/client/init.lua
2580 if (client->modsLoaded())
2581 client->getScript()->on_death();
2583 showDeathFormspec();
2585 /* Handle visualization */
2586 LocalPlayer *player = client->getEnv().getLocalPlayer();
2587 runData.damage_flash = 0;
2588 player->hurt_tilt_timer = 0;
2589 player->hurt_tilt_strength = 0;
2592 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2594 if (event->show_formspec.formspec->empty()) {
2595 auto formspec = m_game_ui->getFormspecGUI();
2596 if (formspec && (event->show_formspec.formname->empty()
2597 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2598 formspec->quitMenu();
2601 FormspecFormSource *fs_src =
2602 new FormspecFormSource(*(event->show_formspec.formspec));
2603 TextDestPlayerInventory *txt_dst =
2604 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2606 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2607 GUIFormSpecMenu::create(formspec, client, &input->joystick,
2608 fs_src, txt_dst, client->getFormspecPrepend(), sound);
2611 delete event->show_formspec.formspec;
2612 delete event->show_formspec.formname;
2615 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2617 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2618 LocalFormspecHandler *txt_dst =
2619 new LocalFormspecHandler(*event->show_formspec.formname, client);
2620 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, &input->joystick,
2621 fs_src, txt_dst, client->getFormspecPrepend(), sound);
2623 delete event->show_formspec.formspec;
2624 delete event->show_formspec.formname;
2627 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2628 CameraOrientation *cam)
2630 LocalPlayer *player = client->getEnv().getLocalPlayer();
2631 client->getParticleManager()->handleParticleEvent(event, client, player);
2634 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2636 LocalPlayer *player = client->getEnv().getLocalPlayer();
2637 auto &hud_server_to_client = client->getHUDTranslationMap();
2639 u32 server_id = event->hudadd.server_id;
2640 // ignore if we already have a HUD with that ID
2641 auto i = hud_server_to_client.find(server_id);
2642 if (i != hud_server_to_client.end()) {
2643 delete event->hudadd.pos;
2644 delete event->hudadd.name;
2645 delete event->hudadd.scale;
2646 delete event->hudadd.text;
2647 delete event->hudadd.align;
2648 delete event->hudadd.offset;
2649 delete event->hudadd.world_pos;
2650 delete event->hudadd.size;
2651 delete event->hudadd.text2;
2655 HudElement *e = new HudElement;
2656 e->type = (HudElementType)event->hudadd.type;
2657 e->pos = *event->hudadd.pos;
2658 e->name = *event->hudadd.name;
2659 e->scale = *event->hudadd.scale;
2660 e->text = *event->hudadd.text;
2661 e->number = event->hudadd.number;
2662 e->item = event->hudadd.item;
2663 e->dir = event->hudadd.dir;
2664 e->align = *event->hudadd.align;
2665 e->offset = *event->hudadd.offset;
2666 e->world_pos = *event->hudadd.world_pos;
2667 e->size = *event->hudadd.size;
2668 e->z_index = event->hudadd.z_index;
2669 e->text2 = *event->hudadd.text2;
2670 hud_server_to_client[server_id] = player->addHud(e);
2672 delete event->hudadd.pos;
2673 delete event->hudadd.name;
2674 delete event->hudadd.scale;
2675 delete event->hudadd.text;
2676 delete event->hudadd.align;
2677 delete event->hudadd.offset;
2678 delete event->hudadd.world_pos;
2679 delete event->hudadd.size;
2680 delete event->hudadd.text2;
2683 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2685 LocalPlayer *player = client->getEnv().getLocalPlayer();
2686 HudElement *e = player->removeHud(event->hudrm.id);
2690 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2692 LocalPlayer *player = client->getEnv().getLocalPlayer();
2694 u32 id = event->hudchange.id;
2695 HudElement *e = player->getHud(id);
2698 delete event->hudchange.v3fdata;
2699 delete event->hudchange.v2fdata;
2700 delete event->hudchange.sdata;
2701 delete event->hudchange.v2s32data;
2705 switch (event->hudchange.stat) {
2707 e->pos = *event->hudchange.v2fdata;
2711 e->name = *event->hudchange.sdata;
2714 case HUD_STAT_SCALE:
2715 e->scale = *event->hudchange.v2fdata;
2719 e->text = *event->hudchange.sdata;
2722 case HUD_STAT_NUMBER:
2723 e->number = event->hudchange.data;
2727 e->item = event->hudchange.data;
2731 e->dir = event->hudchange.data;
2734 case HUD_STAT_ALIGN:
2735 e->align = *event->hudchange.v2fdata;
2738 case HUD_STAT_OFFSET:
2739 e->offset = *event->hudchange.v2fdata;
2742 case HUD_STAT_WORLD_POS:
2743 e->world_pos = *event->hudchange.v3fdata;
2747 e->size = *event->hudchange.v2s32data;
2750 case HUD_STAT_Z_INDEX:
2751 e->z_index = event->hudchange.data;
2754 case HUD_STAT_TEXT2:
2755 e->text2 = *event->hudchange.sdata;
2759 delete event->hudchange.v3fdata;
2760 delete event->hudchange.v2fdata;
2761 delete event->hudchange.sdata;
2762 delete event->hudchange.v2s32data;
2765 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2767 sky->setVisible(false);
2768 // Whether clouds are visible in front of a custom skybox.
2769 sky->setCloudsEnabled(event->set_sky->clouds);
2775 // Clear the old textures out in case we switch rendering type.
2776 sky->clearSkyboxTextures();
2777 // Handle according to type
2778 if (event->set_sky->type == "regular") {
2779 // Shows the mesh skybox
2780 sky->setVisible(true);
2781 // Update mesh based skybox colours if applicable.
2782 sky->setSkyColors(event->set_sky->sky_color);
2783 sky->setHorizonTint(
2784 event->set_sky->fog_sun_tint,
2785 event->set_sky->fog_moon_tint,
2786 event->set_sky->fog_tint_type
2788 } else if (event->set_sky->type == "skybox" &&
2789 event->set_sky->textures.size() == 6) {
2790 // Disable the dyanmic mesh skybox:
2791 sky->setVisible(false);
2793 sky->setFallbackBgColor(event->set_sky->bgcolor);
2794 // Set sunrise and sunset fog tinting:
2795 sky->setHorizonTint(
2796 event->set_sky->fog_sun_tint,
2797 event->set_sky->fog_moon_tint,
2798 event->set_sky->fog_tint_type
2800 // Add textures to skybox.
2801 for (int i = 0; i < 6; i++)
2802 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2804 // Handle everything else as plain color.
2805 if (event->set_sky->type != "plain")
2806 infostream << "Unknown sky type: "
2807 << (event->set_sky->type) << std::endl;
2808 sky->setVisible(false);
2809 sky->setFallbackBgColor(event->set_sky->bgcolor);
2810 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2811 sky->setHorizonTint(
2812 event->set_sky->bgcolor,
2813 event->set_sky->bgcolor,
2817 delete event->set_sky;
2820 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2822 sky->setSunVisible(event->sun_params->visible);
2823 sky->setSunTexture(event->sun_params->texture,
2824 event->sun_params->tonemap, texture_src);
2825 sky->setSunScale(event->sun_params->scale);
2826 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2827 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2828 delete event->sun_params;
2831 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2833 sky->setMoonVisible(event->moon_params->visible);
2834 sky->setMoonTexture(event->moon_params->texture,
2835 event->moon_params->tonemap, texture_src);
2836 sky->setMoonScale(event->moon_params->scale);
2837 delete event->moon_params;
2840 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2842 sky->setStarsVisible(event->star_params->visible);
2843 sky->setStarCount(event->star_params->count, false);
2844 sky->setStarColor(event->star_params->starcolor);
2845 sky->setStarScale(event->star_params->scale);
2846 delete event->star_params;
2849 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2850 CameraOrientation *cam)
2852 client->getEnv().setDayNightRatioOverride(
2853 event->override_day_night_ratio.do_override,
2854 event->override_day_night_ratio.ratio_f * 1000.0f);
2857 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2862 clouds->setDensity(event->cloud_params.density);
2863 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2864 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2865 clouds->setHeight(event->cloud_params.height);
2866 clouds->setThickness(event->cloud_params.thickness);
2867 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2870 void Game::processClientEvents(CameraOrientation *cam)
2872 while (client->hasClientEvents()) {
2873 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2874 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2875 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2876 (this->*evHandler.handler)(event.get(), cam);
2880 void Game::updateChat(f32 dtime, const v2u32 &screensize)
2882 // Get new messages from error log buffer
2883 while (!m_chat_log_buf.empty())
2884 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2886 // Get new messages from client
2887 std::wstring message;
2888 while (client->getChatMessage(message)) {
2889 chat_backend->addUnparsedMessage(message);
2892 // Remove old messages
2893 chat_backend->step(dtime);
2895 // Display all messages in a static text element
2896 m_game_ui->setChatText(chat_backend->getRecentChat(),
2897 chat_backend->getRecentBuffer().getLineCount());
2900 void Game::updateCamera(u32 busy_time, f32 dtime)
2902 LocalPlayer *player = client->getEnv().getLocalPlayer();
2905 For interaction purposes, get info about the held item
2907 - Is it a usable item?
2908 - Can it point to liquids?
2910 ItemStack playeritem;
2912 ItemStack selected, hand;
2913 playeritem = player->getWieldedItem(&selected, &hand);
2916 ToolCapabilities playeritem_toolcap =
2917 playeritem.getToolCapabilities(itemdef_manager);
2919 v3s16 old_camera_offset = camera->getOffset();
2921 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2922 GenericCAO *playercao = player->getCAO();
2924 // If playercao not loaded, don't change camera
2928 camera->toggleCameraMode();
2930 // Make the player visible depending on camera mode.
2931 playercao->updateMeshCulling();
2932 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2935 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2936 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2938 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2939 camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
2940 camera->step(dtime);
2942 v3f camera_position = camera->getPosition();
2943 v3f camera_direction = camera->getDirection();
2944 f32 camera_fov = camera->getFovMax();
2945 v3s16 camera_offset = camera->getOffset();
2947 m_camera_offset_changed = (camera_offset != old_camera_offset);
2949 if (!m_flags.disable_camera_update) {
2950 client->getEnv().getClientMap().updateCamera(camera_position,
2951 camera_direction, camera_fov, camera_offset);
2953 if (m_camera_offset_changed) {
2954 client->updateCameraOffset(camera_offset);
2955 client->getEnv().updateCameraOffset(camera_offset);
2958 clouds->updateCameraOffset(camera_offset);
2964 void Game::updateSound(f32 dtime)
2966 // Update sound listener
2967 v3s16 camera_offset = camera->getOffset();
2968 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
2969 v3f(0, 0, 0), // velocity
2970 camera->getDirection(),
2971 camera->getCameraNode()->getUpVector());
2973 bool mute_sound = g_settings->getBool("mute_sound");
2975 sound->setListenerGain(0.0f);
2977 // Check if volume is in the proper range, else fix it.
2978 float old_volume = g_settings->getFloat("sound_volume");
2979 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
2980 sound->setListenerGain(new_volume);
2982 if (old_volume != new_volume) {
2983 g_settings->setFloat("sound_volume", new_volume);
2987 LocalPlayer *player = client->getEnv().getLocalPlayer();
2989 // Tell the sound maker whether to make footstep sounds
2990 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
2992 // Update sound maker
2993 if (player->makes_footstep_sound)
2994 soundmaker->step(dtime);
2996 ClientMap &map = client->getEnv().getClientMap();
2997 MapNode n = map.getNode(player->getFootstepNodePos());
2998 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3002 void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
3004 LocalPlayer *player = client->getEnv().getLocalPlayer();
3006 const v3f camera_direction = camera->getDirection();
3007 const v3s16 camera_offset = camera->getOffset();
3010 Calculate what block is the crosshair pointing to
3013 ItemStack selected_item, hand_item;
3014 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3016 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3017 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3019 core::line3d<f32> shootline;
3021 switch (camera->getCameraMode()) {
3022 case CAMERA_MODE_FIRST:
3023 // Shoot from camera position, with bobbing
3024 shootline.start = camera->getPosition();
3026 case CAMERA_MODE_THIRD:
3027 // Shoot from player head, no bobbing
3028 shootline.start = camera->getHeadPosition();
3030 case CAMERA_MODE_THIRD_FRONT:
3031 shootline.start = camera->getHeadPosition();
3032 // prevent player pointing anything in front-view
3036 shootline.end = shootline.start + camera_direction * BS * d;
3038 #ifdef HAVE_TOUCHSCREENGUI
3040 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3041 shootline = g_touchscreengui->getShootline();
3042 // Scale shootline to the acual distance the player can reach
3043 shootline.end = shootline.start
3044 + shootline.getVector().normalize() * BS * d;
3045 shootline.start += intToFloat(camera_offset, BS);
3046 shootline.end += intToFloat(camera_offset, BS);
3051 PointedThing pointed = updatePointedThing(shootline,
3052 selected_def.liquids_pointable,
3053 !runData.btn_down_for_dig,
3056 if (pointed != runData.pointed_old) {
3057 infostream << "Pointing at " << pointed.dump() << std::endl;
3058 hud->updateSelectionMesh(camera_offset);
3061 // Allow digging again if button is not pressed
3062 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3063 runData.digging_blocked = false;
3067 - releasing dig button
3068 - pointing away from node
3070 if (runData.digging) {
3071 if (wasKeyReleased(KeyType::DIG)) {
3072 infostream << "Dig button released (stopped digging)" << std::endl;
3073 runData.digging = false;
3074 } else if (pointed != runData.pointed_old) {
3075 if (pointed.type == POINTEDTHING_NODE
3076 && runData.pointed_old.type == POINTEDTHING_NODE
3077 && pointed.node_undersurface
3078 == runData.pointed_old.node_undersurface) {
3079 // Still pointing to the same node, but a different face.
3082 infostream << "Pointing away from node (stopped digging)" << std::endl;
3083 runData.digging = false;
3084 hud->updateSelectionMesh(camera_offset);
3088 if (!runData.digging) {
3089 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3090 client->setCrack(-1, v3s16(0, 0, 0));
3091 runData.dig_time = 0.0;
3093 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3094 // Remove e.g. torches faster when clicking instead of holding dig button
3095 runData.nodig_delay_timer = 0;
3096 runData.dig_instantly = false;
3099 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3100 runData.btn_down_for_dig = false;
3102 runData.punching = false;
3104 soundmaker->m_player_leftpunch_sound.name = "";
3106 // Prepare for repeating, unless we're not supposed to
3107 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3108 runData.repeat_place_timer += dtime;
3110 runData.repeat_place_timer = 0;
3112 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3113 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3114 !client->getScript()->on_item_use(selected_item, pointed)))
3115 client->interact(INTERACT_USE, pointed);
3116 } else if (pointed.type == POINTEDTHING_NODE) {
3117 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3118 } else if (pointed.type == POINTEDTHING_OBJECT) {
3119 v3f player_position = player->getPosition();
3120 handlePointingAtObject(pointed, tool_item, player_position, show_debug);
3121 } else if (isKeyDown(KeyType::DIG)) {
3122 // When button is held down in air, show continuous animation
3123 runData.punching = true;
3124 // Run callback even though item is not usable
3125 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3126 client->getScript()->on_item_use(selected_item, pointed);
3127 } else if (wasKeyPressed(KeyType::PLACE)) {
3128 handlePointingAtNothing(selected_item);
3131 runData.pointed_old = pointed;
3133 if (runData.punching || wasKeyPressed(KeyType::DIG))
3134 camera->setDigging(0); // dig animation
3136 input->clearWasKeyPressed();
3137 input->clearWasKeyReleased();
3138 // Ensure DIG & PLACE are marked as handled
3139 wasKeyDown(KeyType::DIG);
3140 wasKeyDown(KeyType::PLACE);
3142 input->joystick.clearWasKeyPressed(KeyType::DIG);
3143 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3145 input->joystick.clearWasKeyReleased(KeyType::DIG);
3146 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3150 PointedThing Game::updatePointedThing(
3151 const core::line3d<f32> &shootline,
3152 bool liquids_pointable,
3153 bool look_for_object,
3154 const v3s16 &camera_offset)
3156 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3157 selectionboxes->clear();
3158 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3159 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3160 "show_entity_selectionbox");
3162 ClientEnvironment &env = client->getEnv();
3163 ClientMap &map = env.getClientMap();
3164 const NodeDefManager *nodedef = map.getNodeDefManager();
3166 runData.selected_object = NULL;
3167 hud->pointing_at_object = false;
3169 RaycastState s(shootline, look_for_object, liquids_pointable);
3170 PointedThing result;
3171 env.continueRaycast(&s, &result);
3172 if (result.type == POINTEDTHING_OBJECT) {
3173 hud->pointing_at_object = true;
3175 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3176 aabb3f selection_box;
3177 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3178 runData.selected_object->getSelectionBox(&selection_box)) {
3179 v3f pos = runData.selected_object->getPosition();
3180 selectionboxes->push_back(aabb3f(selection_box));
3181 hud->setSelectionPos(pos, camera_offset);
3183 } else if (result.type == POINTEDTHING_NODE) {
3184 // Update selection boxes
3185 MapNode n = map.getNode(result.node_undersurface);
3186 std::vector<aabb3f> boxes;
3187 n.getSelectionBoxes(nodedef, &boxes,
3188 n.getNeighbors(result.node_undersurface, &map));
3191 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3192 i != boxes.end(); ++i) {
3194 box.MinEdge -= v3f(d, d, d);
3195 box.MaxEdge += v3f(d, d, d);
3196 selectionboxes->push_back(box);
3198 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3200 hud->setSelectedFaceNormal(v3f(
3201 result.intersection_normal.X,
3202 result.intersection_normal.Y,
3203 result.intersection_normal.Z));
3206 // Update selection mesh light level and vertex colors
3207 if (!selectionboxes->empty()) {
3208 v3f pf = hud->getSelectionPos();
3209 v3s16 p = floatToInt(pf, BS);
3211 // Get selection mesh light level
3212 MapNode n = map.getNode(p);
3213 u16 node_light = getInteriorLight(n, -1, nodedef);
3214 u16 light_level = node_light;
3216 for (const v3s16 &dir : g_6dirs) {
3217 n = map.getNode(p + dir);
3218 node_light = getInteriorLight(n, -1, nodedef);
3219 if (node_light > light_level)
3220 light_level = node_light;
3223 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3225 final_color_blend(&c, light_level, daynight_ratio);
3227 // Modify final color a bit with time
3228 u32 timer = porting::getTimeMs() % 5000;
3229 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3230 float sin_r = 0.08f * std::sin(timerf);
3231 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3232 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3233 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3234 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3235 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3237 // Set mesh final color
3238 hud->setSelectionMeshColor(c);
3244 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3246 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3247 PointedThing fauxPointed;
3248 fauxPointed.type = POINTEDTHING_NOTHING;
3249 client->interact(INTERACT_ACTIVATE, fauxPointed);
3253 void Game::handlePointingAtNode(const PointedThing &pointed,
3254 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3256 v3s16 nodepos = pointed.node_undersurface;
3257 v3s16 neighbourpos = pointed.node_abovesurface;
3260 Check information text of node
3263 ClientMap &map = client->getEnv().getClientMap();
3265 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3266 && !runData.digging_blocked
3267 && client->checkPrivilege("interact")) {
3268 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3271 // This should be done after digging handling
3272 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3275 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3276 meta->getString("infotext"))));
3278 MapNode n = map.getNode(nodepos);
3280 if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
3281 m_game_ui->setInfoText(L"Unknown node: " +
3282 utf8_to_wide(nodedef_manager->get(n).name));
3286 if ((wasKeyPressed(KeyType::PLACE) ||
3287 runData.repeat_place_timer >= m_repeat_place_time) &&
3288 client->checkPrivilege("interact")) {
3289 runData.repeat_place_timer = 0;
3290 infostream << "Place button pressed while looking at ground" << std::endl;
3292 // Placing animation (always shown for feedback)
3293 camera->setDigging(1);
3295 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3297 // If the wielded item has node placement prediction,
3299 // And also set the sound and send the interact
3300 // But first check for meta formspec and rightclickable
3301 auto &def = selected_item.getDefinition(itemdef_manager);
3302 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3305 if (placed && client->modsLoaded())
3306 client->getScript()->on_placenode(pointed, def);
3310 bool Game::nodePlacement(const ItemDefinition &selected_def,
3311 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3312 const PointedThing &pointed, const NodeMetadata *meta)
3314 std::string prediction = selected_def.node_placement_prediction;
3315 const NodeDefManager *nodedef = client->ndef();
3316 ClientMap &map = client->getEnv().getClientMap();
3318 bool is_valid_position;
3320 node = map.getNode(nodepos, &is_valid_position);
3321 if (!is_valid_position) {
3322 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3327 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3328 && !isKeyDown(KeyType::SNEAK)) {
3329 // on_rightclick callbacks are called anyway
3330 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3331 client->interact(INTERACT_PLACE, pointed);
3333 infostream << "Launching custom inventory view" << std::endl;
3335 InventoryLocation inventoryloc;
3336 inventoryloc.setNodeMeta(nodepos);
3338 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3339 &client->getEnv().getClientMap(), nodepos);
3340 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3342 auto *&formspec = m_game_ui->updateFormspec("");
3343 GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
3344 txt_dst, client->getFormspecPrepend(), sound);
3346 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3350 // on_rightclick callback
3351 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3352 !isKeyDown(KeyType::SNEAK))) {
3354 client->interact(INTERACT_PLACE, pointed);
3358 verbosestream << "Node placement prediction for "
3359 << selected_def.name << " is " << prediction << std::endl;
3360 v3s16 p = neighbourpos;
3362 // Place inside node itself if buildable_to
3363 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3364 if (is_valid_position) {
3365 if (nodedef->get(n_under).buildable_to) {
3368 node = map.getNode(p, &is_valid_position);
3369 if (is_valid_position && !nodedef->get(node).buildable_to) {
3370 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3372 client->interact(INTERACT_PLACE, pointed);
3378 // Find id of predicted node
3380 bool found = nodedef->getId(prediction, id);
3383 errorstream << "Node placement prediction failed for "
3384 << selected_def.name << " (places "
3386 << ") - Name not known" << std::endl;
3387 // Handle this as if prediction was empty
3389 client->interact(INTERACT_PLACE, pointed);
3393 const ContentFeatures &predicted_f = nodedef->get(id);
3395 // Predict param2 for facedir and wallmounted nodes
3398 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3399 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3400 v3s16 dir = nodepos - neighbourpos;
3402 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3403 param2 = dir.Y < 0 ? 1 : 0;
3404 } else if (abs(dir.X) > abs(dir.Z)) {
3405 param2 = dir.X < 0 ? 3 : 2;
3407 param2 = dir.Z < 0 ? 5 : 4;
3411 if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3412 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3413 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3415 if (abs(dir.X) > abs(dir.Z)) {
3416 param2 = dir.X < 0 ? 3 : 1;
3418 param2 = dir.Z < 0 ? 2 : 0;
3422 assert(param2 <= 5);
3424 //Check attachment if node is in group attached_node
3425 if (((ItemGroupList) predicted_f.groups)["attached_node"] != 0) {
3426 static v3s16 wallmounted_dirs[8] = {
3436 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3437 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3438 pp = p + wallmounted_dirs[param2];
3440 pp = p + v3s16(0, -1, 0);
3442 if (!nodedef->get(map.getNode(pp)).walkable) {
3443 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3445 client->interact(INTERACT_PLACE, pointed);
3451 if ((predicted_f.param_type_2 == CPT2_COLOR
3452 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3453 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3454 const std::string &indexstr = selected_item.metadata.getString(
3455 "palette_index", 0);
3456 if (!indexstr.empty()) {
3457 s32 index = mystoi(indexstr);
3458 if (predicted_f.param_type_2 == CPT2_COLOR) {
3460 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3461 // param2 = pure palette index + other
3462 param2 = (index & 0xf8) | (param2 & 0x07);
3463 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3464 // param2 = pure palette index + other
3465 param2 = (index & 0xe0) | (param2 & 0x1f);
3470 // Add node to client map
3471 MapNode n(id, 0, param2);
3474 LocalPlayer *player = client->getEnv().getLocalPlayer();
3476 // Dont place node when player would be inside new node
3477 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3478 if (!nodedef->get(n).walkable ||
3479 g_settings->getBool("enable_build_where_you_stand") ||
3480 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3481 (nodedef->get(n).walkable &&
3482 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3483 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3484 // This triggers the required mesh update too
3485 client->addNode(p, n);
3487 client->interact(INTERACT_PLACE, pointed);
3488 // A node is predicted, also play a sound
3489 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3492 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3495 } catch (InvalidPositionException &e) {
3496 errorstream << "Node placement prediction failed for "
3497 << selected_def.name << " (places "
3499 << ") - Position not loaded" << std::endl;
3500 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3505 void Game::handlePointingAtObject(const PointedThing &pointed,
3506 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3508 std::wstring infotext = unescape_translate(
3509 utf8_to_wide(runData.selected_object->infoText()));
3512 if (!infotext.empty()) {
3515 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3518 m_game_ui->setInfoText(infotext);
3520 if (isKeyDown(KeyType::DIG)) {
3521 bool do_punch = false;
3522 bool do_punch_damage = false;
3524 if (runData.object_hit_delay_timer <= 0.0) {
3526 do_punch_damage = true;
3527 runData.object_hit_delay_timer = object_hit_delay;
3530 if (wasKeyPressed(KeyType::DIG))
3534 infostream << "Punched object" << std::endl;
3535 runData.punching = true;
3538 if (do_punch_damage) {
3539 // Report direct punch
3540 v3f objpos = runData.selected_object->getPosition();
3541 v3f dir = (objpos - player_position).normalize();
3543 bool disable_send = runData.selected_object->directReportPunch(
3544 dir, &tool_item, runData.time_from_last_punch);
3545 runData.time_from_last_punch = 0;
3548 client->interact(INTERACT_START_DIGGING, pointed);
3550 } else if (wasKeyDown(KeyType::PLACE)) {
3551 infostream << "Pressed place button while pointing at object" << std::endl;
3552 client->interact(INTERACT_PLACE, pointed); // place
3557 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3558 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3560 // See also: serverpackethandle.cpp, action == 2
3561 LocalPlayer *player = client->getEnv().getLocalPlayer();
3562 ClientMap &map = client->getEnv().getClientMap();
3563 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3565 // NOTE: Similar piece of code exists on the server side for
3567 // Get digging parameters
3568 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3569 &selected_item.getToolCapabilities(itemdef_manager));
3571 // If can't dig, try hand
3572 if (!params.diggable) {
3573 params = getDigParams(nodedef_manager->get(n).groups,
3574 &hand_item.getToolCapabilities(itemdef_manager));
3577 if (!params.diggable) {
3578 // I guess nobody will wait for this long
3579 runData.dig_time_complete = 10000000.0;
3581 runData.dig_time_complete = params.time;
3583 if (m_cache_enable_particles) {
3584 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3585 client->getParticleManager()->addNodeParticle(client,
3586 player, nodepos, n, features);
3590 if (!runData.digging) {
3591 infostream << "Started digging" << std::endl;
3592 runData.dig_instantly = runData.dig_time_complete == 0;
3593 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3595 client->interact(INTERACT_START_DIGGING, pointed);
3596 runData.digging = true;
3597 runData.btn_down_for_dig = true;
3600 if (!runData.dig_instantly) {
3601 runData.dig_index = (float)crack_animation_length
3603 / runData.dig_time_complete;
3605 // This is for e.g. torches
3606 runData.dig_index = crack_animation_length;
3609 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3611 if (sound_dig.exists() && params.diggable) {
3612 if (sound_dig.name == "__group") {
3613 if (!params.main_group.empty()) {
3614 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3615 soundmaker->m_player_leftpunch_sound.name =
3616 std::string("default_dig_") +
3620 soundmaker->m_player_leftpunch_sound = sound_dig;
3624 // Don't show cracks if not diggable
3625 if (runData.dig_time_complete >= 100000.0) {
3626 } else if (runData.dig_index < crack_animation_length) {
3627 //TimeTaker timer("client.setTempMod");
3628 //infostream<<"dig_index="<<dig_index<<std::endl;
3629 client->setCrack(runData.dig_index, nodepos);
3631 infostream << "Digging completed" << std::endl;
3632 client->setCrack(-1, v3s16(0, 0, 0));
3634 runData.dig_time = 0;
3635 runData.digging = false;
3636 // we successfully dug, now block it from repeating if we want to be safe
3637 if (g_settings->getBool("safe_dig_and_place"))
3638 runData.digging_blocked = true;
3640 runData.nodig_delay_timer =
3641 runData.dig_time_complete / (float)crack_animation_length;
3643 // We don't want a corresponding delay to very time consuming nodes
3644 // and nodes without digging time (e.g. torches) get a fixed delay.
3645 if (runData.nodig_delay_timer > 0.3)
3646 runData.nodig_delay_timer = 0.3;
3647 else if (runData.dig_instantly)
3648 runData.nodig_delay_timer = 0.15;
3650 bool is_valid_position;
3651 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3652 if (is_valid_position) {
3653 if (client->modsLoaded() &&
3654 client->getScript()->on_dignode(nodepos, wasnode)) {
3658 const ContentFeatures &f = client->ndef()->get(wasnode);
3659 if (f.node_dig_prediction == "air") {
3660 client->removeNode(nodepos);
3661 } else if (!f.node_dig_prediction.empty()) {
3663 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3665 client->addNode(nodepos, id, true);
3667 // implicit else: no prediction
3670 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3672 if (m_cache_enable_particles) {
3673 const ContentFeatures &features =
3674 client->getNodeDefManager()->get(wasnode);
3675 client->getParticleManager()->addDiggingParticles(client,
3676 player, nodepos, wasnode, features);
3680 // Send event to trigger sound
3681 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3684 if (runData.dig_time_complete < 100000.0) {
3685 runData.dig_time += dtime;
3687 runData.dig_time = 0;
3688 client->setCrack(-1, nodepos);
3691 camera->setDigging(0); // Dig animation
3694 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3695 const CameraOrientation &cam)
3697 TimeTaker tt_update("Game::updateFrame()");
3698 LocalPlayer *player = client->getEnv().getLocalPlayer();
3704 if (draw_control->range_all) {
3705 runData.fog_range = 100000 * BS;
3707 runData.fog_range = draw_control->wanted_range * BS;
3711 Calculate general brightness
3713 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3714 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3715 float direct_brightness;
3718 if (m_cache_enable_noclip && m_cache_enable_free_move) {
3719 direct_brightness = time_brightness;
3720 sunlight_seen = true;
3722 float old_brightness = sky->getBrightness();
3723 direct_brightness = client->getEnv().getClientMap()
3724 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3725 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3729 float time_of_day_smooth = runData.time_of_day_smooth;
3730 float time_of_day = client->getEnv().getTimeOfDayF();
3732 static const float maxsm = 0.05f;
3733 static const float todsm = 0.05f;
3735 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3736 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3737 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3738 time_of_day_smooth = time_of_day;
3740 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3741 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3742 + (time_of_day + 1.0) * todsm;
3744 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3745 + time_of_day * todsm;
3747 runData.time_of_day_smooth = time_of_day_smooth;
3749 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3750 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3751 player->getPitch());
3757 if (sky->getCloudsVisible()) {
3758 clouds->setVisible(true);
3759 clouds->step(dtime);
3760 // camera->getPosition is not enough for 3rd person views
3761 v3f camera_node_position = camera->getCameraNode()->getPosition();
3762 v3s16 camera_offset = camera->getOffset();
3763 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3764 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3765 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3766 clouds->update(camera_node_position,
3767 sky->getCloudColor());
3768 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3769 // if inside clouds, and fog enabled, use that as sky
3771 video::SColor clouds_dark = clouds->getColor()
3772 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3773 sky->overrideColors(clouds_dark, clouds->getColor());
3774 sky->setInClouds(true);
3775 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3776 // do not draw clouds after all
3777 clouds->setVisible(false);
3780 clouds->setVisible(false);
3787 client->getParticleManager()->step(dtime);
3793 if (m_cache_enable_fog) {
3796 video::EFT_FOG_LINEAR,
3797 runData.fog_range * m_cache_fog_start,
3798 runData.fog_range * 1.0,
3806 video::EFT_FOG_LINEAR,
3816 Get chat messages from client
3819 v2u32 screensize = driver->getScreenSize();
3821 updateChat(dtime, screensize);
3827 if (player->getWieldIndex() != runData.new_playeritem)
3828 client->setPlayerItem(runData.new_playeritem);
3830 if (client->updateWieldedItem()) {
3831 // Update wielded tool
3832 ItemStack selected_item, hand_item;
3833 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3834 camera->wield(tool_item);
3838 Update block draw list every 200ms or when camera direction has
3841 runData.update_draw_list_timer += dtime;
3843 v3f camera_direction = camera->getDirection();
3844 if (runData.update_draw_list_timer >= 0.2
3845 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3846 || m_camera_offset_changed) {
3847 runData.update_draw_list_timer = 0;
3848 client->getEnv().getClientMap().updateDrawList();
3849 runData.update_draw_list_last_cam_dir = camera_direction;
3852 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3855 make sure menu is on top
3856 1. Delete formspec menu reference if menu was removed
3857 2. Else, make sure formspec menu is on top
3859 auto formspec = m_game_ui->getFormspecGUI();
3860 do { // breakable. only runs for one iteration
3864 if (formspec->getReferenceCount() == 1) {
3865 m_game_ui->deleteFormspec();
3869 auto &loc = formspec->getFormspecLocation();
3870 if (loc.type == InventoryLocation::NODEMETA) {
3871 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3872 if (!meta || meta->getString("formspec").empty()) {
3873 formspec->quitMenu();
3879 guiroot->bringToFront(formspec);
3885 const video::SColor &skycolor = sky->getSkyColor();
3887 TimeTaker tt_draw("Draw scene");
3888 driver->beginScene(true, true, skycolor);
3890 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3891 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3892 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3893 bool draw_crosshair = (
3894 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3895 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3896 #ifdef HAVE_TOUCHSCREENGUI
3898 draw_crosshair = !g_settings->getBool("touchtarget");
3899 } catch (SettingNotFoundException) {
3902 RenderingEngine::draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3903 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3908 if (m_game_ui->m_flags.show_profiler_graph)
3909 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3914 if (runData.damage_flash > 0.0f) {
3915 video::SColor color(runData.damage_flash, 180, 0, 0);
3916 driver->draw2DRectangle(color,
3917 core::rect<s32>(0, 0, screensize.X, screensize.Y),
3920 runData.damage_flash -= 384.0f * dtime;
3926 if (player->hurt_tilt_timer > 0.0f) {
3927 player->hurt_tilt_timer -= dtime * 6.0f;
3929 if (player->hurt_tilt_timer < 0.0f)
3930 player->hurt_tilt_strength = 0.0f;
3934 Update minimap pos and rotation
3936 if (mapper && m_game_ui->m_flags.show_hud) {
3937 mapper->setPos(floatToInt(player->getPosition(), BS));
3938 mapper->setAngle(player->getYaw());
3944 if (++m_reset_HW_buffer_counter > 500) {
3946 Periodically remove all mesh HW buffers.
3948 Work around for a quirk in Irrlicht where a HW buffer is only
3949 released after 20000 iterations (triggered from endScene()).
3951 Without this, all loaded but unused meshes will retain their HW
3952 buffers for at least 5 minutes, at which point looking up the HW buffers
3953 becomes a bottleneck and the framerate drops (as much as 30%).
3955 Tests showed that numbers between 50 and 1000 are good, so picked 500.
3956 There are no other public Irrlicht APIs that allow interacting with the
3957 HW buffers without tracking the status of every individual mesh.
3959 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
3961 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
3962 driver->removeAllHardwareBuffers();
3963 m_reset_HW_buffer_counter = 0;
3967 stats->drawtime = tt_draw.stop(true);
3968 g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
3969 g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
3972 /* Log times and stuff for visualization */
3973 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
3975 Profiler::GraphValues values;
3976 g_profiler->graphGet(values);
3982 /****************************************************************************
3984 ****************************************************************************/
3986 /* On some computers framerate doesn't seem to be automatically limited
3988 inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
3990 // not using getRealTime is necessary for wine
3991 device->getTimer()->tick(); // Maker sure device time is up-to-date
3992 u32 time = device->getTimer()->getTime();
3993 u32 last_time = fps_timings->last_time;
3995 if (time > last_time) // Make sure time hasn't overflowed
3996 fps_timings->busy_time = time - last_time;
3998 fps_timings->busy_time = 0;
4000 u32 frametime_min = 1000 / (
4001 device->isWindowFocused() && !g_menumgr.pausesGame()
4002 ? g_settings->getFloat("fps_max")
4003 : g_settings->getFloat("fps_max_unfocused"));
4005 if (fps_timings->busy_time < frametime_min) {
4006 fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
4007 device->sleep(fps_timings->sleep_time);
4009 fps_timings->sleep_time = 0;
4012 /* Get the new value of the device timer. Note that device->sleep() may
4013 * not sleep for the entire requested time as sleep may be interrupted and
4014 * therefore it is arguably more accurate to get the new time from the
4015 * device rather than calculating it by adding sleep_time to time.
4018 device->getTimer()->tick(); // Update device timer
4019 time = device->getTimer()->getTime();
4021 if (time > last_time) // Make sure last_time hasn't overflowed
4022 *dtime = (time - last_time) / 1000.0;
4026 fps_timings->last_time = time;
4029 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4031 const wchar_t *wmsg = wgettext(msg);
4032 RenderingEngine::draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4037 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4039 ((Game *)data)->readSettings();
4042 void Game::readSettings()
4044 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4045 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4046 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4047 m_cache_enable_particles = g_settings->getBool("enable_particles");
4048 m_cache_enable_fog = g_settings->getBool("enable_fog");
4049 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
4050 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4051 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
4053 m_cache_enable_noclip = g_settings->getBool("noclip");
4054 m_cache_enable_free_move = g_settings->getBool("free_move");
4056 m_cache_fog_start = g_settings->getFloat("fog_start");
4058 m_cache_cam_smoothing = 0;
4059 if (g_settings->getBool("cinematic"))
4060 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4062 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4064 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4065 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4066 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4068 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4071 /****************************************************************************/
4072 /****************************************************************************
4074 ****************************************************************************/
4075 /****************************************************************************/
4077 void Game::extendedResourceCleanup()
4079 // Extended resource accounting
4080 infostream << "Irrlicht resources after cleanup:" << std::endl;
4081 infostream << "\tRemaining meshes : "
4082 << RenderingEngine::get_mesh_cache()->getMeshCount() << std::endl;
4083 infostream << "\tRemaining textures : "
4084 << driver->getTextureCount() << std::endl;
4086 for (unsigned int i = 0; i < driver->getTextureCount(); i++) {
4087 irr::video::ITexture *texture = driver->getTextureByIndex(i);
4088 infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str()
4092 clearTextureNameCache();
4093 infostream << "\tRemaining materials: "
4094 << driver-> getMaterialRendererCount()
4095 << " (note: irrlicht doesn't support removing renderers)" << std::endl;
4098 void Game::showDeathFormspec()
4100 static std::string formspec_str =
4101 std::string("formspec_version[1]") +
4103 "bgcolor[#320000b4;true]"
4104 "label[4.85,1.35;" + gettext("You died") + "]"
4105 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4109 /* Note: FormspecFormSource and LocalFormspecHandler *
4110 * are deleted by guiFormSpecMenu */
4111 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4112 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4114 auto *&formspec = m_game_ui->getFormspecGUI();
4115 GUIFormSpecMenu::create(formspec, client, &input->joystick,
4116 fs_src, txt_dst, client->getFormspecPrepend(), sound);
4117 formspec->setFocus("btn_respawn");
4120 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4121 void Game::showPauseMenu()
4124 static const std::string control_text = strgettext("Default Controls:\n"
4125 "No menu visible:\n"
4126 "- single tap: button activate\n"
4127 "- double tap: place/use\n"
4128 "- slide finger: look around\n"
4129 "Menu/Inventory visible:\n"
4130 "- double tap (outside):\n"
4132 "- touch stack, touch slot:\n"
4134 "- touch&drag, tap 2nd finger\n"
4135 " --> place single item to slot\n"
4138 static const std::string control_text_template = strgettext("Controls:\n"
4139 "- %s: move forwards\n"
4140 "- %s: move backwards\n"
4142 "- %s: move right\n"
4143 "- %s: jump/climb up\n"
4146 "- %s: sneak/climb down\n"
4149 "- Mouse: turn/look\n"
4150 "- Mouse wheel: select item\n"
4154 char control_text_buf[600];
4156 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4157 GET_KEY_NAME(keymap_forward),
4158 GET_KEY_NAME(keymap_backward),
4159 GET_KEY_NAME(keymap_left),
4160 GET_KEY_NAME(keymap_right),
4161 GET_KEY_NAME(keymap_jump),
4162 GET_KEY_NAME(keymap_dig),
4163 GET_KEY_NAME(keymap_place),
4164 GET_KEY_NAME(keymap_sneak),
4165 GET_KEY_NAME(keymap_drop),
4166 GET_KEY_NAME(keymap_inventory),
4167 GET_KEY_NAME(keymap_chat)
4170 std::string control_text = std::string(control_text_buf);
4171 str_formspec_escape(control_text);
4174 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4175 std::ostringstream os;
4177 os << "formspec_version[1]" << SIZE_TAG
4178 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4179 << strgettext("Continue") << "]";
4181 if (!simple_singleplayer_mode) {
4182 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4183 << strgettext("Change Password") << "]";
4185 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4190 if (g_settings->getBool("enable_sound")) {
4191 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4192 << strgettext("Sound Volume") << "]";
4195 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4196 << strgettext("Change Keys") << "]";
4198 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4199 << strgettext("Exit to Menu") << "]";
4200 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4201 << strgettext("Exit to OS") << "]"
4202 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4203 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4205 << strgettext("Game info:") << "\n";
4206 const std::string &address = client->getAddressName();
4207 static const std::string mode = strgettext("- Mode: ");
4208 if (!simple_singleplayer_mode) {
4209 Address serverAddress = client->getServerAddress();
4210 if (!address.empty()) {
4211 os << mode << strgettext("Remote server") << "\n"
4212 << strgettext("- Address: ") << address;
4214 os << mode << strgettext("Hosting server");
4216 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4218 os << mode << strgettext("Singleplayer") << "\n";
4220 if (simple_singleplayer_mode || address.empty()) {
4221 static const std::string on = strgettext("On");
4222 static const std::string off = strgettext("Off");
4223 const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
4224 const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
4225 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4226 os << strgettext("- Damage: ") << damage << "\n"
4227 << strgettext("- Creative Mode: ") << creative << "\n";
4228 if (!simple_singleplayer_mode) {
4229 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4230 //~ PvP = Player versus Player
4231 os << strgettext("- PvP: ") << pvp << "\n"
4232 << strgettext("- Public: ") << announced << "\n";
4233 std::string server_name = g_settings->get("server_name");
4234 str_formspec_escape(server_name);
4235 if (announced == on && !server_name.empty())
4236 os << strgettext("- Server Name: ") << server_name;
4243 /* Note: FormspecFormSource and LocalFormspecHandler *
4244 * are deleted by guiFormSpecMenu */
4245 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4246 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4248 auto *&formspec = m_game_ui->getFormspecGUI();
4249 GUIFormSpecMenu::create(formspec, client, &input->joystick,
4250 fs_src, txt_dst, client->getFormspecPrepend(), sound);
4251 formspec->setFocus("btn_continue");
4252 formspec->doPause = true;
4255 /****************************************************************************/
4256 /****************************************************************************
4257 extern function for launching the game
4258 ****************************************************************************/
4259 /****************************************************************************/
4261 void the_game(bool *kill,
4262 InputHandler *input,
4263 const GameStartData &start_data,
4264 std::string &error_message,
4265 ChatBackend &chat_backend,
4266 bool *reconnect_requested) // Used for local game
4270 /* Make a copy of the server address because if a local singleplayer server
4271 * is created then this is updated and we don't want to change the value
4272 * passed to us by the calling function
4277 if (game.startup(kill, input, start_data, error_message,
4278 reconnect_requested, &chat_backend)) {
4282 } catch (SerializationError &e) {
4283 error_message = std::string("A serialization error occurred:\n")
4284 + e.what() + "\n\nThe server is probably "
4285 " running a different version of " PROJECT_NAME_C ".";
4286 errorstream << error_message << std::endl;
4287 } catch (ServerError &e) {
4288 error_message = e.what();
4289 errorstream << "ServerError: " << error_message << std::endl;
4290 } catch (ModError &e) {
4291 error_message = std::string("ModError: ") + e.what() +
4292 strgettext("\nCheck debug.txt for details.");
4293 errorstream << error_message << std::endl;