3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "client/renderingengine.h"
27 #include "client/clientevent.h"
28 #include "client/gameui.h"
29 #include "client/inputhandler.h"
30 #include "client/tile.h" // For TextureSource
31 #include "client/keys.h"
32 #include "client/joystick_controller.h"
33 #include "clientmap.h"
36 #include "content_cao.h"
37 #include "content/subgames.h"
38 #include "client/event_manager.h"
39 #include "fontengine.h"
43 #include "gameparams.h"
45 #include "gui/guiChatConsole.h"
46 #include "gui/guiConfirmRegistration.h"
47 #include "gui/guiFormSpecMenu.h"
48 #include "gui/guiKeyChangeMenu.h"
49 #include "gui/guiPasswordChange.h"
50 #include "gui/guiVolumeChange.h"
51 #include "gui/mainmenumanager.h"
52 #include "gui/profilergraph.h"
55 #include "nodedef.h" // Needed for determining pointing to nodes
56 #include "nodemetadata.h"
57 #include "particles.h"
65 #include "translation.h"
66 #include "util/basic_macros.h"
67 #include "util/directiontables.h"
68 #include "util/pointedthing.h"
69 #include "util/quicktune_shortcutter.h"
70 #include "irrlicht_changes/static_text.h"
73 #include "script/scripting_client.h"
77 #include "client/sound_openal.h"
79 #include "client/sound.h"
85 struct TextDestNodeMetadata : public TextDest
87 TextDestNodeMetadata(v3s16 p, Client *client)
92 // This is deprecated I guess? -celeron55
93 void gotText(const std::wstring &text)
95 std::string ntext = wide_to_utf8(text);
96 infostream << "Submitting 'text' field of node at (" << m_p.X << ","
97 << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
99 fields["text"] = ntext;
100 m_client->sendNodemetaFields(m_p, "", fields);
102 void gotText(const StringMap &fields)
104 m_client->sendNodemetaFields(m_p, "", fields);
111 struct TextDestPlayerInventory : public TextDest
113 TextDestPlayerInventory(Client *client)
118 TextDestPlayerInventory(Client *client, const std::string &formname)
121 m_formname = formname;
123 void gotText(const StringMap &fields)
125 m_client->sendInventoryFields(m_formname, fields);
131 struct LocalFormspecHandler : public TextDest
133 LocalFormspecHandler(const std::string &formname)
135 m_formname = formname;
138 LocalFormspecHandler(const std::string &formname, Client *client):
141 m_formname = formname;
144 void gotText(const StringMap &fields)
146 if (m_formname == "MT_PAUSE_MENU") {
147 if (fields.find("btn_sound") != fields.end()) {
148 g_gamecallback->changeVolume();
152 if (fields.find("btn_key_config") != fields.end()) {
153 g_gamecallback->keyConfig();
157 if (fields.find("btn_exit_menu") != fields.end()) {
158 g_gamecallback->disconnect();
162 if (fields.find("btn_exit_os") != fields.end()) {
163 g_gamecallback->exitToOS();
165 RenderingEngine::get_raw_device()->closeDevice();
170 if (fields.find("btn_change_password") != fields.end()) {
171 g_gamecallback->changePassword();
178 if (m_formname == "MT_DEATH_SCREEN") {
179 assert(m_client != 0);
180 m_client->sendRespawn();
184 if (m_client->modsLoaded())
185 m_client->getScript()->on_formspec_input(m_formname, fields);
188 Client *m_client = nullptr;
191 /* Form update callback */
193 class NodeMetadataFormSource: public IFormSource
196 NodeMetadataFormSource(ClientMap *map, v3s16 p):
201 const std::string &getForm() const
203 static const std::string empty_string = "";
204 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
209 return meta->getString("formspec");
212 virtual std::string resolveText(const std::string &str)
214 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
219 return meta->resolveString(str);
226 class PlayerInventoryFormSource: public IFormSource
229 PlayerInventoryFormSource(Client *client):
234 const std::string &getForm() const
236 LocalPlayer *player = m_client->getEnv().getLocalPlayer();
237 return player->inventory_formspec;
243 class NodeDugEvent: public MtEvent
249 NodeDugEvent(v3s16 p, MapNode n):
253 MtEvent::Type getType() const
255 return MtEvent::NODE_DUG;
261 ISoundManager *m_sound;
262 const NodeDefManager *m_ndef;
264 bool makes_footstep_sound;
265 float m_player_step_timer;
266 float m_player_jump_timer;
268 SimpleSoundSpec m_player_step_sound;
269 SimpleSoundSpec m_player_leftpunch_sound;
270 SimpleSoundSpec m_player_rightpunch_sound;
272 SoundMaker(ISoundManager *sound, const NodeDefManager *ndef):
275 makes_footstep_sound(true),
276 m_player_step_timer(0.0f),
277 m_player_jump_timer(0.0f)
281 void playPlayerStep()
283 if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
284 m_player_step_timer = 0.03;
285 if (makes_footstep_sound)
286 m_sound->playSound(m_player_step_sound, false);
290 void playPlayerJump()
292 if (m_player_jump_timer <= 0.0f) {
293 m_player_jump_timer = 0.2f;
294 m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f), false);
298 static void viewBobbingStep(MtEvent *e, void *data)
300 SoundMaker *sm = (SoundMaker *)data;
301 sm->playPlayerStep();
304 static void playerRegainGround(MtEvent *e, void *data)
306 SoundMaker *sm = (SoundMaker *)data;
307 sm->playPlayerStep();
310 static void playerJump(MtEvent *e, void *data)
312 SoundMaker *sm = (SoundMaker *)data;
313 sm->playPlayerJump();
316 static void cameraPunchLeft(MtEvent *e, void *data)
318 SoundMaker *sm = (SoundMaker *)data;
319 sm->m_sound->playSound(sm->m_player_leftpunch_sound, false);
322 static void cameraPunchRight(MtEvent *e, void *data)
324 SoundMaker *sm = (SoundMaker *)data;
325 sm->m_sound->playSound(sm->m_player_rightpunch_sound, false);
328 static void nodeDug(MtEvent *e, void *data)
330 SoundMaker *sm = (SoundMaker *)data;
331 NodeDugEvent *nde = (NodeDugEvent *)e;
332 sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug, false);
335 static void playerDamage(MtEvent *e, void *data)
337 SoundMaker *sm = (SoundMaker *)data;
338 sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false);
341 static void playerFallingDamage(MtEvent *e, void *data)
343 SoundMaker *sm = (SoundMaker *)data;
344 sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false);
347 void registerReceiver(MtEventManager *mgr)
349 mgr->reg(MtEvent::VIEW_BOBBING_STEP, SoundMaker::viewBobbingStep, this);
350 mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, this);
351 mgr->reg(MtEvent::PLAYER_JUMP, SoundMaker::playerJump, this);
352 mgr->reg(MtEvent::CAMERA_PUNCH_LEFT, SoundMaker::cameraPunchLeft, this);
353 mgr->reg(MtEvent::CAMERA_PUNCH_RIGHT, SoundMaker::cameraPunchRight, this);
354 mgr->reg(MtEvent::NODE_DUG, SoundMaker::nodeDug, this);
355 mgr->reg(MtEvent::PLAYER_DAMAGE, SoundMaker::playerDamage, this);
356 mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, this);
359 void step(float dtime)
361 m_player_step_timer -= dtime;
362 m_player_jump_timer -= dtime;
366 // Locally stored sounds don't need to be preloaded because of this
367 class GameOnDemandSoundFetcher: public OnDemandSoundFetcher
369 std::set<std::string> m_fetched;
371 void paths_insert(std::set<std::string> &dst_paths,
372 const std::string &base,
373 const std::string &name)
375 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg");
376 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg");
377 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg");
378 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg");
379 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg");
380 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg");
381 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg");
382 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg");
383 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg");
384 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg");
385 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg");
388 void fetchSounds(const std::string &name,
389 std::set<std::string> &dst_paths,
390 std::set<std::string> &dst_datas)
392 if (m_fetched.count(name))
395 m_fetched.insert(name);
397 paths_insert(dst_paths, porting::path_share, name);
398 paths_insert(dst_paths, porting::path_user, name);
403 // before 1.8 there isn't a "integer interface", only float
404 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
405 typedef f32 SamplerLayer_t;
407 typedef s32 SamplerLayer_t;
411 class GameGlobalShaderConstantSetter : public IShaderConstantSetter
414 bool *m_force_fog_off;
417 CachedPixelShaderSetting<float, 4> m_sky_bg_color;
418 CachedPixelShaderSetting<float> m_fog_distance;
419 CachedVertexShaderSetting<float> m_animation_timer_vertex;
420 CachedPixelShaderSetting<float> m_animation_timer_pixel;
421 CachedPixelShaderSetting<float, 3> m_day_light;
422 CachedPixelShaderSetting<float, 4> m_star_color;
423 CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
424 CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
425 CachedPixelShaderSetting<float, 3> m_minimap_yaw;
426 CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
427 CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
428 CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
432 void onSettingsChange(const std::string &name)
434 if (name == "enable_fog")
435 m_fog_enabled = g_settings->getBool("enable_fog");
438 static void settingsCallback(const std::string &name, void *userdata)
440 reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
443 void setSky(Sky *sky) { m_sky = sky; }
445 GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
446 f32 *fog_range, Client *client) :
448 m_force_fog_off(force_fog_off),
449 m_fog_range(fog_range),
450 m_sky_bg_color("skyBgColor"),
451 m_fog_distance("fogDistance"),
452 m_animation_timer_vertex("animationTimer"),
453 m_animation_timer_pixel("animationTimer"),
454 m_day_light("dayLight"),
455 m_star_color("starColor"),
456 m_eye_position_pixel("eyePosition"),
457 m_eye_position_vertex("eyePosition"),
458 m_minimap_yaw("yawVec"),
459 m_camera_offset_pixel("cameraOffset"),
460 m_camera_offset_vertex("cameraOffset"),
461 m_base_texture("baseTexture"),
464 g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
465 m_fog_enabled = g_settings->getBool("enable_fog");
468 ~GameGlobalShaderConstantSetter()
470 g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
473 void onSetConstants(video::IMaterialRendererServices *services) override
476 video::SColor bgcolor = m_sky->getBgColor();
477 video::SColorf bgcolorf(bgcolor);
478 float bgcolorfa[4] = {
484 m_sky_bg_color.set(bgcolorfa, services);
487 float fog_distance = 10000 * BS;
489 if (m_fog_enabled && !*m_force_fog_off)
490 fog_distance = *m_fog_range;
492 m_fog_distance.set(&fog_distance, services);
494 u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
495 video::SColorf sunlight;
496 get_sunlight_color(&sunlight, daynight_ratio);
501 m_day_light.set(dnc, services);
503 video::SColorf star_color = m_sky->getCurrentStarColor();
504 float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a};
505 m_star_color.set(clr, services);
507 u32 animation_timer = porting::getTimeMs() % 1000000;
508 float animation_timer_f = (float)animation_timer / 100000.f;
509 m_animation_timer_vertex.set(&animation_timer_f, services);
510 m_animation_timer_pixel.set(&animation_timer_f, services);
512 float eye_position_array[3];
513 v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
514 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
515 eye_position_array[0] = epos.X;
516 eye_position_array[1] = epos.Y;
517 eye_position_array[2] = epos.Z;
519 epos.getAs3Values(eye_position_array);
521 m_eye_position_pixel.set(eye_position_array, services);
522 m_eye_position_vertex.set(eye_position_array, services);
524 if (m_client->getMinimap()) {
525 float minimap_yaw_array[3];
526 v3f minimap_yaw = m_client->getMinimap()->getYawVec();
527 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
528 minimap_yaw_array[0] = minimap_yaw.X;
529 minimap_yaw_array[1] = minimap_yaw.Y;
530 minimap_yaw_array[2] = minimap_yaw.Z;
532 minimap_yaw.getAs3Values(minimap_yaw_array);
534 m_minimap_yaw.set(minimap_yaw_array, services);
537 float camera_offset_array[3];
538 v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
539 #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
540 camera_offset_array[0] = offset.X;
541 camera_offset_array[1] = offset.Y;
542 camera_offset_array[2] = offset.Z;
544 offset.getAs3Values(camera_offset_array);
546 m_camera_offset_pixel.set(camera_offset_array, services);
547 m_camera_offset_vertex.set(camera_offset_array, services);
549 SamplerLayer_t base_tex = 0;
550 m_base_texture.set(&base_tex, services);
555 class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
558 bool *m_force_fog_off;
561 std::vector<GameGlobalShaderConstantSetter *> created_nosky;
563 GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
564 f32 *fog_range, Client *client) :
566 m_force_fog_off(force_fog_off),
567 m_fog_range(fog_range),
571 void setSky(Sky *sky) {
573 for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
574 ggscs->setSky(m_sky);
576 created_nosky.clear();
579 virtual IShaderConstantSetter* create()
581 auto *scs = new GameGlobalShaderConstantSetter(
582 m_sky, m_force_fog_off, m_fog_range, m_client);
584 created_nosky.push_back(scs);
590 #define SIZE_TAG "size[11,5.5]"
592 #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
595 /****************************************************************************
596 ****************************************************************************/
598 const float object_hit_delay = 0.2;
601 u32 last_time, busy_time, sleep_time;
605 /* The reason the following structs are not anonymous structs within the
606 * class is that they are not used by the majority of member functions and
607 * many functions that do require objects of thse types do not modify them
608 * (so they can be passed as a const qualified parameter)
614 PointedThing pointed_old;
617 bool btn_down_for_dig;
619 bool digging_blocked;
620 bool reset_jump_timer;
621 float nodig_delay_timer;
623 float dig_time_complete;
624 float repeat_place_timer;
625 float object_hit_delay_timer;
626 float time_from_last_punch;
627 ClientActiveObject *selected_object;
631 float update_draw_list_timer;
635 v3f update_draw_list_last_cam_dir;
637 float time_of_day_smooth;
642 struct ClientEventHandler
644 void (Game::*handler)(ClientEvent *, CameraOrientation *);
647 /****************************************************************************
649 ****************************************************************************/
651 using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
653 /* This is not intended to be a public class. If a public class becomes
654 * desirable then it may be better to create another 'wrapper' class that
655 * hides most of the stuff in this class (nothing in this class is required
656 * by any other file) but exposes the public methods/data only.
663 bool startup(bool *kill,
665 const GameStartData &game_params,
666 std::string &error_message,
668 ChatBackend *chat_backend);
675 void extendedResourceCleanup();
677 // Basic initialisation
678 bool init(const std::string &map_dir, const std::string &address,
679 u16 port, const SubgameSpec &gamespec);
681 bool createSingleplayerServer(const std::string &map_dir,
682 const SubgameSpec &gamespec, u16 port);
685 bool createClient(const GameStartData &start_data);
689 bool connectToServer(const GameStartData &start_data,
690 bool *connect_ok, bool *aborted);
691 bool getServerContent(bool *aborted);
695 void updateInteractTimers(f32 dtime);
696 bool checkConnection();
697 bool handleCallbacks();
698 void processQueues();
699 void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
700 void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
701 void updateProfilerGraphs(ProfilerGraph *graph);
704 void processUserInput(f32 dtime);
705 void processKeyInput();
706 void processItemSelection(u16 *new_playeritem);
708 void dropSelectedItem(bool single_item = false);
709 void openInventory();
710 void openConsole(float scale, const wchar_t *line=NULL);
711 void toggleFreeMove();
712 void toggleFreeMoveAlt();
713 void togglePitchMove();
716 void toggleCinematic();
717 void toggleAutoforward();
719 void toggleMinimap(bool shift_pressed);
722 void toggleUpdateCamera();
724 void increaseViewRange();
725 void decreaseViewRange();
726 void toggleFullViewRange();
727 void checkZoomEnabled();
729 void updateCameraDirection(CameraOrientation *cam, float dtime);
730 void updateCameraOrientation(CameraOrientation *cam, float dtime);
731 void updatePlayerControl(const CameraOrientation &cam);
732 void step(f32 *dtime);
733 void processClientEvents(CameraOrientation *cam);
734 void updateCamera(u32 busy_time, f32 dtime);
735 void updateSound(f32 dtime);
736 void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug);
738 * Returns the object or node the player is pointing at.
739 * Also updates the selected thing in the Hud.
741 * @param[in] shootline the shootline, starting from
742 * the camera position. This also gives the maximal distance
744 * @param[in] liquids_pointable if false, liquids are ignored
745 * @param[in] look_for_object if false, objects are ignored
746 * @param[in] camera_offset offset of the camera
747 * @param[out] selected_object the selected object or
750 PointedThing updatePointedThing(
751 const core::line3d<f32> &shootline, bool liquids_pointable,
752 bool look_for_object, const v3s16 &camera_offset);
753 void handlePointingAtNothing(const ItemStack &playerItem);
754 void handlePointingAtNode(const PointedThing &pointed,
755 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
756 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
757 const v3f &player_position, bool show_debug);
758 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
759 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
760 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
761 const CameraOrientation &cam);
764 void limitFps(FpsControl *fps_timings, f32 *dtime);
766 void showOverlayMessage(const char *msg, float dtime, int percent,
767 bool draw_clouds = true);
769 static void settingChangedCallback(const std::string &setting_name, void *data);
772 inline bool isKeyDown(GameKeyType k)
774 return input->isKeyDown(k);
776 inline bool wasKeyDown(GameKeyType k)
778 return input->wasKeyDown(k);
780 inline bool wasKeyPressed(GameKeyType k)
782 return input->wasKeyPressed(k);
784 inline bool wasKeyReleased(GameKeyType k)
786 return input->wasKeyReleased(k);
790 void handleAndroidChatInput();
795 bool force_fog_off = false;
796 bool disable_camera_update = false;
799 void showDeathFormspec();
800 void showPauseMenu();
802 void pauseAnimation();
803 void resumeAnimation();
805 // ClientEvent handlers
806 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
807 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
808 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
809 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
810 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
811 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
812 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
813 CameraOrientation *cam);
814 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
815 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
816 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
817 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
818 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
819 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
820 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
821 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
822 CameraOrientation *cam);
823 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
825 void updateChat(f32 dtime, const v2u32 &screensize);
827 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
828 const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
829 const NodeMetadata *meta);
830 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
832 InputHandler *input = nullptr;
834 Client *client = nullptr;
835 Server *server = nullptr;
837 IWritableTextureSource *texture_src = nullptr;
838 IWritableShaderSource *shader_src = nullptr;
840 // When created, these will be filled with data received from the server
841 IWritableItemDefManager *itemdef_manager = nullptr;
842 NodeDefManager *nodedef_manager = nullptr;
844 GameOnDemandSoundFetcher soundfetcher; // useful when testing
845 ISoundManager *sound = nullptr;
846 bool sound_is_dummy = false;
847 SoundMaker *soundmaker = nullptr;
849 ChatBackend *chat_backend = nullptr;
850 LogOutputBuffer m_chat_log_buf;
852 EventManager *eventmgr = nullptr;
853 QuicktuneShortcutter *quicktune = nullptr;
854 bool registration_confirmation_shown = false;
856 std::unique_ptr<GameUI> m_game_ui;
857 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
858 MapDrawControl *draw_control = nullptr;
859 Camera *camera = nullptr;
860 Clouds *clouds = nullptr; // Free using ->Drop()
861 Sky *sky = nullptr; // Free using ->Drop()
863 Minimap *mapper = nullptr;
865 // Map server hud ids to client hud ids
866 std::unordered_map<u32, u32> m_hud_server_to_client;
872 This class does take ownership/responsibily for cleaning up etc of any of
873 these items (e.g. device)
875 IrrlichtDevice *device;
876 video::IVideoDriver *driver;
877 scene::ISceneManager *smgr;
879 std::string *error_message;
880 bool *reconnect_requested;
881 scene::ISceneNode *skybox;
882 PausedNodesList paused_animated_nodes;
884 bool simple_singleplayer_mode;
887 /* Pre-calculated values
889 int crack_animation_length;
891 IntervalLimiter profiler_interval;
894 * TODO: Local caching of settings is not optimal and should at some stage
895 * be updated to use a global settings object for getting thse values
896 * (as opposed to the this local caching). This can be addressed in
899 bool m_cache_doubletap_jump;
900 bool m_cache_enable_clouds;
901 bool m_cache_enable_joysticks;
902 bool m_cache_enable_particles;
903 bool m_cache_enable_fog;
904 bool m_cache_enable_noclip;
905 bool m_cache_enable_free_move;
906 f32 m_cache_mouse_sensitivity;
907 f32 m_cache_joystick_frustum_sensitivity;
908 f32 m_repeat_place_time;
909 f32 m_cache_cam_smoothing;
910 f32 m_cache_fog_start;
912 bool m_invert_mouse = false;
913 bool m_first_loop_after_window_activation = false;
914 bool m_camera_offset_changed = false;
916 bool m_does_lost_focus_pause_game = false;
918 int m_reset_HW_buffer_counter = 0;
920 bool m_cache_hold_aux1;
921 bool m_android_chat_open;
926 m_chat_log_buf(g_logger),
927 m_game_ui(new GameUI())
929 g_settings->registerChangedCallback("doubletap_jump",
930 &settingChangedCallback, this);
931 g_settings->registerChangedCallback("enable_clouds",
932 &settingChangedCallback, this);
933 g_settings->registerChangedCallback("doubletap_joysticks",
934 &settingChangedCallback, this);
935 g_settings->registerChangedCallback("enable_particles",
936 &settingChangedCallback, this);
937 g_settings->registerChangedCallback("enable_fog",
938 &settingChangedCallback, this);
939 g_settings->registerChangedCallback("mouse_sensitivity",
940 &settingChangedCallback, this);
941 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
942 &settingChangedCallback, this);
943 g_settings->registerChangedCallback("repeat_place_time",
944 &settingChangedCallback, this);
945 g_settings->registerChangedCallback("noclip",
946 &settingChangedCallback, this);
947 g_settings->registerChangedCallback("free_move",
948 &settingChangedCallback, this);
949 g_settings->registerChangedCallback("cinematic",
950 &settingChangedCallback, this);
951 g_settings->registerChangedCallback("cinematic_camera_smoothing",
952 &settingChangedCallback, this);
953 g_settings->registerChangedCallback("camera_smoothing",
954 &settingChangedCallback, this);
959 m_cache_hold_aux1 = false; // This is initialised properly later
966 /****************************************************************************
968 ****************************************************************************/
977 delete server; // deleted first to stop all server threads
985 delete nodedef_manager;
986 delete itemdef_manager;
989 extendedResourceCleanup();
991 g_settings->deregisterChangedCallback("doubletap_jump",
992 &settingChangedCallback, this);
993 g_settings->deregisterChangedCallback("enable_clouds",
994 &settingChangedCallback, this);
995 g_settings->deregisterChangedCallback("enable_particles",
996 &settingChangedCallback, this);
997 g_settings->deregisterChangedCallback("enable_fog",
998 &settingChangedCallback, this);
999 g_settings->deregisterChangedCallback("mouse_sensitivity",
1000 &settingChangedCallback, this);
1001 g_settings->deregisterChangedCallback("repeat_place_time",
1002 &settingChangedCallback, this);
1003 g_settings->deregisterChangedCallback("noclip",
1004 &settingChangedCallback, this);
1005 g_settings->deregisterChangedCallback("free_move",
1006 &settingChangedCallback, this);
1007 g_settings->deregisterChangedCallback("cinematic",
1008 &settingChangedCallback, this);
1009 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
1010 &settingChangedCallback, this);
1011 g_settings->deregisterChangedCallback("camera_smoothing",
1012 &settingChangedCallback, this);
1015 bool Game::startup(bool *kill,
1016 InputHandler *input,
1017 const GameStartData &start_data,
1018 std::string &error_message,
1020 ChatBackend *chat_backend)
1024 this->device = RenderingEngine::get_raw_device();
1026 this->error_message = &error_message;
1027 this->reconnect_requested = reconnect;
1028 this->input = input;
1029 this->chat_backend = chat_backend;
1030 this->simple_singleplayer_mode = start_data.isSinglePlayer();
1032 input->keycache.populate();
1034 driver = device->getVideoDriver();
1035 smgr = RenderingEngine::get_scene_manager();
1037 RenderingEngine::get_scene_manager()->getParameters()->
1038 setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1041 runData = GameRunData();
1042 runData.time_from_last_punch = 10.0;
1044 m_game_ui->initFlags();
1046 m_invert_mouse = g_settings->getBool("invert_mouse");
1047 m_first_loop_after_window_activation = true;
1049 g_client_translations->clear();
1051 // address can change if simple_singleplayer_mode
1052 if (!init(start_data.world_spec.path, start_data.address,
1053 start_data.socket_port, start_data.game_spec))
1056 if (!createClient(start_data))
1059 RenderingEngine::initialize(client, hud);
1067 ProfilerGraph graph;
1068 RunStats stats = { 0 };
1069 CameraOrientation cam_view_target = { 0 };
1070 CameraOrientation cam_view = { 0 };
1071 FpsControl draw_times = { 0 };
1072 f32 dtime; // in seconds
1074 /* Clear the profiler */
1075 Profiler::GraphValues dummyvalues;
1076 g_profiler->graphGet(dummyvalues);
1078 draw_times.last_time = RenderingEngine::get_timer_time();
1080 set_light_table(g_settings->getFloat("display_gamma"));
1083 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1084 && client->checkPrivilege("fast");
1087 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1088 g_settings->getU16("screen_h"));
1090 while (RenderingEngine::run()
1091 && !(*kill || g_gamecallback->shutdown_requested
1092 || (server && server->isShutdownRequested()))) {
1094 const irr::core::dimension2d<u32> ¤t_screen_size =
1095 RenderingEngine::get_video_driver()->getScreenSize();
1096 // Verify if window size has changed and save it if it's the case
1097 // Ensure evaluating settings->getBool after verifying screensize
1098 // First condition is cheaper
1099 if (previous_screen_size != current_screen_size &&
1100 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1101 g_settings->getBool("autosave_screensize")) {
1102 g_settings->setU16("screen_w", current_screen_size.Width);
1103 g_settings->setU16("screen_h", current_screen_size.Height);
1104 previous_screen_size = current_screen_size;
1107 // Calculate dtime =
1108 // RenderingEngine::run() from this iteration
1109 // + Sleep time until the wanted FPS are reached
1110 limitFps(&draw_times, &dtime);
1112 // Prepare render data for next iteration
1114 updateStats(&stats, draw_times, dtime);
1115 updateInteractTimers(dtime);
1117 if (!checkConnection())
1119 if (!handleCallbacks())
1124 m_game_ui->clearInfoText();
1125 hud->resizeHotbar();
1127 updateProfilers(stats, draw_times, dtime);
1128 processUserInput(dtime);
1129 // Update camera before player movement to avoid camera lag of one frame
1130 updateCameraDirection(&cam_view_target, dtime);
1131 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1132 cam_view.camera_yaw) * m_cache_cam_smoothing;
1133 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1134 cam_view.camera_pitch) * m_cache_cam_smoothing;
1135 updatePlayerControl(cam_view);
1137 processClientEvents(&cam_view_target);
1138 updateCamera(draw_times.busy_time, dtime);
1140 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud,
1141 m_game_ui->m_flags.show_debug);
1142 updateFrame(&graph, &stats, dtime, cam_view);
1143 updateProfilerGraphs(&graph);
1145 // Update if minimap has been disabled by the server
1146 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1148 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1155 void Game::shutdown()
1157 RenderingEngine::finalize();
1158 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1159 if (g_settings->get("3d_mode") == "pageflip") {
1160 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1163 auto formspec = m_game_ui->getFormspecGUI();
1165 formspec->quitMenu();
1167 #ifdef HAVE_TOUCHSCREENGUI
1168 g_touchscreengui->hide();
1171 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1176 if (gui_chat_console)
1177 gui_chat_console->drop();
1183 while (g_menumgr.menuCount() > 0) {
1184 g_menumgr.m_stack.front()->setVisible(false);
1185 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1188 m_game_ui->deleteFormspec();
1190 chat_backend->addMessage(L"", L"# Disconnected.");
1191 chat_backend->addMessage(L"", L"");
1192 m_chat_log_buf.clear();
1196 while (!client->isShutdown()) {
1197 assert(texture_src != NULL);
1198 assert(shader_src != NULL);
1199 texture_src->processQueue();
1200 shader_src->processQueue();
1207 /****************************************************************************/
1208 /****************************************************************************
1210 ****************************************************************************/
1211 /****************************************************************************/
1214 const std::string &map_dir,
1215 const std::string &address,
1217 const SubgameSpec &gamespec)
1219 texture_src = createTextureSource();
1221 showOverlayMessage(N_("Loading..."), 0, 0);
1223 shader_src = createShaderSource();
1225 itemdef_manager = createItemDefManager();
1226 nodedef_manager = createNodeDefManager();
1228 eventmgr = new EventManager();
1229 quicktune = new QuicktuneShortcutter();
1231 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1232 && eventmgr && quicktune))
1238 // Create a server if not connecting to an existing one
1239 if (address.empty()) {
1240 if (!createSingleplayerServer(map_dir, gamespec, port))
1247 bool Game::initSound()
1250 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1251 infostream << "Attempting to use OpenAL audio" << std::endl;
1252 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1254 infostream << "Failed to initialize OpenAL audio" << std::endl;
1256 infostream << "Sound disabled." << std::endl;
1260 infostream << "Using dummy audio." << std::endl;
1261 sound = &dummySoundManager;
1262 sound_is_dummy = true;
1265 soundmaker = new SoundMaker(sound, nodedef_manager);
1269 soundmaker->registerReceiver(eventmgr);
1274 bool Game::createSingleplayerServer(const std::string &map_dir,
1275 const SubgameSpec &gamespec, u16 port)
1277 showOverlayMessage(N_("Creating server..."), 0, 5);
1279 std::string bind_str = g_settings->get("bind_address");
1280 Address bind_addr(0, 0, 0, 0, port);
1282 if (g_settings->getBool("ipv6_server")) {
1283 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1287 bind_addr.Resolve(bind_str.c_str());
1288 } catch (ResolveError &e) {
1289 infostream << "Resolving bind address \"" << bind_str
1290 << "\" failed: " << e.what()
1291 << " -- Listening on all addresses." << std::endl;
1294 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1295 *error_message = "Unable to listen on " +
1296 bind_addr.serializeString() +
1297 " because IPv6 is disabled";
1298 errorstream << *error_message << std::endl;
1302 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1303 false, nullptr, error_message);
1309 bool Game::createClient(const GameStartData &start_data)
1311 showOverlayMessage(N_("Creating client..."), 0, 10);
1313 draw_control = new MapDrawControl;
1317 bool could_connect, connect_aborted;
1318 #ifdef HAVE_TOUCHSCREENGUI
1319 if (g_touchscreengui) {
1320 g_touchscreengui->init(texture_src);
1321 g_touchscreengui->hide();
1324 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1327 if (!could_connect) {
1328 if (error_message->empty() && !connect_aborted) {
1329 // Should not happen if error messages are set properly
1330 *error_message = "Connection failed for unknown reason";
1331 errorstream << *error_message << std::endl;
1336 if (!getServerContent(&connect_aborted)) {
1337 if (error_message->empty() && !connect_aborted) {
1338 // Should not happen if error messages are set properly
1339 *error_message = "Connection failed for unknown reason";
1340 errorstream << *error_message << std::endl;
1345 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1346 &m_flags.force_fog_off, &runData.fog_range, client);
1347 shader_src->addShaderConstantSetterFactory(scsf);
1349 // Update cached textures, meshes and materials
1350 client->afterContentReceived();
1354 camera = new Camera(*draw_control, client);
1355 if (!camera->successfullyCreated(*error_message))
1357 client->setCamera(camera);
1361 if (m_cache_enable_clouds)
1362 clouds = new Clouds(smgr, -1, time(0));
1366 sky = new Sky(-1, texture_src, shader_src);
1368 skybox = NULL; // This is used/set later on in the main run loop
1370 /* Pre-calculated values
1372 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1374 v2u32 size = t->getOriginalSize();
1375 crack_animation_length = size.Y / size.X;
1377 crack_animation_length = 5;
1383 /* Set window caption
1385 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1387 str += utf8_to_wide(g_version_hash);
1389 str += driver->getName();
1391 device->setWindowCaption(str.c_str());
1393 LocalPlayer *player = client->getEnv().getLocalPlayer();
1394 player->hurt_tilt_timer = 0;
1395 player->hurt_tilt_strength = 0;
1397 hud = new Hud(guienv, client, player, &player->inventory);
1399 mapper = client->getMinimap();
1401 if (mapper && client->modsLoaded())
1402 client->getScript()->on_minimap_ready(mapper);
1407 bool Game::initGui()
1411 // Remove stale "recent" chat messages from previous connections
1412 chat_backend->clearRecentChat();
1414 // Make sure the size of the recent messages buffer is right
1415 chat_backend->applySettings();
1417 // Chat backend and console
1418 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1419 -1, chat_backend, client, &g_menumgr);
1421 #ifdef HAVE_TOUCHSCREENGUI
1423 if (g_touchscreengui)
1424 g_touchscreengui->show();
1431 bool Game::connectToServer(const GameStartData &start_data,
1432 bool *connect_ok, bool *connection_aborted)
1434 *connect_ok = false; // Let's not be overly optimistic
1435 *connection_aborted = false;
1436 bool local_server_mode = false;
1438 showOverlayMessage(N_("Resolving address..."), 0, 15);
1440 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1443 connect_address.Resolve(start_data.address.c_str());
1445 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1446 //connect_address.Resolve("localhost");
1447 if (connect_address.isIPv6()) {
1448 IPv6AddressBytes addr_bytes;
1449 addr_bytes.bytes[15] = 1;
1450 connect_address.setAddress(&addr_bytes);
1452 connect_address.setAddress(127, 0, 0, 1);
1454 local_server_mode = true;
1456 } catch (ResolveError &e) {
1457 *error_message = std::string("Couldn't resolve address: ") + e.what();
1458 errorstream << *error_message << std::endl;
1462 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1463 *error_message = "Unable to connect to " +
1464 connect_address.serializeString() +
1465 " because IPv6 is disabled";
1466 errorstream << *error_message << std::endl;
1470 client = new Client(start_data.name.c_str(),
1471 start_data.password, start_data.address,
1472 *draw_control, texture_src, shader_src,
1473 itemdef_manager, nodedef_manager, sound, eventmgr,
1474 connect_address.isIPv6(), m_game_ui.get());
1476 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1478 infostream << "Connecting to server at ";
1479 connect_address.print(&infostream);
1480 infostream << std::endl;
1482 client->connect(connect_address,
1483 simple_singleplayer_mode || local_server_mode);
1486 Wait for server to accept connection
1492 FpsControl fps_control = { 0 };
1494 f32 wait_time = 0; // in seconds
1496 fps_control.last_time = RenderingEngine::get_timer_time();
1498 while (RenderingEngine::run()) {
1500 limitFps(&fps_control, &dtime);
1502 // Update client and server
1503 client->step(dtime);
1506 server->step(dtime);
1509 if (client->getState() == LC_Init) {
1515 if (*connection_aborted)
1518 if (client->accessDenied()) {
1519 *error_message = "Access denied. Reason: "
1520 + client->accessDeniedReason();
1521 *reconnect_requested = client->reconnectRequested();
1522 errorstream << *error_message << std::endl;
1526 if (input->cancelPressed()) {
1527 *connection_aborted = true;
1528 infostream << "Connect aborted [Escape]" << std::endl;
1532 if (client->m_is_registration_confirmation_state) {
1533 if (registration_confirmation_shown) {
1534 // Keep drawing the GUI
1535 RenderingEngine::draw_menu_scene(guienv, dtime, true);
1537 registration_confirmation_shown = true;
1538 (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
1539 &g_menumgr, client, start_data.name, start_data.password,
1540 connection_aborted, texture_src))->drop();
1544 // Only time out if we aren't waiting for the server we started
1545 if (!start_data.isSinglePlayer() && wait_time > 10) {
1546 *error_message = "Connection timed out.";
1547 errorstream << *error_message << std::endl;
1552 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1555 } catch (con::PeerNotFoundException &e) {
1556 // TODO: Should something be done here? At least an info/error
1564 bool Game::getServerContent(bool *aborted)
1568 FpsControl fps_control = { 0 };
1569 f32 dtime; // in seconds
1571 fps_control.last_time = RenderingEngine::get_timer_time();
1573 while (RenderingEngine::run()) {
1575 limitFps(&fps_control, &dtime);
1577 // Update client and server
1578 client->step(dtime);
1581 server->step(dtime);
1584 if (client->mediaReceived() && client->itemdefReceived() &&
1585 client->nodedefReceived()) {
1590 if (!checkConnection())
1593 if (client->getState() < LC_Init) {
1594 *error_message = "Client disconnected";
1595 errorstream << *error_message << std::endl;
1599 if (input->cancelPressed()) {
1601 infostream << "Connect aborted [Escape]" << std::endl;
1608 if (!client->itemdefReceived()) {
1609 const wchar_t *text = wgettext("Item definitions...");
1611 RenderingEngine::draw_load_screen(text, guienv, texture_src,
1614 } else if (!client->nodedefReceived()) {
1615 const wchar_t *text = wgettext("Node definitions...");
1617 RenderingEngine::draw_load_screen(text, guienv, texture_src,
1621 std::stringstream message;
1622 std::fixed(message);
1623 message.precision(0);
1624 float receive = client->mediaReceiveProgress() * 100;
1625 message << gettext("Media...");
1627 message << " " << receive << "%";
1628 message.precision(2);
1630 if ((USE_CURL == 0) ||
1631 (!g_settings->getBool("enable_remote_media_server"))) {
1632 float cur = client->getCurRate();
1633 std::string cur_unit = gettext("KiB/s");
1637 cur_unit = gettext("MiB/s");
1640 message << " (" << cur << ' ' << cur_unit << ")";
1643 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1644 RenderingEngine::draw_load_screen(utf8_to_wide(message.str()), guienv,
1645 texture_src, dtime, progress);
1653 /****************************************************************************/
1654 /****************************************************************************
1656 ****************************************************************************/
1657 /****************************************************************************/
1659 inline void Game::updateInteractTimers(f32 dtime)
1661 if (runData.nodig_delay_timer >= 0)
1662 runData.nodig_delay_timer -= dtime;
1664 if (runData.object_hit_delay_timer >= 0)
1665 runData.object_hit_delay_timer -= dtime;
1667 runData.time_from_last_punch += dtime;
1671 /* returns false if game should exit, otherwise true
1673 inline bool Game::checkConnection()
1675 if (client->accessDenied()) {
1676 *error_message = "Access denied. Reason: "
1677 + client->accessDeniedReason();
1678 *reconnect_requested = client->reconnectRequested();
1679 errorstream << *error_message << std::endl;
1687 /* returns false if game should exit, otherwise true
1689 inline bool Game::handleCallbacks()
1691 if (g_gamecallback->disconnect_requested) {
1692 g_gamecallback->disconnect_requested = false;
1696 if (g_gamecallback->changepassword_requested) {
1697 (new GUIPasswordChange(guienv, guiroot, -1,
1698 &g_menumgr, client, texture_src))->drop();
1699 g_gamecallback->changepassword_requested = false;
1702 if (g_gamecallback->changevolume_requested) {
1703 (new GUIVolumeChange(guienv, guiroot, -1,
1704 &g_menumgr, texture_src))->drop();
1705 g_gamecallback->changevolume_requested = false;
1708 if (g_gamecallback->keyconfig_requested) {
1709 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1710 &g_menumgr, texture_src))->drop();
1711 g_gamecallback->keyconfig_requested = false;
1714 if (g_gamecallback->keyconfig_changed) {
1715 input->keycache.populate(); // update the cache with new settings
1716 g_gamecallback->keyconfig_changed = false;
1723 void Game::processQueues()
1725 texture_src->processQueue();
1726 itemdef_manager->processQueue(client);
1727 shader_src->processQueue();
1731 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1734 float profiler_print_interval =
1735 g_settings->getFloat("profiler_print_interval");
1736 bool print_to_log = true;
1738 if (profiler_print_interval == 0) {
1739 print_to_log = false;
1740 profiler_print_interval = 3;
1743 if (profiler_interval.step(dtime, profiler_print_interval)) {
1745 infostream << "Profiler:" << std::endl;
1746 g_profiler->print(infostream);
1749 m_game_ui->updateProfiler();
1750 g_profiler->clear();
1753 // Update update graphs
1754 g_profiler->graphAdd("Time non-rendering [ms]",
1755 draw_times.busy_time - stats.drawtime);
1757 g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time);
1758 g_profiler->graphAdd("FPS", 1.0f / dtime);
1761 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1768 /* Time average and jitter calculation
1770 jp = &stats->dtime_jitter;
1771 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1773 jitter = dtime - jp->avg;
1775 if (jitter > jp->max)
1778 jp->counter += dtime;
1780 if (jp->counter > 0.0) {
1782 jp->max_sample = jp->max;
1783 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1787 /* Busytime average and jitter calculation
1789 jp = &stats->busy_time_jitter;
1790 jp->avg = jp->avg + draw_times.busy_time * 0.02;
1792 jitter = draw_times.busy_time - jp->avg;
1794 if (jitter > jp->max)
1796 if (jitter < jp->min)
1799 jp->counter += dtime;
1801 if (jp->counter > 0.0) {
1803 jp->max_sample = jp->max;
1804 jp->min_sample = jp->min;
1812 /****************************************************************************
1814 ****************************************************************************/
1816 void Game::processUserInput(f32 dtime)
1818 // Reset input if window not active or some menu is active
1819 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1821 #ifdef HAVE_TOUCHSCREENGUI
1822 g_touchscreengui->hide();
1825 #ifdef HAVE_TOUCHSCREENGUI
1826 else if (g_touchscreengui) {
1827 /* on touchscreengui step may generate own input events which ain't
1828 * what we want in case we just did clear them */
1829 g_touchscreengui->step(dtime);
1833 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1834 gui_chat_console->closeConsoleAtOnce();
1837 // Input handler step() (used by the random input generator)
1841 auto formspec = m_game_ui->getFormspecGUI();
1843 formspec->getAndroidUIInput();
1845 handleAndroidChatInput();
1848 // Increase timer for double tap of "keymap_jump"
1849 if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1850 runData.jump_timer += dtime;
1853 processItemSelection(&runData.new_playeritem);
1857 void Game::processKeyInput()
1859 if (wasKeyDown(KeyType::DROP)) {
1860 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1861 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1862 toggleAutoforward();
1863 } else if (wasKeyDown(KeyType::BACKWARD)) {
1864 if (g_settings->getBool("continuous_forward"))
1865 toggleAutoforward();
1866 } else if (wasKeyDown(KeyType::INVENTORY)) {
1868 } else if (input->cancelPressed()) {
1870 m_android_chat_open = false;
1872 if (!gui_chat_console->isOpenInhibited()) {
1875 } else if (wasKeyDown(KeyType::CHAT)) {
1876 openConsole(0.2, L"");
1877 } else if (wasKeyDown(KeyType::CMD)) {
1878 openConsole(0.2, L"/");
1879 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1880 if (client->modsLoaded())
1881 openConsole(0.2, L".");
1883 m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1884 } else if (wasKeyDown(KeyType::CONSOLE)) {
1885 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1886 } else if (wasKeyDown(KeyType::FREEMOVE)) {
1888 } else if (wasKeyDown(KeyType::JUMP)) {
1889 toggleFreeMoveAlt();
1890 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1892 } else if (wasKeyDown(KeyType::FASTMOVE)) {
1894 } else if (wasKeyDown(KeyType::NOCLIP)) {
1897 } else if (wasKeyDown(KeyType::MUTE)) {
1898 if (g_settings->getBool("enable_sound")) {
1899 bool new_mute_sound = !g_settings->getBool("mute_sound");
1900 g_settings->setBool("mute_sound", new_mute_sound);
1902 m_game_ui->showTranslatedStatusText("Sound muted");
1904 m_game_ui->showTranslatedStatusText("Sound unmuted");
1906 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1908 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1909 if (g_settings->getBool("enable_sound")) {
1910 float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1912 g_settings->setFloat("sound_volume", new_volume);
1913 const wchar_t *str = wgettext("Volume changed to %d%%");
1914 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1916 m_game_ui->showStatusText(buf);
1918 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1920 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1921 if (g_settings->getBool("enable_sound")) {
1922 float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1924 g_settings->setFloat("sound_volume", new_volume);
1925 const wchar_t *str = wgettext("Volume changed to %d%%");
1926 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
1928 m_game_ui->showStatusText(buf);
1930 m_game_ui->showTranslatedStatusText("Sound system is disabled");
1933 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1934 || wasKeyDown(KeyType::DEC_VOLUME)) {
1935 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1937 } else if (wasKeyDown(KeyType::CINEMATIC)) {
1939 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1940 client->makeScreenshot();
1941 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1942 m_game_ui->toggleHud();
1943 } else if (wasKeyDown(KeyType::MINIMAP)) {
1944 toggleMinimap(isKeyDown(KeyType::SNEAK));
1945 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1946 m_game_ui->toggleChat();
1947 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1949 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1950 toggleUpdateCamera();
1951 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1953 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1954 m_game_ui->toggleProfiler();
1955 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1956 increaseViewRange();
1957 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1958 decreaseViewRange();
1959 } else if (wasKeyDown(KeyType::RANGESELECT)) {
1960 toggleFullViewRange();
1961 } else if (wasKeyDown(KeyType::ZOOM)) {
1963 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1965 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1967 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
1969 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
1973 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
1974 runData.reset_jump_timer = false;
1975 runData.jump_timer = 0.0f;
1978 if (quicktune->hasMessage()) {
1979 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
1983 void Game::processItemSelection(u16 *new_playeritem)
1985 LocalPlayer *player = client->getEnv().getLocalPlayer();
1987 /* Item selection using mouse wheel
1989 *new_playeritem = player->getWieldIndex();
1991 s32 wheel = input->getMouseWheel();
1992 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
1993 player->hud_hotbar_itemcount - 1);
1997 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2000 if (wasKeyDown(KeyType::HOTBAR_PREV))
2004 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2006 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2009 /* Item selection using hotbar slot keys
2011 for (u16 i = 0; i <= max_item; i++) {
2012 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2013 *new_playeritem = i;
2020 void Game::dropSelectedItem(bool single_item)
2022 IDropAction *a = new IDropAction();
2023 a->count = single_item ? 1 : 0;
2024 a->from_inv.setCurrentPlayer();
2025 a->from_list = "main";
2026 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2027 client->inventoryAction(a);
2031 void Game::openInventory()
2034 * Don't permit to open inventory is CAO or player doesn't exists.
2035 * This prevent showing an empty inventory at player load
2038 LocalPlayer *player = client->getEnv().getLocalPlayer();
2039 if (!player || !player->getCAO())
2042 infostream << "Game: Launching inventory" << std::endl;
2044 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2046 InventoryLocation inventoryloc;
2047 inventoryloc.setCurrentPlayer();
2049 if (!client->modsLoaded()
2050 || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2051 TextDest *txt_dst = new TextDestPlayerInventory(client);
2052 auto *&formspec = m_game_ui->updateFormspec("");
2053 GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
2054 txt_dst, client->getFormspecPrepend(), sound);
2056 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2061 void Game::openConsole(float scale, const wchar_t *line)
2063 assert(scale > 0.0f && scale <= 1.0f);
2066 porting::showInputDialog(gettext("ok"), "", "", 2);
2067 m_android_chat_open = true;
2069 if (gui_chat_console->isOpenInhibited())
2071 gui_chat_console->openConsole(scale);
2073 gui_chat_console->setCloseOnEnter(true);
2074 gui_chat_console->replaceAndAddToHistory(line);
2080 void Game::handleAndroidChatInput()
2082 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2083 std::string text = porting::getInputDialogValue();
2084 client->typeChatMessage(utf8_to_wide(text));
2085 m_android_chat_open = false;
2091 void Game::toggleFreeMove()
2093 bool free_move = !g_settings->getBool("free_move");
2094 g_settings->set("free_move", bool_to_cstr(free_move));
2097 if (client->checkPrivilege("fly")) {
2098 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2100 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2103 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2107 void Game::toggleFreeMoveAlt()
2109 if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2112 runData.reset_jump_timer = true;
2116 void Game::togglePitchMove()
2118 bool pitch_move = !g_settings->getBool("pitch_move");
2119 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2122 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2124 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2129 void Game::toggleFast()
2131 bool fast_move = !g_settings->getBool("fast_move");
2132 bool has_fast_privs = client->checkPrivilege("fast");
2133 g_settings->set("fast_move", bool_to_cstr(fast_move));
2136 if (has_fast_privs) {
2137 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2139 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2142 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2146 m_cache_hold_aux1 = fast_move && has_fast_privs;
2151 void Game::toggleNoClip()
2153 bool noclip = !g_settings->getBool("noclip");
2154 g_settings->set("noclip", bool_to_cstr(noclip));
2157 if (client->checkPrivilege("noclip")) {
2158 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2160 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2163 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2167 void Game::toggleCinematic()
2169 bool cinematic = !g_settings->getBool("cinematic");
2170 g_settings->set("cinematic", bool_to_cstr(cinematic));
2173 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2175 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2178 // Autoforward by toggling continuous forward.
2179 void Game::toggleAutoforward()
2181 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2182 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2184 if (autorun_enabled)
2185 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2187 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2190 void Game::toggleMinimap(bool shift_pressed)
2192 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2196 mapper->toggleMinimapShape();
2200 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2202 // Not so satisying code to keep compatibility with old fixed mode system
2204 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2206 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2207 m_game_ui->m_flags.show_minimap = false;
2210 // If radar is disabled, try to find a non radar mode or fall back to 0
2211 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2212 while (mapper->getModeIndex() &&
2213 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2216 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2220 // End of 'not so satifying code'
2221 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2222 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2223 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2225 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2228 void Game::toggleFog()
2230 bool fog_enabled = g_settings->getBool("enable_fog");
2231 g_settings->setBool("enable_fog", !fog_enabled);
2233 m_game_ui->showTranslatedStatusText("Fog disabled");
2235 m_game_ui->showTranslatedStatusText("Fog enabled");
2239 void Game::toggleDebug()
2241 // Initial / 4x toggle: Chat only
2242 // 1x toggle: Debug text with chat
2243 // 2x toggle: Debug text with profiler graph
2244 // 3x toggle: Debug text and wireframe
2245 if (!m_game_ui->m_flags.show_debug) {
2246 m_game_ui->m_flags.show_debug = true;
2247 m_game_ui->m_flags.show_profiler_graph = false;
2248 draw_control->show_wireframe = false;
2249 m_game_ui->showTranslatedStatusText("Debug info shown");
2250 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2251 m_game_ui->m_flags.show_profiler_graph = true;
2252 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2253 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2254 m_game_ui->m_flags.show_profiler_graph = false;
2255 draw_control->show_wireframe = true;
2256 m_game_ui->showTranslatedStatusText("Wireframe shown");
2258 m_game_ui->m_flags.show_debug = false;
2259 m_game_ui->m_flags.show_profiler_graph = false;
2260 draw_control->show_wireframe = false;
2261 if (client->checkPrivilege("debug")) {
2262 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2264 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2270 void Game::toggleUpdateCamera()
2272 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2273 if (m_flags.disable_camera_update)
2274 m_game_ui->showTranslatedStatusText("Camera update disabled");
2276 m_game_ui->showTranslatedStatusText("Camera update enabled");
2280 void Game::increaseViewRange()
2282 s16 range = g_settings->getS16("viewing_range");
2283 s16 range_new = range + 10;
2287 if (range_new > 4000) {
2289 str = wgettext("Viewing range is at maximum: %d");
2290 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2292 m_game_ui->showStatusText(buf);
2295 str = wgettext("Viewing range changed to %d");
2296 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2298 m_game_ui->showStatusText(buf);
2300 g_settings->set("viewing_range", itos(range_new));
2304 void Game::decreaseViewRange()
2306 s16 range = g_settings->getS16("viewing_range");
2307 s16 range_new = range - 10;
2311 if (range_new < 20) {
2313 str = wgettext("Viewing range is at minimum: %d");
2314 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2316 m_game_ui->showStatusText(buf);
2318 str = wgettext("Viewing range changed to %d");
2319 swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new);
2321 m_game_ui->showStatusText(buf);
2323 g_settings->set("viewing_range", itos(range_new));
2327 void Game::toggleFullViewRange()
2329 draw_control->range_all = !draw_control->range_all;
2330 if (draw_control->range_all)
2331 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2333 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2337 void Game::checkZoomEnabled()
2339 LocalPlayer *player = client->getEnv().getLocalPlayer();
2340 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2341 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2345 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2347 if ((device->isWindowActive() && device->isWindowFocused()
2348 && !isMenuActive()) || input->isRandom()) {
2351 if (!input->isRandom()) {
2352 // Mac OSX gets upset if this is set every frame
2353 if (device->getCursorControl()->isVisible())
2354 device->getCursorControl()->setVisible(false);
2358 if (m_first_loop_after_window_activation) {
2359 m_first_loop_after_window_activation = false;
2361 input->setMousePos(driver->getScreenSize().Width / 2,
2362 driver->getScreenSize().Height / 2);
2364 updateCameraOrientation(cam, dtime);
2370 // Mac OSX gets upset if this is set every frame
2371 if (!device->getCursorControl()->isVisible())
2372 device->getCursorControl()->setVisible(true);
2375 m_first_loop_after_window_activation = true;
2380 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2382 #ifdef HAVE_TOUCHSCREENGUI
2383 if (g_touchscreengui) {
2384 cam->camera_yaw += g_touchscreengui->getYawChange();
2385 cam->camera_pitch = g_touchscreengui->getPitch();
2388 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2389 v2s32 dist = input->getMousePos() - center;
2391 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2395 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity;
2396 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity;
2398 if (dist.X != 0 || dist.Y != 0)
2399 input->setMousePos(center.X, center.Y);
2400 #ifdef HAVE_TOUCHSCREENGUI
2404 if (m_cache_enable_joysticks) {
2405 f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime;
2406 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2407 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2410 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2414 void Game::updatePlayerControl(const CameraOrientation &cam)
2416 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2418 // DO NOT use the isKeyDown method for the forward, backward, left, right
2419 // buttons, as the code that uses the controls needs to be able to
2420 // distinguish between the two in order to know when to use joysticks.
2422 PlayerControl control(
2423 input->isKeyDown(KeyType::FORWARD),
2424 input->isKeyDown(KeyType::BACKWARD),
2425 input->isKeyDown(KeyType::LEFT),
2426 input->isKeyDown(KeyType::RIGHT),
2427 isKeyDown(KeyType::JUMP),
2428 isKeyDown(KeyType::AUX1),
2429 isKeyDown(KeyType::SNEAK),
2430 isKeyDown(KeyType::ZOOM),
2431 isKeyDown(KeyType::DIG),
2432 isKeyDown(KeyType::PLACE),
2435 input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE),
2436 input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE)
2439 u32 keypress_bits = (
2440 ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) |
2441 ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) |
2442 ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) |
2443 ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) |
2444 ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) |
2445 ( (u32)(isKeyDown(KeyType::AUX1) & 0x1) << 5) |
2446 ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) |
2447 ( (u32)(isKeyDown(KeyType::DIG) & 0x1) << 7) |
2448 ( (u32)(isKeyDown(KeyType::PLACE) & 0x1) << 8) |
2449 ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9)
2453 /* For Android, simulate holding down AUX1 (fast move) if the user has
2454 * the fast_move setting toggled on. If there is an aux1 key defined for
2455 * Android then its meaning is inverted (i.e. holding aux1 means walk and
2458 if (m_cache_hold_aux1) {
2459 control.aux1 = control.aux1 ^ true;
2460 keypress_bits ^= ((u32)(1U << 5));
2464 LocalPlayer *player = client->getEnv().getLocalPlayer();
2466 // autojump if set: simulate "jump" key
2467 if (player->getAutojump()) {
2468 control.jump = true;
2469 keypress_bits |= 1U << 4;
2472 // autoforward if set: simulate "up" key
2473 if (player->getPlayerSettings().continuous_forward &&
2474 client->activeObjectsReceived() && !player->isDead()) {
2476 keypress_bits |= 1U << 0;
2479 client->setPlayerControl(control);
2480 player->keyPressed = keypress_bits;
2486 inline void Game::step(f32 *dtime)
2488 bool can_be_and_is_paused =
2489 (simple_singleplayer_mode && g_menumgr.pausesGame());
2491 if (can_be_and_is_paused) { // This is for a singleplayer server
2492 *dtime = 0; // No time passes
2494 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
2498 server->step(*dtime);
2500 client->step(*dtime);
2504 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2507 for (auto &&child: node->getChildren())
2508 pauseNodeAnimation(paused, child);
2509 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2511 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2512 float speed = animated_node->getAnimationSpeed();
2515 paused.push_back({grab(animated_node), speed});
2516 animated_node->setAnimationSpeed(0.0f);
2519 void Game::pauseAnimation()
2521 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2524 void Game::resumeAnimation()
2526 for (auto &&pair: paused_animated_nodes)
2527 pair.first->setAnimationSpeed(pair.second);
2528 paused_animated_nodes.clear();
2531 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2532 {&Game::handleClientEvent_None},
2533 {&Game::handleClientEvent_PlayerDamage},
2534 {&Game::handleClientEvent_PlayerForceMove},
2535 {&Game::handleClientEvent_Deathscreen},
2536 {&Game::handleClientEvent_ShowFormSpec},
2537 {&Game::handleClientEvent_ShowLocalFormSpec},
2538 {&Game::handleClientEvent_HandleParticleEvent},
2539 {&Game::handleClientEvent_HandleParticleEvent},
2540 {&Game::handleClientEvent_HandleParticleEvent},
2541 {&Game::handleClientEvent_HudAdd},
2542 {&Game::handleClientEvent_HudRemove},
2543 {&Game::handleClientEvent_HudChange},
2544 {&Game::handleClientEvent_SetSky},
2545 {&Game::handleClientEvent_SetSun},
2546 {&Game::handleClientEvent_SetMoon},
2547 {&Game::handleClientEvent_SetStars},
2548 {&Game::handleClientEvent_OverrideDayNigthRatio},
2549 {&Game::handleClientEvent_CloudParams},
2552 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2554 FATAL_ERROR("ClientEvent type None received");
2557 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2559 if (client->modsLoaded())
2560 client->getScript()->on_damage_taken(event->player_damage.amount);
2562 // Damage flash and hurt tilt are not used at death
2563 if (client->getHP() > 0) {
2564 runData.damage_flash += 95.0f + 3.2f * event->player_damage.amount;
2565 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2567 LocalPlayer *player = client->getEnv().getLocalPlayer();
2569 player->hurt_tilt_timer = 1.5f;
2570 player->hurt_tilt_strength =
2571 rangelim(event->player_damage.amount / 4.0f, 1.0f, 4.0f);
2574 // Play damage sound
2575 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2578 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2580 cam->camera_yaw = event->player_force_move.yaw;
2581 cam->camera_pitch = event->player_force_move.pitch;
2584 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2586 // If client scripting is enabled, deathscreen is handled by CSM code in
2587 // builtin/client/init.lua
2588 if (client->modsLoaded())
2589 client->getScript()->on_death();
2591 showDeathFormspec();
2593 /* Handle visualization */
2594 LocalPlayer *player = client->getEnv().getLocalPlayer();
2595 runData.damage_flash = 0;
2596 player->hurt_tilt_timer = 0;
2597 player->hurt_tilt_strength = 0;
2600 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2602 if (event->show_formspec.formspec->empty()) {
2603 auto formspec = m_game_ui->getFormspecGUI();
2604 if (formspec && (event->show_formspec.formname->empty()
2605 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2606 formspec->quitMenu();
2609 FormspecFormSource *fs_src =
2610 new FormspecFormSource(*(event->show_formspec.formspec));
2611 TextDestPlayerInventory *txt_dst =
2612 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2614 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2615 GUIFormSpecMenu::create(formspec, client, &input->joystick,
2616 fs_src, txt_dst, client->getFormspecPrepend(), sound);
2619 delete event->show_formspec.formspec;
2620 delete event->show_formspec.formname;
2623 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2625 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2626 LocalFormspecHandler *txt_dst =
2627 new LocalFormspecHandler(*event->show_formspec.formname, client);
2628 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, &input->joystick,
2629 fs_src, txt_dst, client->getFormspecPrepend(), sound);
2631 delete event->show_formspec.formspec;
2632 delete event->show_formspec.formname;
2635 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2636 CameraOrientation *cam)
2638 LocalPlayer *player = client->getEnv().getLocalPlayer();
2639 client->getParticleManager()->handleParticleEvent(event, client, player);
2642 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2644 LocalPlayer *player = client->getEnv().getLocalPlayer();
2646 u32 server_id = event->hudadd->server_id;
2647 // ignore if we already have a HUD with that ID
2648 auto i = m_hud_server_to_client.find(server_id);
2649 if (i != m_hud_server_to_client.end()) {
2650 delete event->hudadd;
2654 HudElement *e = new HudElement;
2655 e->type = static_cast<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 m_hud_server_to_client[server_id] = player->addHud(e);
2671 delete event->hudadd;
2674 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2676 LocalPlayer *player = client->getEnv().getLocalPlayer();
2678 auto i = m_hud_server_to_client.find(event->hudrm.id);
2679 if (i != m_hud_server_to_client.end()) {
2680 HudElement *e = player->removeHud(i->second);
2682 m_hud_server_to_client.erase(i);
2687 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2689 LocalPlayer *player = client->getEnv().getLocalPlayer();
2691 HudElement *e = nullptr;
2693 auto i = m_hud_server_to_client.find(event->hudchange->id);
2694 if (i != m_hud_server_to_client.end()) {
2695 e = player->getHud(i->second);
2699 delete event->hudchange;
2703 #define CASE_SET(statval, prop, dataprop) \
2705 e->prop = event->hudchange->dataprop; \
2708 switch (event->hudchange->stat) {
2709 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2711 CASE_SET(HUD_STAT_NAME, name, sdata);
2713 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2715 CASE_SET(HUD_STAT_TEXT, text, sdata);
2717 CASE_SET(HUD_STAT_NUMBER, number, data);
2719 CASE_SET(HUD_STAT_ITEM, item, data);
2721 CASE_SET(HUD_STAT_DIR, dir, data);
2723 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2725 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2727 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2729 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2731 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2733 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2738 delete event->hudchange;
2741 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2743 sky->setVisible(false);
2744 // Whether clouds are visible in front of a custom skybox.
2745 sky->setCloudsEnabled(event->set_sky->clouds);
2751 // Clear the old textures out in case we switch rendering type.
2752 sky->clearSkyboxTextures();
2753 // Handle according to type
2754 if (event->set_sky->type == "regular") {
2755 // Shows the mesh skybox
2756 sky->setVisible(true);
2757 // Update mesh based skybox colours if applicable.
2758 sky->setSkyColors(event->set_sky->sky_color);
2759 sky->setHorizonTint(
2760 event->set_sky->fog_sun_tint,
2761 event->set_sky->fog_moon_tint,
2762 event->set_sky->fog_tint_type
2764 } else if (event->set_sky->type == "skybox" &&
2765 event->set_sky->textures.size() == 6) {
2766 // Disable the dyanmic mesh skybox:
2767 sky->setVisible(false);
2769 sky->setFallbackBgColor(event->set_sky->bgcolor);
2770 // Set sunrise and sunset fog tinting:
2771 sky->setHorizonTint(
2772 event->set_sky->fog_sun_tint,
2773 event->set_sky->fog_moon_tint,
2774 event->set_sky->fog_tint_type
2776 // Add textures to skybox.
2777 for (int i = 0; i < 6; i++)
2778 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2780 // Handle everything else as plain color.
2781 if (event->set_sky->type != "plain")
2782 infostream << "Unknown sky type: "
2783 << (event->set_sky->type) << std::endl;
2784 sky->setVisible(false);
2785 sky->setFallbackBgColor(event->set_sky->bgcolor);
2786 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2787 sky->setHorizonTint(
2788 event->set_sky->bgcolor,
2789 event->set_sky->bgcolor,
2793 delete event->set_sky;
2796 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2798 sky->setSunVisible(event->sun_params->visible);
2799 sky->setSunTexture(event->sun_params->texture,
2800 event->sun_params->tonemap, texture_src);
2801 sky->setSunScale(event->sun_params->scale);
2802 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2803 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2804 delete event->sun_params;
2807 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2809 sky->setMoonVisible(event->moon_params->visible);
2810 sky->setMoonTexture(event->moon_params->texture,
2811 event->moon_params->tonemap, texture_src);
2812 sky->setMoonScale(event->moon_params->scale);
2813 delete event->moon_params;
2816 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2818 sky->setStarsVisible(event->star_params->visible);
2819 sky->setStarCount(event->star_params->count, false);
2820 sky->setStarColor(event->star_params->starcolor);
2821 sky->setStarScale(event->star_params->scale);
2822 delete event->star_params;
2825 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2826 CameraOrientation *cam)
2828 client->getEnv().setDayNightRatioOverride(
2829 event->override_day_night_ratio.do_override,
2830 event->override_day_night_ratio.ratio_f * 1000.0f);
2833 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2838 clouds->setDensity(event->cloud_params.density);
2839 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2840 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2841 clouds->setHeight(event->cloud_params.height);
2842 clouds->setThickness(event->cloud_params.thickness);
2843 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2846 void Game::processClientEvents(CameraOrientation *cam)
2848 while (client->hasClientEvents()) {
2849 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2850 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2851 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2852 (this->*evHandler.handler)(event.get(), cam);
2856 void Game::updateChat(f32 dtime, const v2u32 &screensize)
2858 // Get new messages from error log buffer
2859 while (!m_chat_log_buf.empty())
2860 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2862 // Get new messages from client
2863 std::wstring message;
2864 while (client->getChatMessage(message)) {
2865 chat_backend->addUnparsedMessage(message);
2868 // Remove old messages
2869 chat_backend->step(dtime);
2871 // Display all messages in a static text element
2872 m_game_ui->setChatText(chat_backend->getRecentChat(),
2873 chat_backend->getRecentBuffer().getLineCount());
2876 void Game::updateCamera(u32 busy_time, f32 dtime)
2878 LocalPlayer *player = client->getEnv().getLocalPlayer();
2881 For interaction purposes, get info about the held item
2883 - Is it a usable item?
2884 - Can it point to liquids?
2886 ItemStack playeritem;
2888 ItemStack selected, hand;
2889 playeritem = player->getWieldedItem(&selected, &hand);
2892 ToolCapabilities playeritem_toolcap =
2893 playeritem.getToolCapabilities(itemdef_manager);
2895 v3s16 old_camera_offset = camera->getOffset();
2897 if (wasKeyDown(KeyType::CAMERA_MODE)) {
2898 GenericCAO *playercao = player->getCAO();
2900 // If playercao not loaded, don't change camera
2904 camera->toggleCameraMode();
2906 // Make the player visible depending on camera mode.
2907 playercao->updateMeshCulling();
2908 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2911 float full_punch_interval = playeritem_toolcap.full_punch_interval;
2912 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2914 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2915 camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio);
2916 camera->step(dtime);
2918 v3f camera_position = camera->getPosition();
2919 v3f camera_direction = camera->getDirection();
2920 f32 camera_fov = camera->getFovMax();
2921 v3s16 camera_offset = camera->getOffset();
2923 m_camera_offset_changed = (camera_offset != old_camera_offset);
2925 if (!m_flags.disable_camera_update) {
2926 client->getEnv().getClientMap().updateCamera(camera_position,
2927 camera_direction, camera_fov, camera_offset);
2929 if (m_camera_offset_changed) {
2930 client->updateCameraOffset(camera_offset);
2931 client->getEnv().updateCameraOffset(camera_offset);
2934 clouds->updateCameraOffset(camera_offset);
2940 void Game::updateSound(f32 dtime)
2942 // Update sound listener
2943 v3s16 camera_offset = camera->getOffset();
2944 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
2945 v3f(0, 0, 0), // velocity
2946 camera->getDirection(),
2947 camera->getCameraNode()->getUpVector());
2949 bool mute_sound = g_settings->getBool("mute_sound");
2951 sound->setListenerGain(0.0f);
2953 // Check if volume is in the proper range, else fix it.
2954 float old_volume = g_settings->getFloat("sound_volume");
2955 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
2956 sound->setListenerGain(new_volume);
2958 if (old_volume != new_volume) {
2959 g_settings->setFloat("sound_volume", new_volume);
2963 LocalPlayer *player = client->getEnv().getLocalPlayer();
2965 // Tell the sound maker whether to make footstep sounds
2966 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
2968 // Update sound maker
2969 if (player->makes_footstep_sound)
2970 soundmaker->step(dtime);
2972 ClientMap &map = client->getEnv().getClientMap();
2973 MapNode n = map.getNode(player->getFootstepNodePos());
2974 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
2978 void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
2980 LocalPlayer *player = client->getEnv().getLocalPlayer();
2982 const v3f camera_direction = camera->getDirection();
2983 const v3s16 camera_offset = camera->getOffset();
2986 Calculate what block is the crosshair pointing to
2989 ItemStack selected_item, hand_item;
2990 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
2992 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
2993 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
2995 core::line3d<f32> shootline;
2997 switch (camera->getCameraMode()) {
2998 case CAMERA_MODE_FIRST:
2999 // Shoot from camera position, with bobbing
3000 shootline.start = camera->getPosition();
3002 case CAMERA_MODE_THIRD:
3003 // Shoot from player head, no bobbing
3004 shootline.start = camera->getHeadPosition();
3006 case CAMERA_MODE_THIRD_FRONT:
3007 shootline.start = camera->getHeadPosition();
3008 // prevent player pointing anything in front-view
3012 shootline.end = shootline.start + camera_direction * BS * d;
3014 #ifdef HAVE_TOUCHSCREENGUI
3016 if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3017 shootline = g_touchscreengui->getShootline();
3018 // Scale shootline to the acual distance the player can reach
3019 shootline.end = shootline.start
3020 + shootline.getVector().normalize() * BS * d;
3021 shootline.start += intToFloat(camera_offset, BS);
3022 shootline.end += intToFloat(camera_offset, BS);
3027 PointedThing pointed = updatePointedThing(shootline,
3028 selected_def.liquids_pointable,
3029 !runData.btn_down_for_dig,
3032 if (pointed != runData.pointed_old) {
3033 infostream << "Pointing at " << pointed.dump() << std::endl;
3034 hud->updateSelectionMesh(camera_offset);
3037 // Allow digging again if button is not pressed
3038 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3039 runData.digging_blocked = false;
3043 - releasing dig button
3044 - pointing away from node
3046 if (runData.digging) {
3047 if (wasKeyReleased(KeyType::DIG)) {
3048 infostream << "Dig button released (stopped digging)" << std::endl;
3049 runData.digging = false;
3050 } else if (pointed != runData.pointed_old) {
3051 if (pointed.type == POINTEDTHING_NODE
3052 && runData.pointed_old.type == POINTEDTHING_NODE
3053 && pointed.node_undersurface
3054 == runData.pointed_old.node_undersurface) {
3055 // Still pointing to the same node, but a different face.
3058 infostream << "Pointing away from node (stopped digging)" << std::endl;
3059 runData.digging = false;
3060 hud->updateSelectionMesh(camera_offset);
3064 if (!runData.digging) {
3065 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3066 client->setCrack(-1, v3s16(0, 0, 0));
3067 runData.dig_time = 0.0;
3069 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3070 // Remove e.g. torches faster when clicking instead of holding dig button
3071 runData.nodig_delay_timer = 0;
3072 runData.dig_instantly = false;
3075 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3076 runData.btn_down_for_dig = false;
3078 runData.punching = false;
3080 soundmaker->m_player_leftpunch_sound.name = "";
3082 // Prepare for repeating, unless we're not supposed to
3083 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3084 runData.repeat_place_timer += dtime;
3086 runData.repeat_place_timer = 0;
3088 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3089 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3090 !client->getScript()->on_item_use(selected_item, pointed)))
3091 client->interact(INTERACT_USE, pointed);
3092 } else if (pointed.type == POINTEDTHING_NODE) {
3093 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3094 } else if (pointed.type == POINTEDTHING_OBJECT) {
3095 v3f player_position = player->getPosition();
3096 handlePointingAtObject(pointed, tool_item, player_position, show_debug);
3097 } else if (isKeyDown(KeyType::DIG)) {
3098 // When button is held down in air, show continuous animation
3099 runData.punching = true;
3100 // Run callback even though item is not usable
3101 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3102 client->getScript()->on_item_use(selected_item, pointed);
3103 } else if (wasKeyPressed(KeyType::PLACE)) {
3104 handlePointingAtNothing(selected_item);
3107 runData.pointed_old = pointed;
3109 if (runData.punching || wasKeyPressed(KeyType::DIG))
3110 camera->setDigging(0); // dig animation
3112 input->clearWasKeyPressed();
3113 input->clearWasKeyReleased();
3114 // Ensure DIG & PLACE are marked as handled
3115 wasKeyDown(KeyType::DIG);
3116 wasKeyDown(KeyType::PLACE);
3118 input->joystick.clearWasKeyPressed(KeyType::DIG);
3119 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3121 input->joystick.clearWasKeyReleased(KeyType::DIG);
3122 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3126 PointedThing Game::updatePointedThing(
3127 const core::line3d<f32> &shootline,
3128 bool liquids_pointable,
3129 bool look_for_object,
3130 const v3s16 &camera_offset)
3132 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3133 selectionboxes->clear();
3134 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3135 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3136 "show_entity_selectionbox");
3138 ClientEnvironment &env = client->getEnv();
3139 ClientMap &map = env.getClientMap();
3140 const NodeDefManager *nodedef = map.getNodeDefManager();
3142 runData.selected_object = NULL;
3143 hud->pointing_at_object = false;
3145 RaycastState s(shootline, look_for_object, liquids_pointable);
3146 PointedThing result;
3147 env.continueRaycast(&s, &result);
3148 if (result.type == POINTEDTHING_OBJECT) {
3149 hud->pointing_at_object = true;
3151 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3152 aabb3f selection_box;
3153 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3154 runData.selected_object->getSelectionBox(&selection_box)) {
3155 v3f pos = runData.selected_object->getPosition();
3156 selectionboxes->push_back(aabb3f(selection_box));
3157 hud->setSelectionPos(pos, camera_offset);
3159 } else if (result.type == POINTEDTHING_NODE) {
3160 // Update selection boxes
3161 MapNode n = map.getNode(result.node_undersurface);
3162 std::vector<aabb3f> boxes;
3163 n.getSelectionBoxes(nodedef, &boxes,
3164 n.getNeighbors(result.node_undersurface, &map));
3167 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3168 i != boxes.end(); ++i) {
3170 box.MinEdge -= v3f(d, d, d);
3171 box.MaxEdge += v3f(d, d, d);
3172 selectionboxes->push_back(box);
3174 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3176 hud->setSelectedFaceNormal(v3f(
3177 result.intersection_normal.X,
3178 result.intersection_normal.Y,
3179 result.intersection_normal.Z));
3182 // Update selection mesh light level and vertex colors
3183 if (!selectionboxes->empty()) {
3184 v3f pf = hud->getSelectionPos();
3185 v3s16 p = floatToInt(pf, BS);
3187 // Get selection mesh light level
3188 MapNode n = map.getNode(p);
3189 u16 node_light = getInteriorLight(n, -1, nodedef);
3190 u16 light_level = node_light;
3192 for (const v3s16 &dir : g_6dirs) {
3193 n = map.getNode(p + dir);
3194 node_light = getInteriorLight(n, -1, nodedef);
3195 if (node_light > light_level)
3196 light_level = node_light;
3199 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3201 final_color_blend(&c, light_level, daynight_ratio);
3203 // Modify final color a bit with time
3204 u32 timer = porting::getTimeMs() % 5000;
3205 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3206 float sin_r = 0.08f * std::sin(timerf);
3207 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3208 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3209 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3210 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3211 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3213 // Set mesh final color
3214 hud->setSelectionMeshColor(c);
3220 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3222 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3223 PointedThing fauxPointed;
3224 fauxPointed.type = POINTEDTHING_NOTHING;
3225 client->interact(INTERACT_ACTIVATE, fauxPointed);
3229 void Game::handlePointingAtNode(const PointedThing &pointed,
3230 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3232 v3s16 nodepos = pointed.node_undersurface;
3233 v3s16 neighbourpos = pointed.node_abovesurface;
3236 Check information text of node
3239 ClientMap &map = client->getEnv().getClientMap();
3241 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3242 && !runData.digging_blocked
3243 && client->checkPrivilege("interact")) {
3244 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3247 // This should be done after digging handling
3248 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3251 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3252 meta->getString("infotext"))));
3254 MapNode n = map.getNode(nodepos);
3256 if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
3257 m_game_ui->setInfoText(L"Unknown node: " +
3258 utf8_to_wide(nodedef_manager->get(n).name));
3262 if ((wasKeyPressed(KeyType::PLACE) ||
3263 runData.repeat_place_timer >= m_repeat_place_time) &&
3264 client->checkPrivilege("interact")) {
3265 runData.repeat_place_timer = 0;
3266 infostream << "Place button pressed while looking at ground" << std::endl;
3268 // Placing animation (always shown for feedback)
3269 camera->setDigging(1);
3271 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3273 // If the wielded item has node placement prediction,
3275 // And also set the sound and send the interact
3276 // But first check for meta formspec and rightclickable
3277 auto &def = selected_item.getDefinition(itemdef_manager);
3278 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3281 if (placed && client->modsLoaded())
3282 client->getScript()->on_placenode(pointed, def);
3286 bool Game::nodePlacement(const ItemDefinition &selected_def,
3287 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3288 const PointedThing &pointed, const NodeMetadata *meta)
3290 const auto &prediction = selected_def.node_placement_prediction;
3292 const NodeDefManager *nodedef = client->ndef();
3293 ClientMap &map = client->getEnv().getClientMap();
3295 bool is_valid_position;
3297 node = map.getNode(nodepos, &is_valid_position);
3298 if (!is_valid_position) {
3299 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3304 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3305 && !isKeyDown(KeyType::SNEAK)) {
3306 // on_rightclick callbacks are called anyway
3307 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3308 client->interact(INTERACT_PLACE, pointed);
3310 infostream << "Launching custom inventory view" << std::endl;
3312 InventoryLocation inventoryloc;
3313 inventoryloc.setNodeMeta(nodepos);
3315 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3316 &client->getEnv().getClientMap(), nodepos);
3317 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3319 auto *&formspec = m_game_ui->updateFormspec("");
3320 GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
3321 txt_dst, client->getFormspecPrepend(), sound);
3323 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3327 // on_rightclick callback
3328 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3329 !isKeyDown(KeyType::SNEAK))) {
3331 client->interact(INTERACT_PLACE, pointed);
3335 verbosestream << "Node placement prediction for "
3336 << selected_def.name << " is " << prediction << std::endl;
3337 v3s16 p = neighbourpos;
3339 // Place inside node itself if buildable_to
3340 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3341 if (is_valid_position) {
3342 if (nodedef->get(n_under).buildable_to) {
3345 node = map.getNode(p, &is_valid_position);
3346 if (is_valid_position && !nodedef->get(node).buildable_to) {
3347 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3349 client->interact(INTERACT_PLACE, pointed);
3355 // Find id of predicted node
3357 bool found = nodedef->getId(prediction, id);
3360 errorstream << "Node placement prediction failed for "
3361 << selected_def.name << " (places " << prediction
3362 << ") - Name not known" << std::endl;
3363 // Handle this as if prediction was empty
3365 client->interact(INTERACT_PLACE, pointed);
3369 const ContentFeatures &predicted_f = nodedef->get(id);
3371 // Predict param2 for facedir and wallmounted nodes
3372 // Compare core.item_place_node() for what the server does
3375 const u8 place_param2 = selected_def.place_param2;
3378 param2 = place_param2;
3379 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3380 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3381 v3s16 dir = nodepos - neighbourpos;
3383 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3384 param2 = dir.Y < 0 ? 1 : 0;
3385 } else if (abs(dir.X) > abs(dir.Z)) {
3386 param2 = dir.X < 0 ? 3 : 2;
3388 param2 = dir.Z < 0 ? 5 : 4;
3390 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3391 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3392 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3394 if (abs(dir.X) > abs(dir.Z)) {
3395 param2 = dir.X < 0 ? 3 : 1;
3397 param2 = dir.Z < 0 ? 2 : 0;
3401 // Check attachment if node is in group attached_node
3402 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3403 const static v3s16 wallmounted_dirs[8] = {
3413 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3414 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3415 pp = p + wallmounted_dirs[param2];
3417 pp = p + v3s16(0, -1, 0);
3419 if (!nodedef->get(map.getNode(pp)).walkable) {
3420 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3422 client->interact(INTERACT_PLACE, pointed);
3428 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3429 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3430 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3431 const auto &indexstr = selected_item.metadata.
3432 getString("palette_index", 0);
3433 if (!indexstr.empty()) {
3434 s32 index = mystoi(indexstr);
3435 if (predicted_f.param_type_2 == CPT2_COLOR) {
3437 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3438 // param2 = pure palette index + other
3439 param2 = (index & 0xf8) | (param2 & 0x07);
3440 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3441 // param2 = pure palette index + other
3442 param2 = (index & 0xe0) | (param2 & 0x1f);
3447 // Add node to client map
3448 MapNode n(id, 0, param2);
3451 LocalPlayer *player = client->getEnv().getLocalPlayer();
3453 // Dont place node when player would be inside new node
3454 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3455 if (!nodedef->get(n).walkable ||
3456 g_settings->getBool("enable_build_where_you_stand") ||
3457 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3458 (nodedef->get(n).walkable &&
3459 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3460 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3461 // This triggers the required mesh update too
3462 client->addNode(p, n);
3464 client->interact(INTERACT_PLACE, pointed);
3465 // A node is predicted, also play a sound
3466 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3469 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3472 } catch (const InvalidPositionException &e) {
3473 errorstream << "Node placement prediction failed for "
3474 << selected_def.name << " (places "
3475 << prediction << ") - Position not loaded" << std::endl;
3476 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3481 void Game::handlePointingAtObject(const PointedThing &pointed,
3482 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3484 std::wstring infotext = unescape_translate(
3485 utf8_to_wide(runData.selected_object->infoText()));
3488 if (!infotext.empty()) {
3491 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3494 m_game_ui->setInfoText(infotext);
3496 if (isKeyDown(KeyType::DIG)) {
3497 bool do_punch = false;
3498 bool do_punch_damage = false;
3500 if (runData.object_hit_delay_timer <= 0.0) {
3502 do_punch_damage = true;
3503 runData.object_hit_delay_timer = object_hit_delay;
3506 if (wasKeyPressed(KeyType::DIG))
3510 infostream << "Punched object" << std::endl;
3511 runData.punching = true;
3514 if (do_punch_damage) {
3515 // Report direct punch
3516 v3f objpos = runData.selected_object->getPosition();
3517 v3f dir = (objpos - player_position).normalize();
3519 bool disable_send = runData.selected_object->directReportPunch(
3520 dir, &tool_item, runData.time_from_last_punch);
3521 runData.time_from_last_punch = 0;
3524 client->interact(INTERACT_START_DIGGING, pointed);
3526 } else if (wasKeyDown(KeyType::PLACE)) {
3527 infostream << "Pressed place button while pointing at object" << std::endl;
3528 client->interact(INTERACT_PLACE, pointed); // place
3533 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3534 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3536 // See also: serverpackethandle.cpp, action == 2
3537 LocalPlayer *player = client->getEnv().getLocalPlayer();
3538 ClientMap &map = client->getEnv().getClientMap();
3539 MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3541 // NOTE: Similar piece of code exists on the server side for
3543 // Get digging parameters
3544 DigParams params = getDigParams(nodedef_manager->get(n).groups,
3545 &selected_item.getToolCapabilities(itemdef_manager));
3547 // If can't dig, try hand
3548 if (!params.diggable) {
3549 params = getDigParams(nodedef_manager->get(n).groups,
3550 &hand_item.getToolCapabilities(itemdef_manager));
3553 if (!params.diggable) {
3554 // I guess nobody will wait for this long
3555 runData.dig_time_complete = 10000000.0;
3557 runData.dig_time_complete = params.time;
3559 if (m_cache_enable_particles) {
3560 const ContentFeatures &features = client->getNodeDefManager()->get(n);
3561 client->getParticleManager()->addNodeParticle(client,
3562 player, nodepos, n, features);
3566 if (!runData.digging) {
3567 infostream << "Started digging" << std::endl;
3568 runData.dig_instantly = runData.dig_time_complete == 0;
3569 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3571 client->interact(INTERACT_START_DIGGING, pointed);
3572 runData.digging = true;
3573 runData.btn_down_for_dig = true;
3576 if (!runData.dig_instantly) {
3577 runData.dig_index = (float)crack_animation_length
3579 / runData.dig_time_complete;
3581 // This is for e.g. torches
3582 runData.dig_index = crack_animation_length;
3585 SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3587 if (sound_dig.exists() && params.diggable) {
3588 if (sound_dig.name == "__group") {
3589 if (!params.main_group.empty()) {
3590 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3591 soundmaker->m_player_leftpunch_sound.name =
3592 std::string("default_dig_") +
3596 soundmaker->m_player_leftpunch_sound = sound_dig;
3600 // Don't show cracks if not diggable
3601 if (runData.dig_time_complete >= 100000.0) {
3602 } else if (runData.dig_index < crack_animation_length) {
3603 //TimeTaker timer("client.setTempMod");
3604 //infostream<<"dig_index="<<dig_index<<std::endl;
3605 client->setCrack(runData.dig_index, nodepos);
3607 infostream << "Digging completed" << std::endl;
3608 client->setCrack(-1, v3s16(0, 0, 0));
3610 runData.dig_time = 0;
3611 runData.digging = false;
3612 // we successfully dug, now block it from repeating if we want to be safe
3613 if (g_settings->getBool("safe_dig_and_place"))
3614 runData.digging_blocked = true;
3616 runData.nodig_delay_timer =
3617 runData.dig_time_complete / (float)crack_animation_length;
3619 // We don't want a corresponding delay to very time consuming nodes
3620 // and nodes without digging time (e.g. torches) get a fixed delay.
3621 if (runData.nodig_delay_timer > 0.3)
3622 runData.nodig_delay_timer = 0.3;
3623 else if (runData.dig_instantly)
3624 runData.nodig_delay_timer = 0.15;
3626 bool is_valid_position;
3627 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3628 if (is_valid_position) {
3629 if (client->modsLoaded() &&
3630 client->getScript()->on_dignode(nodepos, wasnode)) {
3634 const ContentFeatures &f = client->ndef()->get(wasnode);
3635 if (f.node_dig_prediction == "air") {
3636 client->removeNode(nodepos);
3637 } else if (!f.node_dig_prediction.empty()) {
3639 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3641 client->addNode(nodepos, id, true);
3643 // implicit else: no prediction
3646 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3648 if (m_cache_enable_particles) {
3649 const ContentFeatures &features =
3650 client->getNodeDefManager()->get(wasnode);
3651 client->getParticleManager()->addDiggingParticles(client,
3652 player, nodepos, wasnode, features);
3656 // Send event to trigger sound
3657 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3660 if (runData.dig_time_complete < 100000.0) {
3661 runData.dig_time += dtime;
3663 runData.dig_time = 0;
3664 client->setCrack(-1, nodepos);
3667 camera->setDigging(0); // Dig animation
3670 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3671 const CameraOrientation &cam)
3673 TimeTaker tt_update("Game::updateFrame()");
3674 LocalPlayer *player = client->getEnv().getLocalPlayer();
3680 if (draw_control->range_all) {
3681 runData.fog_range = 100000 * BS;
3683 runData.fog_range = draw_control->wanted_range * BS;
3687 Calculate general brightness
3689 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3690 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3691 float direct_brightness;
3694 if (m_cache_enable_noclip && m_cache_enable_free_move) {
3695 direct_brightness = time_brightness;
3696 sunlight_seen = true;
3698 float old_brightness = sky->getBrightness();
3699 direct_brightness = client->getEnv().getClientMap()
3700 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3701 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3705 float time_of_day_smooth = runData.time_of_day_smooth;
3706 float time_of_day = client->getEnv().getTimeOfDayF();
3708 static const float maxsm = 0.05f;
3709 static const float todsm = 0.05f;
3711 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3712 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3713 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3714 time_of_day_smooth = time_of_day;
3716 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3717 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3718 + (time_of_day + 1.0) * todsm;
3720 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3721 + time_of_day * todsm;
3723 runData.time_of_day_smooth = time_of_day_smooth;
3725 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3726 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3727 player->getPitch());
3733 if (sky->getCloudsVisible()) {
3734 clouds->setVisible(true);
3735 clouds->step(dtime);
3736 // camera->getPosition is not enough for 3rd person views
3737 v3f camera_node_position = camera->getCameraNode()->getPosition();
3738 v3s16 camera_offset = camera->getOffset();
3739 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3740 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3741 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3742 clouds->update(camera_node_position,
3743 sky->getCloudColor());
3744 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3745 // if inside clouds, and fog enabled, use that as sky
3747 video::SColor clouds_dark = clouds->getColor()
3748 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3749 sky->overrideColors(clouds_dark, clouds->getColor());
3750 sky->setInClouds(true);
3751 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3752 // do not draw clouds after all
3753 clouds->setVisible(false);
3756 clouds->setVisible(false);
3763 client->getParticleManager()->step(dtime);
3769 if (m_cache_enable_fog) {
3772 video::EFT_FOG_LINEAR,
3773 runData.fog_range * m_cache_fog_start,
3774 runData.fog_range * 1.0,
3782 video::EFT_FOG_LINEAR,
3792 Get chat messages from client
3795 v2u32 screensize = driver->getScreenSize();
3797 updateChat(dtime, screensize);
3803 if (player->getWieldIndex() != runData.new_playeritem)
3804 client->setPlayerItem(runData.new_playeritem);
3806 if (client->updateWieldedItem()) {
3807 // Update wielded tool
3808 ItemStack selected_item, hand_item;
3809 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3810 camera->wield(tool_item);
3814 Update block draw list every 200ms or when camera direction has
3817 runData.update_draw_list_timer += dtime;
3819 v3f camera_direction = camera->getDirection();
3820 if (runData.update_draw_list_timer >= 0.2
3821 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3822 || m_camera_offset_changed) {
3823 runData.update_draw_list_timer = 0;
3824 client->getEnv().getClientMap().updateDrawList();
3825 runData.update_draw_list_last_cam_dir = camera_direction;
3828 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3831 make sure menu is on top
3832 1. Delete formspec menu reference if menu was removed
3833 2. Else, make sure formspec menu is on top
3835 auto formspec = m_game_ui->getFormspecGUI();
3836 do { // breakable. only runs for one iteration
3840 if (formspec->getReferenceCount() == 1) {
3841 m_game_ui->deleteFormspec();
3845 auto &loc = formspec->getFormspecLocation();
3846 if (loc.type == InventoryLocation::NODEMETA) {
3847 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3848 if (!meta || meta->getString("formspec").empty()) {
3849 formspec->quitMenu();
3855 guiroot->bringToFront(formspec);
3861 const video::SColor &skycolor = sky->getSkyColor();
3863 TimeTaker tt_draw("Draw scene");
3864 driver->beginScene(true, true, skycolor);
3866 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3867 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3868 (camera->getCameraMode() == CAMERA_MODE_FIRST));
3869 bool draw_crosshair = (
3870 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3871 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3872 #ifdef HAVE_TOUCHSCREENGUI
3874 draw_crosshair = !g_settings->getBool("touchtarget");
3875 } catch (SettingNotFoundException) {
3878 RenderingEngine::draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3879 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3884 if (m_game_ui->m_flags.show_profiler_graph)
3885 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3890 if (runData.damage_flash > 0.0f) {
3891 video::SColor color(runData.damage_flash, 180, 0, 0);
3892 driver->draw2DRectangle(color,
3893 core::rect<s32>(0, 0, screensize.X, screensize.Y),
3896 runData.damage_flash -= 384.0f * dtime;
3902 if (player->hurt_tilt_timer > 0.0f) {
3903 player->hurt_tilt_timer -= dtime * 6.0f;
3905 if (player->hurt_tilt_timer < 0.0f)
3906 player->hurt_tilt_strength = 0.0f;
3910 Update minimap pos and rotation
3912 if (mapper && m_game_ui->m_flags.show_hud) {
3913 mapper->setPos(floatToInt(player->getPosition(), BS));
3914 mapper->setAngle(player->getYaw());
3920 if (++m_reset_HW_buffer_counter > 500) {
3922 Periodically remove all mesh HW buffers.
3924 Work around for a quirk in Irrlicht where a HW buffer is only
3925 released after 20000 iterations (triggered from endScene()).
3927 Without this, all loaded but unused meshes will retain their HW
3928 buffers for at least 5 minutes, at which point looking up the HW buffers
3929 becomes a bottleneck and the framerate drops (as much as 30%).
3931 Tests showed that numbers between 50 and 1000 are good, so picked 500.
3932 There are no other public Irrlicht APIs that allow interacting with the
3933 HW buffers without tracking the status of every individual mesh.
3935 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
3937 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
3938 driver->removeAllHardwareBuffers();
3939 m_reset_HW_buffer_counter = 0;
3943 stats->drawtime = tt_draw.stop(true);
3944 g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
3945 g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
3948 /* Log times and stuff for visualization */
3949 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
3951 Profiler::GraphValues values;
3952 g_profiler->graphGet(values);
3958 /****************************************************************************
3960 ****************************************************************************/
3962 /* On some computers framerate doesn't seem to be automatically limited
3964 inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
3966 // not using getRealTime is necessary for wine
3967 device->getTimer()->tick(); // Maker sure device time is up-to-date
3968 u32 time = device->getTimer()->getTime();
3969 u32 last_time = fps_timings->last_time;
3971 if (time > last_time) // Make sure time hasn't overflowed
3972 fps_timings->busy_time = time - last_time;
3974 fps_timings->busy_time = 0;
3976 u32 frametime_min = 1000 / (
3977 device->isWindowFocused() && !g_menumgr.pausesGame()
3978 ? g_settings->getFloat("fps_max")
3979 : g_settings->getFloat("fps_max_unfocused"));
3981 if (fps_timings->busy_time < frametime_min) {
3982 fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
3983 device->sleep(fps_timings->sleep_time);
3985 fps_timings->sleep_time = 0;
3988 /* Get the new value of the device timer. Note that device->sleep() may
3989 * not sleep for the entire requested time as sleep may be interrupted and
3990 * therefore it is arguably more accurate to get the new time from the
3991 * device rather than calculating it by adding sleep_time to time.
3994 device->getTimer()->tick(); // Update device timer
3995 time = device->getTimer()->getTime();
3997 if (time > last_time) // Make sure last_time hasn't overflowed
3998 *dtime = (time - last_time) / 1000.0;
4002 fps_timings->last_time = time;
4005 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4007 const wchar_t *wmsg = wgettext(msg);
4008 RenderingEngine::draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4013 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4015 ((Game *)data)->readSettings();
4018 void Game::readSettings()
4020 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4021 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4022 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4023 m_cache_enable_particles = g_settings->getBool("enable_particles");
4024 m_cache_enable_fog = g_settings->getBool("enable_fog");
4025 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
4026 m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4027 m_repeat_place_time = g_settings->getFloat("repeat_place_time");
4029 m_cache_enable_noclip = g_settings->getBool("noclip");
4030 m_cache_enable_free_move = g_settings->getBool("free_move");
4032 m_cache_fog_start = g_settings->getFloat("fog_start");
4034 m_cache_cam_smoothing = 0;
4035 if (g_settings->getBool("cinematic"))
4036 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4038 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4040 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4041 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4042 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4044 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4047 /****************************************************************************/
4048 /****************************************************************************
4050 ****************************************************************************/
4051 /****************************************************************************/
4053 void Game::extendedResourceCleanup()
4055 // Extended resource accounting
4056 infostream << "Irrlicht resources after cleanup:" << std::endl;
4057 infostream << "\tRemaining meshes : "
4058 << RenderingEngine::get_mesh_cache()->getMeshCount() << std::endl;
4059 infostream << "\tRemaining textures : "
4060 << driver->getTextureCount() << std::endl;
4062 for (unsigned int i = 0; i < driver->getTextureCount(); i++) {
4063 irr::video::ITexture *texture = driver->getTextureByIndex(i);
4064 infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str()
4068 clearTextureNameCache();
4069 infostream << "\tRemaining materials: "
4070 << driver-> getMaterialRendererCount()
4071 << " (note: irrlicht doesn't support removing renderers)" << std::endl;
4074 void Game::showDeathFormspec()
4076 static std::string formspec_str =
4077 std::string("formspec_version[1]") +
4079 "bgcolor[#320000b4;true]"
4080 "label[4.85,1.35;" + gettext("You died") + "]"
4081 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4085 /* Note: FormspecFormSource and LocalFormspecHandler *
4086 * are deleted by guiFormSpecMenu */
4087 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4088 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4090 auto *&formspec = m_game_ui->getFormspecGUI();
4091 GUIFormSpecMenu::create(formspec, client, &input->joystick,
4092 fs_src, txt_dst, client->getFormspecPrepend(), sound);
4093 formspec->setFocus("btn_respawn");
4096 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4097 void Game::showPauseMenu()
4100 static const std::string control_text = strgettext("Default Controls:\n"
4101 "No menu visible:\n"
4102 "- single tap: button activate\n"
4103 "- double tap: place/use\n"
4104 "- slide finger: look around\n"
4105 "Menu/Inventory visible:\n"
4106 "- double tap (outside):\n"
4108 "- touch stack, touch slot:\n"
4110 "- touch&drag, tap 2nd finger\n"
4111 " --> place single item to slot\n"
4114 static const std::string control_text_template = strgettext("Controls:\n"
4115 "- %s: move forwards\n"
4116 "- %s: move backwards\n"
4118 "- %s: move right\n"
4119 "- %s: jump/climb up\n"
4122 "- %s: sneak/climb down\n"
4125 "- Mouse: turn/look\n"
4126 "- Mouse wheel: select item\n"
4130 char control_text_buf[600];
4132 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4133 GET_KEY_NAME(keymap_forward),
4134 GET_KEY_NAME(keymap_backward),
4135 GET_KEY_NAME(keymap_left),
4136 GET_KEY_NAME(keymap_right),
4137 GET_KEY_NAME(keymap_jump),
4138 GET_KEY_NAME(keymap_dig),
4139 GET_KEY_NAME(keymap_place),
4140 GET_KEY_NAME(keymap_sneak),
4141 GET_KEY_NAME(keymap_drop),
4142 GET_KEY_NAME(keymap_inventory),
4143 GET_KEY_NAME(keymap_chat)
4146 std::string control_text = std::string(control_text_buf);
4147 str_formspec_escape(control_text);
4150 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4151 std::ostringstream os;
4153 os << "formspec_version[1]" << SIZE_TAG
4154 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4155 << strgettext("Continue") << "]";
4157 if (!simple_singleplayer_mode) {
4158 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4159 << strgettext("Change Password") << "]";
4161 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4166 if (g_settings->getBool("enable_sound")) {
4167 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4168 << strgettext("Sound Volume") << "]";
4171 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4172 << strgettext("Change Keys") << "]";
4174 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4175 << strgettext("Exit to Menu") << "]";
4176 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4177 << strgettext("Exit to OS") << "]"
4178 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4179 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4181 << strgettext("Game info:") << "\n";
4182 const std::string &address = client->getAddressName();
4183 static const std::string mode = strgettext("- Mode: ");
4184 if (!simple_singleplayer_mode) {
4185 Address serverAddress = client->getServerAddress();
4186 if (!address.empty()) {
4187 os << mode << strgettext("Remote server") << "\n"
4188 << strgettext("- Address: ") << address;
4190 os << mode << strgettext("Hosting server");
4192 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4194 os << mode << strgettext("Singleplayer") << "\n";
4196 if (simple_singleplayer_mode || address.empty()) {
4197 static const std::string on = strgettext("On");
4198 static const std::string off = strgettext("Off");
4199 const std::string &damage = g_settings->getBool("enable_damage") ? on : off;
4200 const std::string &creative = g_settings->getBool("creative_mode") ? on : off;
4201 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4202 os << strgettext("- Damage: ") << damage << "\n"
4203 << strgettext("- Creative Mode: ") << creative << "\n";
4204 if (!simple_singleplayer_mode) {
4205 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4206 //~ PvP = Player versus Player
4207 os << strgettext("- PvP: ") << pvp << "\n"
4208 << strgettext("- Public: ") << announced << "\n";
4209 std::string server_name = g_settings->get("server_name");
4210 str_formspec_escape(server_name);
4211 if (announced == on && !server_name.empty())
4212 os << strgettext("- Server Name: ") << server_name;
4219 /* Note: FormspecFormSource and LocalFormspecHandler *
4220 * are deleted by guiFormSpecMenu */
4221 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4222 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4224 auto *&formspec = m_game_ui->getFormspecGUI();
4225 GUIFormSpecMenu::create(formspec, client, &input->joystick,
4226 fs_src, txt_dst, client->getFormspecPrepend(), sound);
4227 formspec->setFocus("btn_continue");
4228 formspec->doPause = true;
4230 if (simple_singleplayer_mode)
4234 /****************************************************************************/
4235 /****************************************************************************
4236 extern function for launching the game
4237 ****************************************************************************/
4238 /****************************************************************************/
4240 void the_game(bool *kill,
4241 InputHandler *input,
4242 const GameStartData &start_data,
4243 std::string &error_message,
4244 ChatBackend &chat_backend,
4245 bool *reconnect_requested) // Used for local game
4249 /* Make a copy of the server address because if a local singleplayer server
4250 * is created then this is updated and we don't want to change the value
4251 * passed to us by the calling function
4256 if (game.startup(kill, input, start_data, error_message,
4257 reconnect_requested, &chat_backend)) {
4261 } catch (SerializationError &e) {
4262 error_message = std::string("A serialization error occurred:\n")
4263 + e.what() + "\n\nThe server is probably "
4264 " running a different version of " PROJECT_NAME_C ".";
4265 errorstream << error_message << std::endl;
4266 } catch (ServerError &e) {
4267 error_message = e.what();
4268 errorstream << "ServerError: " << error_message << std::endl;
4269 } catch (ModError &e) {
4270 error_message = std::string("ModError: ") + e.what() +
4271 strgettext("\nCheck debug.txt for details.");
4272 errorstream << error_message << std::endl;