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, 3> m_eye_position_pixel;
428 CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
429 CachedPixelShaderSetting<float, 3> m_minimap_yaw;
430 CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
431 CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
432 CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
436 void onSettingsChange(const std::string &name)
438 if (name == "enable_fog")
439 m_fog_enabled = g_settings->getBool("enable_fog");
442 static void settingsCallback(const std::string &name, void *userdata)
444 reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
447 void setSky(Sky *sky) { m_sky = sky; }
449 GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
450 f32 *fog_range, Client *client) :
452 m_force_fog_off(force_fog_off),
453 m_fog_range(fog_range),
454 m_sky_bg_color("skyBgColor"),
455 m_fog_distance("fogDistance"),
456 m_animation_timer_vertex("animationTimer"),
457 m_animation_timer_pixel("animationTimer"),
458 m_day_light("dayLight"),
459 m_eye_position_pixel("eyePosition"),
460 m_eye_position_vertex("eyePosition"),
461 m_minimap_yaw("yawVec"),
462 m_camera_offset_pixel("cameraOffset"),
463 m_camera_offset_vertex("cameraOffset"),
464 m_base_texture("baseTexture"),
467 g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
468 m_fog_enabled = g_settings->getBool("enable_fog");
471 ~GameGlobalShaderConstantSetter()
473 g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
476 virtual void onSetConstants(video::IMaterialRendererServices *services,
483 video::SColor bgcolor = m_sky->getBgColor();
484 video::SColorf bgcolorf(bgcolor);
485 float bgcolorfa[4] = {
491 m_sky_bg_color.set(bgcolorfa, services);
494 float fog_distance = 10000 * BS;
496 if (m_fog_enabled && !*m_force_fog_off)
497 fog_distance = *m_fog_range;
499 m_fog_distance.set(&fog_distance, services);
501 u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
502 video::SColorf sunlight;
503 get_sunlight_color(&sunlight, daynight_ratio);
508 m_day_light.set(dnc, services);
510 u32 animation_timer = porting::getTimeMs() % 1000000;
511 float animation_timer_f = (float)animation_timer / 100000.f;
512 m_animation_timer_vertex.set(&animation_timer_f, services);
513 m_animation_timer_pixel.set(&animation_timer_f, services);
515 float eye_position_array[3];
516 v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
517 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
518 eye_position_array[0] = epos.X;
519 eye_position_array[1] = epos.Y;
520 eye_position_array[2] = epos.Z;
522 epos.getAs3Values(eye_position_array);
524 m_eye_position_pixel.set(eye_position_array, services);
525 m_eye_position_vertex.set(eye_position_array, services);
527 if (m_client->getMinimap()) {
528 float minimap_yaw_array[3];
529 v3f minimap_yaw = m_client->getMinimap()->getYawVec();
530 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
531 minimap_yaw_array[0] = minimap_yaw.X;
532 minimap_yaw_array[1] = minimap_yaw.Y;
533 minimap_yaw_array[2] = minimap_yaw.Z;
535 minimap_yaw.getAs3Values(minimap_yaw_array);
537 m_minimap_yaw.set(minimap_yaw_array, services);
540 float camera_offset_array[3];
541 v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
542 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
543 camera_offset_array[0] = offset.X;
544 camera_offset_array[1] = offset.Y;
545 camera_offset_array[2] = offset.Z;
547 offset.getAs3Values(camera_offset_array);
549 m_camera_offset_pixel.set(camera_offset_array, services);
550 m_camera_offset_vertex.set(camera_offset_array, services);
552 SamplerLayer_t base_tex = 0;
553 m_base_texture.set(&base_tex, services);
558 class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
561 bool *m_force_fog_off;
564 std::vector<GameGlobalShaderConstantSetter *> created_nosky;
566 GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
567 f32 *fog_range, Client *client) :
569 m_force_fog_off(force_fog_off),
570 m_fog_range(fog_range),
574 void setSky(Sky *sky) {
576 for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
577 ggscs->setSky(m_sky);
579 created_nosky.clear();
582 virtual IShaderConstantSetter* create()
584 GameGlobalShaderConstantSetter *scs = new GameGlobalShaderConstantSetter(
585 m_sky, m_force_fog_off, m_fog_range, m_client);
587 created_nosky.push_back(scs);
593 #define SIZE_TAG "size[11,5.5]"
595 #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
598 /****************************************************************************
599 ****************************************************************************/
601 const float object_hit_delay = 0.2;
604 u32 last_time, busy_time, sleep_time;
608 /* The reason the following structs are not anonymous structs within the
609 * class is that they are not used by the majority of member functions and
610 * many functions that do require objects of thse types do not modify them
611 * (so they can be passed as a const qualified parameter)
617 PointedThing pointed_old;
620 bool btn_down_for_dig;
622 bool digging_blocked;
623 bool reset_jump_timer;
624 float nodig_delay_timer;
626 float dig_time_complete;
627 float repeat_place_timer;
628 float object_hit_delay_timer;
629 float time_from_last_punch;
630 ClientActiveObject *selected_object;
634 float update_draw_list_timer;
638 v3f update_draw_list_last_cam_dir;
640 float time_of_day_smooth;
645 struct ClientEventHandler
647 void (Game::*handler)(ClientEvent *, CameraOrientation *);
650 /****************************************************************************
652 ****************************************************************************/
654 /* This is not intended to be a public class. If a public class becomes
655 * desirable then it may be better to create another 'wrapper' class that
656 * hides most of the stuff in this class (nothing in this class is required
657 * by any other file) but exposes the public methods/data only.
664 bool startup(bool *kill,
666 const GameStartData &game_params,
667 std::string &error_message,
669 ChatBackend *chat_backend);
676 void extendedResourceCleanup();
678 // Basic initialisation
679 bool init(const std::string &map_dir, const std::string &address,
680 u16 port, const SubgameSpec &gamespec);
682 bool createSingleplayerServer(const std::string &map_dir,
683 const SubgameSpec &gamespec, u16 port);
686 bool createClient(const GameStartData &start_data);
690 bool connectToServer(const GameStartData &start_data,
691 bool *connect_ok, bool *aborted);
692 bool getServerContent(bool *aborted);
696 void updateInteractTimers(f32 dtime);
697 bool checkConnection();
698 bool handleCallbacks();
699 void processQueues();
700 void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
701 void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
702 void updateProfilerGraphs(ProfilerGraph *graph);
705 void processUserInput(f32 dtime);
706 void processKeyInput();
707 void processItemSelection(u16 *new_playeritem);
709 void dropSelectedItem(bool single_item = false);
710 void openInventory();
711 void openConsole(float scale, const wchar_t *line=NULL);
712 void toggleFreeMove();
713 void toggleFreeMoveAlt();
714 void togglePitchMove();
717 void toggleCinematic();
718 void toggleAutoforward();
720 void toggleMinimap(bool shift_pressed);
723 void toggleUpdateCamera();
725 void increaseViewRange();
726 void decreaseViewRange();
727 void toggleFullViewRange();
728 void checkZoomEnabled();
730 void updateCameraDirection(CameraOrientation *cam, float dtime);
731 void updateCameraOrientation(CameraOrientation *cam, float dtime);
732 void updatePlayerControl(const CameraOrientation &cam);
733 void step(f32 *dtime);
734 void processClientEvents(CameraOrientation *cam);
735 void updateCamera(u32 busy_time, f32 dtime);
736 void updateSound(f32 dtime);
737 void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug);
739 * Returns the object or node the player is pointing at.
740 * Also updates the selected thing in the Hud.
742 * @param[in] shootline the shootline, starting from
743 * the camera position. This also gives the maximal distance
745 * @param[in] liquids_pointable if false, liquids are ignored
746 * @param[in] look_for_object if false, objects are ignored
747 * @param[in] camera_offset offset of the camera
748 * @param[out] selected_object the selected object or
751 PointedThing updatePointedThing(
752 const core::line3d<f32> &shootline, bool liquids_pointable,
753 bool look_for_object, const v3s16 &camera_offset);
754 void handlePointingAtNothing(const ItemStack &playerItem);
755 void handlePointingAtNode(const PointedThing &pointed,
756 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
757 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
758 const v3f &player_position, bool show_debug);
759 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
760 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
761 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
762 const CameraOrientation &cam);
765 void limitFps(FpsControl *fps_timings, f32 *dtime);
767 void showOverlayMessage(const char *msg, float dtime, int percent,
768 bool draw_clouds = true);
770 static void settingChangedCallback(const std::string &setting_name, void *data);
773 inline bool isKeyDown(GameKeyType k)
775 return input->isKeyDown(k);
777 inline bool wasKeyDown(GameKeyType k)
779 return input->wasKeyDown(k);
781 inline bool wasKeyPressed(GameKeyType k)
783 return input->wasKeyPressed(k);
785 inline bool wasKeyReleased(GameKeyType k)
787 return input->wasKeyReleased(k);
791 void handleAndroidChatInput();
796 bool force_fog_off = false;
797 bool disable_camera_update = false;
800 void showDeathFormspec();
801 void showPauseMenu();
803 // ClientEvent handlers
804 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
805 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
806 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
807 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
808 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
809 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
810 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
811 CameraOrientation *cam);
812 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
813 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
814 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
815 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
816 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
817 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
818 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
819 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
820 CameraOrientation *cam);
821 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
823 void updateChat(f32 dtime, const v2u32 &screensize);
825 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
826 const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
827 const NodeMetadata *meta);
828 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
830 InputHandler *input = nullptr;
832 Client *client = nullptr;
833 Server *server = nullptr;
835 IWritableTextureSource *texture_src = nullptr;
836 IWritableShaderSource *shader_src = nullptr;
838 // When created, these will be filled with data received from the server
839 IWritableItemDefManager *itemdef_manager = nullptr;
840 NodeDefManager *nodedef_manager = nullptr;
842 GameOnDemandSoundFetcher soundfetcher; // useful when testing
843 ISoundManager *sound = nullptr;
844 bool sound_is_dummy = false;
845 SoundMaker *soundmaker = nullptr;
847 ChatBackend *chat_backend = nullptr;
848 LogOutputBuffer m_chat_log_buf;
850 EventManager *eventmgr = nullptr;
851 QuicktuneShortcutter *quicktune = nullptr;
852 bool registration_confirmation_shown = false;
854 std::unique_ptr<GameUI> m_game_ui;
855 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
856 MapDrawControl *draw_control = nullptr;
857 Camera *camera = nullptr;
858 Clouds *clouds = nullptr; // Free using ->Drop()
859 Sky *sky = nullptr; // Free using ->Drop()
861 Minimap *mapper = nullptr;
867 This class does take ownership/responsibily for cleaning up etc of any of
868 these items (e.g. device)
870 IrrlichtDevice *device;
871 video::IVideoDriver *driver;
872 scene::ISceneManager *smgr;
874 std::string *error_message;
875 bool *reconnect_requested;
876 scene::ISceneNode *skybox;
878 bool simple_singleplayer_mode;
881 /* Pre-calculated values
883 int crack_animation_length;
885 IntervalLimiter profiler_interval;
888 * TODO: Local caching of settings is not optimal and should at some stage
889 * be updated to use a global settings object for getting thse values
890 * (as opposed to the this local caching). This can be addressed in
893 bool m_cache_doubletap_jump;
894 bool m_cache_enable_clouds;
895 bool m_cache_enable_joysticks;
896 bool m_cache_enable_particles;
897 bool m_cache_enable_fog;
898 bool m_cache_enable_noclip;
899 bool m_cache_enable_free_move;
900 f32 m_cache_mouse_sensitivity;
901 f32 m_cache_joystick_frustum_sensitivity;
902 f32 m_repeat_place_time;
903 f32 m_cache_cam_smoothing;
904 f32 m_cache_fog_start;
906 bool m_invert_mouse = false;
907 bool m_first_loop_after_window_activation = false;
908 bool m_camera_offset_changed = false;
910 bool m_does_lost_focus_pause_game = false;
912 int m_reset_HW_buffer_counter = 0;
914 bool m_cache_hold_aux1;
915 bool m_android_chat_open;
920 m_chat_log_buf(g_logger),
921 m_game_ui(new GameUI())
923 g_settings->registerChangedCallback("doubletap_jump",
924 &settingChangedCallback, this);
925 g_settings->registerChangedCallback("enable_clouds",
926 &settingChangedCallback, this);
927 g_settings->registerChangedCallback("doubletap_joysticks",
928 &settingChangedCallback, this);
929 g_settings->registerChangedCallback("enable_particles",
930 &settingChangedCallback, this);
931 g_settings->registerChangedCallback("enable_fog",
932 &settingChangedCallback, this);
933 g_settings->registerChangedCallback("mouse_sensitivity",
934 &settingChangedCallback, this);
935 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
936 &settingChangedCallback, this);
937 g_settings->registerChangedCallback("repeat_place_time",
938 &settingChangedCallback, this);
939 g_settings->registerChangedCallback("noclip",
940 &settingChangedCallback, this);
941 g_settings->registerChangedCallback("free_move",
942 &settingChangedCallback, this);
943 g_settings->registerChangedCallback("cinematic",
944 &settingChangedCallback, this);
945 g_settings->registerChangedCallback("cinematic_camera_smoothing",
946 &settingChangedCallback, this);
947 g_settings->registerChangedCallback("camera_smoothing",
948 &settingChangedCallback, this);
953 m_cache_hold_aux1 = false; // This is initialised properly later
960 /****************************************************************************
962 ****************************************************************************/
971 delete server; // deleted first to stop all server threads
979 delete nodedef_manager;
980 delete itemdef_manager;
983 extendedResourceCleanup();
985 g_settings->deregisterChangedCallback("doubletap_jump",
986 &settingChangedCallback, this);
987 g_settings->deregisterChangedCallback("enable_clouds",
988 &settingChangedCallback, this);
989 g_settings->deregisterChangedCallback("enable_particles",
990 &settingChangedCallback, this);
991 g_settings->deregisterChangedCallback("enable_fog",
992 &settingChangedCallback, this);
993 g_settings->deregisterChangedCallback("mouse_sensitivity",
994 &settingChangedCallback, this);
995 g_settings->deregisterChangedCallback("repeat_place_time",
996 &settingChangedCallback, this);
997 g_settings->deregisterChangedCallback("noclip",
998 &settingChangedCallback, this);
999 g_settings->deregisterChangedCallback("free_move",
1000 &settingChangedCallback, this);
1001 g_settings->deregisterChangedCallback("cinematic",
1002 &settingChangedCallback, this);
1003 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
1004 &settingChangedCallback, this);
1005 g_settings->deregisterChangedCallback("camera_smoothing",
1006 &settingChangedCallback, this);
1009 bool Game::startup(bool *kill,
1010 InputHandler *input,
1011 const GameStartData &start_data,
1012 std::string &error_message,
1014 ChatBackend *chat_backend)
1018 this->device = RenderingEngine::get_raw_device();
1020 this->error_message = &error_message;
1021 this->reconnect_requested = reconnect;
1022 this->input = input;
1023 this->chat_backend = chat_backend;
1024 this->simple_singleplayer_mode = start_data.isSinglePlayer();
1026 input->keycache.populate();
1028 driver = device->getVideoDriver();
1029 smgr = RenderingEngine::get_scene_manager();
1031 RenderingEngine::get_scene_manager()->getParameters()->
1032 setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1035 runData = GameRunData();
1036 runData.time_from_last_punch = 10.0;
1038 m_game_ui->initFlags();
1040 m_invert_mouse = g_settings->getBool("invert_mouse");
1041 m_first_loop_after_window_activation = true;
1043 g_client_translations->clear();
1045 // address can change if simple_singleplayer_mode
1046 if (!init(start_data.world_spec.path, start_data.address,
1047 start_data.socket_port, start_data.game_spec))
1050 if (!createClient(start_data))
1053 RenderingEngine::initialize(client, hud);
1061 ProfilerGraph graph;
1062 RunStats stats = { 0 };
1063 CameraOrientation cam_view_target = { 0 };
1064 CameraOrientation cam_view = { 0 };
1065 FpsControl draw_times = { 0 };
1066 f32 dtime; // in seconds
1068 /* Clear the profiler */
1069 Profiler::GraphValues dummyvalues;
1070 g_profiler->graphGet(dummyvalues);
1072 draw_times.last_time = RenderingEngine::get_timer_time();
1074 set_light_table(g_settings->getFloat("display_gamma"));
1077 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1078 && client->checkPrivilege("fast");
1081 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1082 g_settings->getU16("screen_h"));
1084 while (RenderingEngine::run()
1085 && !(*kill || g_gamecallback->shutdown_requested
1086 || (server && server->isShutdownRequested()))) {
1088 const irr::core::dimension2d<u32> ¤t_screen_size =
1089 RenderingEngine::get_video_driver()->getScreenSize();
1090 // Verify if window size has changed and save it if it's the case
1091 // Ensure evaluating settings->getBool after verifying screensize
1092 // First condition is cheaper
1093 if (previous_screen_size != current_screen_size &&
1094 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1095 g_settings->getBool("autosave_screensize")) {
1096 g_settings->setU16("screen_w", current_screen_size.Width);
1097 g_settings->setU16("screen_h", current_screen_size.Height);
1098 previous_screen_size = current_screen_size;
1101 // Calculate dtime =
1102 // RenderingEngine::run() from this iteration
1103 // + Sleep time until the wanted FPS are reached
1104 limitFps(&draw_times, &dtime);
1106 // Prepare render data for next iteration
1108 updateStats(&stats, draw_times, dtime);
1109 updateInteractTimers(dtime);
1111 if (!checkConnection())
1113 if (!handleCallbacks())
1118 m_game_ui->clearInfoText();
1119 hud->resizeHotbar();
1121 updateProfilers(stats, draw_times, dtime);
1122 processUserInput(dtime);
1123 // Update camera before player movement to avoid camera lag of one frame
1124 updateCameraDirection(&cam_view_target, dtime);
1125 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1126 cam_view.camera_yaw) * m_cache_cam_smoothing;
1127 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1128 cam_view.camera_pitch) * m_cache_cam_smoothing;
1129 updatePlayerControl(cam_view);
1131 processClientEvents(&cam_view_target);
1132 updateCamera(draw_times.busy_time, dtime);
1134 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
1135 m_game_ui->m_flags.show_debug);
1136 updateFrame(&graph, &stats, dtime, cam_view);
1137 updateProfilerGraphs(&graph);
1139 // Update if minimap has been disabled by the server
1140 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1142 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1149 void Game::shutdown()
1151 RenderingEngine::finalize();
1152 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1153 if (g_settings->get("3d_mode") == "pageflip") {
1154 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1157 auto formspec = m_game_ui->getFormspecGUI();
1159 formspec->quitMenu();
1161 #ifdef HAVE_TOUCHSCREENGUI
1162 g_touchscreengui->hide();
1165 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1170 if (gui_chat_console)
1171 gui_chat_console->drop();
1177 while (g_menumgr.menuCount() > 0) {
1178 g_menumgr.m_stack.front()->setVisible(false);
1179 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1182 m_game_ui->deleteFormspec();
1184 chat_backend->addMessage(L"", L"# Disconnected.");
1185 chat_backend->addMessage(L"", L"");
1186 m_chat_log_buf.clear();
1190 while (!client->isShutdown()) {
1191 assert(texture_src != NULL);
1192 assert(shader_src != NULL);
1193 texture_src->processQueue();
1194 shader_src->processQueue();
1201 /****************************************************************************/
1202 /****************************************************************************
1204 ****************************************************************************/
1205 /****************************************************************************/
1208 const std::string &map_dir,
1209 const std::string &address,
1211 const SubgameSpec &gamespec)
1213 texture_src = createTextureSource();
1215 showOverlayMessage(N_("Loading..."), 0, 0);
1217 shader_src = createShaderSource();
1219 itemdef_manager = createItemDefManager();
1220 nodedef_manager = createNodeDefManager();
1222 eventmgr = new EventManager();
1223 quicktune = new QuicktuneShortcutter();
1225 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1226 && eventmgr && quicktune))
1232 // Create a server if not connecting to an existing one
1233 if (address.empty()) {
1234 if (!createSingleplayerServer(map_dir, gamespec, port))
1241 bool Game::initSound()
1244 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1245 infostream << "Attempting to use OpenAL audio" << std::endl;
1246 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1248 infostream << "Failed to initialize OpenAL audio" << std::endl;
1250 infostream << "Sound disabled." << std::endl;
1254 infostream << "Using dummy audio." << std::endl;
1255 sound = &dummySoundManager;
1256 sound_is_dummy = true;
1259 soundmaker = new SoundMaker(sound, nodedef_manager);
1263 soundmaker->registerReceiver(eventmgr);
1268 bool Game::createSingleplayerServer(const std::string &map_dir,
1269 const SubgameSpec &gamespec, u16 port)
1271 showOverlayMessage(N_("Creating server..."), 0, 5);
1273 std::string bind_str = g_settings->get("bind_address");
1274 Address bind_addr(0, 0, 0, 0, port);
1276 if (g_settings->getBool("ipv6_server")) {
1277 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1281 bind_addr.Resolve(bind_str.c_str());
1282 } catch (ResolveError &e) {
1283 infostream << "Resolving bind address \"" << bind_str
1284 << "\" failed: " << e.what()
1285 << " -- Listening on all addresses." << std::endl;
1288 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1289 *error_message = "Unable to listen on " +
1290 bind_addr.serializeString() +
1291 " because IPv6 is disabled";
1292 errorstream << *error_message << std::endl;
1296 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1297 false, nullptr, error_message);
1303 bool Game::createClient(const GameStartData &start_data)
1305 showOverlayMessage(N_("Creating client..."), 0, 10);
1307 draw_control = new MapDrawControl;
1311 bool could_connect, connect_aborted;
1312 #ifdef HAVE_TOUCHSCREENGUI
1313 if (g_touchscreengui) {
1314 g_touchscreengui->init(texture_src);
1315 g_touchscreengui->hide();
1318 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1321 if (!could_connect) {
1322 if (error_message->empty() && !connect_aborted) {
1323 // Should not happen if error messages are set properly
1324 *error_message = "Connection failed for unknown reason";
1325 errorstream << *error_message << std::endl;
1330 if (!getServerContent(&connect_aborted)) {
1331 if (error_message->empty() && !connect_aborted) {
1332 // Should not happen if error messages are set properly
1333 *error_message = "Connection failed for unknown reason";
1334 errorstream << *error_message << std::endl;
1339 GameGlobalShaderConstantSetterFactory *scsf = new GameGlobalShaderConstantSetterFactory(
1340 &m_flags.force_fog_off, &runData.fog_range, client);
1341 shader_src->addShaderConstantSetterFactory(scsf);
1343 // Update cached textures, meshes and materials
1344 client->afterContentReceived();
1348 camera = new Camera(*draw_control, client);
1349 if (!camera || !camera->successfullyCreated(*error_message))
1351 client->setCamera(camera);
1355 if (m_cache_enable_clouds) {
1356 clouds = new Clouds(smgr, -1, time(0));
1358 *error_message = "Memory allocation error (clouds)";
1359 errorstream << *error_message << std::endl;
1366 sky = new Sky(-1, texture_src);
1368 skybox = NULL; // This is used/set later on in the main run loop
1371 *error_message = "Memory allocation error sky";
1372 errorstream << *error_message << std::endl;
1376 /* Pre-calculated values
1378 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1380 v2u32 size = t->getOriginalSize();
1381 crack_animation_length = size.Y / size.X;
1383 crack_animation_length = 5;
1389 /* Set window caption
1391 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1393 str += utf8_to_wide(g_version_hash);
1395 str += driver->getName();
1397 device->setWindowCaption(str.c_str());
1399 LocalPlayer *player = client->getEnv().getLocalPlayer();
1400 player->hurt_tilt_timer = 0;
1401 player->hurt_tilt_strength = 0;
1403 hud = new Hud(guienv, client, player, &player->inventory);
1406 *error_message = "Memory error: could not create HUD";
1407 errorstream << *error_message << std::endl;
1411 mapper = client->getMinimap();
1413 if (mapper && client->modsLoaded())
1414 client->getScript()->on_minimap_ready(mapper);
1419 bool Game::initGui()
1423 // Remove stale "recent" chat messages from previous connections
1424 chat_backend->clearRecentChat();
1426 // Make sure the size of the recent messages buffer is right
1427 chat_backend->applySettings();
1429 // Chat backend and console
1430 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1431 -1, chat_backend, client, &g_menumgr);
1432 if (!gui_chat_console) {
1433 *error_message = "Could not allocate memory for chat console";
1434 errorstream << *error_message << std::endl;
1438 #ifdef HAVE_TOUCHSCREENGUI
1440 if (g_touchscreengui)
1441 g_touchscreengui->show();
1448 bool Game::connectToServer(const GameStartData &start_data,
1449 bool *connect_ok, bool *connection_aborted)
1451 *connect_ok = false; // Let's not be overly optimistic
1452 *connection_aborted = false;
1453 bool local_server_mode = false;
1455 showOverlayMessage(N_("Resolving address..."), 0, 15);
1457 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1460 connect_address.Resolve(start_data.address.c_str());
1462 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1463 //connect_address.Resolve("localhost");
1464 if (connect_address.isIPv6()) {
1465 IPv6AddressBytes addr_bytes;
1466 addr_bytes.bytes[15] = 1;
1467 connect_address.setAddress(&addr_bytes);
1469 connect_address.setAddress(127, 0, 0, 1);
1471 local_server_mode = true;
1473 } catch (ResolveError &e) {
1474 *error_message = std::string("Couldn't resolve address: ") + e.what();
1475 errorstream << *error_message << std::endl;
1479 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1480 *error_message = "Unable to connect to " +
1481 connect_address.serializeString() +
1482 " because IPv6 is disabled";
1483 errorstream << *error_message << std::endl;
1487 client = new Client(start_data.name.c_str(),
1488 start_data.password, start_data.address,
1489 *draw_control, texture_src, shader_src,
1490 itemdef_manager, nodedef_manager, sound, eventmgr,
1491 connect_address.isIPv6(), m_game_ui.get());
1496 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1498 infostream << "Connecting to server at ";
1499 connect_address.print(&infostream);
1500 infostream << std::endl;
1502 client->connect(connect_address,
1503 simple_singleplayer_mode || local_server_mode);
1506 Wait for server to accept connection
1512 FpsControl fps_control = { 0 };
1514 f32 wait_time = 0; // in seconds
1516 fps_control.last_time = RenderingEngine::get_timer_time();
1518 while (RenderingEngine::run()) {
1520 limitFps(&fps_control, &dtime);
1522 // Update client and server
1523 client->step(dtime);
1526 server->step(dtime);
1529 if (client->getState() == LC_Init) {
1535 if (*connection_aborted)
1538 if (client->accessDenied()) {
1539 *error_message = "Access denied. Reason: "
1540 + client->accessDeniedReason();
1541 *reconnect_requested = client->reconnectRequested();
1542 errorstream << *error_message << std::endl;
1546 if (input->cancelPressed()) {
1547 *connection_aborted = true;
1548 infostream << "Connect aborted [Escape]" << std::endl;
1552 if (client->m_is_registration_confirmation_state) {
1553 if (registration_confirmation_shown) {
1554 // Keep drawing the GUI
1555 RenderingEngine::draw_menu_scene(guienv, dtime, true);
1557 registration_confirmation_shown = true;
1558 (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
1559 &g_menumgr, client, start_data.name, start_data.password,
1560 connection_aborted, texture_src))->drop();
1564 // Only time out if we aren't waiting for the server we started
1565 if (!start_data.isSinglePlayer() && wait_time > 10) {
1566 *error_message = "Connection timed out.";
1567 errorstream << *error_message << std::endl;
1572 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1575 } catch (con::PeerNotFoundException &e) {
1576 // TODO: Should something be done here? At least an info/error
1584 bool Game::getServerContent(bool *aborted)
1588 FpsControl fps_control = { 0 };
1589 f32 dtime; // in seconds
1591 fps_control.last_time = RenderingEngine::get_timer_time();
1593 while (RenderingEngine::run()) {
1595 limitFps(&fps_control, &dtime);
1597 // Update client and server
1598 client->step(dtime);
1601 server->step(dtime);
1604 if (client->mediaReceived() && client->itemdefReceived() &&
1605 client->nodedefReceived()) {
1610 if (!checkConnection())
1613 if (client->getState() < LC_Init) {
1614 *error_message = "Client disconnected";
1615 errorstream << *error_message << std::endl;
1619 if (input->cancelPressed()) {
1621 infostream << "Connect aborted [Escape]" << std::endl;
1628 if (!client->itemdefReceived()) {
1629 const wchar_t *text = wgettext("Item definitions...");
1631 RenderingEngine::draw_load_screen(text, guienv, texture_src,
1634 } else if (!client->nodedefReceived()) {
1635 const wchar_t *text = wgettext("Node definitions...");
1637 RenderingEngine::draw_load_screen(text, guienv, texture_src,
1641 std::stringstream message;
1642 std::fixed(message);
1643 message.precision(0);
1644 float receive = client->mediaReceiveProgress() * 100;
1645 message << gettext("Media...");
1647 message << " " << receive << "%";
1648 message.precision(2);
1650 if ((USE_CURL == 0) ||
1651 (!g_settings->getBool("enable_remote_media_server"))) {
1652 float cur = client->getCurRate();
1653 std::string cur_unit = gettext("KiB/s");
1657 cur_unit = gettext("MiB/s");
1660 message << " (" << cur << ' ' << cur_unit << ")";
1663 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1664 RenderingEngine::draw_load_screen(utf8_to_wide(message.str()), guienv,
1665 texture_src, dtime, progress);
1673 /****************************************************************************/
1674 /****************************************************************************
1676 ****************************************************************************/
1677 /****************************************************************************/
1679 inline void Game::updateInteractTimers(f32 dtime)
1681 if (runData.nodig_delay_timer >= 0)
1682 runData.nodig_delay_timer -= dtime;
1684 if (runData.object_hit_delay_timer >= 0)
1685 runData.object_hit_delay_timer -= dtime;
1687 runData.time_from_last_punch += dtime;
1691 /* returns false if game should exit, otherwise true
1693 inline bool Game::checkConnection()
1695 if (client->accessDenied()) {
1696 *error_message = "Access denied. Reason: "
1697 + client->accessDeniedReason();
1698 *reconnect_requested = client->reconnectRequested();
1699 errorstream << *error_message << std::endl;
1707 /* returns false if game should exit, otherwise true
1709 inline bool Game::handleCallbacks()
1711 if (g_gamecallback->disconnect_requested) {
1712 g_gamecallback->disconnect_requested = false;
1716 if (g_gamecallback->changepassword_requested) {
1717 (new GUIPasswordChange(guienv, guiroot, -1,
1718 &g_menumgr, client, texture_src))->drop();
1719 g_gamecallback->changepassword_requested = false;
1722 if (g_gamecallback->changevolume_requested) {
1723 (new GUIVolumeChange(guienv, guiroot, -1,
1724 &g_menumgr, texture_src))->drop();
1725 g_gamecallback->changevolume_requested = false;
1728 if (g_gamecallback->keyconfig_requested) {
1729 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1730 &g_menumgr, texture_src))->drop();
1731 g_gamecallback->keyconfig_requested = false;
1734 if (g_gamecallback->keyconfig_changed) {
1735 input->keycache.populate(); // update the cache with new settings
1736 g_gamecallback->keyconfig_changed = false;
1743 void Game::processQueues()
1745 texture_src->processQueue();
1746 itemdef_manager->processQueue(client);
1747 shader_src->processQueue();
1751 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1754 float profiler_print_interval =
1755 g_settings->getFloat("profiler_print_interval");
1756 bool print_to_log = true;
1758 if (profiler_print_interval == 0) {
1759 print_to_log = false;
1760 profiler_print_interval = 3;
1763 if (profiler_interval.step(dtime, profiler_print_interval)) {
1765 infostream << "Profiler:" << std::endl;
1766 g_profiler->print(infostream);
1769 m_game_ui->updateProfiler();
1770 g_profiler->clear();
1773 // Update update graphs
1774 g_profiler->graphAdd("Time non-rendering [ms]",
1775 draw_times.busy_time - stats.drawtime);
1777 g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time);
1778 g_profiler->graphAdd("FPS", 1.0f / dtime);
1781 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1788 /* Time average and jitter calculation
1790 jp = &stats->dtime_jitter;
1791 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1793 jitter = dtime - jp->avg;
1795 if (jitter > jp->max)
1798 jp->counter += dtime;
1800 if (jp->counter > 0.0) {
1802 jp->max_sample = jp->max;
1803 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1807 /* Busytime average and jitter calculation
1809 jp = &stats->busy_time_jitter;
1810 jp->avg = jp->avg + draw_times.busy_time * 0.02;
1812 jitter = draw_times.busy_time - jp->avg;
1814 if (jitter > jp->max)
1816 if (jitter < jp->min)
1819 jp->counter += dtime;
1821 if (jp->counter > 0.0) {
1823 jp->max_sample = jp->max;
1824 jp->min_sample = jp->min;
1832 /****************************************************************************
1834 ****************************************************************************/
1836 void Game::processUserInput(f32 dtime)
1838 // Reset input if window not active or some menu is active
1839 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1841 #ifdef HAVE_TOUCHSCREENGUI
1842 g_touchscreengui->hide();
1845 #ifdef HAVE_TOUCHSCREENGUI
1846 else if (g_touchscreengui) {
1847 /* on touchscreengui step may generate own input events which ain't
1848 * what we want in case we just did clear them */
1849 g_touchscreengui->step(dtime);
1853 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1854 gui_chat_console->closeConsoleAtOnce();
1857 // Input handler step() (used by the random input generator)
1861 auto formspec = m_game_ui->getFormspecGUI();
1863 formspec->getAndroidUIInput();
1865 handleAndroidChatInput();
1868 // Increase timer for double tap of "keymap_jump"
1869 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1870 runData.jump_timer += dtime;
1873 processItemSelection(&runData.new_playeritem);
1877 void Game::processKeyInput()
1879 if (wasKeyDown(KeyType::DROP)) {
1880 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1881 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1882 toggleAutoforward();
1883 } else if (wasKeyDown(KeyType::BACKWARD)) {
1884 if (g_settings->getBool("continuous_forward"))
1885 toggleAutoforward();
1886 } else if (wasKeyDown(KeyType::INVENTORY)) {
1888 } else if (input->cancelPressed()) {
1890 m_android_chat_open = false;
1892 if (!gui_chat_console->isOpenInhibited()) {
1895 } else if (wasKeyDown(KeyType::CHAT)) {
1896 openConsole(0.2, L"");
1897 } else if (wasKeyDown(KeyType::CMD)) {
1898 openConsole(0.2, L"/");
1899 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1900 if (client->modsLoaded())
1901 openConsole(0.2, L".");
1903 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1904 } else if (wasKeyDown(KeyType::CONSOLE)) {
1905 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1906 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1908 } else if (wasKeyDown(KeyType::JUMP)) {
1909 toggleFreeMoveAlt();
1910 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1912 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1914 } else if (wasKeyDown(KeyType::NOCLIP)) {
1917 } else if (wasKeyDown(KeyType::MUTE)) {
1918 if (g_settings->getBool("enable_sound")) {
1919 bool new_mute_sound = !g_settings->getBool("mute_sound");
1920 g_settings->setBool("mute_sound", new_mute_sound);
1922 m_game_ui->showTranslatedStatusText("Sound muted");
1924 m_game_ui->showTranslatedStatusText("Sound unmuted");
1926 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1928 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1929 if (g_settings->getBool("enable_sound")) {
1930 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1932 g_settings->setFloat("sound_volume", new_volume);
1933 const wchar_t *str = wgettext("Volume changed to %d%%");
1934 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1936 m_game_ui->showStatusText(buf);
1938 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1940 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1941 if (g_settings->getBool("enable_sound")) {
1942 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1944 g_settings->setFloat("sound_volume", new_volume);
1945 const wchar_t *str = wgettext("Volume changed to %d%%");
1946 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1948 m_game_ui->showStatusText(buf);
1950 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1953 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1954 || wasKeyDown(KeyType::DEC_VOLUME)) {
1955 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1957 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1959 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1960 client->makeScreenshot();
1961 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1962 m_game_ui->toggleHud();
1963 } else if (wasKeyDown(KeyType::MINIMAP)) {
1964 toggleMinimap(isKeyDown(KeyType::SNEAK));
1965 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1966 m_game_ui->toggleChat();
1967 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1969 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1970 toggleUpdateCamera();
1971 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1973 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1974 m_game_ui->toggleProfiler();
1975 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1976 increaseViewRange();
1977 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1978 decreaseViewRange();
1979 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1980 toggleFullViewRange();
1981 } else if (wasKeyDown(KeyType::ZOOM)) {
1983 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1985 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1987 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1989 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1993 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1994 runData.reset_jump_timer = false;
1995 runData.jump_timer = 0.0f;
1998 if (quicktune->hasMessage()) {
1999 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2003 void Game::processItemSelection(u16 *new_playeritem)
2005 LocalPlayer *player = client->getEnv().getLocalPlayer();
2007 /* Item selection using mouse wheel
2009 *new_playeritem = player->getWieldIndex();
2011 s32 wheel = input->getMouseWheel();
2012 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2013 player->hud_hotbar_itemcount - 1);
2017 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2020 if (wasKeyDown(KeyType::HOTBAR_PREV))
2024 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2026 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2029 /* Item selection using hotbar slot keys
2031 for (u16 i = 0; i <= max_item; i++) {
2032 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2033 *new_playeritem = i;
2040 void Game::dropSelectedItem(bool single_item)
2042 IDropAction *a = new IDropAction();
2043 a->count = single_item ? 1 : 0;
2044 a->from_inv.setCurrentPlayer();
2045 a->from_list = "main";
2046 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2047 client->inventoryAction(a);
2051 void Game::openInventory()
2054 * Don't permit to open inventory is CAO or player doesn't exists.
2055 * This prevent showing an empty inventory at player load
2058 LocalPlayer *player = client->getEnv().getLocalPlayer();
2059 if (!player || !player->getCAO())
2062 infostream << "Game: Launching inventory" << std::endl;
2064 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2066 InventoryLocation inventoryloc;
2067 inventoryloc.setCurrentPlayer();
2069 if (!client->modsLoaded()
2070 || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2071 TextDest *txt_dst = new TextDestPlayerInventory(client);
2072 auto *&formspec = m_game_ui->updateFormspec("");
2073 GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
2074 txt_dst, client->getFormspecPrepend());
2076 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2081 void Game::openConsole(float scale, const wchar_t *line)
2083 assert(scale > 0.0f && scale <= 1.0f);
2086 porting::showInputDialog(gettext("ok"), "", "", 2);
2087 m_android_chat_open = true;
2089 if (gui_chat_console->isOpenInhibited())
2091 gui_chat_console->openConsole(scale);
2093 gui_chat_console->setCloseOnEnter(true);
2094 gui_chat_console->replaceAndAddToHistory(line);
2100 void Game::handleAndroidChatInput()
2102 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2103 std::string text = porting::getInputDialogValue();
2104 client->typeChatMessage(utf8_to_wide(text));
2105 m_android_chat_open = false;
2111 void Game::toggleFreeMove()
2113 bool free_move = !g_settings->getBool("free_move");
2114 g_settings->set("free_move", bool_to_cstr(free_move));
2117 if (client->checkPrivilege("fly")) {
2118 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2120 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2123 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2127 void Game::toggleFreeMoveAlt()
2129 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2132 runData.reset_jump_timer = true;
2136 void Game::togglePitchMove()
2138 bool pitch_move = !g_settings->getBool("pitch_move");
2139 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2142 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2144 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2149 void Game::toggleFast()
2151 bool fast_move = !g_settings->getBool("fast_move");
2152 bool has_fast_privs = client->checkPrivilege("fast");
2153 g_settings->set("fast_move", bool_to_cstr(fast_move));
2156 if (has_fast_privs) {
2157 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2159 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2162 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2166 m_cache_hold_aux1 = fast_move && has_fast_privs;
2171 void Game::toggleNoClip()
2173 bool noclip = !g_settings->getBool("noclip");
2174 g_settings->set("noclip", bool_to_cstr(noclip));
2177 if (client->checkPrivilege("noclip")) {
2178 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2180 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2183 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2187 void Game::toggleCinematic()
2189 bool cinematic = !g_settings->getBool("cinematic");
2190 g_settings->set("cinematic", bool_to_cstr(cinematic));
2193 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2195 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2198 // Autoforward by toggling continuous forward.
2199 void Game::toggleAutoforward()
2201 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2202 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2204 if (autorun_enabled)
2205 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2207 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2210 void Game::toggleMinimap(bool shift_pressed)
2212 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2216 mapper->toggleMinimapShape();
2220 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2222 // Not so satisying code to keep compatibility with old fixed mode system
2224 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2226 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2227 m_game_ui->m_flags.show_minimap = false;
2230 // If radar is disabled, try to find a non radar mode or fall back to 0
2231 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2232 while (mapper->getModeIndex() &&
2233 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2236 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2240 // End of 'not so satifying code'
2241 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2242 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2243 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2245 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2248 void Game::toggleFog()
2250 bool fog_enabled = g_settings->getBool("enable_fog");
2251 g_settings->setBool("enable_fog", !fog_enabled);
2253 m_game_ui->showTranslatedStatusText("Fog disabled");
2255 m_game_ui->showTranslatedStatusText("Fog enabled");
2259 void Game::toggleDebug()
2261 // Initial / 4x toggle: Chat only
2262 // 1x toggle: Debug text with chat
2263 // 2x toggle: Debug text with profiler graph
2264 // 3x toggle: Debug text and wireframe
2265 if (!m_game_ui->m_flags.show_debug) {
2266 m_game_ui->m_flags.show_debug = true;
2267 m_game_ui->m_flags.show_profiler_graph = false;
2268 draw_control->show_wireframe = false;
2269 m_game_ui->showTranslatedStatusText("Debug info shown");
2270 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2271 m_game_ui->m_flags.show_profiler_graph = true;
2272 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2273 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2274 m_game_ui->m_flags.show_profiler_graph = false;
2275 draw_control->show_wireframe = true;
2276 m_game_ui->showTranslatedStatusText("Wireframe shown");
2278 m_game_ui->m_flags.show_debug = false;
2279 m_game_ui->m_flags.show_profiler_graph = false;
2280 draw_control->show_wireframe = false;
2281 if (client->checkPrivilege("debug")) {
2282 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2284 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2290 void Game::toggleUpdateCamera()
2292 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2293 if (m_flags.disable_camera_update)
2294 m_game_ui->showTranslatedStatusText("Camera update disabled");
2296 m_game_ui->showTranslatedStatusText("Camera update enabled");
2300 void Game::increaseViewRange()
2302 s16 range = g_settings->getS16("viewing_range");
2303 s16 range_new = range + 10;
2307 if (range_new > 4000) {
2309 str = wgettext("Viewing range is at maximum: %d");
2310 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2312 m_game_ui->showStatusText(buf);
2315 str = wgettext("Viewing range changed to %d");
2316 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2318 m_game_ui->showStatusText(buf);
2320 g_settings->set("viewing_range", itos(range_new));
2324 void Game::decreaseViewRange()
2326 s16 range = g_settings->getS16("viewing_range");
2327 s16 range_new = range - 10;
2331 if (range_new < 20) {
2333 str = wgettext("Viewing range is at minimum: %d");
2334 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2336 m_game_ui->showStatusText(buf);
2338 str = wgettext("Viewing range changed to %d");
2339 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2341 m_game_ui->showStatusText(buf);
2343 g_settings->set("viewing_range", itos(range_new));
2347 void Game::toggleFullViewRange()
2349 draw_control->range_all = !draw_control->range_all;
2350 if (draw_control->range_all)
2351 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2353 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2357 void Game::checkZoomEnabled()
2359 LocalPlayer *player = client->getEnv().getLocalPlayer();
2360 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2361 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2365 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2367 if ((device->isWindowActive() && device->isWindowFocused()
2368 && !isMenuActive()) || input->isRandom()) {
2371 if (!input->isRandom()) {
2372 // Mac OSX gets upset if this is set every frame
2373 if (device->getCursorControl()->isVisible())
2374 device->getCursorControl()->setVisible(false);
2378 if (m_first_loop_after_window_activation) {
2379 m_first_loop_after_window_activation = false;
2381 input->setMousePos(driver->getScreenSize().Width / 2,
2382 driver->getScreenSize().Height / 2);
2384 updateCameraOrientation(cam, dtime);
2390 // Mac OSX gets upset if this is set every frame
2391 if (!device->getCursorControl()->isVisible())
2392 device->getCursorControl()->setVisible(true);
2395 m_first_loop_after_window_activation = true;
2400 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2402 #ifdef HAVE_TOUCHSCREENGUI
2403 if (g_touchscreengui) {
2404 cam->camera_yaw += g_touchscreengui->getYawChange();
2405 cam->camera_pitch = g_touchscreengui->getPitch();
2408 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2409 v2s32 dist = input->getMousePos() - center;
2411 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2415 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity;
2416 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity;
2418 if (dist.X != 0 || dist.Y != 0)
2419 input->setMousePos(center.X, center.Y);
2420 #ifdef HAVE_TOUCHSCREENGUI
2424 if (m_cache_enable_joysticks) {
2425 f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime;
2426 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2427 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2430 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2434 void Game::updatePlayerControl(const CameraOrientation &cam)
2436 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2438 // DO NOT use the isKeyDown method for the forward, backward, left, right
2439 // buttons, as the code that uses the controls needs to be able to
2440 // distinguish between the two in order to know when to use joysticks.
2442 PlayerControl control(
2443 input->isKeyDown(KeyType::FORWARD),
2444 input->isKeyDown(KeyType::BACKWARD),
2445 input->isKeyDown(KeyType::LEFT),
2446 input->isKeyDown(KeyType::RIGHT),
2447 isKeyDown(KeyType::JUMP),
2448 isKeyDown(KeyType::SPECIAL1),
2449 isKeyDown(KeyType::SNEAK),
2450 isKeyDown(KeyType::ZOOM),
2451 isKeyDown(KeyType::DIG),
2452 isKeyDown(KeyType::PLACE),
2455 input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE),
2456 input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE)
2459 u32 keypress_bits = (
2460 ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) |
2461 ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) |
2462 ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) |
2463 ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) |
2464 ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) |
2465 ( (u32)(isKeyDown(KeyType::SPECIAL1) & 0x1) << 5) |
2466 ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) |
2467 ( (u32)(isKeyDown(KeyType::DIG) & 0x1) << 7) |
2468 ( (u32)(isKeyDown(KeyType::PLACE) & 0x1) << 8) |
2469 ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9)
2473 /* For Android, simulate holding down AUX1 (fast move) if the user has
2474 * the fast_move setting toggled on. If there is an aux1 key defined for
2475 * Android then its meaning is inverted (i.e. holding aux1 means walk and
2478 if (m_cache_hold_aux1) {
2479 control.aux1 = control.aux1 ^ true;
2480 keypress_bits ^= ((u32)(1U << 5));
2484 LocalPlayer *player = client->getEnv().getLocalPlayer();
2486 // autojump if set: simulate "jump" key
2487 if (player->getAutojump()) {
2488 control.jump = true;
2489 keypress_bits |= 1U << 4;
2492 // autoforward if set: simulate "up" key
2493 if (player->getPlayerSettings().continuous_forward &&
2494 client->activeObjectsReceived() && !player->isDead()) {
2496 keypress_bits |= 1U << 0;
2499 client->setPlayerControl(control);
2500 player->keyPressed = keypress_bits;
2506 inline void Game::step(f32 *dtime)
2508 bool can_be_and_is_paused =
2509 (simple_singleplayer_mode && g_menumgr.pausesGame());
2511 if (can_be_and_is_paused) { // This is for a singleplayer server
2512 *dtime = 0; // No time passes
2515 server->step(*dtime);
2517 client->step(*dtime);
2521 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2522 {&Game::handleClientEvent_None},
2523 {&Game::handleClientEvent_PlayerDamage},
2524 {&Game::handleClientEvent_PlayerForceMove},
2525 {&Game::handleClientEvent_Deathscreen},
2526 {&Game::handleClientEvent_ShowFormSpec},
2527 {&Game::handleClientEvent_ShowLocalFormSpec},
2528 {&Game::handleClientEvent_HandleParticleEvent},
2529 {&Game::handleClientEvent_HandleParticleEvent},
2530 {&Game::handleClientEvent_HandleParticleEvent},
2531 {&Game::handleClientEvent_HudAdd},
2532 {&Game::handleClientEvent_HudRemove},
2533 {&Game::handleClientEvent_HudChange},
2534 {&Game::handleClientEvent_SetSky},
2535 {&Game::handleClientEvent_SetSun},
2536 {&Game::handleClientEvent_SetMoon},
2537 {&Game::handleClientEvent_SetStars},
2538 {&Game::handleClientEvent_OverrideDayNigthRatio},
2539 {&Game::handleClientEvent_CloudParams},
2542 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2544 FATAL_ERROR("ClientEvent type None received");
2547 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2549 if (client->modsLoaded())
2550 client->getScript()->on_damage_taken(event->player_damage.amount);
2552 // Damage flash and hurt tilt are not used at death
2553 if (client->getHP() > 0) {
2554 runData.damage_flash += 95.0f + 3.2f * event->player_damage.amount;
2555 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2557 LocalPlayer *player = client->getEnv().getLocalPlayer();
2559 player->hurt_tilt_timer = 1.5f;
2560 player->hurt_tilt_strength =
2561 rangelim(event->player_damage.amount / 4.0f, 1.0f, 4.0f);
2564 // Play damage sound
2565 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2568 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2570 cam->camera_yaw = event->player_force_move.yaw;
2571 cam->camera_pitch = event->player_force_move.pitch;
2574 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2576 // If client scripting is enabled, deathscreen is handled by CSM code in
2577 // builtin/client/init.lua
2578 if (client->modsLoaded())
2579 client->getScript()->on_death();
2581 showDeathFormspec();
2583 /* Handle visualization */
2584 LocalPlayer *player = client->getEnv().getLocalPlayer();
2585 runData.damage_flash = 0;
2586 player->hurt_tilt_timer = 0;
2587 player->hurt_tilt_strength = 0;
2590 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2592 if (event->show_formspec.formspec->empty()) {
2593 auto formspec = m_game_ui->getFormspecGUI();
2594 if (formspec && (event->show_formspec.formname->empty()
2595 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2596 formspec->quitMenu();
2599 FormspecFormSource *fs_src =
2600 new FormspecFormSource(*(event->show_formspec.formspec));
2601 TextDestPlayerInventory *txt_dst =
2602 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2604 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2605 GUIFormSpecMenu::create(formspec, client, &input->joystick,
2606 fs_src, txt_dst, client->getFormspecPrepend());
2609 delete event->show_formspec.formspec;
2610 delete event->show_formspec.formname;
2613 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2615 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2616 LocalFormspecHandler *txt_dst =
2617 new LocalFormspecHandler(*event->show_formspec.formname, client);
2618 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, &input->joystick,
2619 fs_src, txt_dst, client->getFormspecPrepend());
2621 delete event->show_formspec.formspec;
2622 delete event->show_formspec.formname;
2625 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2626 CameraOrientation *cam)
2628 LocalPlayer *player = client->getEnv().getLocalPlayer();
2629 client->getParticleManager()->handleParticleEvent(event, client, player);
2632 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2634 LocalPlayer *player = client->getEnv().getLocalPlayer();
2635 auto &hud_server_to_client = client->getHUDTranslationMap();
2637 u32 server_id = event->hudadd.server_id;
2638 // ignore if we already have a HUD with that ID
2639 auto i = hud_server_to_client.find(server_id);
2640 if (i != hud_server_to_client.end()) {
2641 delete event->hudadd.pos;
2642 delete event->hudadd.name;
2643 delete event->hudadd.scale;
2644 delete event->hudadd.text;
2645 delete event->hudadd.align;
2646 delete event->hudadd.offset;
2647 delete event->hudadd.world_pos;
2648 delete event->hudadd.size;
2649 delete event->hudadd.text2;
2653 HudElement *e = new HudElement;
2654 e->type = (HudElementType)event->hudadd.type;
2655 e->pos = *event->hudadd.pos;
2656 e->name = *event->hudadd.name;
2657 e->scale = *event->hudadd.scale;
2658 e->text = *event->hudadd.text;
2659 e->number = event->hudadd.number;
2660 e->item = event->hudadd.item;
2661 e->dir = event->hudadd.dir;
2662 e->align = *event->hudadd.align;
2663 e->offset = *event->hudadd.offset;
2664 e->world_pos = *event->hudadd.world_pos;
2665 e->size = *event->hudadd.size;
2666 e->z_index = event->hudadd.z_index;
2667 e->text2 = *event->hudadd.text2;
2668 hud_server_to_client[server_id] = player->addHud(e);
2670 delete event->hudadd.pos;
2671 delete event->hudadd.name;
2672 delete event->hudadd.scale;
2673 delete event->hudadd.text;
2674 delete event->hudadd.align;
2675 delete event->hudadd.offset;
2676 delete event->hudadd.world_pos;
2677 delete event->hudadd.size;
2678 delete event->hudadd.text2;
2681 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2683 LocalPlayer *player = client->getEnv().getLocalPlayer();
2684 HudElement *e = player->removeHud(event->hudrm.id);
2688 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2690 LocalPlayer *player = client->getEnv().getLocalPlayer();
2692 u32 id = event->hudchange.id;
2693 HudElement *e = player->getHud(id);
2696 delete event->hudchange.v3fdata;
2697 delete event->hudchange.v2fdata;
2698 delete event->hudchange.sdata;
2699 delete event->hudchange.v2s32data;
2703 switch (event->hudchange.stat) {
2705 e->pos = *event->hudchange.v2fdata;
2709 e->name = *event->hudchange.sdata;
2712 case HUD_STAT_SCALE:
2713 e->scale = *event->hudchange.v2fdata;
2717 e->text = *event->hudchange.sdata;
2720 case HUD_STAT_NUMBER:
2721 e->number = event->hudchange.data;
2725 e->item = event->hudchange.data;
2729 e->dir = event->hudchange.data;
2732 case HUD_STAT_ALIGN:
2733 e->align = *event->hudchange.v2fdata;
2736 case HUD_STAT_OFFSET:
2737 e->offset = *event->hudchange.v2fdata;
2740 case HUD_STAT_WORLD_POS:
2741 e->world_pos = *event->hudchange.v3fdata;
2745 e->size = *event->hudchange.v2s32data;
2748 case HUD_STAT_Z_INDEX:
2749 e->z_index = event->hudchange.data;
2752 case HUD_STAT_TEXT2:
2753 e->text2 = *event->hudchange.sdata;
2757 delete event->hudchange.v3fdata;
2758 delete event->hudchange.v2fdata;
2759 delete event->hudchange.sdata;
2760 delete event->hudchange.v2s32data;
2763 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2765 sky->setVisible(false);
2766 // Whether clouds are visible in front of a custom skybox.
2767 sky->setCloudsEnabled(event->set_sky->clouds);
2773 // Clear the old textures out in case we switch rendering type.
2774 sky->clearSkyboxTextures();
2775 // Handle according to type
2776 if (event->set_sky->type == "regular") {
2777 // Shows the mesh skybox
2778 sky->setVisible(true);
2779 // Update mesh based skybox colours if applicable.
2780 sky->setSkyColors(event->set_sky->sky_color);
2781 sky->setHorizonTint(
2782 event->set_sky->fog_sun_tint,
2783 event->set_sky->fog_moon_tint,
2784 event->set_sky->fog_tint_type
2786 } else if (event->set_sky->type == "skybox" &&
2787 event->set_sky->textures.size() == 6) {
2788 // Disable the dyanmic mesh skybox:
2789 sky->setVisible(false);
2791 sky->setFallbackBgColor(event->set_sky->bgcolor);
2792 // Set sunrise and sunset fog tinting:
2793 sky->setHorizonTint(
2794 event->set_sky->fog_sun_tint,
2795 event->set_sky->fog_moon_tint,
2796 event->set_sky->fog_tint_type
2798 // Add textures to skybox.
2799 for (int i = 0; i < 6; i++)
2800 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2802 // Handle everything else as plain color.
2803 if (event->set_sky->type != "plain")
2804 infostream << "Unknown sky type: "
2805 << (event->set_sky->type) << std::endl;
2806 sky->setVisible(false);
2807 sky->setFallbackBgColor(event->set_sky->bgcolor);
2808 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2809 sky->setHorizonTint(
2810 event->set_sky->bgcolor,
2811 event->set_sky->bgcolor,
2815 delete event->set_sky;
2818 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2820 sky->setSunVisible(event->sun_params->visible);
2821 sky->setSunTexture(event->sun_params->texture,
2822 event->sun_params->tonemap, texture_src);
2823 sky->setSunScale(event->sun_params->scale);
2824 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2825 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2826 delete event->sun_params;
2829 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2831 sky->setMoonVisible(event->moon_params->visible);
2832 sky->setMoonTexture(event->moon_params->texture,
2833 event->moon_params->tonemap, texture_src);
2834 sky->setMoonScale(event->moon_params->scale);
2835 delete event->moon_params;
2838 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2840 sky->setStarsVisible(event->star_params->visible);
2841 sky->setStarCount(event->star_params->count, false);
2842 sky->setStarColor(event->star_params->starcolor);
2843 sky->setStarScale(event->star_params->scale);
2844 delete event->star_params;
2847 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2848 CameraOrientation *cam)
2850 client->getEnv().setDayNightRatioOverride(
2851 event->override_day_night_ratio.do_override,
2852 event->override_day_night_ratio.ratio_f * 1000.0f);
2855 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2860 clouds->setDensity(event->cloud_params.density);
2861 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2862 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2863 clouds->setHeight(event->cloud_params.height);
2864 clouds->setThickness(event->cloud_params.thickness);
2865 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2868 void Game::processClientEvents(CameraOrientation *cam)
2870 while (client->hasClientEvents()) {
2871 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2872 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2873 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2874 (this->*evHandler.handler)(event.get(), cam);
2878 void Game::updateChat(f32 dtime, const v2u32 &screensize)
2880 // Get new messages from error log buffer
2881 while (!m_chat_log_buf.empty())
2882 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2884 // Get new messages from client
2885 std::wstring message;
2886 while (client->getChatMessage(message)) {
2887 chat_backend->addUnparsedMessage(message);
2890 // Remove old messages
2891 chat_backend->step(dtime);
2893 // Display all messages in a static text element
2894 m_game_ui->setChatText(chat_backend->getRecentChat(),
2895 chat_backend->getRecentBuffer().getLineCount());
2898 void Game::updateCamera(u32 busy_time, f32 dtime)
2900 LocalPlayer *player = client->getEnv().getLocalPlayer();
2903 For interaction purposes, get info about the held item
2905 - Is it a usable item?
2906 - Can it point to liquids?
2908 ItemStack playeritem;
2910 ItemStack selected, hand;
2911 playeritem = player->getWieldedItem(&selected, &hand);
2914 ToolCapabilities playeritem_toolcap =
2915 playeritem.getToolCapabilities(itemdef_manager);
2917 v3s16 old_camera_offset = camera->getOffset();
2919 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2920 GenericCAO *playercao = player->getCAO();
2922 // If playercao not loaded, don't change camera
2926 camera->toggleCameraMode();
2928 // Make the player visible depending on camera mode.
2929 playercao->updateMeshCulling();
2930 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2933 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2934 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2936 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2937 camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
2938 camera->step(dtime);
2940 v3f camera_position = camera->getPosition();
2941 v3f camera_direction = camera->getDirection();
2942 f32 camera_fov = camera->getFovMax();
2943 v3s16 camera_offset = camera->getOffset();
2945 m_camera_offset_changed = (camera_offset != old_camera_offset);
2947 if (!m_flags.disable_camera_update) {
2948 client->getEnv().getClientMap().updateCamera(camera_position,
2949 camera_direction, camera_fov, camera_offset);
2951 if (m_camera_offset_changed) {
2952 client->updateCameraOffset(camera_offset);
2953 client->getEnv().updateCameraOffset(camera_offset);
2956 clouds->updateCameraOffset(camera_offset);
2962 void Game::updateSound(f32 dtime)
2964 // Update sound listener
2965 v3s16 camera_offset = camera->getOffset();
2966 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
2967 v3f(0, 0, 0), // velocity
2968 camera->getDirection(),
2969 camera->getCameraNode()->getUpVector());
2971 bool mute_sound = g_settings->getBool("mute_sound");
2973 sound->setListenerGain(0.0f);
2975 // Check if volume is in the proper range, else fix it.
2976 float old_volume = g_settings->getFloat("sound_volume");
2977 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
2978 sound->setListenerGain(new_volume);
2980 if (old_volume != new_volume) {
2981 g_settings->setFloat("sound_volume", new_volume);
2985 LocalPlayer *player = client->getEnv().getLocalPlayer();
2987 // Tell the sound maker whether to make footstep sounds
2988 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
2990 // Update sound maker
2991 if (player->makes_footstep_sound)
2992 soundmaker->step(dtime);
2994 ClientMap &map = client->getEnv().getClientMap();
2995 MapNode n = map.getNode(player->getFootstepNodePos());
2996 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3000 void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
3002 LocalPlayer *player = client->getEnv().getLocalPlayer();
3004 const v3f camera_direction = camera->getDirection();
3005 const v3s16 camera_offset = camera->getOffset();
3008 Calculate what block is the crosshair pointing to
3011 ItemStack selected_item, hand_item;
3012 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3014 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3015 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3017 core::line3d<f32> shootline;
3019 switch (camera->getCameraMode()) {
3020 case CAMERA_MODE_FIRST:
3021 // Shoot from camera position, with bobbing
3022 shootline.start = camera->getPosition();
3024 case CAMERA_MODE_THIRD:
3025 // Shoot from player head, no bobbing
3026 shootline.start = camera->getHeadPosition();
3028 case CAMERA_MODE_THIRD_FRONT:
3029 shootline.start = camera->getHeadPosition();
3030 // prevent player pointing anything in front-view
3034 shootline.end = shootline.start + camera_direction * BS * d;
3036 #ifdef HAVE_TOUCHSCREENGUI
3038 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3039 shootline = g_touchscreengui->getShootline();
3040 // Scale shootline to the acual distance the player can reach
3041 shootline.end = shootline.start
3042 + shootline.getVector().normalize() * BS * d;
3043 shootline.start += intToFloat(camera_offset, BS);
3044 shootline.end += intToFloat(camera_offset, BS);
3049 PointedThing pointed = updatePointedThing(shootline,
3050 selected_def.liquids_pointable,
3051 !runData.btn_down_for_dig,
3054 if (pointed != runData.pointed_old) {
3055 infostream << "Pointing at " << pointed.dump() << std::endl;
3056 hud->updateSelectionMesh(camera_offset);
3059 // Allow digging again if button is not pressed
3060 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3061 runData.digging_blocked = false;
3065 - releasing dig button
3066 - pointing away from node
3068 if (runData.digging) {
3069 if (wasKeyReleased(KeyType::DIG)) {
3070 infostream << "Dig button released (stopped digging)" << std::endl;
3071 runData.digging = false;
3072 } else if (pointed != runData.pointed_old) {
3073 if (pointed.type == POINTEDTHING_NODE
3074 && runData.pointed_old.type == POINTEDTHING_NODE
3075 && pointed.node_undersurface
3076 == runData.pointed_old.node_undersurface) {
3077 // Still pointing to the same node, but a different face.
3080 infostream << "Pointing away from node (stopped digging)" << std::endl;
3081 runData.digging = false;
3082 hud->updateSelectionMesh(camera_offset);
3086 if (!runData.digging) {
3087 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3088 client->setCrack(-1, v3s16(0, 0, 0));
3089 runData.dig_time = 0.0;
3091 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3092 // Remove e.g. torches faster when clicking instead of holding dig button
3093 runData.nodig_delay_timer = 0;
3094 runData.dig_instantly = false;
3097 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3098 runData.btn_down_for_dig = false;
3100 runData.punching = false;
3102 soundmaker->m_player_leftpunch_sound.name = "";
3104 // Prepare for repeating, unless we're not supposed to
3105 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3106 runData.repeat_place_timer += dtime;
3108 runData.repeat_place_timer = 0;
3110 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3111 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3112 !client->getScript()->on_item_use(selected_item, pointed)))
3113 client->interact(INTERACT_USE, pointed);
3114 } else if (pointed.type == POINTEDTHING_NODE) {
3115 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3116 } else if (pointed.type == POINTEDTHING_OBJECT) {
3117 v3f player_position = player->getPosition();
3118 handlePointingAtObject(pointed, tool_item, player_position, show_debug);
3119 } else if (isKeyDown(KeyType::DIG)) {
3120 // When button is held down in air, show continuous animation
3121 runData.punching = true;
3122 // Run callback even though item is not usable
3123 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3124 client->getScript()->on_item_use(selected_item, pointed);
3125 } else if (wasKeyPressed(KeyType::PLACE)) {
3126 handlePointingAtNothing(selected_item);
3129 runData.pointed_old = pointed;
3131 if (runData.punching || wasKeyPressed(KeyType::DIG))
3132 camera->setDigging(0); // dig animation
3134 input->clearWasKeyPressed();
3135 input->clearWasKeyReleased();
3137 input->joystick.clearWasKeyDown(KeyType::DIG);
3138 input->joystick.clearWasKeyDown(KeyType::PLACE);
3140 input->joystick.clearWasKeyReleased(KeyType::DIG);
3141 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3145 PointedThing Game::updatePointedThing(
3146 const core::line3d<f32> &shootline,
3147 bool liquids_pointable,
3148 bool look_for_object,
3149 const v3s16 &camera_offset)
3151 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3152 selectionboxes->clear();
3153 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3154 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3155 "show_entity_selectionbox");
3157 ClientEnvironment &env = client->getEnv();
3158 ClientMap &map = env.getClientMap();
3159 const NodeDefManager *nodedef = map.getNodeDefManager();
3161 runData.selected_object = NULL;
3162 hud->pointing_at_object = false;
3164 RaycastState s(shootline, look_for_object, liquids_pointable);
3165 PointedThing result;
3166 env.continueRaycast(&s, &result);
3167 if (result.type == POINTEDTHING_OBJECT) {
3168 hud->pointing_at_object = true;
3170 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3171 aabb3f selection_box;
3172 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3173 runData.selected_object->getSelectionBox(&selection_box)) {
3174 v3f pos = runData.selected_object->getPosition();
3175 selectionboxes->push_back(aabb3f(selection_box));
3176 hud->setSelectionPos(pos, camera_offset);
3178 } else if (result.type == POINTEDTHING_NODE) {
3179 // Update selection boxes
3180 MapNode n = map.getNode(result.node_undersurface);
3181 std::vector<aabb3f> boxes;
3182 n.getSelectionBoxes(nodedef, &boxes,
3183 n.getNeighbors(result.node_undersurface, &map));
3186 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3187 i != boxes.end(); ++i) {
3189 box.MinEdge -= v3f(d, d, d);
3190 box.MaxEdge += v3f(d, d, d);
3191 selectionboxes->push_back(box);
3193 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3195 hud->setSelectedFaceNormal(v3f(
3196 result.intersection_normal.X,
3197 result.intersection_normal.Y,
3198 result.intersection_normal.Z));
3201 // Update selection mesh light level and vertex colors
3202 if (!selectionboxes->empty()) {
3203 v3f pf = hud->getSelectionPos();
3204 v3s16 p = floatToInt(pf, BS);
3206 // Get selection mesh light level
3207 MapNode n = map.getNode(p);
3208 u16 node_light = getInteriorLight(n, -1, nodedef);
3209 u16 light_level = node_light;
3211 for (const v3s16 &dir : g_6dirs) {
3212 n = map.getNode(p + dir);
3213 node_light = getInteriorLight(n, -1, nodedef);
3214 if (node_light > light_level)
3215 light_level = node_light;
3218 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3220 final_color_blend(&c, light_level, daynight_ratio);
3222 // Modify final color a bit with time
3223 u32 timer = porting::getTimeMs() % 5000;
3224 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3225 float sin_r = 0.08f * std::sin(timerf);
3226 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3227 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3228 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3229 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3230 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3232 // Set mesh final color
3233 hud->setSelectionMeshColor(c);
3239 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3241 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3242 PointedThing fauxPointed;
3243 fauxPointed.type = POINTEDTHING_NOTHING;
3244 client->interact(INTERACT_ACTIVATE, fauxPointed);
3248 void Game::handlePointingAtNode(const PointedThing &pointed,
3249 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3251 v3s16 nodepos = pointed.node_undersurface;
3252 v3s16 neighbourpos = pointed.node_abovesurface;
3255 Check information text of node
3258 ClientMap &map = client->getEnv().getClientMap();
3260 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3261 && !runData.digging_blocked
3262 && client->checkPrivilege("interact")) {
3263 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3266 // This should be done after digging handling
3267 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3270 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3271 meta->getString("infotext"))));
3273 MapNode n = map.getNode(nodepos);
3275 if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
3276 m_game_ui->setInfoText(L"Unknown node: " +
3277 utf8_to_wide(nodedef_manager->get(n).name));
3281 if ((wasKeyPressed(KeyType::PLACE) ||
3282 runData.repeat_place_timer >= m_repeat_place_time) &&
3283 client->checkPrivilege("interact")) {
3284 runData.repeat_place_timer = 0;
3285 infostream << "Place button pressed while looking at ground" << std::endl;
3287 // Placing animation (always shown for feedback)
3288 camera->setDigging(1);
3290 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3292 // If the wielded item has node placement prediction,
3294 // And also set the sound and send the interact
3295 // But first check for meta formspec and rightclickable
3296 auto &def = selected_item.getDefinition(itemdef_manager);
3297 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3300 if (placed && client->modsLoaded())
3301 client->getScript()->on_placenode(pointed, def);
3305 bool Game::nodePlacement(const ItemDefinition &selected_def,
3306 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3307 const PointedThing &pointed, const NodeMetadata *meta)
3309 std::string prediction = selected_def.node_placement_prediction;
3310 const NodeDefManager *nodedef = client->ndef();
3311 ClientMap &map = client->getEnv().getClientMap();
3313 bool is_valid_position;
3315 node = map.getNode(nodepos, &is_valid_position);
3316 if (!is_valid_position) {
3317 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3322 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3323 && !isKeyDown(KeyType::SNEAK)) {
3324 // on_rightclick callbacks are called anyway
3325 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3326 client->interact(INTERACT_PLACE, pointed);
3328 infostream << "Launching custom inventory view" << std::endl;
3330 InventoryLocation inventoryloc;
3331 inventoryloc.setNodeMeta(nodepos);
3333 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3334 &client->getEnv().getClientMap(), nodepos);
3335 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3337 auto *&formspec = m_game_ui->updateFormspec("");
3338 GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
3339 txt_dst, client->getFormspecPrepend());
3341 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3345 // on_rightclick callback
3346 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3347 !isKeyDown(KeyType::SNEAK))) {
3349 client->interact(INTERACT_PLACE, pointed);
3353 verbosestream << "Node placement prediction for "
3354 << selected_def.name << " is " << prediction << std::endl;
3355 v3s16 p = neighbourpos;
3357 // Place inside node itself if buildable_to
3358 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3359 if (is_valid_position) {
3360 if (nodedef->get(n_under).buildable_to) {
3363 node = map.getNode(p, &is_valid_position);
3364 if (is_valid_position && !nodedef->get(node).buildable_to) {
3365 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3367 client->interact(INTERACT_PLACE, pointed);
3373 // Find id of predicted node
3375 bool found = nodedef->getId(prediction, id);
3378 errorstream << "Node placement prediction failed for "
3379 << selected_def.name << " (places "
3381 << ") - Name not known" << std::endl;
3382 // Handle this as if prediction was empty
3384 client->interact(INTERACT_PLACE, pointed);
3388 const ContentFeatures &predicted_f = nodedef->get(id);
3390 // Predict param2 for facedir and wallmounted nodes
3393 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3394 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3395 v3s16 dir = nodepos - neighbourpos;
3397 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3398 param2 = dir.Y < 0 ? 1 : 0;
3399 } else if (abs(dir.X) > abs(dir.Z)) {
3400 param2 = dir.X < 0 ? 3 : 2;
3402 param2 = dir.Z < 0 ? 5 : 4;
3406 if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3407 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3408 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3410 if (abs(dir.X) > abs(dir.Z)) {
3411 param2 = dir.X < 0 ? 3 : 1;
3413 param2 = dir.Z < 0 ? 2 : 0;
3417 assert(param2 <= 5);
3419 //Check attachment if node is in group attached_node
3420 if (((ItemGroupList) predicted_f.groups)["attached_node"] != 0) {
3421 static v3s16 wallmounted_dirs[8] = {
3431 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3432 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3433 pp = p + wallmounted_dirs[param2];
3435 pp = p + v3s16(0, -1, 0);
3437 if (!nodedef->get(map.getNode(pp)).walkable) {
3438 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3440 client->interact(INTERACT_PLACE, pointed);
3446 if ((predicted_f.param_type_2 == CPT2_COLOR
3447 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3448 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3449 const std::string &indexstr = selected_item.metadata.getString(
3450 "palette_index", 0);
3451 if (!indexstr.empty()) {
3452 s32 index = mystoi(indexstr);
3453 if (predicted_f.param_type_2 == CPT2_COLOR) {
3455 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3456 // param2 = pure palette index + other
3457 param2 = (index & 0xf8) | (param2 & 0x07);
3458 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3459 // param2 = pure palette index + other
3460 param2 = (index & 0xe0) | (param2 & 0x1f);
3465 // Add node to client map
3466 MapNode n(id, 0, param2);
3469 LocalPlayer *player = client->getEnv().getLocalPlayer();
3471 // Dont place node when player would be inside new node
3472 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3473 if (!nodedef->get(n).walkable ||
3474 g_settings->getBool("enable_build_where_you_stand") ||
3475 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3476 (nodedef->get(n).walkable &&
3477 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3478 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3479 // This triggers the required mesh update too
3480 client->addNode(p, n);
3482 client->interact(INTERACT_PLACE, pointed);
3483 // A node is predicted, also play a sound
3484 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3487 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3490 } catch (InvalidPositionException &e) {
3491 errorstream << "Node placement prediction failed for "
3492 << selected_def.name << " (places "
3494 << ") - Position not loaded" << std::endl;
3495 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3500 void Game::handlePointingAtObject(const PointedThing &pointed,
3501 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3503 std::wstring infotext = unescape_translate(
3504 utf8_to_wide(runData.selected_object->infoText()));
3507 if (!infotext.empty()) {
3510 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3513 m_game_ui->setInfoText(infotext);
3515 if (isKeyDown(KeyType::DIG)) {
3516 bool do_punch = false;
3517 bool do_punch_damage = false;
3519 if (runData.object_hit_delay_timer <= 0.0) {
3521 do_punch_damage = true;
3522 runData.object_hit_delay_timer = object_hit_delay;
3525 if (wasKeyPressed(KeyType::DIG))
3529 infostream << "Punched object" << std::endl;
3530 runData.punching = true;
3533 if (do_punch_damage) {
3534 // Report direct punch
3535 v3f objpos = runData.selected_object->getPosition();
3536 v3f dir = (objpos - player_position).normalize();
3538 bool disable_send = runData.selected_object->directReportPunch(
3539 dir, &tool_item, runData.time_from_last_punch);
3540 runData.time_from_last_punch = 0;
3543 client->interact(INTERACT_START_DIGGING, pointed);
3545 } else if (wasKeyDown(KeyType::PLACE)) {
3546 infostream << "Pressed place button while pointing at object" << std::endl;
3547 client->interact(INTERACT_PLACE, pointed); // place
3552 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3553 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3555 // See also: serverpackethandle.cpp, action == 2
3556 LocalPlayer *player = client->getEnv().getLocalPlayer();
3557 ClientMap &map = client->getEnv().getClientMap();
3558 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3560 // NOTE: Similar piece of code exists on the server side for
3562 // Get digging parameters
3563 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3564 &selected_item.getToolCapabilities(itemdef_manager));
3566 // If can't dig, try hand
3567 if (!params.diggable) {
3568 params = getDigParams(nodedef_manager->get(n).groups,
3569 &hand_item.getToolCapabilities(itemdef_manager));
3572 if (!params.diggable) {
3573 // I guess nobody will wait for this long
3574 runData.dig_time_complete = 10000000.0;
3576 runData.dig_time_complete = params.time;
3578 if (m_cache_enable_particles) {
3579 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3580 client->getParticleManager()->addNodeParticle(client,
3581 player, nodepos, n, features);
3585 if (!runData.digging) {
3586 infostream << "Started digging" << std::endl;
3587 runData.dig_instantly = runData.dig_time_complete == 0;
3588 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3590 client->interact(INTERACT_START_DIGGING, pointed);
3591 runData.digging = true;
3592 runData.btn_down_for_dig = true;
3595 if (!runData.dig_instantly) {
3596 runData.dig_index = (float)crack_animation_length
3598 / runData.dig_time_complete;
3600 // This is for e.g. torches
3601 runData.dig_index = crack_animation_length;
3604 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3606 if (sound_dig.exists() && params.diggable) {
3607 if (sound_dig.name == "__group") {
3608 if (!params.main_group.empty()) {
3609 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3610 soundmaker->m_player_leftpunch_sound.name =
3611 std::string("default_dig_") +
3615 soundmaker->m_player_leftpunch_sound = sound_dig;
3619 // Don't show cracks if not diggable
3620 if (runData.dig_time_complete >= 100000.0) {
3621 } else if (runData.dig_index < crack_animation_length) {
3622 //TimeTaker timer("client.setTempMod");
3623 //infostream<<"dig_index="<<dig_index<<std::endl;
3624 client->setCrack(runData.dig_index, nodepos);
3626 infostream << "Digging completed" << std::endl;
3627 client->setCrack(-1, v3s16(0, 0, 0));
3629 runData.dig_time = 0;
3630 runData.digging = false;
3631 // we successfully dug, now block it from repeating if we want to be safe
3632 if (g_settings->getBool("safe_dig_and_place"))
3633 runData.digging_blocked = true;
3635 runData.nodig_delay_timer =
3636 runData.dig_time_complete / (float)crack_animation_length;
3638 // We don't want a corresponding delay to very time consuming nodes
3639 // and nodes without digging time (e.g. torches) get a fixed delay.
3640 if (runData.nodig_delay_timer > 0.3)
3641 runData.nodig_delay_timer = 0.3;
3642 else if (runData.dig_instantly)
3643 runData.nodig_delay_timer = 0.15;
3645 bool is_valid_position;
3646 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3647 if (is_valid_position) {
3648 if (client->modsLoaded() &&
3649 client->getScript()->on_dignode(nodepos, wasnode)) {
3653 const ContentFeatures &f = client->ndef()->get(wasnode);
3654 if (f.node_dig_prediction == "air") {
3655 client->removeNode(nodepos);
3656 } else if (!f.node_dig_prediction.empty()) {
3658 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3660 client->addNode(nodepos, id, true);
3662 // implicit else: no prediction
3665 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3667 if (m_cache_enable_particles) {
3668 const ContentFeatures &features =
3669 client->getNodeDefManager()->get(wasnode);
3670 client->getParticleManager()->addDiggingParticles(client,
3671 player, nodepos, wasnode, features);
3675 // Send event to trigger sound
3676 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3679 if (runData.dig_time_complete < 100000.0) {
3680 runData.dig_time += dtime;
3682 runData.dig_time = 0;
3683 client->setCrack(-1, nodepos);
3686 camera->setDigging(0); // Dig animation
3689 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3690 const CameraOrientation &cam)
3692 TimeTaker tt_update("Game::updateFrame()");
3693 LocalPlayer *player = client->getEnv().getLocalPlayer();
3699 if (draw_control->range_all) {
3700 runData.fog_range = 100000 * BS;
3702 runData.fog_range = draw_control->wanted_range * BS;
3706 Calculate general brightness
3708 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3709 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3710 float direct_brightness;
3713 if (m_cache_enable_noclip && m_cache_enable_free_move) {
3714 direct_brightness = time_brightness;
3715 sunlight_seen = true;
3717 float old_brightness = sky->getBrightness();
3718 direct_brightness = client->getEnv().getClientMap()
3719 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3720 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3724 float time_of_day_smooth = runData.time_of_day_smooth;
3725 float time_of_day = client->getEnv().getTimeOfDayF();
3727 static const float maxsm = 0.05f;
3728 static const float todsm = 0.05f;
3730 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3731 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3732 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3733 time_of_day_smooth = time_of_day;
3735 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3736 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3737 + (time_of_day + 1.0) * todsm;
3739 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3740 + time_of_day * todsm;
3742 runData.time_of_day_smooth = time_of_day_smooth;
3744 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3745 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3746 player->getPitch());
3752 if (sky->getCloudsVisible()) {
3753 clouds->setVisible(true);
3754 clouds->step(dtime);
3755 // camera->getPosition is not enough for 3rd person views
3756 v3f camera_node_position = camera->getCameraNode()->getPosition();
3757 v3s16 camera_offset = camera->getOffset();
3758 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3759 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3760 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3761 clouds->update(camera_node_position,
3762 sky->getCloudColor());
3763 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3764 // if inside clouds, and fog enabled, use that as sky
3766 video::SColor clouds_dark = clouds->getColor()
3767 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3768 sky->overrideColors(clouds_dark, clouds->getColor());
3769 sky->setInClouds(true);
3770 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3771 // do not draw clouds after all
3772 clouds->setVisible(false);
3775 clouds->setVisible(false);
3782 client->getParticleManager()->step(dtime);
3788 if (m_cache_enable_fog) {
3791 video::EFT_FOG_LINEAR,
3792 runData.fog_range * m_cache_fog_start,
3793 runData.fog_range * 1.0,
3801 video::EFT_FOG_LINEAR,
3811 Get chat messages from client
3814 v2u32 screensize = driver->getScreenSize();
3816 updateChat(dtime, screensize);
3822 if (player->getWieldIndex() != runData.new_playeritem)
3823 client->setPlayerItem(runData.new_playeritem);
3825 if (client->updateWieldedItem()) {
3826 // Update wielded tool
3827 ItemStack selected_item, hand_item;
3828 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3829 camera->wield(tool_item);
3833 Update block draw list every 200ms or when camera direction has
3836 runData.update_draw_list_timer += dtime;
3838 v3f camera_direction = camera->getDirection();
3839 if (runData.update_draw_list_timer >= 0.2
3840 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3841 || m_camera_offset_changed) {
3842 runData.update_draw_list_timer = 0;
3843 client->getEnv().getClientMap().updateDrawList();
3844 runData.update_draw_list_last_cam_dir = camera_direction;
3847 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3850 make sure menu is on top
3851 1. Delete formspec menu reference if menu was removed
3852 2. Else, make sure formspec menu is on top
3854 auto formspec = m_game_ui->getFormspecGUI();
3855 do { // breakable. only runs for one iteration
3859 if (formspec->getReferenceCount() == 1) {
3860 m_game_ui->deleteFormspec();
3864 auto &loc = formspec->getFormspecLocation();
3865 if (loc.type == InventoryLocation::NODEMETA) {
3866 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3867 if (!meta || meta->getString("formspec").empty()) {
3868 formspec->quitMenu();
3874 guiroot->bringToFront(formspec);
3880 const video::SColor &skycolor = sky->getSkyColor();
3882 TimeTaker tt_draw("Draw scene");
3883 driver->beginScene(true, true, skycolor);
3885 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3886 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3887 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3888 bool draw_crosshair = (
3889 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3890 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3891 #ifdef HAVE_TOUCHSCREENGUI
3893 draw_crosshair = !g_settings->getBool("touchtarget");
3894 } catch (SettingNotFoundException) {
3897 RenderingEngine::draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3898 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3903 if (m_game_ui->m_flags.show_profiler_graph)
3904 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3909 if (runData.damage_flash > 0.0f) {
3910 video::SColor color(runData.damage_flash, 180, 0, 0);
3911 driver->draw2DRectangle(color,
3912 core::rect<s32>(0, 0, screensize.X, screensize.Y),
3915 runData.damage_flash -= 384.0f * dtime;
3921 if (player->hurt_tilt_timer > 0.0f) {
3922 player->hurt_tilt_timer -= dtime * 6.0f;
3924 if (player->hurt_tilt_timer < 0.0f)
3925 player->hurt_tilt_strength = 0.0f;
3929 Update minimap pos and rotation
3931 if (mapper && m_game_ui->m_flags.show_hud) {
3932 mapper->setPos(floatToInt(player->getPosition(), BS));
3933 mapper->setAngle(player->getYaw());
3939 if (++m_reset_HW_buffer_counter > 500) {
3941 Periodically remove all mesh HW buffers.
3943 Work around for a quirk in Irrlicht where a HW buffer is only
3944 released after 20000 iterations (triggered from endScene()).
3946 Without this, all loaded but unused meshes will retain their HW
3947 buffers for at least 5 minutes, at which point looking up the HW buffers
3948 becomes a bottleneck and the framerate drops (as much as 30%).
3950 Tests showed that numbers between 50 and 1000 are good, so picked 500.
3951 There are no other public Irrlicht APIs that allow interacting with the
3952 HW buffers without tracking the status of every individual mesh.
3954 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
3956 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
3957 driver->removeAllHardwareBuffers();
3958 m_reset_HW_buffer_counter = 0;
3962 stats->drawtime = tt_draw.stop(true);
3963 g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
3964 g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
3967 /* Log times and stuff for visualization */
3968 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
3970 Profiler::GraphValues values;
3971 g_profiler->graphGet(values);
3977 /****************************************************************************
3979 ****************************************************************************/
3981 /* On some computers framerate doesn't seem to be automatically limited
3983 inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
3985 // not using getRealTime is necessary for wine
3986 device->getTimer()->tick(); // Maker sure device time is up-to-date
3987 u32 time = device->getTimer()->getTime();
3988 u32 last_time = fps_timings->last_time;
3990 if (time > last_time) // Make sure time hasn't overflowed
3991 fps_timings->busy_time = time - last_time;
3993 fps_timings->busy_time = 0;
3995 u32 frametime_min = 1000 / (
3996 device->isWindowFocused() && !g_menumgr.pausesGame()
3997 ? g_settings->getFloat("fps_max")
3998 : g_settings->getFloat("fps_max_unfocused"));
4000 if (fps_timings->busy_time < frametime_min) {
4001 fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
4002 device->sleep(fps_timings->sleep_time);
4004 fps_timings->sleep_time = 0;
4007 /* Get the new value of the device timer. Note that device->sleep() may
4008 * not sleep for the entire requested time as sleep may be interrupted and
4009 * therefore it is arguably more accurate to get the new time from the
4010 * device rather than calculating it by adding sleep_time to time.
4013 device->getTimer()->tick(); // Update device timer
4014 time = device->getTimer()->getTime();
4016 if (time > last_time) // Make sure last_time hasn't overflowed
4017 *dtime = (time - last_time) / 1000.0;
4021 fps_timings->last_time = time;
4024 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4026 const wchar_t *wmsg = wgettext(msg);
4027 RenderingEngine::draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4032 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4034 ((Game *)data)->readSettings();
4037 void Game::readSettings()
4039 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4040 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4041 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4042 m_cache_enable_particles = g_settings->getBool("enable_particles");
4043 m_cache_enable_fog = g_settings->getBool("enable_fog");
4044 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
4045 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4046 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
4048 m_cache_enable_noclip = g_settings->getBool("noclip");
4049 m_cache_enable_free_move = g_settings->getBool("free_move");
4051 m_cache_fog_start = g_settings->getFloat("fog_start");
4053 m_cache_cam_smoothing = 0;
4054 if (g_settings->getBool("cinematic"))
4055 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4057 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4059 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4060 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4061 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4063 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4066 /****************************************************************************/
4067 /****************************************************************************
4069 ****************************************************************************/
4070 /****************************************************************************/
4072 void Game::extendedResourceCleanup()
4074 // Extended resource accounting
4075 infostream << "Irrlicht resources after cleanup:" << std::endl;
4076 infostream << "\tRemaining meshes : "
4077 << RenderingEngine::get_mesh_cache()->getMeshCount() << std::endl;
4078 infostream << "\tRemaining textures : "
4079 << driver->getTextureCount() << std::endl;
4081 for (unsigned int i = 0; i < driver->getTextureCount(); i++) {
4082 irr::video::ITexture *texture = driver->getTextureByIndex(i);
4083 infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str()
4087 clearTextureNameCache();
4088 infostream << "\tRemaining materials: "
4089 << driver-> getMaterialRendererCount()
4090 << " (note: irrlicht doesn't support removing renderers)" << std::endl;
4093 void Game::showDeathFormspec()
4095 static std::string formspec_str =
4096 std::string("formspec_version[1]") +
4098 "bgcolor[#320000b4;true]"
4099 "label[4.85,1.35;" + gettext("You died") + "]"
4100 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4104 /* Note: FormspecFormSource and LocalFormspecHandler *
4105 * are deleted by guiFormSpecMenu */
4106 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4107 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4109 auto *&formspec = m_game_ui->getFormspecGUI();
4110 GUIFormSpecMenu::create(formspec, client, &input->joystick,
4111 fs_src, txt_dst, client->getFormspecPrepend());
4112 formspec->setFocus("btn_respawn");
4115 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4116 void Game::showPauseMenu()
4119 static const std::string control_text = strgettext("Default Controls:\n"
4120 "No menu visible:\n"
4121 "- single tap: button activate\n"
4122 "- double tap: place/use\n"
4123 "- slide finger: look around\n"
4124 "Menu/Inventory visible:\n"
4125 "- double tap (outside):\n"
4127 "- touch stack, touch slot:\n"
4129 "- touch&drag, tap 2nd finger\n"
4130 " --> place single item to slot\n"
4133 static const std::string control_text_template = strgettext("Controls:\n"
4134 "- %s: move forwards\n"
4135 "- %s: move backwards\n"
4137 "- %s: move right\n"
4138 "- %s: jump/climb up\n"
4141 "- %s: sneak/climb down\n"
4144 "- Mouse: turn/look\n"
4145 "- Mouse wheel: select item\n"
4149 char control_text_buf[600];
4151 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4152 GET_KEY_NAME(keymap_forward),
4153 GET_KEY_NAME(keymap_backward),
4154 GET_KEY_NAME(keymap_left),
4155 GET_KEY_NAME(keymap_right),
4156 GET_KEY_NAME(keymap_jump),
4157 GET_KEY_NAME(keymap_dig),
4158 GET_KEY_NAME(keymap_place),
4159 GET_KEY_NAME(keymap_sneak),
4160 GET_KEY_NAME(keymap_drop),
4161 GET_KEY_NAME(keymap_inventory),
4162 GET_KEY_NAME(keymap_chat)
4165 std::string control_text = std::string(control_text_buf);
4166 str_formspec_escape(control_text);
4169 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4170 std::ostringstream os;
4172 os << "formspec_version[1]" << SIZE_TAG
4173 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4174 << strgettext("Continue") << "]";
4176 if (!simple_singleplayer_mode) {
4177 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4178 << strgettext("Change Password") << "]";
4180 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4185 if (g_settings->getBool("enable_sound")) {
4186 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4187 << strgettext("Sound Volume") << "]";
4190 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4191 << strgettext("Change Keys") << "]";
4193 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4194 << strgettext("Exit to Menu") << "]";
4195 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4196 << strgettext("Exit to OS") << "]"
4197 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4198 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4200 << strgettext("Game info:") << "\n";
4201 const std::string &address = client->getAddressName();
4202 static const std::string mode = strgettext("- Mode: ");
4203 if (!simple_singleplayer_mode) {
4204 Address serverAddress = client->getServerAddress();
4205 if (!address.empty()) {
4206 os << mode << strgettext("Remote server") << "\n"
4207 << strgettext("- Address: ") << address;
4209 os << mode << strgettext("Hosting server");
4211 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4213 os << mode << strgettext("Singleplayer") << "\n";
4215 if (simple_singleplayer_mode || address.empty()) {
4216 static const std::string on = strgettext("On");
4217 static const std::string off = strgettext("Off");
4218 const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
4219 const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
4220 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4221 os << strgettext("- Damage: ") << damage << "\n"
4222 << strgettext("- Creative Mode: ") << creative << "\n";
4223 if (!simple_singleplayer_mode) {
4224 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4225 //~ PvP = Player versus Player
4226 os << strgettext("- PvP: ") << pvp << "\n"
4227 << strgettext("- Public: ") << announced << "\n";
4228 std::string server_name = g_settings->get("server_name");
4229 str_formspec_escape(server_name);
4230 if (announced == on && !server_name.empty())
4231 os << strgettext("- Server Name: ") << server_name;
4238 /* Note: FormspecFormSource and LocalFormspecHandler *
4239 * are deleted by guiFormSpecMenu */
4240 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4241 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4243 auto *&formspec = m_game_ui->getFormspecGUI();
4244 GUIFormSpecMenu::create(formspec, client, &input->joystick,
4245 fs_src, txt_dst, client->getFormspecPrepend());
4246 formspec->setFocus("btn_continue");
4247 formspec->doPause = true;
4250 /****************************************************************************/
4251 /****************************************************************************
4252 extern function for launching the game
4253 ****************************************************************************/
4254 /****************************************************************************/
4256 void the_game(bool *kill,
4257 InputHandler *input,
4258 const GameStartData &start_data,
4259 std::string &error_message,
4260 ChatBackend &chat_backend,
4261 bool *reconnect_requested) // Used for local game
4265 /* Make a copy of the server address because if a local singleplayer server
4266 * is created then this is updated and we don't want to change the value
4267 * passed to us by the calling function
4272 if (game.startup(kill, input, start_data, error_message,
4273 reconnect_requested, &chat_backend)) {
4277 } catch (SerializationError &e) {
4278 error_message = std::string("A serialization error occurred:\n")
4279 + e.what() + "\n\nThe server is probably "
4280 " running a different version of " PROJECT_NAME_C ".";
4281 errorstream << error_message << std::endl;
4282 } catch (ServerError &e) {
4283 error_message = e.what();
4284 errorstream << "ServerError: " << error_message << std::endl;
4285 } catch (ModError &e) {
4286 error_message = std::string("ModError: ") + e.what() +
4287 strgettext("\nCheck debug.txt for details.");
4288 errorstream << error_message << std::endl;