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 message << gettext("Media...") << " " << (client->mediaReceiveProgress()*100) << "%";
1645 message.precision(2);
1647 if ((USE_CURL == 0) ||
1648 (!g_settings->getBool("enable_remote_media_server"))) {
1649 float cur = client->getCurRate();
1650 std::string cur_unit = gettext("KiB/s");
1654 cur_unit = gettext("MiB/s");
1657 message << " (" << cur << ' ' << cur_unit << ")";
1660 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1661 RenderingEngine::draw_load_screen(utf8_to_wide(message.str()), guienv,
1662 texture_src, dtime, progress);
1670 /****************************************************************************/
1671 /****************************************************************************
1673 ****************************************************************************/
1674 /****************************************************************************/
1676 inline void Game::updateInteractTimers(f32 dtime)
1678 if (runData.nodig_delay_timer >= 0)
1679 runData.nodig_delay_timer -= dtime;
1681 if (runData.object_hit_delay_timer >= 0)
1682 runData.object_hit_delay_timer -= dtime;
1684 runData.time_from_last_punch += dtime;
1688 /* returns false if game should exit, otherwise true
1690 inline bool Game::checkConnection()
1692 if (client->accessDenied()) {
1693 *error_message = "Access denied. Reason: "
1694 + client->accessDeniedReason();
1695 *reconnect_requested = client->reconnectRequested();
1696 errorstream << *error_message << std::endl;
1704 /* returns false if game should exit, otherwise true
1706 inline bool Game::handleCallbacks()
1708 if (g_gamecallback->disconnect_requested) {
1709 g_gamecallback->disconnect_requested = false;
1713 if (g_gamecallback->changepassword_requested) {
1714 (new GUIPasswordChange(guienv, guiroot, -1,
1715 &g_menumgr, client, texture_src))->drop();
1716 g_gamecallback->changepassword_requested = false;
1719 if (g_gamecallback->changevolume_requested) {
1720 (new GUIVolumeChange(guienv, guiroot, -1,
1721 &g_menumgr, texture_src))->drop();
1722 g_gamecallback->changevolume_requested = false;
1725 if (g_gamecallback->keyconfig_requested) {
1726 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1727 &g_menumgr, texture_src))->drop();
1728 g_gamecallback->keyconfig_requested = false;
1731 if (g_gamecallback->keyconfig_changed) {
1732 input->keycache.populate(); // update the cache with new settings
1733 g_gamecallback->keyconfig_changed = false;
1740 void Game::processQueues()
1742 texture_src->processQueue();
1743 itemdef_manager->processQueue(client);
1744 shader_src->processQueue();
1748 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1751 float profiler_print_interval =
1752 g_settings->getFloat("profiler_print_interval");
1753 bool print_to_log = true;
1755 if (profiler_print_interval == 0) {
1756 print_to_log = false;
1757 profiler_print_interval = 3;
1760 if (profiler_interval.step(dtime, profiler_print_interval)) {
1762 infostream << "Profiler:" << std::endl;
1763 g_profiler->print(infostream);
1766 m_game_ui->updateProfiler();
1767 g_profiler->clear();
1770 // Update update graphs
1771 g_profiler->graphAdd("Time non-rendering [ms]",
1772 draw_times.busy_time - stats.drawtime);
1774 g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time);
1775 g_profiler->graphAdd("FPS", 1.0f / dtime);
1778 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1785 /* Time average and jitter calculation
1787 jp = &stats->dtime_jitter;
1788 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1790 jitter = dtime - jp->avg;
1792 if (jitter > jp->max)
1795 jp->counter += dtime;
1797 if (jp->counter > 0.0) {
1799 jp->max_sample = jp->max;
1800 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1804 /* Busytime average and jitter calculation
1806 jp = &stats->busy_time_jitter;
1807 jp->avg = jp->avg + draw_times.busy_time * 0.02;
1809 jitter = draw_times.busy_time - jp->avg;
1811 if (jitter > jp->max)
1813 if (jitter < jp->min)
1816 jp->counter += dtime;
1818 if (jp->counter > 0.0) {
1820 jp->max_sample = jp->max;
1821 jp->min_sample = jp->min;
1829 /****************************************************************************
1831 ****************************************************************************/
1833 void Game::processUserInput(f32 dtime)
1835 // Reset input if window not active or some menu is active
1836 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1838 #ifdef HAVE_TOUCHSCREENGUI
1839 g_touchscreengui->hide();
1842 #ifdef HAVE_TOUCHSCREENGUI
1843 else if (g_touchscreengui) {
1844 /* on touchscreengui step may generate own input events which ain't
1845 * what we want in case we just did clear them */
1846 g_touchscreengui->step(dtime);
1850 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1851 gui_chat_console->closeConsoleAtOnce();
1854 // Input handler step() (used by the random input generator)
1858 auto formspec = m_game_ui->getFormspecGUI();
1860 formspec->getAndroidUIInput();
1862 handleAndroidChatInput();
1865 // Increase timer for double tap of "keymap_jump"
1866 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1867 runData.jump_timer += dtime;
1870 processItemSelection(&runData.new_playeritem);
1874 void Game::processKeyInput()
1876 if (wasKeyDown(KeyType::DROP)) {
1877 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1878 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1879 toggleAutoforward();
1880 } else if (wasKeyDown(KeyType::BACKWARD)) {
1881 if (g_settings->getBool("continuous_forward"))
1882 toggleAutoforward();
1883 } else if (wasKeyDown(KeyType::INVENTORY)) {
1885 } else if (input->cancelPressed()) {
1887 m_android_chat_open = false;
1889 if (!gui_chat_console->isOpenInhibited()) {
1892 } else if (wasKeyDown(KeyType::CHAT)) {
1893 openConsole(0.2, L"");
1894 } else if (wasKeyDown(KeyType::CMD)) {
1895 openConsole(0.2, L"/");
1896 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1897 if (client->modsLoaded())
1898 openConsole(0.2, L".");
1900 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1901 } else if (wasKeyDown(KeyType::CONSOLE)) {
1902 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1903 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1905 } else if (wasKeyDown(KeyType::JUMP)) {
1906 toggleFreeMoveAlt();
1907 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1909 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1911 } else if (wasKeyDown(KeyType::NOCLIP)) {
1914 } else if (wasKeyDown(KeyType::MUTE)) {
1915 if (g_settings->getBool("enable_sound")) {
1916 bool new_mute_sound = !g_settings->getBool("mute_sound");
1917 g_settings->setBool("mute_sound", new_mute_sound);
1919 m_game_ui->showTranslatedStatusText("Sound muted");
1921 m_game_ui->showTranslatedStatusText("Sound unmuted");
1923 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1925 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1926 if (g_settings->getBool("enable_sound")) {
1927 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1929 g_settings->setFloat("sound_volume", new_volume);
1930 const wchar_t *str = wgettext("Volume changed to %d%%");
1931 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1933 m_game_ui->showStatusText(buf);
1935 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1937 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1938 if (g_settings->getBool("enable_sound")) {
1939 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1941 g_settings->setFloat("sound_volume", new_volume);
1942 const wchar_t *str = wgettext("Volume changed to %d%%");
1943 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1945 m_game_ui->showStatusText(buf);
1947 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1950 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1951 || wasKeyDown(KeyType::DEC_VOLUME)) {
1952 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1954 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1956 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1957 client->makeScreenshot();
1958 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1959 m_game_ui->toggleHud();
1960 } else if (wasKeyDown(KeyType::MINIMAP)) {
1961 toggleMinimap(isKeyDown(KeyType::SNEAK));
1962 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1963 m_game_ui->toggleChat();
1964 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1966 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1967 toggleUpdateCamera();
1968 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1970 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1971 m_game_ui->toggleProfiler();
1972 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1973 increaseViewRange();
1974 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1975 decreaseViewRange();
1976 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1977 toggleFullViewRange();
1978 } else if (wasKeyDown(KeyType::ZOOM)) {
1980 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1982 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1984 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1986 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1990 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1991 runData.reset_jump_timer = false;
1992 runData.jump_timer = 0.0f;
1995 if (quicktune->hasMessage()) {
1996 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2000 void Game::processItemSelection(u16 *new_playeritem)
2002 LocalPlayer *player = client->getEnv().getLocalPlayer();
2004 /* Item selection using mouse wheel
2006 *new_playeritem = player->getWieldIndex();
2008 s32 wheel = input->getMouseWheel();
2009 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2010 player->hud_hotbar_itemcount - 1);
2014 if (input->joystick.wasKeyDown(KeyType::SCROLL_DOWN) ||
2015 wasKeyDown(KeyType::HOTBAR_NEXT)) {
2019 if (input->joystick.wasKeyDown(KeyType::SCROLL_UP) ||
2020 wasKeyDown(KeyType::HOTBAR_PREV)) {
2025 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2027 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2030 /* Item selection using hotbar slot keys
2032 for (u16 i = 0; i <= max_item; i++) {
2033 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2034 *new_playeritem = i;
2041 void Game::dropSelectedItem(bool single_item)
2043 IDropAction *a = new IDropAction();
2044 a->count = single_item ? 1 : 0;
2045 a->from_inv.setCurrentPlayer();
2046 a->from_list = "main";
2047 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2048 client->inventoryAction(a);
2052 void Game::openInventory()
2055 * Don't permit to open inventory is CAO or player doesn't exists.
2056 * This prevent showing an empty inventory at player load
2059 LocalPlayer *player = client->getEnv().getLocalPlayer();
2060 if (!player || !player->getCAO())
2063 infostream << "Game: Launching inventory" << std::endl;
2065 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2067 InventoryLocation inventoryloc;
2068 inventoryloc.setCurrentPlayer();
2070 if (!client->modsLoaded()
2071 || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2072 TextDest *txt_dst = new TextDestPlayerInventory(client);
2073 auto *&formspec = m_game_ui->updateFormspec("");
2074 GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
2075 txt_dst, client->getFormspecPrepend());
2077 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2082 void Game::openConsole(float scale, const wchar_t *line)
2084 assert(scale > 0.0f && scale <= 1.0f);
2087 porting::showInputDialog(gettext("ok"), "", "", 2);
2088 m_android_chat_open = true;
2090 if (gui_chat_console->isOpenInhibited())
2092 gui_chat_console->openConsole(scale);
2094 gui_chat_console->setCloseOnEnter(true);
2095 gui_chat_console->replaceAndAddToHistory(line);
2101 void Game::handleAndroidChatInput()
2103 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2104 std::string text = porting::getInputDialogValue();
2105 client->typeChatMessage(utf8_to_wide(text));
2106 m_android_chat_open = false;
2112 void Game::toggleFreeMove()
2114 bool free_move = !g_settings->getBool("free_move");
2115 g_settings->set("free_move", bool_to_cstr(free_move));
2118 if (client->checkPrivilege("fly")) {
2119 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2121 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2124 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2128 void Game::toggleFreeMoveAlt()
2130 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2133 runData.reset_jump_timer = true;
2137 void Game::togglePitchMove()
2139 bool pitch_move = !g_settings->getBool("pitch_move");
2140 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2143 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2145 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2150 void Game::toggleFast()
2152 bool fast_move = !g_settings->getBool("fast_move");
2153 bool has_fast_privs = client->checkPrivilege("fast");
2154 g_settings->set("fast_move", bool_to_cstr(fast_move));
2157 if (has_fast_privs) {
2158 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2160 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2163 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2167 m_cache_hold_aux1 = fast_move && has_fast_privs;
2172 void Game::toggleNoClip()
2174 bool noclip = !g_settings->getBool("noclip");
2175 g_settings->set("noclip", bool_to_cstr(noclip));
2178 if (client->checkPrivilege("noclip")) {
2179 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2181 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2184 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2188 void Game::toggleCinematic()
2190 bool cinematic = !g_settings->getBool("cinematic");
2191 g_settings->set("cinematic", bool_to_cstr(cinematic));
2194 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2196 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2199 // Autoforward by toggling continuous forward.
2200 void Game::toggleAutoforward()
2202 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2203 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2205 if (autorun_enabled)
2206 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2208 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2211 void Game::toggleMinimap(bool shift_pressed)
2213 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2217 mapper->toggleMinimapShape();
2221 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2223 // Not so satisying code to keep compatibility with old fixed mode system
2225 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2227 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2228 m_game_ui->m_flags.show_minimap = false;
2231 // If radar is disabled, try to find a non radar mode or fall back to 0
2232 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2233 while (mapper->getModeIndex() &&
2234 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2237 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2241 // End of 'not so satifying code'
2242 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2243 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2244 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2246 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2249 void Game::toggleFog()
2251 bool fog_enabled = g_settings->getBool("enable_fog");
2252 g_settings->setBool("enable_fog", !fog_enabled);
2254 m_game_ui->showTranslatedStatusText("Fog disabled");
2256 m_game_ui->showTranslatedStatusText("Fog enabled");
2260 void Game::toggleDebug()
2262 // Initial / 4x toggle: Chat only
2263 // 1x toggle: Debug text with chat
2264 // 2x toggle: Debug text with profiler graph
2265 // 3x toggle: Debug text and wireframe
2266 if (!m_game_ui->m_flags.show_debug) {
2267 m_game_ui->m_flags.show_debug = true;
2268 m_game_ui->m_flags.show_profiler_graph = false;
2269 draw_control->show_wireframe = false;
2270 m_game_ui->showTranslatedStatusText("Debug info shown");
2271 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2272 m_game_ui->m_flags.show_profiler_graph = true;
2273 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2274 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2275 m_game_ui->m_flags.show_profiler_graph = false;
2276 draw_control->show_wireframe = true;
2277 m_game_ui->showTranslatedStatusText("Wireframe shown");
2279 m_game_ui->m_flags.show_debug = false;
2280 m_game_ui->m_flags.show_profiler_graph = false;
2281 draw_control->show_wireframe = false;
2282 if (client->checkPrivilege("debug")) {
2283 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2285 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2291 void Game::toggleUpdateCamera()
2293 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2294 if (m_flags.disable_camera_update)
2295 m_game_ui->showTranslatedStatusText("Camera update disabled");
2297 m_game_ui->showTranslatedStatusText("Camera update enabled");
2301 void Game::increaseViewRange()
2303 s16 range = g_settings->getS16("viewing_range");
2304 s16 range_new = range + 10;
2308 if (range_new > 4000) {
2310 str = wgettext("Viewing range is at maximum: %d");
2311 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2313 m_game_ui->showStatusText(buf);
2316 str = wgettext("Viewing range changed to %d");
2317 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2319 m_game_ui->showStatusText(buf);
2321 g_settings->set("viewing_range", itos(range_new));
2325 void Game::decreaseViewRange()
2327 s16 range = g_settings->getS16("viewing_range");
2328 s16 range_new = range - 10;
2332 if (range_new < 20) {
2334 str = wgettext("Viewing range is at minimum: %d");
2335 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2337 m_game_ui->showStatusText(buf);
2339 str = wgettext("Viewing range changed to %d");
2340 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2342 m_game_ui->showStatusText(buf);
2344 g_settings->set("viewing_range", itos(range_new));
2348 void Game::toggleFullViewRange()
2350 draw_control->range_all = !draw_control->range_all;
2351 if (draw_control->range_all)
2352 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2354 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2358 void Game::checkZoomEnabled()
2360 LocalPlayer *player = client->getEnv().getLocalPlayer();
2361 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2362 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2366 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2368 if ((device->isWindowActive() && device->isWindowFocused()
2369 && !isMenuActive()) || input->isRandom()) {
2372 if (!input->isRandom()) {
2373 // Mac OSX gets upset if this is set every frame
2374 if (device->getCursorControl()->isVisible())
2375 device->getCursorControl()->setVisible(false);
2379 if (m_first_loop_after_window_activation) {
2380 m_first_loop_after_window_activation = false;
2382 input->setMousePos(driver->getScreenSize().Width / 2,
2383 driver->getScreenSize().Height / 2);
2385 updateCameraOrientation(cam, dtime);
2391 // Mac OSX gets upset if this is set every frame
2392 if (!device->getCursorControl()->isVisible())
2393 device->getCursorControl()->setVisible(true);
2396 m_first_loop_after_window_activation = true;
2401 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2403 #ifdef HAVE_TOUCHSCREENGUI
2404 if (g_touchscreengui) {
2405 cam->camera_yaw += g_touchscreengui->getYawChange();
2406 cam->camera_pitch = g_touchscreengui->getPitch();
2409 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2410 v2s32 dist = input->getMousePos() - center;
2412 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2416 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity;
2417 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity;
2419 if (dist.X != 0 || dist.Y != 0)
2420 input->setMousePos(center.X, center.Y);
2421 #ifdef HAVE_TOUCHSCREENGUI
2425 if (m_cache_enable_joysticks) {
2426 f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime;
2427 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2428 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2431 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2435 void Game::updatePlayerControl(const CameraOrientation &cam)
2437 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2439 // DO NOT use the isKeyDown method for the forward, backward, left, right
2440 // buttons, as the code that uses the controls needs to be able to
2441 // distinguish between the two in order to know when to use joysticks.
2443 PlayerControl control(
2444 input->isKeyDown(KeyType::FORWARD),
2445 input->isKeyDown(KeyType::BACKWARD),
2446 input->isKeyDown(KeyType::LEFT),
2447 input->isKeyDown(KeyType::RIGHT),
2448 isKeyDown(KeyType::JUMP),
2449 isKeyDown(KeyType::SPECIAL1),
2450 isKeyDown(KeyType::SNEAK),
2451 isKeyDown(KeyType::ZOOM),
2452 isKeyDown(KeyType::DIG),
2453 isKeyDown(KeyType::PLACE),
2456 input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE),
2457 input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE)
2460 u32 keypress_bits = (
2461 ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) |
2462 ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) |
2463 ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) |
2464 ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) |
2465 ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) |
2466 ( (u32)(isKeyDown(KeyType::SPECIAL1) & 0x1) << 5) |
2467 ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) |
2468 ( (u32)(isKeyDown(KeyType::DIG) & 0x1) << 7) |
2469 ( (u32)(isKeyDown(KeyType::PLACE) & 0x1) << 8) |
2470 ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9)
2474 /* For Android, simulate holding down AUX1 (fast move) if the user has
2475 * the fast_move setting toggled on. If there is an aux1 key defined for
2476 * Android then its meaning is inverted (i.e. holding aux1 means walk and
2479 if (m_cache_hold_aux1) {
2480 control.aux1 = control.aux1 ^ true;
2481 keypress_bits ^= ((u32)(1U << 5));
2485 LocalPlayer *player = client->getEnv().getLocalPlayer();
2487 // autojump if set: simulate "jump" key
2488 if (player->getAutojump()) {
2489 control.jump = true;
2490 keypress_bits |= 1U << 4;
2493 // autoforward if set: simulate "up" key
2494 if (player->getPlayerSettings().continuous_forward &&
2495 client->activeObjectsReceived() && !player->isDead()) {
2497 keypress_bits |= 1U << 0;
2500 client->setPlayerControl(control);
2501 player->keyPressed = keypress_bits;
2507 inline void Game::step(f32 *dtime)
2509 bool can_be_and_is_paused =
2510 (simple_singleplayer_mode && g_menumgr.pausesGame());
2512 if (can_be_and_is_paused) { // This is for a singleplayer server
2513 *dtime = 0; // No time passes
2516 server->step(*dtime);
2518 client->step(*dtime);
2522 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2523 {&Game::handleClientEvent_None},
2524 {&Game::handleClientEvent_PlayerDamage},
2525 {&Game::handleClientEvent_PlayerForceMove},
2526 {&Game::handleClientEvent_Deathscreen},
2527 {&Game::handleClientEvent_ShowFormSpec},
2528 {&Game::handleClientEvent_ShowLocalFormSpec},
2529 {&Game::handleClientEvent_HandleParticleEvent},
2530 {&Game::handleClientEvent_HandleParticleEvent},
2531 {&Game::handleClientEvent_HandleParticleEvent},
2532 {&Game::handleClientEvent_HudAdd},
2533 {&Game::handleClientEvent_HudRemove},
2534 {&Game::handleClientEvent_HudChange},
2535 {&Game::handleClientEvent_SetSky},
2536 {&Game::handleClientEvent_SetSun},
2537 {&Game::handleClientEvent_SetMoon},
2538 {&Game::handleClientEvent_SetStars},
2539 {&Game::handleClientEvent_OverrideDayNigthRatio},
2540 {&Game::handleClientEvent_CloudParams},
2543 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2545 FATAL_ERROR("ClientEvent type None received");
2548 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2550 if (client->modsLoaded())
2551 client->getScript()->on_damage_taken(event->player_damage.amount);
2553 // Damage flash and hurt tilt are not used at death
2554 if (client->getHP() > 0) {
2555 runData.damage_flash += 95.0f + 3.2f * event->player_damage.amount;
2556 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2558 LocalPlayer *player = client->getEnv().getLocalPlayer();
2560 player->hurt_tilt_timer = 1.5f;
2561 player->hurt_tilt_strength =
2562 rangelim(event->player_damage.amount / 4.0f, 1.0f, 4.0f);
2565 // Play damage sound
2566 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2569 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2571 cam->camera_yaw = event->player_force_move.yaw;
2572 cam->camera_pitch = event->player_force_move.pitch;
2575 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2577 // If client scripting is enabled, deathscreen is handled by CSM code in
2578 // builtin/client/init.lua
2579 if (client->modsLoaded())
2580 client->getScript()->on_death();
2582 showDeathFormspec();
2584 /* Handle visualization */
2585 LocalPlayer *player = client->getEnv().getLocalPlayer();
2586 runData.damage_flash = 0;
2587 player->hurt_tilt_timer = 0;
2588 player->hurt_tilt_strength = 0;
2591 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2593 if (event->show_formspec.formspec->empty()) {
2594 auto formspec = m_game_ui->getFormspecGUI();
2595 if (formspec && (event->show_formspec.formname->empty()
2596 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2597 formspec->quitMenu();
2600 FormspecFormSource *fs_src =
2601 new FormspecFormSource(*(event->show_formspec.formspec));
2602 TextDestPlayerInventory *txt_dst =
2603 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2605 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2606 GUIFormSpecMenu::create(formspec, client, &input->joystick,
2607 fs_src, txt_dst, client->getFormspecPrepend());
2610 delete event->show_formspec.formspec;
2611 delete event->show_formspec.formname;
2614 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2616 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2617 LocalFormspecHandler *txt_dst =
2618 new LocalFormspecHandler(*event->show_formspec.formname, client);
2619 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, &input->joystick,
2620 fs_src, txt_dst, client->getFormspecPrepend());
2622 delete event->show_formspec.formspec;
2623 delete event->show_formspec.formname;
2626 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2627 CameraOrientation *cam)
2629 LocalPlayer *player = client->getEnv().getLocalPlayer();
2630 client->getParticleManager()->handleParticleEvent(event, client, player);
2633 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2635 LocalPlayer *player = client->getEnv().getLocalPlayer();
2636 auto &hud_server_to_client = client->getHUDTranslationMap();
2638 u32 server_id = event->hudadd.server_id;
2639 // ignore if we already have a HUD with that ID
2640 auto i = hud_server_to_client.find(server_id);
2641 if (i != hud_server_to_client.end()) {
2642 delete event->hudadd.pos;
2643 delete event->hudadd.name;
2644 delete event->hudadd.scale;
2645 delete event->hudadd.text;
2646 delete event->hudadd.align;
2647 delete event->hudadd.offset;
2648 delete event->hudadd.world_pos;
2649 delete event->hudadd.size;
2650 delete event->hudadd.text2;
2654 HudElement *e = new HudElement;
2655 e->type = (HudElementType)event->hudadd.type;
2656 e->pos = *event->hudadd.pos;
2657 e->name = *event->hudadd.name;
2658 e->scale = *event->hudadd.scale;
2659 e->text = *event->hudadd.text;
2660 e->number = event->hudadd.number;
2661 e->item = event->hudadd.item;
2662 e->dir = event->hudadd.dir;
2663 e->align = *event->hudadd.align;
2664 e->offset = *event->hudadd.offset;
2665 e->world_pos = *event->hudadd.world_pos;
2666 e->size = *event->hudadd.size;
2667 e->z_index = event->hudadd.z_index;
2668 e->text2 = *event->hudadd.text2;
2669 hud_server_to_client[server_id] = player->addHud(e);
2671 delete event->hudadd.pos;
2672 delete event->hudadd.name;
2673 delete event->hudadd.scale;
2674 delete event->hudadd.text;
2675 delete event->hudadd.align;
2676 delete event->hudadd.offset;
2677 delete event->hudadd.world_pos;
2678 delete event->hudadd.size;
2679 delete event->hudadd.text2;
2682 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2684 LocalPlayer *player = client->getEnv().getLocalPlayer();
2685 HudElement *e = player->removeHud(event->hudrm.id);
2689 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2691 LocalPlayer *player = client->getEnv().getLocalPlayer();
2693 u32 id = event->hudchange.id;
2694 HudElement *e = player->getHud(id);
2697 delete event->hudchange.v3fdata;
2698 delete event->hudchange.v2fdata;
2699 delete event->hudchange.sdata;
2700 delete event->hudchange.v2s32data;
2704 switch (event->hudchange.stat) {
2706 e->pos = *event->hudchange.v2fdata;
2710 e->name = *event->hudchange.sdata;
2713 case HUD_STAT_SCALE:
2714 e->scale = *event->hudchange.v2fdata;
2718 e->text = *event->hudchange.sdata;
2721 case HUD_STAT_NUMBER:
2722 e->number = event->hudchange.data;
2726 e->item = event->hudchange.data;
2730 e->dir = event->hudchange.data;
2733 case HUD_STAT_ALIGN:
2734 e->align = *event->hudchange.v2fdata;
2737 case HUD_STAT_OFFSET:
2738 e->offset = *event->hudchange.v2fdata;
2741 case HUD_STAT_WORLD_POS:
2742 e->world_pos = *event->hudchange.v3fdata;
2746 e->size = *event->hudchange.v2s32data;
2749 case HUD_STAT_Z_INDEX:
2750 e->z_index = event->hudchange.data;
2753 case HUD_STAT_TEXT2:
2754 e->text2 = *event->hudchange.sdata;
2758 delete event->hudchange.v3fdata;
2759 delete event->hudchange.v2fdata;
2760 delete event->hudchange.sdata;
2761 delete event->hudchange.v2s32data;
2764 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2766 sky->setVisible(false);
2767 // Whether clouds are visible in front of a custom skybox.
2768 sky->setCloudsEnabled(event->set_sky->clouds);
2774 // Clear the old textures out in case we switch rendering type.
2775 sky->clearSkyboxTextures();
2776 // Handle according to type
2777 if (event->set_sky->type == "regular") {
2778 // Shows the mesh skybox
2779 sky->setVisible(true);
2780 // Update mesh based skybox colours if applicable.
2781 sky->setSkyColors(event->set_sky->sky_color);
2782 sky->setHorizonTint(
2783 event->set_sky->fog_sun_tint,
2784 event->set_sky->fog_moon_tint,
2785 event->set_sky->fog_tint_type
2787 } else if (event->set_sky->type == "skybox" &&
2788 event->set_sky->textures.size() == 6) {
2789 // Disable the dyanmic mesh skybox:
2790 sky->setVisible(false);
2792 sky->setFallbackBgColor(event->set_sky->bgcolor);
2793 // Set sunrise and sunset fog tinting:
2794 sky->setHorizonTint(
2795 event->set_sky->fog_sun_tint,
2796 event->set_sky->fog_moon_tint,
2797 event->set_sky->fog_tint_type
2799 // Add textures to skybox.
2800 for (int i = 0; i < 6; i++)
2801 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2803 // Handle everything else as plain color.
2804 if (event->set_sky->type != "plain")
2805 infostream << "Unknown sky type: "
2806 << (event->set_sky->type) << std::endl;
2807 sky->setVisible(false);
2808 sky->setFallbackBgColor(event->set_sky->bgcolor);
2809 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2810 sky->setHorizonTint(
2811 event->set_sky->bgcolor,
2812 event->set_sky->bgcolor,
2816 delete event->set_sky;
2819 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2821 sky->setSunVisible(event->sun_params->visible);
2822 sky->setSunTexture(event->sun_params->texture,
2823 event->sun_params->tonemap, texture_src);
2824 sky->setSunScale(event->sun_params->scale);
2825 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2826 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2827 delete event->sun_params;
2830 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2832 sky->setMoonVisible(event->moon_params->visible);
2833 sky->setMoonTexture(event->moon_params->texture,
2834 event->moon_params->tonemap, texture_src);
2835 sky->setMoonScale(event->moon_params->scale);
2836 delete event->moon_params;
2839 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2841 sky->setStarsVisible(event->star_params->visible);
2842 sky->setStarCount(event->star_params->count, false);
2843 sky->setStarColor(event->star_params->starcolor);
2844 sky->setStarScale(event->star_params->scale);
2845 delete event->star_params;
2848 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2849 CameraOrientation *cam)
2851 client->getEnv().setDayNightRatioOverride(
2852 event->override_day_night_ratio.do_override,
2853 event->override_day_night_ratio.ratio_f * 1000.0f);
2856 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2861 clouds->setDensity(event->cloud_params.density);
2862 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2863 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2864 clouds->setHeight(event->cloud_params.height);
2865 clouds->setThickness(event->cloud_params.thickness);
2866 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2869 void Game::processClientEvents(CameraOrientation *cam)
2871 while (client->hasClientEvents()) {
2872 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2873 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2874 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2875 (this->*evHandler.handler)(event.get(), cam);
2879 void Game::updateChat(f32 dtime, const v2u32 &screensize)
2881 // Get new messages from error log buffer
2882 while (!m_chat_log_buf.empty())
2883 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2885 // Get new messages from client
2886 std::wstring message;
2887 while (client->getChatMessage(message)) {
2888 chat_backend->addUnparsedMessage(message);
2891 // Remove old messages
2892 chat_backend->step(dtime);
2894 // Display all messages in a static text element
2895 m_game_ui->setChatText(chat_backend->getRecentChat(),
2896 chat_backend->getRecentBuffer().getLineCount());
2899 void Game::updateCamera(u32 busy_time, f32 dtime)
2901 LocalPlayer *player = client->getEnv().getLocalPlayer();
2904 For interaction purposes, get info about the held item
2906 - Is it a usable item?
2907 - Can it point to liquids?
2909 ItemStack playeritem;
2911 ItemStack selected, hand;
2912 playeritem = player->getWieldedItem(&selected, &hand);
2915 ToolCapabilities playeritem_toolcap =
2916 playeritem.getToolCapabilities(itemdef_manager);
2918 v3s16 old_camera_offset = camera->getOffset();
2920 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2921 GenericCAO *playercao = player->getCAO();
2923 // If playercao not loaded, don't change camera
2927 camera->toggleCameraMode();
2929 // Make the player visible depending on camera mode.
2930 playercao->updateMeshCulling();
2931 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2934 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2935 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2937 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2938 camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
2939 camera->step(dtime);
2941 v3f camera_position = camera->getPosition();
2942 v3f camera_direction = camera->getDirection();
2943 f32 camera_fov = camera->getFovMax();
2944 v3s16 camera_offset = camera->getOffset();
2946 m_camera_offset_changed = (camera_offset != old_camera_offset);
2948 if (!m_flags.disable_camera_update) {
2949 client->getEnv().getClientMap().updateCamera(camera_position,
2950 camera_direction, camera_fov, camera_offset);
2952 if (m_camera_offset_changed) {
2953 client->updateCameraOffset(camera_offset);
2954 client->getEnv().updateCameraOffset(camera_offset);
2957 clouds->updateCameraOffset(camera_offset);
2963 void Game::updateSound(f32 dtime)
2965 // Update sound listener
2966 v3s16 camera_offset = camera->getOffset();
2967 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
2968 v3f(0, 0, 0), // velocity
2969 camera->getDirection(),
2970 camera->getCameraNode()->getUpVector());
2972 bool mute_sound = g_settings->getBool("mute_sound");
2974 sound->setListenerGain(0.0f);
2976 // Check if volume is in the proper range, else fix it.
2977 float old_volume = g_settings->getFloat("sound_volume");
2978 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
2979 sound->setListenerGain(new_volume);
2981 if (old_volume != new_volume) {
2982 g_settings->setFloat("sound_volume", new_volume);
2986 LocalPlayer *player = client->getEnv().getLocalPlayer();
2988 // Tell the sound maker whether to make footstep sounds
2989 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
2991 // Update sound maker
2992 if (player->makes_footstep_sound)
2993 soundmaker->step(dtime);
2995 ClientMap &map = client->getEnv().getClientMap();
2996 MapNode n = map.getNode(player->getFootstepNodePos());
2997 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3001 void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
3003 LocalPlayer *player = client->getEnv().getLocalPlayer();
3005 const v3f camera_direction = camera->getDirection();
3006 const v3s16 camera_offset = camera->getOffset();
3009 Calculate what block is the crosshair pointing to
3012 ItemStack selected_item, hand_item;
3013 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3015 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3016 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3018 core::line3d<f32> shootline;
3020 switch (camera->getCameraMode()) {
3021 case CAMERA_MODE_FIRST:
3022 // Shoot from camera position, with bobbing
3023 shootline.start = camera->getPosition();
3025 case CAMERA_MODE_THIRD:
3026 // Shoot from player head, no bobbing
3027 shootline.start = camera->getHeadPosition();
3029 case CAMERA_MODE_THIRD_FRONT:
3030 shootline.start = camera->getHeadPosition();
3031 // prevent player pointing anything in front-view
3035 shootline.end = shootline.start + camera_direction * BS * d;
3037 #ifdef HAVE_TOUCHSCREENGUI
3039 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3040 shootline = g_touchscreengui->getShootline();
3041 // Scale shootline to the acual distance the player can reach
3042 shootline.end = shootline.start
3043 + shootline.getVector().normalize() * BS * d;
3044 shootline.start += intToFloat(camera_offset, BS);
3045 shootline.end += intToFloat(camera_offset, BS);
3050 PointedThing pointed = updatePointedThing(shootline,
3051 selected_def.liquids_pointable,
3052 !runData.btn_down_for_dig,
3055 if (pointed != runData.pointed_old) {
3056 infostream << "Pointing at " << pointed.dump() << std::endl;
3057 hud->updateSelectionMesh(camera_offset);
3060 // Allow digging again if button is not pressed
3061 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3062 runData.digging_blocked = false;
3066 - releasing dig button
3067 - pointing away from node
3069 if (runData.digging) {
3070 if (wasKeyReleased(KeyType::DIG)) {
3071 infostream << "Dig button released (stopped digging)" << std::endl;
3072 runData.digging = false;
3073 } else if (pointed != runData.pointed_old) {
3074 if (pointed.type == POINTEDTHING_NODE
3075 && runData.pointed_old.type == POINTEDTHING_NODE
3076 && pointed.node_undersurface
3077 == runData.pointed_old.node_undersurface) {
3078 // Still pointing to the same node, but a different face.
3081 infostream << "Pointing away from node (stopped digging)" << std::endl;
3082 runData.digging = false;
3083 hud->updateSelectionMesh(camera_offset);
3087 if (!runData.digging) {
3088 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3089 client->setCrack(-1, v3s16(0, 0, 0));
3090 runData.dig_time = 0.0;
3092 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3093 // Remove e.g. torches faster when clicking instead of holding dig button
3094 runData.nodig_delay_timer = 0;
3095 runData.dig_instantly = false;
3098 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3099 runData.btn_down_for_dig = false;
3101 runData.punching = false;
3103 soundmaker->m_player_leftpunch_sound.name = "";
3105 // Prepare for repeating, unless we're not supposed to
3106 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3107 runData.repeat_place_timer += dtime;
3109 runData.repeat_place_timer = 0;
3111 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3112 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3113 !client->getScript()->on_item_use(selected_item, pointed)))
3114 client->interact(INTERACT_USE, pointed);
3115 } else if (pointed.type == POINTEDTHING_NODE) {
3116 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3117 } else if (pointed.type == POINTEDTHING_OBJECT) {
3118 v3f player_position = player->getPosition();
3119 handlePointingAtObject(pointed, tool_item, player_position, show_debug);
3120 } else if (isKeyDown(KeyType::DIG)) {
3121 // When button is held down in air, show continuous animation
3122 runData.punching = true;
3123 // Run callback even though item is not usable
3124 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3125 client->getScript()->on_item_use(selected_item, pointed);
3126 } else if (wasKeyPressed(KeyType::PLACE)) {
3127 handlePointingAtNothing(selected_item);
3130 runData.pointed_old = pointed;
3132 if (runData.punching || wasKeyPressed(KeyType::DIG))
3133 camera->setDigging(0); // dig animation
3135 input->clearWasKeyPressed();
3136 input->clearWasKeyReleased();
3138 input->joystick.clearWasKeyDown(KeyType::MOUSE_L);
3139 input->joystick.clearWasKeyDown(KeyType::MOUSE_R);
3141 input->joystick.clearWasKeyReleased(KeyType::MOUSE_L);
3142 input->joystick.clearWasKeyReleased(KeyType::MOUSE_R);
3146 PointedThing Game::updatePointedThing(
3147 const core::line3d<f32> &shootline,
3148 bool liquids_pointable,
3149 bool look_for_object,
3150 const v3s16 &camera_offset)
3152 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3153 selectionboxes->clear();
3154 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3155 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3156 "show_entity_selectionbox");
3158 ClientEnvironment &env = client->getEnv();
3159 ClientMap &map = env.getClientMap();
3160 const NodeDefManager *nodedef = map.getNodeDefManager();
3162 runData.selected_object = NULL;
3163 hud->pointing_at_object = false;
3165 RaycastState s(shootline, look_for_object, liquids_pointable);
3166 PointedThing result;
3167 env.continueRaycast(&s, &result);
3168 if (result.type == POINTEDTHING_OBJECT) {
3169 hud->pointing_at_object = true;
3171 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3172 aabb3f selection_box;
3173 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3174 runData.selected_object->getSelectionBox(&selection_box)) {
3175 v3f pos = runData.selected_object->getPosition();
3176 selectionboxes->push_back(aabb3f(selection_box));
3177 hud->setSelectionPos(pos, camera_offset);
3179 } else if (result.type == POINTEDTHING_NODE) {
3180 // Update selection boxes
3181 MapNode n = map.getNode(result.node_undersurface);
3182 std::vector<aabb3f> boxes;
3183 n.getSelectionBoxes(nodedef, &boxes,
3184 n.getNeighbors(result.node_undersurface, &map));
3187 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3188 i != boxes.end(); ++i) {
3190 box.MinEdge -= v3f(d, d, d);
3191 box.MaxEdge += v3f(d, d, d);
3192 selectionboxes->push_back(box);
3194 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3196 hud->setSelectedFaceNormal(v3f(
3197 result.intersection_normal.X,
3198 result.intersection_normal.Y,
3199 result.intersection_normal.Z));
3202 // Update selection mesh light level and vertex colors
3203 if (!selectionboxes->empty()) {
3204 v3f pf = hud->getSelectionPos();
3205 v3s16 p = floatToInt(pf, BS);
3207 // Get selection mesh light level
3208 MapNode n = map.getNode(p);
3209 u16 node_light = getInteriorLight(n, -1, nodedef);
3210 u16 light_level = node_light;
3212 for (const v3s16 &dir : g_6dirs) {
3213 n = map.getNode(p + dir);
3214 node_light = getInteriorLight(n, -1, nodedef);
3215 if (node_light > light_level)
3216 light_level = node_light;
3219 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3221 final_color_blend(&c, light_level, daynight_ratio);
3223 // Modify final color a bit with time
3224 u32 timer = porting::getTimeMs() % 5000;
3225 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3226 float sin_r = 0.08f * std::sin(timerf);
3227 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3228 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3229 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3230 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3231 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3233 // Set mesh final color
3234 hud->setSelectionMeshColor(c);
3240 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3242 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3243 PointedThing fauxPointed;
3244 fauxPointed.type = POINTEDTHING_NOTHING;
3245 client->interact(INTERACT_ACTIVATE, fauxPointed);
3249 void Game::handlePointingAtNode(const PointedThing &pointed,
3250 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3252 v3s16 nodepos = pointed.node_undersurface;
3253 v3s16 neighbourpos = pointed.node_abovesurface;
3256 Check information text of node
3259 ClientMap &map = client->getEnv().getClientMap();
3261 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3262 && !runData.digging_blocked
3263 && client->checkPrivilege("interact")) {
3264 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3267 // This should be done after digging handling
3268 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3271 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3272 meta->getString("infotext"))));
3274 MapNode n = map.getNode(nodepos);
3276 if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
3277 m_game_ui->setInfoText(L"Unknown node: " +
3278 utf8_to_wide(nodedef_manager->get(n).name));
3282 if ((wasKeyPressed(KeyType::PLACE) ||
3283 runData.repeat_place_timer >= m_repeat_place_time) &&
3284 client->checkPrivilege("interact")) {
3285 runData.repeat_place_timer = 0;
3286 infostream << "Place button pressed while looking at ground" << std::endl;
3288 // Placing animation (always shown for feedback)
3289 camera->setDigging(1);
3291 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3293 // If the wielded item has node placement prediction,
3295 // And also set the sound and send the interact
3296 // But first check for meta formspec and rightclickable
3297 auto &def = selected_item.getDefinition(itemdef_manager);
3298 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3301 if (placed && client->modsLoaded())
3302 client->getScript()->on_placenode(pointed, def);
3306 bool Game::nodePlacement(const ItemDefinition &selected_def,
3307 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3308 const PointedThing &pointed, const NodeMetadata *meta)
3310 std::string prediction = selected_def.node_placement_prediction;
3311 const NodeDefManager *nodedef = client->ndef();
3312 ClientMap &map = client->getEnv().getClientMap();
3314 bool is_valid_position;
3316 node = map.getNode(nodepos, &is_valid_position);
3317 if (!is_valid_position) {
3318 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3323 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3324 && !isKeyDown(KeyType::SNEAK)) {
3325 // on_rightclick callbacks are called anyway
3326 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3327 client->interact(INTERACT_PLACE, pointed);
3329 infostream << "Launching custom inventory view" << std::endl;
3331 InventoryLocation inventoryloc;
3332 inventoryloc.setNodeMeta(nodepos);
3334 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3335 &client->getEnv().getClientMap(), nodepos);
3336 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3338 auto *&formspec = m_game_ui->updateFormspec("");
3339 GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
3340 txt_dst, client->getFormspecPrepend());
3342 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3346 // on_rightclick callback
3347 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3348 !isKeyDown(KeyType::SNEAK))) {
3350 client->interact(INTERACT_PLACE, pointed);
3354 verbosestream << "Node placement prediction for "
3355 << selected_def.name << " is " << prediction << std::endl;
3356 v3s16 p = neighbourpos;
3358 // Place inside node itself if buildable_to
3359 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3360 if (is_valid_position) {
3361 if (nodedef->get(n_under).buildable_to) {
3364 node = map.getNode(p, &is_valid_position);
3365 if (is_valid_position && !nodedef->get(node).buildable_to) {
3366 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3368 client->interact(INTERACT_PLACE, pointed);
3374 // Find id of predicted node
3376 bool found = nodedef->getId(prediction, id);
3379 errorstream << "Node placement prediction failed for "
3380 << selected_def.name << " (places "
3382 << ") - Name not known" << std::endl;
3383 // Handle this as if prediction was empty
3385 client->interact(INTERACT_PLACE, pointed);
3389 const ContentFeatures &predicted_f = nodedef->get(id);
3391 // Predict param2 for facedir and wallmounted nodes
3394 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3395 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3396 v3s16 dir = nodepos - neighbourpos;
3398 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3399 param2 = dir.Y < 0 ? 1 : 0;
3400 } else if (abs(dir.X) > abs(dir.Z)) {
3401 param2 = dir.X < 0 ? 3 : 2;
3403 param2 = dir.Z < 0 ? 5 : 4;
3407 if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3408 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3409 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3411 if (abs(dir.X) > abs(dir.Z)) {
3412 param2 = dir.X < 0 ? 3 : 1;
3414 param2 = dir.Z < 0 ? 2 : 0;
3418 assert(param2 <= 5);
3420 //Check attachment if node is in group attached_node
3421 if (((ItemGroupList) predicted_f.groups)["attached_node"] != 0) {
3422 static v3s16 wallmounted_dirs[8] = {
3432 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3433 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3434 pp = p + wallmounted_dirs[param2];
3436 pp = p + v3s16(0, -1, 0);
3438 if (!nodedef->get(map.getNode(pp)).walkable) {
3439 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3441 client->interact(INTERACT_PLACE, pointed);
3447 if ((predicted_f.param_type_2 == CPT2_COLOR
3448 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3449 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3450 const std::string &indexstr = selected_item.metadata.getString(
3451 "palette_index", 0);
3452 if (!indexstr.empty()) {
3453 s32 index = mystoi(indexstr);
3454 if (predicted_f.param_type_2 == CPT2_COLOR) {
3456 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3457 // param2 = pure palette index + other
3458 param2 = (index & 0xf8) | (param2 & 0x07);
3459 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3460 // param2 = pure palette index + other
3461 param2 = (index & 0xe0) | (param2 & 0x1f);
3466 // Add node to client map
3467 MapNode n(id, 0, param2);
3470 LocalPlayer *player = client->getEnv().getLocalPlayer();
3472 // Dont place node when player would be inside new node
3473 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3474 if (!nodedef->get(n).walkable ||
3475 g_settings->getBool("enable_build_where_you_stand") ||
3476 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3477 (nodedef->get(n).walkable &&
3478 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3479 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3480 // This triggers the required mesh update too
3481 client->addNode(p, n);
3483 client->interact(INTERACT_PLACE, pointed);
3484 // A node is predicted, also play a sound
3485 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3488 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3491 } catch (InvalidPositionException &e) {
3492 errorstream << "Node placement prediction failed for "
3493 << selected_def.name << " (places "
3495 << ") - Position not loaded" << std::endl;
3496 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3501 void Game::handlePointingAtObject(const PointedThing &pointed,
3502 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3504 std::wstring infotext = unescape_translate(
3505 utf8_to_wide(runData.selected_object->infoText()));
3508 if (!infotext.empty()) {
3511 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3514 m_game_ui->setInfoText(infotext);
3516 if (isKeyDown(KeyType::DIG)) {
3517 bool do_punch = false;
3518 bool do_punch_damage = false;
3520 if (runData.object_hit_delay_timer <= 0.0) {
3522 do_punch_damage = true;
3523 runData.object_hit_delay_timer = object_hit_delay;
3526 if (wasKeyPressed(KeyType::DIG))
3530 infostream << "Punched object" << std::endl;
3531 runData.punching = true;
3534 if (do_punch_damage) {
3535 // Report direct punch
3536 v3f objpos = runData.selected_object->getPosition();
3537 v3f dir = (objpos - player_position).normalize();
3539 bool disable_send = runData.selected_object->directReportPunch(
3540 dir, &tool_item, runData.time_from_last_punch);
3541 runData.time_from_last_punch = 0;
3544 client->interact(INTERACT_START_DIGGING, pointed);
3546 } else if (wasKeyDown(KeyType::PLACE)) {
3547 infostream << "Pressed place button while pointing at object" << std::endl;
3548 client->interact(INTERACT_PLACE, pointed); // place
3553 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3554 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3556 // See also: serverpackethandle.cpp, action == 2
3557 LocalPlayer *player = client->getEnv().getLocalPlayer();
3558 ClientMap &map = client->getEnv().getClientMap();
3559 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3561 // NOTE: Similar piece of code exists on the server side for
3563 // Get digging parameters
3564 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3565 &selected_item.getToolCapabilities(itemdef_manager));
3567 // If can't dig, try hand
3568 if (!params.diggable) {
3569 params = getDigParams(nodedef_manager->get(n).groups,
3570 &hand_item.getToolCapabilities(itemdef_manager));
3573 if (!params.diggable) {
3574 // I guess nobody will wait for this long
3575 runData.dig_time_complete = 10000000.0;
3577 runData.dig_time_complete = params.time;
3579 if (m_cache_enable_particles) {
3580 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3581 client->getParticleManager()->addNodeParticle(client,
3582 player, nodepos, n, features);
3586 if (!runData.digging) {
3587 infostream << "Started digging" << std::endl;
3588 runData.dig_instantly = runData.dig_time_complete == 0;
3589 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3591 client->interact(INTERACT_START_DIGGING, pointed);
3592 runData.digging = true;
3593 runData.btn_down_for_dig = true;
3596 if (!runData.dig_instantly) {
3597 runData.dig_index = (float)crack_animation_length
3599 / runData.dig_time_complete;
3601 // This is for e.g. torches
3602 runData.dig_index = crack_animation_length;
3605 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3607 if (sound_dig.exists() && params.diggable) {
3608 if (sound_dig.name == "__group") {
3609 if (!params.main_group.empty()) {
3610 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3611 soundmaker->m_player_leftpunch_sound.name =
3612 std::string("default_dig_") +
3616 soundmaker->m_player_leftpunch_sound = sound_dig;
3620 // Don't show cracks if not diggable
3621 if (runData.dig_time_complete >= 100000.0) {
3622 } else if (runData.dig_index < crack_animation_length) {
3623 //TimeTaker timer("client.setTempMod");
3624 //infostream<<"dig_index="<<dig_index<<std::endl;
3625 client->setCrack(runData.dig_index, nodepos);
3627 infostream << "Digging completed" << std::endl;
3628 client->setCrack(-1, v3s16(0, 0, 0));
3630 runData.dig_time = 0;
3631 runData.digging = false;
3632 // we successfully dug, now block it from repeating if we want to be safe
3633 if (g_settings->getBool("safe_dig_and_place"))
3634 runData.digging_blocked = true;
3636 runData.nodig_delay_timer =
3637 runData.dig_time_complete / (float)crack_animation_length;
3639 // We don't want a corresponding delay to very time consuming nodes
3640 // and nodes without digging time (e.g. torches) get a fixed delay.
3641 if (runData.nodig_delay_timer > 0.3)
3642 runData.nodig_delay_timer = 0.3;
3643 else if (runData.dig_instantly)
3644 runData.nodig_delay_timer = 0.15;
3646 bool is_valid_position;
3647 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3648 if (is_valid_position) {
3649 if (client->modsLoaded() &&
3650 client->getScript()->on_dignode(nodepos, wasnode)) {
3654 const ContentFeatures &f = client->ndef()->get(wasnode);
3655 if (f.node_dig_prediction == "air") {
3656 client->removeNode(nodepos);
3657 } else if (!f.node_dig_prediction.empty()) {
3659 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3661 client->addNode(nodepos, id, true);
3663 // implicit else: no prediction
3666 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3668 if (m_cache_enable_particles) {
3669 const ContentFeatures &features =
3670 client->getNodeDefManager()->get(wasnode);
3671 client->getParticleManager()->addDiggingParticles(client,
3672 player, nodepos, wasnode, features);
3676 // Send event to trigger sound
3677 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3680 if (runData.dig_time_complete < 100000.0) {
3681 runData.dig_time += dtime;
3683 runData.dig_time = 0;
3684 client->setCrack(-1, nodepos);
3687 camera->setDigging(0); // Dig animation
3690 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3691 const CameraOrientation &cam)
3693 TimeTaker tt_update("Game::updateFrame()");
3694 LocalPlayer *player = client->getEnv().getLocalPlayer();
3700 if (draw_control->range_all) {
3701 runData.fog_range = 100000 * BS;
3703 runData.fog_range = draw_control->wanted_range * BS;
3707 Calculate general brightness
3709 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3710 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3711 float direct_brightness;
3714 if (m_cache_enable_noclip && m_cache_enable_free_move) {
3715 direct_brightness = time_brightness;
3716 sunlight_seen = true;
3718 float old_brightness = sky->getBrightness();
3719 direct_brightness = client->getEnv().getClientMap()
3720 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3721 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3725 float time_of_day_smooth = runData.time_of_day_smooth;
3726 float time_of_day = client->getEnv().getTimeOfDayF();
3728 static const float maxsm = 0.05f;
3729 static const float todsm = 0.05f;
3731 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3732 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3733 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3734 time_of_day_smooth = time_of_day;
3736 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3737 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3738 + (time_of_day + 1.0) * todsm;
3740 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3741 + time_of_day * todsm;
3743 runData.time_of_day_smooth = time_of_day_smooth;
3745 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3746 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3747 player->getPitch());
3753 if (sky->getCloudsVisible()) {
3754 clouds->setVisible(true);
3755 clouds->step(dtime);
3756 // camera->getPosition is not enough for 3rd person views
3757 v3f camera_node_position = camera->getCameraNode()->getPosition();
3758 v3s16 camera_offset = camera->getOffset();
3759 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3760 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3761 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3762 clouds->update(camera_node_position,
3763 sky->getCloudColor());
3764 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3765 // if inside clouds, and fog enabled, use that as sky
3767 video::SColor clouds_dark = clouds->getColor()
3768 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3769 sky->overrideColors(clouds_dark, clouds->getColor());
3770 sky->setInClouds(true);
3771 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3772 // do not draw clouds after all
3773 clouds->setVisible(false);
3776 clouds->setVisible(false);
3783 client->getParticleManager()->step(dtime);
3789 if (m_cache_enable_fog) {
3792 video::EFT_FOG_LINEAR,
3793 runData.fog_range * m_cache_fog_start,
3794 runData.fog_range * 1.0,
3802 video::EFT_FOG_LINEAR,
3812 Get chat messages from client
3815 v2u32 screensize = driver->getScreenSize();
3817 updateChat(dtime, screensize);
3823 if (player->getWieldIndex() != runData.new_playeritem)
3824 client->setPlayerItem(runData.new_playeritem);
3826 if (client->updateWieldedItem()) {
3827 // Update wielded tool
3828 ItemStack selected_item, hand_item;
3829 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3830 camera->wield(tool_item);
3834 Update block draw list every 200ms or when camera direction has
3837 runData.update_draw_list_timer += dtime;
3839 v3f camera_direction = camera->getDirection();
3840 if (runData.update_draw_list_timer >= 0.2
3841 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3842 || m_camera_offset_changed) {
3843 runData.update_draw_list_timer = 0;
3844 client->getEnv().getClientMap().updateDrawList();
3845 runData.update_draw_list_last_cam_dir = camera_direction;
3848 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3851 make sure menu is on top
3852 1. Delete formspec menu reference if menu was removed
3853 2. Else, make sure formspec menu is on top
3855 auto formspec = m_game_ui->getFormspecGUI();
3856 do { // breakable. only runs for one iteration
3860 if (formspec->getReferenceCount() == 1) {
3861 m_game_ui->deleteFormspec();
3865 auto &loc = formspec->getFormspecLocation();
3866 if (loc.type == InventoryLocation::NODEMETA) {
3867 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3868 if (!meta || meta->getString("formspec").empty()) {
3869 formspec->quitMenu();
3875 guiroot->bringToFront(formspec);
3881 const video::SColor &skycolor = sky->getSkyColor();
3883 TimeTaker tt_draw("Draw scene");
3884 driver->beginScene(true, true, skycolor);
3886 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3887 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3888 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3889 bool draw_crosshair = (
3890 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3891 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3892 #ifdef HAVE_TOUCHSCREENGUI
3894 draw_crosshair = !g_settings->getBool("touchtarget");
3895 } catch (SettingNotFoundException) {
3898 RenderingEngine::draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3899 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3904 if (m_game_ui->m_flags.show_profiler_graph)
3905 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3910 if (runData.damage_flash > 0.0f) {
3911 video::SColor color(runData.damage_flash, 180, 0, 0);
3912 driver->draw2DRectangle(color,
3913 core::rect<s32>(0, 0, screensize.X, screensize.Y),
3916 runData.damage_flash -= 384.0f * dtime;
3922 if (player->hurt_tilt_timer > 0.0f) {
3923 player->hurt_tilt_timer -= dtime * 6.0f;
3925 if (player->hurt_tilt_timer < 0.0f)
3926 player->hurt_tilt_strength = 0.0f;
3930 Update minimap pos and rotation
3932 if (mapper && m_game_ui->m_flags.show_hud) {
3933 mapper->setPos(floatToInt(player->getPosition(), BS));
3934 mapper->setAngle(player->getYaw());
3940 if (++m_reset_HW_buffer_counter > 500) {
3942 Periodically remove all mesh HW buffers.
3944 Work around for a quirk in Irrlicht where a HW buffer is only
3945 released after 20000 iterations (triggered from endScene()).
3947 Without this, all loaded but unused meshes will retain their HW
3948 buffers for at least 5 minutes, at which point looking up the HW buffers
3949 becomes a bottleneck and the framerate drops (as much as 30%).
3951 Tests showed that numbers between 50 and 1000 are good, so picked 500.
3952 There are no other public Irrlicht APIs that allow interacting with the
3953 HW buffers without tracking the status of every individual mesh.
3955 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
3957 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
3958 driver->removeAllHardwareBuffers();
3959 m_reset_HW_buffer_counter = 0;
3963 stats->drawtime = tt_draw.stop(true);
3964 g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
3965 g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
3968 /* Log times and stuff for visualization */
3969 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
3971 Profiler::GraphValues values;
3972 g_profiler->graphGet(values);
3978 /****************************************************************************
3980 ****************************************************************************/
3982 /* On some computers framerate doesn't seem to be automatically limited
3984 inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
3986 // not using getRealTime is necessary for wine
3987 device->getTimer()->tick(); // Maker sure device time is up-to-date
3988 u32 time = device->getTimer()->getTime();
3989 u32 last_time = fps_timings->last_time;
3991 if (time > last_time) // Make sure time hasn't overflowed
3992 fps_timings->busy_time = time - last_time;
3994 fps_timings->busy_time = 0;
3996 u32 frametime_min = 1000 / (
3997 device->isWindowFocused() && !g_menumgr.pausesGame()
3998 ? g_settings->getFloat("fps_max")
3999 : g_settings->getFloat("fps_max_unfocused"));
4001 if (fps_timings->busy_time < frametime_min) {
4002 fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
4003 device->sleep(fps_timings->sleep_time);
4005 fps_timings->sleep_time = 0;
4008 /* Get the new value of the device timer. Note that device->sleep() may
4009 * not sleep for the entire requested time as sleep may be interrupted and
4010 * therefore it is arguably more accurate to get the new time from the
4011 * device rather than calculating it by adding sleep_time to time.
4014 device->getTimer()->tick(); // Update device timer
4015 time = device->getTimer()->getTime();
4017 if (time > last_time) // Make sure last_time hasn't overflowed
4018 *dtime = (time - last_time) / 1000.0;
4022 fps_timings->last_time = time;
4025 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4027 const wchar_t *wmsg = wgettext(msg);
4028 RenderingEngine::draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4033 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4035 ((Game *)data)->readSettings();
4038 void Game::readSettings()
4040 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4041 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4042 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4043 m_cache_enable_particles = g_settings->getBool("enable_particles");
4044 m_cache_enable_fog = g_settings->getBool("enable_fog");
4045 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
4046 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4047 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
4049 m_cache_enable_noclip = g_settings->getBool("noclip");
4050 m_cache_enable_free_move = g_settings->getBool("free_move");
4052 m_cache_fog_start = g_settings->getFloat("fog_start");
4054 m_cache_cam_smoothing = 0;
4055 if (g_settings->getBool("cinematic"))
4056 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4058 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4060 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4061 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4062 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4064 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4067 /****************************************************************************/
4068 /****************************************************************************
4070 ****************************************************************************/
4071 /****************************************************************************/
4073 void Game::extendedResourceCleanup()
4075 // Extended resource accounting
4076 infostream << "Irrlicht resources after cleanup:" << std::endl;
4077 infostream << "\tRemaining meshes : "
4078 << RenderingEngine::get_mesh_cache()->getMeshCount() << std::endl;
4079 infostream << "\tRemaining textures : "
4080 << driver->getTextureCount() << std::endl;
4082 for (unsigned int i = 0; i < driver->getTextureCount(); i++) {
4083 irr::video::ITexture *texture = driver->getTextureByIndex(i);
4084 infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str()
4088 clearTextureNameCache();
4089 infostream << "\tRemaining materials: "
4090 << driver-> getMaterialRendererCount()
4091 << " (note: irrlicht doesn't support removing renderers)" << std::endl;
4094 void Game::showDeathFormspec()
4096 static std::string formspec_str =
4097 std::string("formspec_version[1]") +
4099 "bgcolor[#320000b4;true]"
4100 "label[4.85,1.35;" + gettext("You died") + "]"
4101 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4105 /* Note: FormspecFormSource and LocalFormspecHandler *
4106 * are deleted by guiFormSpecMenu */
4107 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4108 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4110 auto *&formspec = m_game_ui->getFormspecGUI();
4111 GUIFormSpecMenu::create(formspec, client, &input->joystick,
4112 fs_src, txt_dst, client->getFormspecPrepend());
4113 formspec->setFocus("btn_respawn");
4116 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4117 void Game::showPauseMenu()
4120 static const std::string control_text = strgettext("Default Controls:\n"
4121 "No menu visible:\n"
4122 "- single tap: button activate\n"
4123 "- double tap: place/use\n"
4124 "- slide finger: look around\n"
4125 "Menu/Inventory visible:\n"
4126 "- double tap (outside):\n"
4128 "- touch stack, touch slot:\n"
4130 "- touch&drag, tap 2nd finger\n"
4131 " --> place single item to slot\n"
4134 static const std::string control_text_template = strgettext("Controls:\n"
4135 "- %s: move forwards\n"
4136 "- %s: move backwards\n"
4138 "- %s: move right\n"
4139 "- %s: jump/climb up\n"
4142 "- %s: sneak/climb down\n"
4145 "- Mouse: turn/look\n"
4146 "- Mouse wheel: select item\n"
4150 char control_text_buf[600];
4152 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4153 GET_KEY_NAME(keymap_forward),
4154 GET_KEY_NAME(keymap_backward),
4155 GET_KEY_NAME(keymap_left),
4156 GET_KEY_NAME(keymap_right),
4157 GET_KEY_NAME(keymap_jump),
4158 GET_KEY_NAME(keymap_dig),
4159 GET_KEY_NAME(keymap_place),
4160 GET_KEY_NAME(keymap_sneak),
4161 GET_KEY_NAME(keymap_drop),
4162 GET_KEY_NAME(keymap_inventory),
4163 GET_KEY_NAME(keymap_chat)
4166 std::string control_text = std::string(control_text_buf);
4167 str_formspec_escape(control_text);
4170 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4171 std::ostringstream os;
4173 os << "formspec_version[1]" << SIZE_TAG
4174 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4175 << strgettext("Continue") << "]";
4177 if (!simple_singleplayer_mode) {
4178 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4179 << strgettext("Change Password") << "]";
4181 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4186 if (g_settings->getBool("enable_sound")) {
4187 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4188 << strgettext("Sound Volume") << "]";
4191 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4192 << strgettext("Change Keys") << "]";
4194 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4195 << strgettext("Exit to Menu") << "]";
4196 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4197 << strgettext("Exit to OS") << "]"
4198 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4199 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4201 << strgettext("Game info:") << "\n";
4202 const std::string &address = client->getAddressName();
4203 static const std::string mode = strgettext("- Mode: ");
4204 if (!simple_singleplayer_mode) {
4205 Address serverAddress = client->getServerAddress();
4206 if (!address.empty()) {
4207 os << mode << strgettext("Remote server") << "\n"
4208 << strgettext("- Address: ") << address;
4210 os << mode << strgettext("Hosting server");
4212 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4214 os << mode << strgettext("Singleplayer") << "\n";
4216 if (simple_singleplayer_mode || address.empty()) {
4217 static const std::string on = strgettext("On");
4218 static const std::string off = strgettext("Off");
4219 const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
4220 const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
4221 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4222 os << strgettext("- Damage: ") << damage << "\n"
4223 << strgettext("- Creative Mode: ") << creative << "\n";
4224 if (!simple_singleplayer_mode) {
4225 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4226 //~ PvP = Player versus Player
4227 os << strgettext("- PvP: ") << pvp << "\n"
4228 << strgettext("- Public: ") << announced << "\n";
4229 std::string server_name = g_settings->get("server_name");
4230 str_formspec_escape(server_name);
4231 if (announced == on && !server_name.empty())
4232 os << strgettext("- Server Name: ") << server_name;
4239 /* Note: FormspecFormSource and LocalFormspecHandler *
4240 * are deleted by guiFormSpecMenu */
4241 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4242 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4244 auto *&formspec = m_game_ui->getFormspecGUI();
4245 GUIFormSpecMenu::create(formspec, client, &input->joystick,
4246 fs_src, txt_dst, client->getFormspecPrepend());
4247 formspec->setFocus("btn_continue");
4248 formspec->doPause = true;
4251 /****************************************************************************/
4252 /****************************************************************************
4253 extern function for launching the game
4254 ****************************************************************************/
4255 /****************************************************************************/
4257 void the_game(bool *kill,
4258 InputHandler *input,
4259 const GameStartData &start_data,
4260 std::string &error_message,
4261 ChatBackend &chat_backend,
4262 bool *reconnect_requested) // Used for local game
4266 /* Make a copy of the server address because if a local singleplayer server
4267 * is created then this is updated and we don't want to change the value
4268 * passed to us by the calling function
4273 if (game.startup(kill, input, start_data, error_message,
4274 reconnect_requested, &chat_backend)) {
4278 } catch (SerializationError &e) {
4279 error_message = std::string("A serialization error occurred:\n")
4280 + e.what() + "\n\nThe server is probably "
4281 " running a different version of " PROJECT_NAME_C ".";
4282 errorstream << error_message << std::endl;
4283 } catch (ServerError &e) {
4284 error_message = e.what();
4285 errorstream << "ServerError: " << error_message << std::endl;
4286 } catch (ModError &e) {
4287 error_message = std::string("ModError: ") + e.what() +
4288 strgettext("\nCheck debug.txt for details.");
4289 errorstream << error_message << std::endl;