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 virtual void onSetConstants(video::IMaterialRendererServices *services,
485 video::SColor bgcolor = m_sky->getBgColor();
486 video::SColorf bgcolorf(bgcolor);
487 float bgcolorfa[4] = {
493 m_sky_bg_color.set(bgcolorfa, services);
496 float fog_distance = 10000 * BS;
498 if (m_fog_enabled && !*m_force_fog_off)
499 fog_distance = *m_fog_range;
501 m_fog_distance.set(&fog_distance, services);
503 u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
504 video::SColorf sunlight;
505 get_sunlight_color(&sunlight, daynight_ratio);
510 m_day_light.set(dnc, services);
512 video::SColorf star_color = m_sky->getCurrentStarColor();
513 float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a};
514 m_star_color.set(clr, services);
516 u32 animation_timer = porting::getTimeMs() % 1000000;
517 float animation_timer_f = (float)animation_timer / 100000.f;
518 m_animation_timer_vertex.set(&animation_timer_f, services);
519 m_animation_timer_pixel.set(&animation_timer_f, services);
521 float eye_position_array[3];
522 v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
523 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
524 eye_position_array[0] = epos.X;
525 eye_position_array[1] = epos.Y;
526 eye_position_array[2] = epos.Z;
528 epos.getAs3Values(eye_position_array);
530 m_eye_position_pixel.set(eye_position_array, services);
531 m_eye_position_vertex.set(eye_position_array, services);
533 if (m_client->getMinimap()) {
534 float minimap_yaw_array[3];
535 v3f minimap_yaw = m_client->getMinimap()->getYawVec();
536 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
537 minimap_yaw_array[0] = minimap_yaw.X;
538 minimap_yaw_array[1] = minimap_yaw.Y;
539 minimap_yaw_array[2] = minimap_yaw.Z;
541 minimap_yaw.getAs3Values(minimap_yaw_array);
543 m_minimap_yaw.set(minimap_yaw_array, services);
546 float camera_offset_array[3];
547 v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
548 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
549 camera_offset_array[0] = offset.X;
550 camera_offset_array[1] = offset.Y;
551 camera_offset_array[2] = offset.Z;
553 offset.getAs3Values(camera_offset_array);
555 m_camera_offset_pixel.set(camera_offset_array, services);
556 m_camera_offset_vertex.set(camera_offset_array, services);
558 SamplerLayer_t base_tex = 0;
559 m_base_texture.set(&base_tex, services);
564 class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
567 bool *m_force_fog_off;
570 std::vector<GameGlobalShaderConstantSetter *> created_nosky;
572 GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
573 f32 *fog_range, Client *client) :
575 m_force_fog_off(force_fog_off),
576 m_fog_range(fog_range),
580 void setSky(Sky *sky) {
582 for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
583 ggscs->setSky(m_sky);
585 created_nosky.clear();
588 virtual IShaderConstantSetter* create()
590 GameGlobalShaderConstantSetter *scs = new GameGlobalShaderConstantSetter(
591 m_sky, m_force_fog_off, m_fog_range, m_client);
593 created_nosky.push_back(scs);
599 #define SIZE_TAG "size[11,5.5]"
601 #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
604 /****************************************************************************
605 ****************************************************************************/
607 const float object_hit_delay = 0.2;
610 u32 last_time, busy_time, sleep_time;
614 /* The reason the following structs are not anonymous structs within the
615 * class is that they are not used by the majority of member functions and
616 * many functions that do require objects of thse types do not modify them
617 * (so they can be passed as a const qualified parameter)
623 PointedThing pointed_old;
626 bool btn_down_for_dig;
628 bool digging_blocked;
629 bool reset_jump_timer;
630 float nodig_delay_timer;
632 float dig_time_complete;
633 float repeat_place_timer;
634 float object_hit_delay_timer;
635 float time_from_last_punch;
636 ClientActiveObject *selected_object;
640 float update_draw_list_timer;
644 v3f update_draw_list_last_cam_dir;
646 float time_of_day_smooth;
651 struct ClientEventHandler
653 void (Game::*handler)(ClientEvent *, CameraOrientation *);
656 /****************************************************************************
658 ****************************************************************************/
660 /* This is not intended to be a public class. If a public class becomes
661 * desirable then it may be better to create another 'wrapper' class that
662 * hides most of the stuff in this class (nothing in this class is required
663 * by any other file) but exposes the public methods/data only.
670 bool startup(bool *kill,
672 const GameStartData &game_params,
673 std::string &error_message,
675 ChatBackend *chat_backend);
682 void extendedResourceCleanup();
684 // Basic initialisation
685 bool init(const std::string &map_dir, const std::string &address,
686 u16 port, const SubgameSpec &gamespec);
688 bool createSingleplayerServer(const std::string &map_dir,
689 const SubgameSpec &gamespec, u16 port);
692 bool createClient(const GameStartData &start_data);
696 bool connectToServer(const GameStartData &start_data,
697 bool *connect_ok, bool *aborted);
698 bool getServerContent(bool *aborted);
702 void updateInteractTimers(f32 dtime);
703 bool checkConnection();
704 bool handleCallbacks();
705 void processQueues();
706 void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
707 void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
708 void updateProfilerGraphs(ProfilerGraph *graph);
711 void processUserInput(f32 dtime);
712 void processKeyInput();
713 void processItemSelection(u16 *new_playeritem);
715 void dropSelectedItem(bool single_item = false);
716 void openInventory();
717 void openConsole(float scale, const wchar_t *line=NULL);
718 void toggleFreeMove();
719 void toggleFreeMoveAlt();
720 void togglePitchMove();
723 void toggleCinematic();
724 void toggleAutoforward();
726 void toggleMinimap(bool shift_pressed);
729 void toggleUpdateCamera();
731 void increaseViewRange();
732 void decreaseViewRange();
733 void toggleFullViewRange();
734 void checkZoomEnabled();
736 void updateCameraDirection(CameraOrientation *cam, float dtime);
737 void updateCameraOrientation(CameraOrientation *cam, float dtime);
738 void updatePlayerControl(const CameraOrientation &cam);
739 void step(f32 *dtime);
740 void processClientEvents(CameraOrientation *cam);
741 void updateCamera(u32 busy_time, f32 dtime);
742 void updateSound(f32 dtime);
743 void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug);
745 * Returns the object or node the player is pointing at.
746 * Also updates the selected thing in the Hud.
748 * @param[in] shootline the shootline, starting from
749 * the camera position. This also gives the maximal distance
751 * @param[in] liquids_pointable if false, liquids are ignored
752 * @param[in] look_for_object if false, objects are ignored
753 * @param[in] camera_offset offset of the camera
754 * @param[out] selected_object the selected object or
757 PointedThing updatePointedThing(
758 const core::line3d<f32> &shootline, bool liquids_pointable,
759 bool look_for_object, const v3s16 &camera_offset);
760 void handlePointingAtNothing(const ItemStack &playerItem);
761 void handlePointingAtNode(const PointedThing &pointed,
762 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
763 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
764 const v3f &player_position, bool show_debug);
765 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
766 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
767 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
768 const CameraOrientation &cam);
771 void limitFps(FpsControl *fps_timings, f32 *dtime);
773 void showOverlayMessage(const char *msg, float dtime, int percent,
774 bool draw_clouds = true);
776 static void settingChangedCallback(const std::string &setting_name, void *data);
779 inline bool isKeyDown(GameKeyType k)
781 return input->isKeyDown(k);
783 inline bool wasKeyDown(GameKeyType k)
785 return input->wasKeyDown(k);
787 inline bool wasKeyPressed(GameKeyType k)
789 return input->wasKeyPressed(k);
791 inline bool wasKeyReleased(GameKeyType k)
793 return input->wasKeyReleased(k);
797 void handleAndroidChatInput();
802 bool force_fog_off = false;
803 bool disable_camera_update = false;
806 void showDeathFormspec();
807 void showPauseMenu();
809 // ClientEvent handlers
810 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
811 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
812 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
813 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
814 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
815 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
816 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
817 CameraOrientation *cam);
818 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
819 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
820 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
821 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
822 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
823 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
824 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
825 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
826 CameraOrientation *cam);
827 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
829 void updateChat(f32 dtime, const v2u32 &screensize);
831 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
832 const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
833 const NodeMetadata *meta);
834 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
836 InputHandler *input = nullptr;
838 Client *client = nullptr;
839 Server *server = nullptr;
841 IWritableTextureSource *texture_src = nullptr;
842 IWritableShaderSource *shader_src = nullptr;
844 // When created, these will be filled with data received from the server
845 IWritableItemDefManager *itemdef_manager = nullptr;
846 NodeDefManager *nodedef_manager = nullptr;
848 GameOnDemandSoundFetcher soundfetcher; // useful when testing
849 ISoundManager *sound = nullptr;
850 bool sound_is_dummy = false;
851 SoundMaker *soundmaker = nullptr;
853 ChatBackend *chat_backend = nullptr;
854 LogOutputBuffer m_chat_log_buf;
856 EventManager *eventmgr = nullptr;
857 QuicktuneShortcutter *quicktune = nullptr;
858 bool registration_confirmation_shown = false;
860 std::unique_ptr<GameUI> m_game_ui;
861 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
862 MapDrawControl *draw_control = nullptr;
863 Camera *camera = nullptr;
864 Clouds *clouds = nullptr; // Free using ->Drop()
865 Sky *sky = nullptr; // Free using ->Drop()
867 Minimap *mapper = nullptr;
873 This class does take ownership/responsibily for cleaning up etc of any of
874 these items (e.g. device)
876 IrrlichtDevice *device;
877 video::IVideoDriver *driver;
878 scene::ISceneManager *smgr;
880 std::string *error_message;
881 bool *reconnect_requested;
882 scene::ISceneNode *skybox;
884 bool simple_singleplayer_mode;
887 /* Pre-calculated values
889 int crack_animation_length;
891 IntervalLimiter profiler_interval;
894 * TODO: Local caching of settings is not optimal and should at some stage
895 * be updated to use a global settings object for getting thse values
896 * (as opposed to the this local caching). This can be addressed in
899 bool m_cache_doubletap_jump;
900 bool m_cache_enable_clouds;
901 bool m_cache_enable_joysticks;
902 bool m_cache_enable_particles;
903 bool m_cache_enable_fog;
904 bool m_cache_enable_noclip;
905 bool m_cache_enable_free_move;
906 f32 m_cache_mouse_sensitivity;
907 f32 m_cache_joystick_frustum_sensitivity;
908 f32 m_repeat_place_time;
909 f32 m_cache_cam_smoothing;
910 f32 m_cache_fog_start;
912 bool m_invert_mouse = false;
913 bool m_first_loop_after_window_activation = false;
914 bool m_camera_offset_changed = false;
916 bool m_does_lost_focus_pause_game = false;
918 int m_reset_HW_buffer_counter = 0;
920 bool m_cache_hold_aux1;
921 bool m_android_chat_open;
926 m_chat_log_buf(g_logger),
927 m_game_ui(new GameUI())
929 g_settings->registerChangedCallback("doubletap_jump",
930 &settingChangedCallback, this);
931 g_settings->registerChangedCallback("enable_clouds",
932 &settingChangedCallback, this);
933 g_settings->registerChangedCallback("doubletap_joysticks",
934 &settingChangedCallback, this);
935 g_settings->registerChangedCallback("enable_particles",
936 &settingChangedCallback, this);
937 g_settings->registerChangedCallback("enable_fog",
938 &settingChangedCallback, this);
939 g_settings->registerChangedCallback("mouse_sensitivity",
940 &settingChangedCallback, this);
941 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
942 &settingChangedCallback, this);
943 g_settings->registerChangedCallback("repeat_place_time",
944 &settingChangedCallback, this);
945 g_settings->registerChangedCallback("noclip",
946 &settingChangedCallback, this);
947 g_settings->registerChangedCallback("free_move",
948 &settingChangedCallback, this);
949 g_settings->registerChangedCallback("cinematic",
950 &settingChangedCallback, this);
951 g_settings->registerChangedCallback("cinematic_camera_smoothing",
952 &settingChangedCallback, this);
953 g_settings->registerChangedCallback("camera_smoothing",
954 &settingChangedCallback, this);
959 m_cache_hold_aux1 = false; // This is initialised properly later
966 /****************************************************************************
968 ****************************************************************************/
977 delete server; // deleted first to stop all server threads
985 delete nodedef_manager;
986 delete itemdef_manager;
989 extendedResourceCleanup();
991 g_settings->deregisterChangedCallback("doubletap_jump",
992 &settingChangedCallback, this);
993 g_settings->deregisterChangedCallback("enable_clouds",
994 &settingChangedCallback, this);
995 g_settings->deregisterChangedCallback("enable_particles",
996 &settingChangedCallback, this);
997 g_settings->deregisterChangedCallback("enable_fog",
998 &settingChangedCallback, this);
999 g_settings->deregisterChangedCallback("mouse_sensitivity",
1000 &settingChangedCallback, this);
1001 g_settings->deregisterChangedCallback("repeat_place_time",
1002 &settingChangedCallback, this);
1003 g_settings->deregisterChangedCallback("noclip",
1004 &settingChangedCallback, this);
1005 g_settings->deregisterChangedCallback("free_move",
1006 &settingChangedCallback, this);
1007 g_settings->deregisterChangedCallback("cinematic",
1008 &settingChangedCallback, this);
1009 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
1010 &settingChangedCallback, this);
1011 g_settings->deregisterChangedCallback("camera_smoothing",
1012 &settingChangedCallback, this);
1015 bool Game::startup(bool *kill,
1016 InputHandler *input,
1017 const GameStartData &start_data,
1018 std::string &error_message,
1020 ChatBackend *chat_backend)
1024 this->device = RenderingEngine::get_raw_device();
1026 this->error_message = &error_message;
1027 this->reconnect_requested = reconnect;
1028 this->input = input;
1029 this->chat_backend = chat_backend;
1030 this->simple_singleplayer_mode = start_data.isSinglePlayer();
1032 input->keycache.populate();
1034 driver = device->getVideoDriver();
1035 smgr = RenderingEngine::get_scene_manager();
1037 RenderingEngine::get_scene_manager()->getParameters()->
1038 setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1041 runData = GameRunData();
1042 runData.time_from_last_punch = 10.0;
1044 m_game_ui->initFlags();
1046 m_invert_mouse = g_settings->getBool("invert_mouse");
1047 m_first_loop_after_window_activation = true;
1049 g_client_translations->clear();
1051 // address can change if simple_singleplayer_mode
1052 if (!init(start_data.world_spec.path, start_data.address,
1053 start_data.socket_port, start_data.game_spec))
1056 if (!createClient(start_data))
1059 RenderingEngine::initialize(client, hud);
1067 ProfilerGraph graph;
1068 RunStats stats = { 0 };
1069 CameraOrientation cam_view_target = { 0 };
1070 CameraOrientation cam_view = { 0 };
1071 FpsControl draw_times = { 0 };
1072 f32 dtime; // in seconds
1074 /* Clear the profiler */
1075 Profiler::GraphValues dummyvalues;
1076 g_profiler->graphGet(dummyvalues);
1078 draw_times.last_time = RenderingEngine::get_timer_time();
1080 set_light_table(g_settings->getFloat("display_gamma"));
1083 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1084 && client->checkPrivilege("fast");
1087 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1088 g_settings->getU16("screen_h"));
1090 while (RenderingEngine::run()
1091 && !(*kill || g_gamecallback->shutdown_requested
1092 || (server && server->isShutdownRequested()))) {
1094 const irr::core::dimension2d<u32> ¤t_screen_size =
1095 RenderingEngine::get_video_driver()->getScreenSize();
1096 // Verify if window size has changed and save it if it's the case
1097 // Ensure evaluating settings->getBool after verifying screensize
1098 // First condition is cheaper
1099 if (previous_screen_size != current_screen_size &&
1100 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1101 g_settings->getBool("autosave_screensize")) {
1102 g_settings->setU16("screen_w", current_screen_size.Width);
1103 g_settings->setU16("screen_h", current_screen_size.Height);
1104 previous_screen_size = current_screen_size;
1107 // Calculate dtime =
1108 // RenderingEngine::run() from this iteration
1109 // + Sleep time until the wanted FPS are reached
1110 limitFps(&draw_times, &dtime);
1112 // Prepare render data for next iteration
1114 updateStats(&stats, draw_times, dtime);
1115 updateInteractTimers(dtime);
1117 if (!checkConnection())
1119 if (!handleCallbacks())
1124 m_game_ui->clearInfoText();
1125 hud->resizeHotbar();
1127 updateProfilers(stats, draw_times, dtime);
1128 processUserInput(dtime);
1129 // Update camera before player movement to avoid camera lag of one frame
1130 updateCameraDirection(&cam_view_target, dtime);
1131 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1132 cam_view.camera_yaw) * m_cache_cam_smoothing;
1133 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1134 cam_view.camera_pitch) * m_cache_cam_smoothing;
1135 updatePlayerControl(cam_view);
1137 processClientEvents(&cam_view_target);
1138 updateCamera(draw_times.busy_time, dtime);
1140 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
1141 m_game_ui->m_flags.show_debug);
1142 updateFrame(&graph, &stats, dtime, cam_view);
1143 updateProfilerGraphs(&graph);
1145 // Update if minimap has been disabled by the server
1146 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1148 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1155 void Game::shutdown()
1157 RenderingEngine::finalize();
1158 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1159 if (g_settings->get("3d_mode") == "pageflip") {
1160 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1163 auto formspec = m_game_ui->getFormspecGUI();
1165 formspec->quitMenu();
1167 #ifdef HAVE_TOUCHSCREENGUI
1168 g_touchscreengui->hide();
1171 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1176 if (gui_chat_console)
1177 gui_chat_console->drop();
1183 while (g_menumgr.menuCount() > 0) {
1184 g_menumgr.m_stack.front()->setVisible(false);
1185 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1188 m_game_ui->deleteFormspec();
1190 chat_backend->addMessage(L"", L"# Disconnected.");
1191 chat_backend->addMessage(L"", L"");
1192 m_chat_log_buf.clear();
1196 while (!client->isShutdown()) {
1197 assert(texture_src != NULL);
1198 assert(shader_src != NULL);
1199 texture_src->processQueue();
1200 shader_src->processQueue();
1207 /****************************************************************************/
1208 /****************************************************************************
1210 ****************************************************************************/
1211 /****************************************************************************/
1214 const std::string &map_dir,
1215 const std::string &address,
1217 const SubgameSpec &gamespec)
1219 texture_src = createTextureSource();
1221 showOverlayMessage(N_("Loading..."), 0, 0);
1223 shader_src = createShaderSource();
1225 itemdef_manager = createItemDefManager();
1226 nodedef_manager = createNodeDefManager();
1228 eventmgr = new EventManager();
1229 quicktune = new QuicktuneShortcutter();
1231 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1232 && eventmgr && quicktune))
1238 // Create a server if not connecting to an existing one
1239 if (address.empty()) {
1240 if (!createSingleplayerServer(map_dir, gamespec, port))
1247 bool Game::initSound()
1250 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1251 infostream << "Attempting to use OpenAL audio" << std::endl;
1252 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1254 infostream << "Failed to initialize OpenAL audio" << std::endl;
1256 infostream << "Sound disabled." << std::endl;
1260 infostream << "Using dummy audio." << std::endl;
1261 sound = &dummySoundManager;
1262 sound_is_dummy = true;
1265 soundmaker = new SoundMaker(sound, nodedef_manager);
1269 soundmaker->registerReceiver(eventmgr);
1274 bool Game::createSingleplayerServer(const std::string &map_dir,
1275 const SubgameSpec &gamespec, u16 port)
1277 showOverlayMessage(N_("Creating server..."), 0, 5);
1279 std::string bind_str = g_settings->get("bind_address");
1280 Address bind_addr(0, 0, 0, 0, port);
1282 if (g_settings->getBool("ipv6_server")) {
1283 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1287 bind_addr.Resolve(bind_str.c_str());
1288 } catch (ResolveError &e) {
1289 infostream << "Resolving bind address \"" << bind_str
1290 << "\" failed: " << e.what()
1291 << " -- Listening on all addresses." << std::endl;
1294 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1295 *error_message = "Unable to listen on " +
1296 bind_addr.serializeString() +
1297 " because IPv6 is disabled";
1298 errorstream << *error_message << std::endl;
1302 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1303 false, nullptr, error_message);
1309 bool Game::createClient(const GameStartData &start_data)
1311 showOverlayMessage(N_("Creating client..."), 0, 10);
1313 draw_control = new MapDrawControl;
1317 bool could_connect, connect_aborted;
1318 #ifdef HAVE_TOUCHSCREENGUI
1319 if (g_touchscreengui) {
1320 g_touchscreengui->init(texture_src);
1321 g_touchscreengui->hide();
1324 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1327 if (!could_connect) {
1328 if (error_message->empty() && !connect_aborted) {
1329 // Should not happen if error messages are set properly
1330 *error_message = "Connection failed for unknown reason";
1331 errorstream << *error_message << std::endl;
1336 if (!getServerContent(&connect_aborted)) {
1337 if (error_message->empty() && !connect_aborted) {
1338 // Should not happen if error messages are set properly
1339 *error_message = "Connection failed for unknown reason";
1340 errorstream << *error_message << std::endl;
1345 GameGlobalShaderConstantSetterFactory *scsf = new GameGlobalShaderConstantSetterFactory(
1346 &m_flags.force_fog_off, &runData.fog_range, client);
1347 shader_src->addShaderConstantSetterFactory(scsf);
1349 // Update cached textures, meshes and materials
1350 client->afterContentReceived();
1354 camera = new Camera(*draw_control, client);
1355 if (!camera || !camera->successfullyCreated(*error_message))
1357 client->setCamera(camera);
1361 if (m_cache_enable_clouds) {
1362 clouds = new Clouds(smgr, -1, time(0));
1364 *error_message = "Memory allocation error (clouds)";
1365 errorstream << *error_message << std::endl;
1372 sky = new Sky(-1, texture_src, shader_src);
1374 skybox = NULL; // This is used/set later on in the main run loop
1377 *error_message = "Memory allocation error sky";
1378 errorstream << *error_message << std::endl;
1382 /* Pre-calculated values
1384 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1386 v2u32 size = t->getOriginalSize();
1387 crack_animation_length = size.Y / size.X;
1389 crack_animation_length = 5;
1395 /* Set window caption
1397 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1399 str += utf8_to_wide(g_version_hash);
1401 str += driver->getName();
1403 device->setWindowCaption(str.c_str());
1405 LocalPlayer *player = client->getEnv().getLocalPlayer();
1406 player->hurt_tilt_timer = 0;
1407 player->hurt_tilt_strength = 0;
1409 hud = new Hud(guienv, client, player, &player->inventory);
1412 *error_message = "Memory error: could not create HUD";
1413 errorstream << *error_message << std::endl;
1417 mapper = client->getMinimap();
1419 if (mapper && client->modsLoaded())
1420 client->getScript()->on_minimap_ready(mapper);
1425 bool Game::initGui()
1429 // Remove stale "recent" chat messages from previous connections
1430 chat_backend->clearRecentChat();
1432 // Make sure the size of the recent messages buffer is right
1433 chat_backend->applySettings();
1435 // Chat backend and console
1436 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1437 -1, chat_backend, client, &g_menumgr);
1438 if (!gui_chat_console) {
1439 *error_message = "Could not allocate memory for chat console";
1440 errorstream << *error_message << std::endl;
1444 #ifdef HAVE_TOUCHSCREENGUI
1446 if (g_touchscreengui)
1447 g_touchscreengui->show();
1454 bool Game::connectToServer(const GameStartData &start_data,
1455 bool *connect_ok, bool *connection_aborted)
1457 *connect_ok = false; // Let's not be overly optimistic
1458 *connection_aborted = false;
1459 bool local_server_mode = false;
1461 showOverlayMessage(N_("Resolving address..."), 0, 15);
1463 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1466 connect_address.Resolve(start_data.address.c_str());
1468 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1469 //connect_address.Resolve("localhost");
1470 if (connect_address.isIPv6()) {
1471 IPv6AddressBytes addr_bytes;
1472 addr_bytes.bytes[15] = 1;
1473 connect_address.setAddress(&addr_bytes);
1475 connect_address.setAddress(127, 0, 0, 1);
1477 local_server_mode = true;
1479 } catch (ResolveError &e) {
1480 *error_message = std::string("Couldn't resolve address: ") + e.what();
1481 errorstream << *error_message << std::endl;
1485 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1486 *error_message = "Unable to connect to " +
1487 connect_address.serializeString() +
1488 " because IPv6 is disabled";
1489 errorstream << *error_message << std::endl;
1493 client = new Client(start_data.name.c_str(),
1494 start_data.password, start_data.address,
1495 *draw_control, texture_src, shader_src,
1496 itemdef_manager, nodedef_manager, sound, eventmgr,
1497 connect_address.isIPv6(), m_game_ui.get());
1502 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1504 infostream << "Connecting to server at ";
1505 connect_address.print(&infostream);
1506 infostream << std::endl;
1508 client->connect(connect_address,
1509 simple_singleplayer_mode || local_server_mode);
1512 Wait for server to accept connection
1518 FpsControl fps_control = { 0 };
1520 f32 wait_time = 0; // in seconds
1522 fps_control.last_time = RenderingEngine::get_timer_time();
1524 while (RenderingEngine::run()) {
1526 limitFps(&fps_control, &dtime);
1528 // Update client and server
1529 client->step(dtime);
1532 server->step(dtime);
1535 if (client->getState() == LC_Init) {
1541 if (*connection_aborted)
1544 if (client->accessDenied()) {
1545 *error_message = "Access denied. Reason: "
1546 + client->accessDeniedReason();
1547 *reconnect_requested = client->reconnectRequested();
1548 errorstream << *error_message << std::endl;
1552 if (input->cancelPressed()) {
1553 *connection_aborted = true;
1554 infostream << "Connect aborted [Escape]" << std::endl;
1558 if (client->m_is_registration_confirmation_state) {
1559 if (registration_confirmation_shown) {
1560 // Keep drawing the GUI
1561 RenderingEngine::draw_menu_scene(guienv, dtime, true);
1563 registration_confirmation_shown = true;
1564 (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
1565 &g_menumgr, client, start_data.name, start_data.password,
1566 connection_aborted, texture_src))->drop();
1570 // Only time out if we aren't waiting for the server we started
1571 if (!start_data.isSinglePlayer() && wait_time > 10) {
1572 *error_message = "Connection timed out.";
1573 errorstream << *error_message << std::endl;
1578 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1581 } catch (con::PeerNotFoundException &e) {
1582 // TODO: Should something be done here? At least an info/error
1590 bool Game::getServerContent(bool *aborted)
1594 FpsControl fps_control = { 0 };
1595 f32 dtime; // in seconds
1597 fps_control.last_time = RenderingEngine::get_timer_time();
1599 while (RenderingEngine::run()) {
1601 limitFps(&fps_control, &dtime);
1603 // Update client and server
1604 client->step(dtime);
1607 server->step(dtime);
1610 if (client->mediaReceived() && client->itemdefReceived() &&
1611 client->nodedefReceived()) {
1616 if (!checkConnection())
1619 if (client->getState() < LC_Init) {
1620 *error_message = "Client disconnected";
1621 errorstream << *error_message << std::endl;
1625 if (input->cancelPressed()) {
1627 infostream << "Connect aborted [Escape]" << std::endl;
1634 if (!client->itemdefReceived()) {
1635 const wchar_t *text = wgettext("Item definitions...");
1637 RenderingEngine::draw_load_screen(text, guienv, texture_src,
1640 } else if (!client->nodedefReceived()) {
1641 const wchar_t *text = wgettext("Node definitions...");
1643 RenderingEngine::draw_load_screen(text, guienv, texture_src,
1647 std::stringstream message;
1648 std::fixed(message);
1649 message.precision(0);
1650 float receive = client->mediaReceiveProgress() * 100;
1651 message << gettext("Media...");
1653 message << " " << receive << "%";
1654 message.precision(2);
1656 if ((USE_CURL == 0) ||
1657 (!g_settings->getBool("enable_remote_media_server"))) {
1658 float cur = client->getCurRate();
1659 std::string cur_unit = gettext("KiB/s");
1663 cur_unit = gettext("MiB/s");
1666 message << " (" << cur << ' ' << cur_unit << ")";
1669 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1670 RenderingEngine::draw_load_screen(utf8_to_wide(message.str()), guienv,
1671 texture_src, dtime, progress);
1679 /****************************************************************************/
1680 /****************************************************************************
1682 ****************************************************************************/
1683 /****************************************************************************/
1685 inline void Game::updateInteractTimers(f32 dtime)
1687 if (runData.nodig_delay_timer >= 0)
1688 runData.nodig_delay_timer -= dtime;
1690 if (runData.object_hit_delay_timer >= 0)
1691 runData.object_hit_delay_timer -= dtime;
1693 runData.time_from_last_punch += dtime;
1697 /* returns false if game should exit, otherwise true
1699 inline bool Game::checkConnection()
1701 if (client->accessDenied()) {
1702 *error_message = "Access denied. Reason: "
1703 + client->accessDeniedReason();
1704 *reconnect_requested = client->reconnectRequested();
1705 errorstream << *error_message << std::endl;
1713 /* returns false if game should exit, otherwise true
1715 inline bool Game::handleCallbacks()
1717 if (g_gamecallback->disconnect_requested) {
1718 g_gamecallback->disconnect_requested = false;
1722 if (g_gamecallback->changepassword_requested) {
1723 (new GUIPasswordChange(guienv, guiroot, -1,
1724 &g_menumgr, client, texture_src))->drop();
1725 g_gamecallback->changepassword_requested = false;
1728 if (g_gamecallback->changevolume_requested) {
1729 (new GUIVolumeChange(guienv, guiroot, -1,
1730 &g_menumgr, texture_src))->drop();
1731 g_gamecallback->changevolume_requested = false;
1734 if (g_gamecallback->keyconfig_requested) {
1735 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1736 &g_menumgr, texture_src))->drop();
1737 g_gamecallback->keyconfig_requested = false;
1740 if (g_gamecallback->keyconfig_changed) {
1741 input->keycache.populate(); // update the cache with new settings
1742 g_gamecallback->keyconfig_changed = false;
1749 void Game::processQueues()
1751 texture_src->processQueue();
1752 itemdef_manager->processQueue(client);
1753 shader_src->processQueue();
1757 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1760 float profiler_print_interval =
1761 g_settings->getFloat("profiler_print_interval");
1762 bool print_to_log = true;
1764 if (profiler_print_interval == 0) {
1765 print_to_log = false;
1766 profiler_print_interval = 3;
1769 if (profiler_interval.step(dtime, profiler_print_interval)) {
1771 infostream << "Profiler:" << std::endl;
1772 g_profiler->print(infostream);
1775 m_game_ui->updateProfiler();
1776 g_profiler->clear();
1779 // Update update graphs
1780 g_profiler->graphAdd("Time non-rendering [ms]",
1781 draw_times.busy_time - stats.drawtime);
1783 g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time);
1784 g_profiler->graphAdd("FPS", 1.0f / dtime);
1787 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1794 /* Time average and jitter calculation
1796 jp = &stats->dtime_jitter;
1797 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1799 jitter = dtime - jp->avg;
1801 if (jitter > jp->max)
1804 jp->counter += dtime;
1806 if (jp->counter > 0.0) {
1808 jp->max_sample = jp->max;
1809 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1813 /* Busytime average and jitter calculation
1815 jp = &stats->busy_time_jitter;
1816 jp->avg = jp->avg + draw_times.busy_time * 0.02;
1818 jitter = draw_times.busy_time - jp->avg;
1820 if (jitter > jp->max)
1822 if (jitter < jp->min)
1825 jp->counter += dtime;
1827 if (jp->counter > 0.0) {
1829 jp->max_sample = jp->max;
1830 jp->min_sample = jp->min;
1838 /****************************************************************************
1840 ****************************************************************************/
1842 void Game::processUserInput(f32 dtime)
1844 // Reset input if window not active or some menu is active
1845 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1847 #ifdef HAVE_TOUCHSCREENGUI
1848 g_touchscreengui->hide();
1851 #ifdef HAVE_TOUCHSCREENGUI
1852 else if (g_touchscreengui) {
1853 /* on touchscreengui step may generate own input events which ain't
1854 * what we want in case we just did clear them */
1855 g_touchscreengui->step(dtime);
1859 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1860 gui_chat_console->closeConsoleAtOnce();
1863 // Input handler step() (used by the random input generator)
1867 auto formspec = m_game_ui->getFormspecGUI();
1869 formspec->getAndroidUIInput();
1871 handleAndroidChatInput();
1874 // Increase timer for double tap of "keymap_jump"
1875 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1876 runData.jump_timer += dtime;
1879 processItemSelection(&runData.new_playeritem);
1883 void Game::processKeyInput()
1885 if (wasKeyDown(KeyType::DROP)) {
1886 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1887 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1888 toggleAutoforward();
1889 } else if (wasKeyDown(KeyType::BACKWARD)) {
1890 if (g_settings->getBool("continuous_forward"))
1891 toggleAutoforward();
1892 } else if (wasKeyDown(KeyType::INVENTORY)) {
1894 } else if (input->cancelPressed()) {
1896 m_android_chat_open = false;
1898 if (!gui_chat_console->isOpenInhibited()) {
1901 } else if (wasKeyDown(KeyType::CHAT)) {
1902 openConsole(0.2, L"");
1903 } else if (wasKeyDown(KeyType::CMD)) {
1904 openConsole(0.2, L"/");
1905 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1906 if (client->modsLoaded())
1907 openConsole(0.2, L".");
1909 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1910 } else if (wasKeyDown(KeyType::CONSOLE)) {
1911 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1912 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1914 } else if (wasKeyDown(KeyType::JUMP)) {
1915 toggleFreeMoveAlt();
1916 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1918 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1920 } else if (wasKeyDown(KeyType::NOCLIP)) {
1923 } else if (wasKeyDown(KeyType::MUTE)) {
1924 if (g_settings->getBool("enable_sound")) {
1925 bool new_mute_sound = !g_settings->getBool("mute_sound");
1926 g_settings->setBool("mute_sound", new_mute_sound);
1928 m_game_ui->showTranslatedStatusText("Sound muted");
1930 m_game_ui->showTranslatedStatusText("Sound unmuted");
1932 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1934 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1935 if (g_settings->getBool("enable_sound")) {
1936 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1938 g_settings->setFloat("sound_volume", new_volume);
1939 const wchar_t *str = wgettext("Volume changed to %d%%");
1940 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1942 m_game_ui->showStatusText(buf);
1944 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1946 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1947 if (g_settings->getBool("enable_sound")) {
1948 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1950 g_settings->setFloat("sound_volume", new_volume);
1951 const wchar_t *str = wgettext("Volume changed to %d%%");
1952 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1954 m_game_ui->showStatusText(buf);
1956 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1959 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1960 || wasKeyDown(KeyType::DEC_VOLUME)) {
1961 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1963 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1965 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1966 client->makeScreenshot();
1967 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1968 m_game_ui->toggleHud();
1969 } else if (wasKeyDown(KeyType::MINIMAP)) {
1970 toggleMinimap(isKeyDown(KeyType::SNEAK));
1971 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1972 m_game_ui->toggleChat();
1973 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1975 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1976 toggleUpdateCamera();
1977 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1979 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1980 m_game_ui->toggleProfiler();
1981 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1982 increaseViewRange();
1983 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1984 decreaseViewRange();
1985 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1986 toggleFullViewRange();
1987 } else if (wasKeyDown(KeyType::ZOOM)) {
1989 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1991 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1993 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1995 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1999 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
2000 runData.reset_jump_timer = false;
2001 runData.jump_timer = 0.0f;
2004 if (quicktune->hasMessage()) {
2005 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2009 void Game::processItemSelection(u16 *new_playeritem)
2011 LocalPlayer *player = client->getEnv().getLocalPlayer();
2013 /* Item selection using mouse wheel
2015 *new_playeritem = player->getWieldIndex();
2017 s32 wheel = input->getMouseWheel();
2018 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2019 player->hud_hotbar_itemcount - 1);
2023 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2026 if (wasKeyDown(KeyType::HOTBAR_PREV))
2030 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2032 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2035 /* Item selection using hotbar slot keys
2037 for (u16 i = 0; i <= max_item; i++) {
2038 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2039 *new_playeritem = i;
2046 void Game::dropSelectedItem(bool single_item)
2048 IDropAction *a = new IDropAction();
2049 a->count = single_item ? 1 : 0;
2050 a->from_inv.setCurrentPlayer();
2051 a->from_list = "main";
2052 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2053 client->inventoryAction(a);
2057 void Game::openInventory()
2060 * Don't permit to open inventory is CAO or player doesn't exists.
2061 * This prevent showing an empty inventory at player load
2064 LocalPlayer *player = client->getEnv().getLocalPlayer();
2065 if (!player || !player->getCAO())
2068 infostream << "Game: Launching inventory" << std::endl;
2070 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2072 InventoryLocation inventoryloc;
2073 inventoryloc.setCurrentPlayer();
2075 if (!client->modsLoaded()
2076 || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2077 TextDest *txt_dst = new TextDestPlayerInventory(client);
2078 auto *&formspec = m_game_ui->updateFormspec("");
2079 GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
2080 txt_dst, client->getFormspecPrepend(), sound);
2082 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2087 void Game::openConsole(float scale, const wchar_t *line)
2089 assert(scale > 0.0f && scale <= 1.0f);
2092 porting::showInputDialog(gettext("ok"), "", "", 2);
2093 m_android_chat_open = true;
2095 if (gui_chat_console->isOpenInhibited())
2097 gui_chat_console->openConsole(scale);
2099 gui_chat_console->setCloseOnEnter(true);
2100 gui_chat_console->replaceAndAddToHistory(line);
2106 void Game::handleAndroidChatInput()
2108 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2109 std::string text = porting::getInputDialogValue();
2110 client->typeChatMessage(utf8_to_wide(text));
2111 m_android_chat_open = false;
2117 void Game::toggleFreeMove()
2119 bool free_move = !g_settings->getBool("free_move");
2120 g_settings->set("free_move", bool_to_cstr(free_move));
2123 if (client->checkPrivilege("fly")) {
2124 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2126 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2129 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2133 void Game::toggleFreeMoveAlt()
2135 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2138 runData.reset_jump_timer = true;
2142 void Game::togglePitchMove()
2144 bool pitch_move = !g_settings->getBool("pitch_move");
2145 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2148 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2150 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2155 void Game::toggleFast()
2157 bool fast_move = !g_settings->getBool("fast_move");
2158 bool has_fast_privs = client->checkPrivilege("fast");
2159 g_settings->set("fast_move", bool_to_cstr(fast_move));
2162 if (has_fast_privs) {
2163 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2165 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2168 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2172 m_cache_hold_aux1 = fast_move && has_fast_privs;
2177 void Game::toggleNoClip()
2179 bool noclip = !g_settings->getBool("noclip");
2180 g_settings->set("noclip", bool_to_cstr(noclip));
2183 if (client->checkPrivilege("noclip")) {
2184 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2186 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2189 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2193 void Game::toggleCinematic()
2195 bool cinematic = !g_settings->getBool("cinematic");
2196 g_settings->set("cinematic", bool_to_cstr(cinematic));
2199 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2201 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2204 // Autoforward by toggling continuous forward.
2205 void Game::toggleAutoforward()
2207 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2208 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2210 if (autorun_enabled)
2211 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2213 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2216 void Game::toggleMinimap(bool shift_pressed)
2218 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2222 mapper->toggleMinimapShape();
2226 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2228 // Not so satisying code to keep compatibility with old fixed mode system
2230 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2232 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2233 m_game_ui->m_flags.show_minimap = false;
2236 // If radar is disabled, try to find a non radar mode or fall back to 0
2237 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2238 while (mapper->getModeIndex() &&
2239 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2242 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2246 // End of 'not so satifying code'
2247 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2248 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2249 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2251 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2254 void Game::toggleFog()
2256 bool fog_enabled = g_settings->getBool("enable_fog");
2257 g_settings->setBool("enable_fog", !fog_enabled);
2259 m_game_ui->showTranslatedStatusText("Fog disabled");
2261 m_game_ui->showTranslatedStatusText("Fog enabled");
2265 void Game::toggleDebug()
2267 // Initial / 4x toggle: Chat only
2268 // 1x toggle: Debug text with chat
2269 // 2x toggle: Debug text with profiler graph
2270 // 3x toggle: Debug text and wireframe
2271 if (!m_game_ui->m_flags.show_debug) {
2272 m_game_ui->m_flags.show_debug = true;
2273 m_game_ui->m_flags.show_profiler_graph = false;
2274 draw_control->show_wireframe = false;
2275 m_game_ui->showTranslatedStatusText("Debug info shown");
2276 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2277 m_game_ui->m_flags.show_profiler_graph = true;
2278 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2279 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2280 m_game_ui->m_flags.show_profiler_graph = false;
2281 draw_control->show_wireframe = true;
2282 m_game_ui->showTranslatedStatusText("Wireframe shown");
2284 m_game_ui->m_flags.show_debug = false;
2285 m_game_ui->m_flags.show_profiler_graph = false;
2286 draw_control->show_wireframe = false;
2287 if (client->checkPrivilege("debug")) {
2288 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2290 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2296 void Game::toggleUpdateCamera()
2298 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2299 if (m_flags.disable_camera_update)
2300 m_game_ui->showTranslatedStatusText("Camera update disabled");
2302 m_game_ui->showTranslatedStatusText("Camera update enabled");
2306 void Game::increaseViewRange()
2308 s16 range = g_settings->getS16("viewing_range");
2309 s16 range_new = range + 10;
2313 if (range_new > 4000) {
2315 str = wgettext("Viewing range is at maximum: %d");
2316 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2318 m_game_ui->showStatusText(buf);
2321 str = wgettext("Viewing range changed to %d");
2322 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2324 m_game_ui->showStatusText(buf);
2326 g_settings->set("viewing_range", itos(range_new));
2330 void Game::decreaseViewRange()
2332 s16 range = g_settings->getS16("viewing_range");
2333 s16 range_new = range - 10;
2337 if (range_new < 20) {
2339 str = wgettext("Viewing range is at minimum: %d");
2340 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2342 m_game_ui->showStatusText(buf);
2344 str = wgettext("Viewing range changed to %d");
2345 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2347 m_game_ui->showStatusText(buf);
2349 g_settings->set("viewing_range", itos(range_new));
2353 void Game::toggleFullViewRange()
2355 draw_control->range_all = !draw_control->range_all;
2356 if (draw_control->range_all)
2357 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2359 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2363 void Game::checkZoomEnabled()
2365 LocalPlayer *player = client->getEnv().getLocalPlayer();
2366 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2367 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2371 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2373 if ((device->isWindowActive() && device->isWindowFocused()
2374 && !isMenuActive()) || input->isRandom()) {
2377 if (!input->isRandom()) {
2378 // Mac OSX gets upset if this is set every frame
2379 if (device->getCursorControl()->isVisible())
2380 device->getCursorControl()->setVisible(false);
2384 if (m_first_loop_after_window_activation) {
2385 m_first_loop_after_window_activation = false;
2387 input->setMousePos(driver->getScreenSize().Width / 2,
2388 driver->getScreenSize().Height / 2);
2390 updateCameraOrientation(cam, dtime);
2396 // Mac OSX gets upset if this is set every frame
2397 if (!device->getCursorControl()->isVisible())
2398 device->getCursorControl()->setVisible(true);
2401 m_first_loop_after_window_activation = true;
2406 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2408 #ifdef HAVE_TOUCHSCREENGUI
2409 if (g_touchscreengui) {
2410 cam->camera_yaw += g_touchscreengui->getYawChange();
2411 cam->camera_pitch = g_touchscreengui->getPitch();
2414 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2415 v2s32 dist = input->getMousePos() - center;
2417 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2421 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity;
2422 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity;
2424 if (dist.X != 0 || dist.Y != 0)
2425 input->setMousePos(center.X, center.Y);
2426 #ifdef HAVE_TOUCHSCREENGUI
2430 if (m_cache_enable_joysticks) {
2431 f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime;
2432 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2433 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2436 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2440 void Game::updatePlayerControl(const CameraOrientation &cam)
2442 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2444 // DO NOT use the isKeyDown method for the forward, backward, left, right
2445 // buttons, as the code that uses the controls needs to be able to
2446 // distinguish between the two in order to know when to use joysticks.
2448 PlayerControl control(
2449 input->isKeyDown(KeyType::FORWARD),
2450 input->isKeyDown(KeyType::BACKWARD),
2451 input->isKeyDown(KeyType::LEFT),
2452 input->isKeyDown(KeyType::RIGHT),
2453 isKeyDown(KeyType::JUMP),
2454 isKeyDown(KeyType::SPECIAL1),
2455 isKeyDown(KeyType::SNEAK),
2456 isKeyDown(KeyType::ZOOM),
2457 isKeyDown(KeyType::DIG),
2458 isKeyDown(KeyType::PLACE),
2461 input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE),
2462 input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE)
2465 u32 keypress_bits = (
2466 ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) |
2467 ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) |
2468 ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) |
2469 ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) |
2470 ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) |
2471 ( (u32)(isKeyDown(KeyType::SPECIAL1) & 0x1) << 5) |
2472 ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) |
2473 ( (u32)(isKeyDown(KeyType::DIG) & 0x1) << 7) |
2474 ( (u32)(isKeyDown(KeyType::PLACE) & 0x1) << 8) |
2475 ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9)
2479 /* For Android, simulate holding down AUX1 (fast move) if the user has
2480 * the fast_move setting toggled on. If there is an aux1 key defined for
2481 * Android then its meaning is inverted (i.e. holding aux1 means walk and
2484 if (m_cache_hold_aux1) {
2485 control.aux1 = control.aux1 ^ true;
2486 keypress_bits ^= ((u32)(1U << 5));
2490 LocalPlayer *player = client->getEnv().getLocalPlayer();
2492 // autojump if set: simulate "jump" key
2493 if (player->getAutojump()) {
2494 control.jump = true;
2495 keypress_bits |= 1U << 4;
2498 // autoforward if set: simulate "up" key
2499 if (player->getPlayerSettings().continuous_forward &&
2500 client->activeObjectsReceived() && !player->isDead()) {
2502 keypress_bits |= 1U << 0;
2505 client->setPlayerControl(control);
2506 player->keyPressed = keypress_bits;
2512 inline void Game::step(f32 *dtime)
2514 bool can_be_and_is_paused =
2515 (simple_singleplayer_mode && g_menumgr.pausesGame());
2517 if (can_be_and_is_paused) { // This is for a singleplayer server
2518 *dtime = 0; // No time passes
2521 server->step(*dtime);
2523 client->step(*dtime);
2527 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2528 {&Game::handleClientEvent_None},
2529 {&Game::handleClientEvent_PlayerDamage},
2530 {&Game::handleClientEvent_PlayerForceMove},
2531 {&Game::handleClientEvent_Deathscreen},
2532 {&Game::handleClientEvent_ShowFormSpec},
2533 {&Game::handleClientEvent_ShowLocalFormSpec},
2534 {&Game::handleClientEvent_HandleParticleEvent},
2535 {&Game::handleClientEvent_HandleParticleEvent},
2536 {&Game::handleClientEvent_HandleParticleEvent},
2537 {&Game::handleClientEvent_HudAdd},
2538 {&Game::handleClientEvent_HudRemove},
2539 {&Game::handleClientEvent_HudChange},
2540 {&Game::handleClientEvent_SetSky},
2541 {&Game::handleClientEvent_SetSun},
2542 {&Game::handleClientEvent_SetMoon},
2543 {&Game::handleClientEvent_SetStars},
2544 {&Game::handleClientEvent_OverrideDayNigthRatio},
2545 {&Game::handleClientEvent_CloudParams},
2548 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2550 FATAL_ERROR("ClientEvent type None received");
2553 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2555 if (client->modsLoaded())
2556 client->getScript()->on_damage_taken(event->player_damage.amount);
2558 // Damage flash and hurt tilt are not used at death
2559 if (client->getHP() > 0) {
2560 runData.damage_flash += 95.0f + 3.2f * event->player_damage.amount;
2561 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2563 LocalPlayer *player = client->getEnv().getLocalPlayer();
2565 player->hurt_tilt_timer = 1.5f;
2566 player->hurt_tilt_strength =
2567 rangelim(event->player_damage.amount / 4.0f, 1.0f, 4.0f);
2570 // Play damage sound
2571 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2574 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2576 cam->camera_yaw = event->player_force_move.yaw;
2577 cam->camera_pitch = event->player_force_move.pitch;
2580 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2582 // If client scripting is enabled, deathscreen is handled by CSM code in
2583 // builtin/client/init.lua
2584 if (client->modsLoaded())
2585 client->getScript()->on_death();
2587 showDeathFormspec();
2589 /* Handle visualization */
2590 LocalPlayer *player = client->getEnv().getLocalPlayer();
2591 runData.damage_flash = 0;
2592 player->hurt_tilt_timer = 0;
2593 player->hurt_tilt_strength = 0;
2596 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2598 if (event->show_formspec.formspec->empty()) {
2599 auto formspec = m_game_ui->getFormspecGUI();
2600 if (formspec && (event->show_formspec.formname->empty()
2601 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2602 formspec->quitMenu();
2605 FormspecFormSource *fs_src =
2606 new FormspecFormSource(*(event->show_formspec.formspec));
2607 TextDestPlayerInventory *txt_dst =
2608 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2610 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2611 GUIFormSpecMenu::create(formspec, client, &input->joystick,
2612 fs_src, txt_dst, client->getFormspecPrepend(), sound);
2615 delete event->show_formspec.formspec;
2616 delete event->show_formspec.formname;
2619 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2621 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2622 LocalFormspecHandler *txt_dst =
2623 new LocalFormspecHandler(*event->show_formspec.formname, client);
2624 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, &input->joystick,
2625 fs_src, txt_dst, client->getFormspecPrepend(), sound);
2627 delete event->show_formspec.formspec;
2628 delete event->show_formspec.formname;
2631 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2632 CameraOrientation *cam)
2634 LocalPlayer *player = client->getEnv().getLocalPlayer();
2635 client->getParticleManager()->handleParticleEvent(event, client, player);
2638 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2640 LocalPlayer *player = client->getEnv().getLocalPlayer();
2641 auto &hud_server_to_client = client->getHUDTranslationMap();
2643 u32 server_id = event->hudadd.server_id;
2644 // ignore if we already have a HUD with that ID
2645 auto i = hud_server_to_client.find(server_id);
2646 if (i != hud_server_to_client.end()) {
2647 delete event->hudadd.pos;
2648 delete event->hudadd.name;
2649 delete event->hudadd.scale;
2650 delete event->hudadd.text;
2651 delete event->hudadd.align;
2652 delete event->hudadd.offset;
2653 delete event->hudadd.world_pos;
2654 delete event->hudadd.size;
2655 delete event->hudadd.text2;
2659 HudElement *e = new HudElement;
2660 e->type = (HudElementType)event->hudadd.type;
2661 e->pos = *event->hudadd.pos;
2662 e->name = *event->hudadd.name;
2663 e->scale = *event->hudadd.scale;
2664 e->text = *event->hudadd.text;
2665 e->number = event->hudadd.number;
2666 e->item = event->hudadd.item;
2667 e->dir = event->hudadd.dir;
2668 e->align = *event->hudadd.align;
2669 e->offset = *event->hudadd.offset;
2670 e->world_pos = *event->hudadd.world_pos;
2671 e->size = *event->hudadd.size;
2672 e->z_index = event->hudadd.z_index;
2673 e->text2 = *event->hudadd.text2;
2674 hud_server_to_client[server_id] = player->addHud(e);
2676 delete event->hudadd.pos;
2677 delete event->hudadd.name;
2678 delete event->hudadd.scale;
2679 delete event->hudadd.text;
2680 delete event->hudadd.align;
2681 delete event->hudadd.offset;
2682 delete event->hudadd.world_pos;
2683 delete event->hudadd.size;
2684 delete event->hudadd.text2;
2687 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2689 LocalPlayer *player = client->getEnv().getLocalPlayer();
2690 HudElement *e = player->removeHud(event->hudrm.id);
2694 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2696 LocalPlayer *player = client->getEnv().getLocalPlayer();
2698 u32 id = event->hudchange.id;
2699 HudElement *e = player->getHud(id);
2702 delete event->hudchange.v3fdata;
2703 delete event->hudchange.v2fdata;
2704 delete event->hudchange.sdata;
2705 delete event->hudchange.v2s32data;
2709 switch (event->hudchange.stat) {
2711 e->pos = *event->hudchange.v2fdata;
2715 e->name = *event->hudchange.sdata;
2718 case HUD_STAT_SCALE:
2719 e->scale = *event->hudchange.v2fdata;
2723 e->text = *event->hudchange.sdata;
2726 case HUD_STAT_NUMBER:
2727 e->number = event->hudchange.data;
2731 e->item = event->hudchange.data;
2735 e->dir = event->hudchange.data;
2738 case HUD_STAT_ALIGN:
2739 e->align = *event->hudchange.v2fdata;
2742 case HUD_STAT_OFFSET:
2743 e->offset = *event->hudchange.v2fdata;
2746 case HUD_STAT_WORLD_POS:
2747 e->world_pos = *event->hudchange.v3fdata;
2751 e->size = *event->hudchange.v2s32data;
2754 case HUD_STAT_Z_INDEX:
2755 e->z_index = event->hudchange.data;
2758 case HUD_STAT_TEXT2:
2759 e->text2 = *event->hudchange.sdata;
2763 delete event->hudchange.v3fdata;
2764 delete event->hudchange.v2fdata;
2765 delete event->hudchange.sdata;
2766 delete event->hudchange.v2s32data;
2769 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2771 sky->setVisible(false);
2772 // Whether clouds are visible in front of a custom skybox.
2773 sky->setCloudsEnabled(event->set_sky->clouds);
2779 // Clear the old textures out in case we switch rendering type.
2780 sky->clearSkyboxTextures();
2781 // Handle according to type
2782 if (event->set_sky->type == "regular") {
2783 // Shows the mesh skybox
2784 sky->setVisible(true);
2785 // Update mesh based skybox colours if applicable.
2786 sky->setSkyColors(event->set_sky->sky_color);
2787 sky->setHorizonTint(
2788 event->set_sky->fog_sun_tint,
2789 event->set_sky->fog_moon_tint,
2790 event->set_sky->fog_tint_type
2792 } else if (event->set_sky->type == "skybox" &&
2793 event->set_sky->textures.size() == 6) {
2794 // Disable the dyanmic mesh skybox:
2795 sky->setVisible(false);
2797 sky->setFallbackBgColor(event->set_sky->bgcolor);
2798 // Set sunrise and sunset fog tinting:
2799 sky->setHorizonTint(
2800 event->set_sky->fog_sun_tint,
2801 event->set_sky->fog_moon_tint,
2802 event->set_sky->fog_tint_type
2804 // Add textures to skybox.
2805 for (int i = 0; i < 6; i++)
2806 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2808 // Handle everything else as plain color.
2809 if (event->set_sky->type != "plain")
2810 infostream << "Unknown sky type: "
2811 << (event->set_sky->type) << std::endl;
2812 sky->setVisible(false);
2813 sky->setFallbackBgColor(event->set_sky->bgcolor);
2814 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2815 sky->setHorizonTint(
2816 event->set_sky->bgcolor,
2817 event->set_sky->bgcolor,
2821 delete event->set_sky;
2824 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2826 sky->setSunVisible(event->sun_params->visible);
2827 sky->setSunTexture(event->sun_params->texture,
2828 event->sun_params->tonemap, texture_src);
2829 sky->setSunScale(event->sun_params->scale);
2830 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2831 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2832 delete event->sun_params;
2835 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2837 sky->setMoonVisible(event->moon_params->visible);
2838 sky->setMoonTexture(event->moon_params->texture,
2839 event->moon_params->tonemap, texture_src);
2840 sky->setMoonScale(event->moon_params->scale);
2841 delete event->moon_params;
2844 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2846 sky->setStarsVisible(event->star_params->visible);
2847 sky->setStarCount(event->star_params->count, false);
2848 sky->setStarColor(event->star_params->starcolor);
2849 sky->setStarScale(event->star_params->scale);
2850 delete event->star_params;
2853 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2854 CameraOrientation *cam)
2856 client->getEnv().setDayNightRatioOverride(
2857 event->override_day_night_ratio.do_override,
2858 event->override_day_night_ratio.ratio_f * 1000.0f);
2861 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2866 clouds->setDensity(event->cloud_params.density);
2867 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2868 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2869 clouds->setHeight(event->cloud_params.height);
2870 clouds->setThickness(event->cloud_params.thickness);
2871 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2874 void Game::processClientEvents(CameraOrientation *cam)
2876 while (client->hasClientEvents()) {
2877 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2878 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2879 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2880 (this->*evHandler.handler)(event.get(), cam);
2884 void Game::updateChat(f32 dtime, const v2u32 &screensize)
2886 // Get new messages from error log buffer
2887 while (!m_chat_log_buf.empty())
2888 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2890 // Get new messages from client
2891 std::wstring message;
2892 while (client->getChatMessage(message)) {
2893 chat_backend->addUnparsedMessage(message);
2896 // Remove old messages
2897 chat_backend->step(dtime);
2899 // Display all messages in a static text element
2900 m_game_ui->setChatText(chat_backend->getRecentChat(),
2901 chat_backend->getRecentBuffer().getLineCount());
2904 void Game::updateCamera(u32 busy_time, f32 dtime)
2906 LocalPlayer *player = client->getEnv().getLocalPlayer();
2909 For interaction purposes, get info about the held item
2911 - Is it a usable item?
2912 - Can it point to liquids?
2914 ItemStack playeritem;
2916 ItemStack selected, hand;
2917 playeritem = player->getWieldedItem(&selected, &hand);
2920 ToolCapabilities playeritem_toolcap =
2921 playeritem.getToolCapabilities(itemdef_manager);
2923 v3s16 old_camera_offset = camera->getOffset();
2925 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2926 GenericCAO *playercao = player->getCAO();
2928 // If playercao not loaded, don't change camera
2932 camera->toggleCameraMode();
2934 // Make the player visible depending on camera mode.
2935 playercao->updateMeshCulling();
2936 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2939 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2940 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2942 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2943 camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
2944 camera->step(dtime);
2946 v3f camera_position = camera->getPosition();
2947 v3f camera_direction = camera->getDirection();
2948 f32 camera_fov = camera->getFovMax();
2949 v3s16 camera_offset = camera->getOffset();
2951 m_camera_offset_changed = (camera_offset != old_camera_offset);
2953 if (!m_flags.disable_camera_update) {
2954 client->getEnv().getClientMap().updateCamera(camera_position,
2955 camera_direction, camera_fov, camera_offset);
2957 if (m_camera_offset_changed) {
2958 client->updateCameraOffset(camera_offset);
2959 client->getEnv().updateCameraOffset(camera_offset);
2962 clouds->updateCameraOffset(camera_offset);
2968 void Game::updateSound(f32 dtime)
2970 // Update sound listener
2971 v3s16 camera_offset = camera->getOffset();
2972 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
2973 v3f(0, 0, 0), // velocity
2974 camera->getDirection(),
2975 camera->getCameraNode()->getUpVector());
2977 bool mute_sound = g_settings->getBool("mute_sound");
2979 sound->setListenerGain(0.0f);
2981 // Check if volume is in the proper range, else fix it.
2982 float old_volume = g_settings->getFloat("sound_volume");
2983 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
2984 sound->setListenerGain(new_volume);
2986 if (old_volume != new_volume) {
2987 g_settings->setFloat("sound_volume", new_volume);
2991 LocalPlayer *player = client->getEnv().getLocalPlayer();
2993 // Tell the sound maker whether to make footstep sounds
2994 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
2996 // Update sound maker
2997 if (player->makes_footstep_sound)
2998 soundmaker->step(dtime);
3000 ClientMap &map = client->getEnv().getClientMap();
3001 MapNode n = map.getNode(player->getFootstepNodePos());
3002 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3006 void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
3008 LocalPlayer *player = client->getEnv().getLocalPlayer();
3010 const v3f camera_direction = camera->getDirection();
3011 const v3s16 camera_offset = camera->getOffset();
3014 Calculate what block is the crosshair pointing to
3017 ItemStack selected_item, hand_item;
3018 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3020 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3021 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3023 core::line3d<f32> shootline;
3025 switch (camera->getCameraMode()) {
3026 case CAMERA_MODE_FIRST:
3027 // Shoot from camera position, with bobbing
3028 shootline.start = camera->getPosition();
3030 case CAMERA_MODE_THIRD:
3031 // Shoot from player head, no bobbing
3032 shootline.start = camera->getHeadPosition();
3034 case CAMERA_MODE_THIRD_FRONT:
3035 shootline.start = camera->getHeadPosition();
3036 // prevent player pointing anything in front-view
3040 shootline.end = shootline.start + camera_direction * BS * d;
3042 #ifdef HAVE_TOUCHSCREENGUI
3044 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3045 shootline = g_touchscreengui->getShootline();
3046 // Scale shootline to the acual distance the player can reach
3047 shootline.end = shootline.start
3048 + shootline.getVector().normalize() * BS * d;
3049 shootline.start += intToFloat(camera_offset, BS);
3050 shootline.end += intToFloat(camera_offset, BS);
3055 PointedThing pointed = updatePointedThing(shootline,
3056 selected_def.liquids_pointable,
3057 !runData.btn_down_for_dig,
3060 if (pointed != runData.pointed_old) {
3061 infostream << "Pointing at " << pointed.dump() << std::endl;
3062 hud->updateSelectionMesh(camera_offset);
3065 // Allow digging again if button is not pressed
3066 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3067 runData.digging_blocked = false;
3071 - releasing dig button
3072 - pointing away from node
3074 if (runData.digging) {
3075 if (wasKeyReleased(KeyType::DIG)) {
3076 infostream << "Dig button released (stopped digging)" << std::endl;
3077 runData.digging = false;
3078 } else if (pointed != runData.pointed_old) {
3079 if (pointed.type == POINTEDTHING_NODE
3080 && runData.pointed_old.type == POINTEDTHING_NODE
3081 && pointed.node_undersurface
3082 == runData.pointed_old.node_undersurface) {
3083 // Still pointing to the same node, but a different face.
3086 infostream << "Pointing away from node (stopped digging)" << std::endl;
3087 runData.digging = false;
3088 hud->updateSelectionMesh(camera_offset);
3092 if (!runData.digging) {
3093 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3094 client->setCrack(-1, v3s16(0, 0, 0));
3095 runData.dig_time = 0.0;
3097 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3098 // Remove e.g. torches faster when clicking instead of holding dig button
3099 runData.nodig_delay_timer = 0;
3100 runData.dig_instantly = false;
3103 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3104 runData.btn_down_for_dig = false;
3106 runData.punching = false;
3108 soundmaker->m_player_leftpunch_sound.name = "";
3110 // Prepare for repeating, unless we're not supposed to
3111 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3112 runData.repeat_place_timer += dtime;
3114 runData.repeat_place_timer = 0;
3116 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3117 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3118 !client->getScript()->on_item_use(selected_item, pointed)))
3119 client->interact(INTERACT_USE, pointed);
3120 } else if (pointed.type == POINTEDTHING_NODE) {
3121 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3122 } else if (pointed.type == POINTEDTHING_OBJECT) {
3123 v3f player_position = player->getPosition();
3124 handlePointingAtObject(pointed, tool_item, player_position, show_debug);
3125 } else if (isKeyDown(KeyType::DIG)) {
3126 // When button is held down in air, show continuous animation
3127 runData.punching = true;
3128 // Run callback even though item is not usable
3129 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3130 client->getScript()->on_item_use(selected_item, pointed);
3131 } else if (wasKeyPressed(KeyType::PLACE)) {
3132 handlePointingAtNothing(selected_item);
3135 runData.pointed_old = pointed;
3137 if (runData.punching || wasKeyPressed(KeyType::DIG))
3138 camera->setDigging(0); // dig animation
3140 input->clearWasKeyPressed();
3141 input->clearWasKeyReleased();
3142 // Ensure DIG & PLACE are marked as handled
3143 wasKeyDown(KeyType::DIG);
3144 wasKeyDown(KeyType::PLACE);
3146 input->joystick.clearWasKeyDown(KeyType::DIG);
3147 input->joystick.clearWasKeyDown(KeyType::PLACE);
3149 input->joystick.clearWasKeyReleased(KeyType::DIG);
3150 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3154 PointedThing Game::updatePointedThing(
3155 const core::line3d<f32> &shootline,
3156 bool liquids_pointable,
3157 bool look_for_object,
3158 const v3s16 &camera_offset)
3160 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3161 selectionboxes->clear();
3162 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3163 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3164 "show_entity_selectionbox");
3166 ClientEnvironment &env = client->getEnv();
3167 ClientMap &map = env.getClientMap();
3168 const NodeDefManager *nodedef = map.getNodeDefManager();
3170 runData.selected_object = NULL;
3171 hud->pointing_at_object = false;
3173 RaycastState s(shootline, look_for_object, liquids_pointable);
3174 PointedThing result;
3175 env.continueRaycast(&s, &result);
3176 if (result.type == POINTEDTHING_OBJECT) {
3177 hud->pointing_at_object = true;
3179 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3180 aabb3f selection_box;
3181 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3182 runData.selected_object->getSelectionBox(&selection_box)) {
3183 v3f pos = runData.selected_object->getPosition();
3184 selectionboxes->push_back(aabb3f(selection_box));
3185 hud->setSelectionPos(pos, camera_offset);
3187 } else if (result.type == POINTEDTHING_NODE) {
3188 // Update selection boxes
3189 MapNode n = map.getNode(result.node_undersurface);
3190 std::vector<aabb3f> boxes;
3191 n.getSelectionBoxes(nodedef, &boxes,
3192 n.getNeighbors(result.node_undersurface, &map));
3195 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3196 i != boxes.end(); ++i) {
3198 box.MinEdge -= v3f(d, d, d);
3199 box.MaxEdge += v3f(d, d, d);
3200 selectionboxes->push_back(box);
3202 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3204 hud->setSelectedFaceNormal(v3f(
3205 result.intersection_normal.X,
3206 result.intersection_normal.Y,
3207 result.intersection_normal.Z));
3210 // Update selection mesh light level and vertex colors
3211 if (!selectionboxes->empty()) {
3212 v3f pf = hud->getSelectionPos();
3213 v3s16 p = floatToInt(pf, BS);
3215 // Get selection mesh light level
3216 MapNode n = map.getNode(p);
3217 u16 node_light = getInteriorLight(n, -1, nodedef);
3218 u16 light_level = node_light;
3220 for (const v3s16 &dir : g_6dirs) {
3221 n = map.getNode(p + dir);
3222 node_light = getInteriorLight(n, -1, nodedef);
3223 if (node_light > light_level)
3224 light_level = node_light;
3227 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3229 final_color_blend(&c, light_level, daynight_ratio);
3231 // Modify final color a bit with time
3232 u32 timer = porting::getTimeMs() % 5000;
3233 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3234 float sin_r = 0.08f * std::sin(timerf);
3235 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3236 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3237 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3238 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3239 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3241 // Set mesh final color
3242 hud->setSelectionMeshColor(c);
3248 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3250 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3251 PointedThing fauxPointed;
3252 fauxPointed.type = POINTEDTHING_NOTHING;
3253 client->interact(INTERACT_ACTIVATE, fauxPointed);
3257 void Game::handlePointingAtNode(const PointedThing &pointed,
3258 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3260 v3s16 nodepos = pointed.node_undersurface;
3261 v3s16 neighbourpos = pointed.node_abovesurface;
3264 Check information text of node
3267 ClientMap &map = client->getEnv().getClientMap();
3269 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3270 && !runData.digging_blocked
3271 && client->checkPrivilege("interact")) {
3272 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3275 // This should be done after digging handling
3276 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3279 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3280 meta->getString("infotext"))));
3282 MapNode n = map.getNode(nodepos);
3284 if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
3285 m_game_ui->setInfoText(L"Unknown node: " +
3286 utf8_to_wide(nodedef_manager->get(n).name));
3290 if ((wasKeyPressed(KeyType::PLACE) ||
3291 runData.repeat_place_timer >= m_repeat_place_time) &&
3292 client->checkPrivilege("interact")) {
3293 runData.repeat_place_timer = 0;
3294 infostream << "Place button pressed while looking at ground" << std::endl;
3296 // Placing animation (always shown for feedback)
3297 camera->setDigging(1);
3299 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3301 // If the wielded item has node placement prediction,
3303 // And also set the sound and send the interact
3304 // But first check for meta formspec and rightclickable
3305 auto &def = selected_item.getDefinition(itemdef_manager);
3306 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3309 if (placed && client->modsLoaded())
3310 client->getScript()->on_placenode(pointed, def);
3314 bool Game::nodePlacement(const ItemDefinition &selected_def,
3315 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3316 const PointedThing &pointed, const NodeMetadata *meta)
3318 std::string prediction = selected_def.node_placement_prediction;
3319 const NodeDefManager *nodedef = client->ndef();
3320 ClientMap &map = client->getEnv().getClientMap();
3322 bool is_valid_position;
3324 node = map.getNode(nodepos, &is_valid_position);
3325 if (!is_valid_position) {
3326 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3331 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3332 && !isKeyDown(KeyType::SNEAK)) {
3333 // on_rightclick callbacks are called anyway
3334 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3335 client->interact(INTERACT_PLACE, pointed);
3337 infostream << "Launching custom inventory view" << std::endl;
3339 InventoryLocation inventoryloc;
3340 inventoryloc.setNodeMeta(nodepos);
3342 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3343 &client->getEnv().getClientMap(), nodepos);
3344 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3346 auto *&formspec = m_game_ui->updateFormspec("");
3347 GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
3348 txt_dst, client->getFormspecPrepend(), sound);
3350 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3354 // on_rightclick callback
3355 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3356 !isKeyDown(KeyType::SNEAK))) {
3358 client->interact(INTERACT_PLACE, pointed);
3362 verbosestream << "Node placement prediction for "
3363 << selected_def.name << " is " << prediction << std::endl;
3364 v3s16 p = neighbourpos;
3366 // Place inside node itself if buildable_to
3367 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3368 if (is_valid_position) {
3369 if (nodedef->get(n_under).buildable_to) {
3372 node = map.getNode(p, &is_valid_position);
3373 if (is_valid_position && !nodedef->get(node).buildable_to) {
3374 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3376 client->interact(INTERACT_PLACE, pointed);
3382 // Find id of predicted node
3384 bool found = nodedef->getId(prediction, id);
3387 errorstream << "Node placement prediction failed for "
3388 << selected_def.name << " (places "
3390 << ") - Name not known" << std::endl;
3391 // Handle this as if prediction was empty
3393 client->interact(INTERACT_PLACE, pointed);
3397 const ContentFeatures &predicted_f = nodedef->get(id);
3399 // Predict param2 for facedir and wallmounted nodes
3402 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3403 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3404 v3s16 dir = nodepos - neighbourpos;
3406 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3407 param2 = dir.Y < 0 ? 1 : 0;
3408 } else if (abs(dir.X) > abs(dir.Z)) {
3409 param2 = dir.X < 0 ? 3 : 2;
3411 param2 = dir.Z < 0 ? 5 : 4;
3415 if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3416 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3417 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3419 if (abs(dir.X) > abs(dir.Z)) {
3420 param2 = dir.X < 0 ? 3 : 1;
3422 param2 = dir.Z < 0 ? 2 : 0;
3426 assert(param2 <= 5);
3428 //Check attachment if node is in group attached_node
3429 if (((ItemGroupList) predicted_f.groups)["attached_node"] != 0) {
3430 static v3s16 wallmounted_dirs[8] = {
3440 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3441 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3442 pp = p + wallmounted_dirs[param2];
3444 pp = p + v3s16(0, -1, 0);
3446 if (!nodedef->get(map.getNode(pp)).walkable) {
3447 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3449 client->interact(INTERACT_PLACE, pointed);
3455 if ((predicted_f.param_type_2 == CPT2_COLOR
3456 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3457 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3458 const std::string &indexstr = selected_item.metadata.getString(
3459 "palette_index", 0);
3460 if (!indexstr.empty()) {
3461 s32 index = mystoi(indexstr);
3462 if (predicted_f.param_type_2 == CPT2_COLOR) {
3464 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3465 // param2 = pure palette index + other
3466 param2 = (index & 0xf8) | (param2 & 0x07);
3467 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3468 // param2 = pure palette index + other
3469 param2 = (index & 0xe0) | (param2 & 0x1f);
3474 // Add node to client map
3475 MapNode n(id, 0, param2);
3478 LocalPlayer *player = client->getEnv().getLocalPlayer();
3480 // Dont place node when player would be inside new node
3481 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3482 if (!nodedef->get(n).walkable ||
3483 g_settings->getBool("enable_build_where_you_stand") ||
3484 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3485 (nodedef->get(n).walkable &&
3486 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3487 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3488 // This triggers the required mesh update too
3489 client->addNode(p, n);
3491 client->interact(INTERACT_PLACE, pointed);
3492 // A node is predicted, also play a sound
3493 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3496 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3499 } catch (InvalidPositionException &e) {
3500 errorstream << "Node placement prediction failed for "
3501 << selected_def.name << " (places "
3503 << ") - Position not loaded" << std::endl;
3504 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3509 void Game::handlePointingAtObject(const PointedThing &pointed,
3510 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3512 std::wstring infotext = unescape_translate(
3513 utf8_to_wide(runData.selected_object->infoText()));
3516 if (!infotext.empty()) {
3519 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3522 m_game_ui->setInfoText(infotext);
3524 if (isKeyDown(KeyType::DIG)) {
3525 bool do_punch = false;
3526 bool do_punch_damage = false;
3528 if (runData.object_hit_delay_timer <= 0.0) {
3530 do_punch_damage = true;
3531 runData.object_hit_delay_timer = object_hit_delay;
3534 if (wasKeyPressed(KeyType::DIG))
3538 infostream << "Punched object" << std::endl;
3539 runData.punching = true;
3542 if (do_punch_damage) {
3543 // Report direct punch
3544 v3f objpos = runData.selected_object->getPosition();
3545 v3f dir = (objpos - player_position).normalize();
3547 bool disable_send = runData.selected_object->directReportPunch(
3548 dir, &tool_item, runData.time_from_last_punch);
3549 runData.time_from_last_punch = 0;
3552 client->interact(INTERACT_START_DIGGING, pointed);
3554 } else if (wasKeyDown(KeyType::PLACE)) {
3555 infostream << "Pressed place button while pointing at object" << std::endl;
3556 client->interact(INTERACT_PLACE, pointed); // place
3561 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3562 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3564 // See also: serverpackethandle.cpp, action == 2
3565 LocalPlayer *player = client->getEnv().getLocalPlayer();
3566 ClientMap &map = client->getEnv().getClientMap();
3567 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3569 // NOTE: Similar piece of code exists on the server side for
3571 // Get digging parameters
3572 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3573 &selected_item.getToolCapabilities(itemdef_manager));
3575 // If can't dig, try hand
3576 if (!params.diggable) {
3577 params = getDigParams(nodedef_manager->get(n).groups,
3578 &hand_item.getToolCapabilities(itemdef_manager));
3581 if (!params.diggable) {
3582 // I guess nobody will wait for this long
3583 runData.dig_time_complete = 10000000.0;
3585 runData.dig_time_complete = params.time;
3587 if (m_cache_enable_particles) {
3588 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3589 client->getParticleManager()->addNodeParticle(client,
3590 player, nodepos, n, features);
3594 if (!runData.digging) {
3595 infostream << "Started digging" << std::endl;
3596 runData.dig_instantly = runData.dig_time_complete == 0;
3597 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3599 client->interact(INTERACT_START_DIGGING, pointed);
3600 runData.digging = true;
3601 runData.btn_down_for_dig = true;
3604 if (!runData.dig_instantly) {
3605 runData.dig_index = (float)crack_animation_length
3607 / runData.dig_time_complete;
3609 // This is for e.g. torches
3610 runData.dig_index = crack_animation_length;
3613 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3615 if (sound_dig.exists() && params.diggable) {
3616 if (sound_dig.name == "__group") {
3617 if (!params.main_group.empty()) {
3618 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3619 soundmaker->m_player_leftpunch_sound.name =
3620 std::string("default_dig_") +
3624 soundmaker->m_player_leftpunch_sound = sound_dig;
3628 // Don't show cracks if not diggable
3629 if (runData.dig_time_complete >= 100000.0) {
3630 } else if (runData.dig_index < crack_animation_length) {
3631 //TimeTaker timer("client.setTempMod");
3632 //infostream<<"dig_index="<<dig_index<<std::endl;
3633 client->setCrack(runData.dig_index, nodepos);
3635 infostream << "Digging completed" << std::endl;
3636 client->setCrack(-1, v3s16(0, 0, 0));
3638 runData.dig_time = 0;
3639 runData.digging = false;
3640 // we successfully dug, now block it from repeating if we want to be safe
3641 if (g_settings->getBool("safe_dig_and_place"))
3642 runData.digging_blocked = true;
3644 runData.nodig_delay_timer =
3645 runData.dig_time_complete / (float)crack_animation_length;
3647 // We don't want a corresponding delay to very time consuming nodes
3648 // and nodes without digging time (e.g. torches) get a fixed delay.
3649 if (runData.nodig_delay_timer > 0.3)
3650 runData.nodig_delay_timer = 0.3;
3651 else if (runData.dig_instantly)
3652 runData.nodig_delay_timer = 0.15;
3654 bool is_valid_position;
3655 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3656 if (is_valid_position) {
3657 if (client->modsLoaded() &&
3658 client->getScript()->on_dignode(nodepos, wasnode)) {
3662 const ContentFeatures &f = client->ndef()->get(wasnode);
3663 if (f.node_dig_prediction == "air") {
3664 client->removeNode(nodepos);
3665 } else if (!f.node_dig_prediction.empty()) {
3667 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3669 client->addNode(nodepos, id, true);
3671 // implicit else: no prediction
3674 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3676 if (m_cache_enable_particles) {
3677 const ContentFeatures &features =
3678 client->getNodeDefManager()->get(wasnode);
3679 client->getParticleManager()->addDiggingParticles(client,
3680 player, nodepos, wasnode, features);
3684 // Send event to trigger sound
3685 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3688 if (runData.dig_time_complete < 100000.0) {
3689 runData.dig_time += dtime;
3691 runData.dig_time = 0;
3692 client->setCrack(-1, nodepos);
3695 camera->setDigging(0); // Dig animation
3698 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3699 const CameraOrientation &cam)
3701 TimeTaker tt_update("Game::updateFrame()");
3702 LocalPlayer *player = client->getEnv().getLocalPlayer();
3708 if (draw_control->range_all) {
3709 runData.fog_range = 100000 * BS;
3711 runData.fog_range = draw_control->wanted_range * BS;
3715 Calculate general brightness
3717 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3718 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3719 float direct_brightness;
3722 if (m_cache_enable_noclip && m_cache_enable_free_move) {
3723 direct_brightness = time_brightness;
3724 sunlight_seen = true;
3726 float old_brightness = sky->getBrightness();
3727 direct_brightness = client->getEnv().getClientMap()
3728 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3729 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3733 float time_of_day_smooth = runData.time_of_day_smooth;
3734 float time_of_day = client->getEnv().getTimeOfDayF();
3736 static const float maxsm = 0.05f;
3737 static const float todsm = 0.05f;
3739 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3740 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3741 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3742 time_of_day_smooth = time_of_day;
3744 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3745 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3746 + (time_of_day + 1.0) * todsm;
3748 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3749 + time_of_day * todsm;
3751 runData.time_of_day_smooth = time_of_day_smooth;
3753 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3754 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3755 player->getPitch());
3761 if (sky->getCloudsVisible()) {
3762 clouds->setVisible(true);
3763 clouds->step(dtime);
3764 // camera->getPosition is not enough for 3rd person views
3765 v3f camera_node_position = camera->getCameraNode()->getPosition();
3766 v3s16 camera_offset = camera->getOffset();
3767 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3768 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3769 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3770 clouds->update(camera_node_position,
3771 sky->getCloudColor());
3772 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3773 // if inside clouds, and fog enabled, use that as sky
3775 video::SColor clouds_dark = clouds->getColor()
3776 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3777 sky->overrideColors(clouds_dark, clouds->getColor());
3778 sky->setInClouds(true);
3779 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3780 // do not draw clouds after all
3781 clouds->setVisible(false);
3784 clouds->setVisible(false);
3791 client->getParticleManager()->step(dtime);
3797 if (m_cache_enable_fog) {
3800 video::EFT_FOG_LINEAR,
3801 runData.fog_range * m_cache_fog_start,
3802 runData.fog_range * 1.0,
3810 video::EFT_FOG_LINEAR,
3820 Get chat messages from client
3823 v2u32 screensize = driver->getScreenSize();
3825 updateChat(dtime, screensize);
3831 if (player->getWieldIndex() != runData.new_playeritem)
3832 client->setPlayerItem(runData.new_playeritem);
3834 if (client->updateWieldedItem()) {
3835 // Update wielded tool
3836 ItemStack selected_item, hand_item;
3837 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3838 camera->wield(tool_item);
3842 Update block draw list every 200ms or when camera direction has
3845 runData.update_draw_list_timer += dtime;
3847 v3f camera_direction = camera->getDirection();
3848 if (runData.update_draw_list_timer >= 0.2
3849 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3850 || m_camera_offset_changed) {
3851 runData.update_draw_list_timer = 0;
3852 client->getEnv().getClientMap().updateDrawList();
3853 runData.update_draw_list_last_cam_dir = camera_direction;
3856 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3859 make sure menu is on top
3860 1. Delete formspec menu reference if menu was removed
3861 2. Else, make sure formspec menu is on top
3863 auto formspec = m_game_ui->getFormspecGUI();
3864 do { // breakable. only runs for one iteration
3868 if (formspec->getReferenceCount() == 1) {
3869 m_game_ui->deleteFormspec();
3873 auto &loc = formspec->getFormspecLocation();
3874 if (loc.type == InventoryLocation::NODEMETA) {
3875 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3876 if (!meta || meta->getString("formspec").empty()) {
3877 formspec->quitMenu();
3883 guiroot->bringToFront(formspec);
3889 const video::SColor &skycolor = sky->getSkyColor();
3891 TimeTaker tt_draw("Draw scene");
3892 driver->beginScene(true, true, skycolor);
3894 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3895 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3896 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3897 bool draw_crosshair = (
3898 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3899 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3900 #ifdef HAVE_TOUCHSCREENGUI
3902 draw_crosshair = !g_settings->getBool("touchtarget");
3903 } catch (SettingNotFoundException) {
3906 RenderingEngine::draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3907 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3912 if (m_game_ui->m_flags.show_profiler_graph)
3913 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3918 if (runData.damage_flash > 0.0f) {
3919 video::SColor color(runData.damage_flash, 180, 0, 0);
3920 driver->draw2DRectangle(color,
3921 core::rect<s32>(0, 0, screensize.X, screensize.Y),
3924 runData.damage_flash -= 384.0f * dtime;
3930 if (player->hurt_tilt_timer > 0.0f) {
3931 player->hurt_tilt_timer -= dtime * 6.0f;
3933 if (player->hurt_tilt_timer < 0.0f)
3934 player->hurt_tilt_strength = 0.0f;
3938 Update minimap pos and rotation
3940 if (mapper && m_game_ui->m_flags.show_hud) {
3941 mapper->setPos(floatToInt(player->getPosition(), BS));
3942 mapper->setAngle(player->getYaw());
3948 if (++m_reset_HW_buffer_counter > 500) {
3950 Periodically remove all mesh HW buffers.
3952 Work around for a quirk in Irrlicht where a HW buffer is only
3953 released after 20000 iterations (triggered from endScene()).
3955 Without this, all loaded but unused meshes will retain their HW
3956 buffers for at least 5 minutes, at which point looking up the HW buffers
3957 becomes a bottleneck and the framerate drops (as much as 30%).
3959 Tests showed that numbers between 50 and 1000 are good, so picked 500.
3960 There are no other public Irrlicht APIs that allow interacting with the
3961 HW buffers without tracking the status of every individual mesh.
3963 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
3965 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
3966 driver->removeAllHardwareBuffers();
3967 m_reset_HW_buffer_counter = 0;
3971 stats->drawtime = tt_draw.stop(true);
3972 g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
3973 g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
3976 /* Log times and stuff for visualization */
3977 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
3979 Profiler::GraphValues values;
3980 g_profiler->graphGet(values);
3986 /****************************************************************************
3988 ****************************************************************************/
3990 /* On some computers framerate doesn't seem to be automatically limited
3992 inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
3994 // not using getRealTime is necessary for wine
3995 device->getTimer()->tick(); // Maker sure device time is up-to-date
3996 u32 time = device->getTimer()->getTime();
3997 u32 last_time = fps_timings->last_time;
3999 if (time > last_time) // Make sure time hasn't overflowed
4000 fps_timings->busy_time = time - last_time;
4002 fps_timings->busy_time = 0;
4004 u32 frametime_min = 1000 / (
4005 device->isWindowFocused() && !g_menumgr.pausesGame()
4006 ? g_settings->getFloat("fps_max")
4007 : g_settings->getFloat("fps_max_unfocused"));
4009 if (fps_timings->busy_time < frametime_min) {
4010 fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
4011 device->sleep(fps_timings->sleep_time);
4013 fps_timings->sleep_time = 0;
4016 /* Get the new value of the device timer. Note that device->sleep() may
4017 * not sleep for the entire requested time as sleep may be interrupted and
4018 * therefore it is arguably more accurate to get the new time from the
4019 * device rather than calculating it by adding sleep_time to time.
4022 device->getTimer()->tick(); // Update device timer
4023 time = device->getTimer()->getTime();
4025 if (time > last_time) // Make sure last_time hasn't overflowed
4026 *dtime = (time - last_time) / 1000.0;
4030 fps_timings->last_time = time;
4033 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4035 const wchar_t *wmsg = wgettext(msg);
4036 RenderingEngine::draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4041 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4043 ((Game *)data)->readSettings();
4046 void Game::readSettings()
4048 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4049 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4050 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4051 m_cache_enable_particles = g_settings->getBool("enable_particles");
4052 m_cache_enable_fog = g_settings->getBool("enable_fog");
4053 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
4054 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4055 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
4057 m_cache_enable_noclip = g_settings->getBool("noclip");
4058 m_cache_enable_free_move = g_settings->getBool("free_move");
4060 m_cache_fog_start = g_settings->getFloat("fog_start");
4062 m_cache_cam_smoothing = 0;
4063 if (g_settings->getBool("cinematic"))
4064 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4066 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4068 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4069 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4070 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4072 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4075 /****************************************************************************/
4076 /****************************************************************************
4078 ****************************************************************************/
4079 /****************************************************************************/
4081 void Game::extendedResourceCleanup()
4083 // Extended resource accounting
4084 infostream << "Irrlicht resources after cleanup:" << std::endl;
4085 infostream << "\tRemaining meshes : "
4086 << RenderingEngine::get_mesh_cache()->getMeshCount() << std::endl;
4087 infostream << "\tRemaining textures : "
4088 << driver->getTextureCount() << std::endl;
4090 for (unsigned int i = 0; i < driver->getTextureCount(); i++) {
4091 irr::video::ITexture *texture = driver->getTextureByIndex(i);
4092 infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str()
4096 clearTextureNameCache();
4097 infostream << "\tRemaining materials: "
4098 << driver-> getMaterialRendererCount()
4099 << " (note: irrlicht doesn't support removing renderers)" << std::endl;
4102 void Game::showDeathFormspec()
4104 static std::string formspec_str =
4105 std::string("formspec_version[1]") +
4107 "bgcolor[#320000b4;true]"
4108 "label[4.85,1.35;" + gettext("You died") + "]"
4109 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4113 /* Note: FormspecFormSource and LocalFormspecHandler *
4114 * are deleted by guiFormSpecMenu */
4115 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4116 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4118 auto *&formspec = m_game_ui->getFormspecGUI();
4119 GUIFormSpecMenu::create(formspec, client, &input->joystick,
4120 fs_src, txt_dst, client->getFormspecPrepend(), sound);
4121 formspec->setFocus("btn_respawn");
4124 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4125 void Game::showPauseMenu()
4128 static const std::string control_text = strgettext("Default Controls:\n"
4129 "No menu visible:\n"
4130 "- single tap: button activate\n"
4131 "- double tap: place/use\n"
4132 "- slide finger: look around\n"
4133 "Menu/Inventory visible:\n"
4134 "- double tap (outside):\n"
4136 "- touch stack, touch slot:\n"
4138 "- touch&drag, tap 2nd finger\n"
4139 " --> place single item to slot\n"
4142 static const std::string control_text_template = strgettext("Controls:\n"
4143 "- %s: move forwards\n"
4144 "- %s: move backwards\n"
4146 "- %s: move right\n"
4147 "- %s: jump/climb up\n"
4150 "- %s: sneak/climb down\n"
4153 "- Mouse: turn/look\n"
4154 "- Mouse wheel: select item\n"
4158 char control_text_buf[600];
4160 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4161 GET_KEY_NAME(keymap_forward),
4162 GET_KEY_NAME(keymap_backward),
4163 GET_KEY_NAME(keymap_left),
4164 GET_KEY_NAME(keymap_right),
4165 GET_KEY_NAME(keymap_jump),
4166 GET_KEY_NAME(keymap_dig),
4167 GET_KEY_NAME(keymap_place),
4168 GET_KEY_NAME(keymap_sneak),
4169 GET_KEY_NAME(keymap_drop),
4170 GET_KEY_NAME(keymap_inventory),
4171 GET_KEY_NAME(keymap_chat)
4174 std::string control_text = std::string(control_text_buf);
4175 str_formspec_escape(control_text);
4178 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4179 std::ostringstream os;
4181 os << "formspec_version[1]" << SIZE_TAG
4182 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4183 << strgettext("Continue") << "]";
4185 if (!simple_singleplayer_mode) {
4186 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4187 << strgettext("Change Password") << "]";
4189 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4194 if (g_settings->getBool("enable_sound")) {
4195 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4196 << strgettext("Sound Volume") << "]";
4199 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4200 << strgettext("Change Keys") << "]";
4202 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4203 << strgettext("Exit to Menu") << "]";
4204 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4205 << strgettext("Exit to OS") << "]"
4206 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4207 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4209 << strgettext("Game info:") << "\n";
4210 const std::string &address = client->getAddressName();
4211 static const std::string mode = strgettext("- Mode: ");
4212 if (!simple_singleplayer_mode) {
4213 Address serverAddress = client->getServerAddress();
4214 if (!address.empty()) {
4215 os << mode << strgettext("Remote server") << "\n"
4216 << strgettext("- Address: ") << address;
4218 os << mode << strgettext("Hosting server");
4220 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4222 os << mode << strgettext("Singleplayer") << "\n";
4224 if (simple_singleplayer_mode || address.empty()) {
4225 static const std::string on = strgettext("On");
4226 static const std::string off = strgettext("Off");
4227 const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
4228 const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
4229 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4230 os << strgettext("- Damage: ") << damage << "\n"
4231 << strgettext("- Creative Mode: ") << creative << "\n";
4232 if (!simple_singleplayer_mode) {
4233 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4234 //~ PvP = Player versus Player
4235 os << strgettext("- PvP: ") << pvp << "\n"
4236 << strgettext("- Public: ") << announced << "\n";
4237 std::string server_name = g_settings->get("server_name");
4238 str_formspec_escape(server_name);
4239 if (announced == on && !server_name.empty())
4240 os << strgettext("- Server Name: ") << server_name;
4247 /* Note: FormspecFormSource and LocalFormspecHandler *
4248 * are deleted by guiFormSpecMenu */
4249 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4250 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4252 auto *&formspec = m_game_ui->getFormspecGUI();
4253 GUIFormSpecMenu::create(formspec, client, &input->joystick,
4254 fs_src, txt_dst, client->getFormspecPrepend(), sound);
4255 formspec->setFocus("btn_continue");
4256 formspec->doPause = true;
4259 /****************************************************************************/
4260 /****************************************************************************
4261 extern function for launching the game
4262 ****************************************************************************/
4263 /****************************************************************************/
4265 void the_game(bool *kill,
4266 InputHandler *input,
4267 const GameStartData &start_data,
4268 std::string &error_message,
4269 ChatBackend &chat_backend,
4270 bool *reconnect_requested) // Used for local game
4274 /* Make a copy of the server address because if a local singleplayer server
4275 * is created then this is updated and we don't want to change the value
4276 * passed to us by the calling function
4281 if (game.startup(kill, input, start_data, error_message,
4282 reconnect_requested, &chat_backend)) {
4286 } catch (SerializationError &e) {
4287 error_message = std::string("A serialization error occurred:\n")
4288 + e.what() + "\n\nThe server is probably "
4289 " running a different version of " PROJECT_NAME_C ".";
4290 errorstream << error_message << std::endl;
4291 } catch (ServerError &e) {
4292 error_message = e.what();
4293 errorstream << "ServerError: " << error_message << std::endl;
4294 } catch (ModError &e) {
4295 error_message = std::string("ModError: ") + e.what() +
4296 strgettext("\nCheck debug.txt for details.");
4297 errorstream << error_message << std::endl;