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/guiFormSpecMenu.h"
47 #include "gui/guiKeyChangeMenu.h"
48 #include "gui/guiPasswordChange.h"
49 #include "gui/guiVolumeChange.h"
50 #include "gui/mainmenumanager.h"
51 #include "gui/profilergraph.h"
54 #include "nodedef.h" // Needed for determining pointing to nodes
55 #include "nodemetadata.h"
56 #include "particles.h"
64 #include "translation.h"
65 #include "util/basic_macros.h"
66 #include "util/directiontables.h"
67 #include "util/pointedthing.h"
68 #include "util/quicktune_shortcutter.h"
69 #include "irrlicht_changes/static_text.h"
72 #include "script/scripting_client.h"
76 #include "client/sound_openal.h"
78 #include "client/sound.h"
84 struct TextDestNodeMetadata : public TextDest
86 TextDestNodeMetadata(v3s16 p, Client *client)
91 // This is deprecated I guess? -celeron55
92 void gotText(const std::wstring &text)
94 std::string ntext = wide_to_utf8(text);
95 infostream << "Submitting 'text' field of node at (" << m_p.X << ","
96 << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
98 fields["text"] = ntext;
99 m_client->sendNodemetaFields(m_p, "", fields);
101 void gotText(const StringMap &fields)
103 m_client->sendNodemetaFields(m_p, "", fields);
110 struct TextDestPlayerInventory : public TextDest
112 TextDestPlayerInventory(Client *client)
117 TextDestPlayerInventory(Client *client, const std::string &formname)
120 m_formname = formname;
122 void gotText(const StringMap &fields)
124 m_client->sendInventoryFields(m_formname, fields);
130 struct LocalFormspecHandler : public TextDest
132 LocalFormspecHandler(const std::string &formname)
134 m_formname = formname;
137 LocalFormspecHandler(const std::string &formname, Client *client):
140 m_formname = formname;
143 void gotText(const StringMap &fields)
145 if (m_formname == "MT_PAUSE_MENU") {
146 if (fields.find("btn_sound") != fields.end()) {
147 g_gamecallback->changeVolume();
151 if (fields.find("btn_key_config") != fields.end()) {
152 g_gamecallback->keyConfig();
156 if (fields.find("btn_exit_menu") != fields.end()) {
157 g_gamecallback->disconnect();
161 if (fields.find("btn_exit_os") != fields.end()) {
162 g_gamecallback->exitToOS();
164 RenderingEngine::get_raw_device()->closeDevice();
169 if (fields.find("btn_change_password") != fields.end()) {
170 g_gamecallback->changePassword();
177 if (m_formname == "MT_DEATH_SCREEN") {
178 assert(m_client != 0);
179 m_client->sendRespawn();
183 if (m_client->modsLoaded())
184 m_client->getScript()->on_formspec_input(m_formname, fields);
187 Client *m_client = nullptr;
190 /* Form update callback */
192 class NodeMetadataFormSource: public IFormSource
195 NodeMetadataFormSource(ClientMap *map, v3s16 p):
200 const std::string &getForm() const
202 static const std::string empty_string = "";
203 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
208 return meta->getString("formspec");
211 virtual std::string resolveText(const std::string &str)
213 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
218 return meta->resolveString(str);
225 class PlayerInventoryFormSource: public IFormSource
228 PlayerInventoryFormSource(Client *client):
233 const std::string &getForm() const
235 LocalPlayer *player = m_client->getEnv().getLocalPlayer();
236 return player->inventory_formspec;
242 class NodeDugEvent : public MtEvent
248 NodeDugEvent(v3s16 p, MapNode n):
252 Type getType() const { return NODE_DUG; }
257 ISoundManager *m_sound;
258 const NodeDefManager *m_ndef;
261 bool makes_footstep_sound;
262 float m_player_step_timer;
263 float m_player_jump_timer;
265 SimpleSoundSpec m_player_step_sound;
266 SimpleSoundSpec m_player_leftpunch_sound;
267 // Second sound made on left punch, currently used for item 'use' sound
268 SimpleSoundSpec m_player_leftpunch_sound2;
269 SimpleSoundSpec m_player_rightpunch_sound;
271 SoundMaker(ISoundManager *sound, const NodeDefManager *ndef):
274 makes_footstep_sound(true),
275 m_player_step_timer(0.0f),
276 m_player_jump_timer(0.0f)
280 void playPlayerStep()
282 if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
283 m_player_step_timer = 0.03;
284 if (makes_footstep_sound)
285 m_sound->playSound(m_player_step_sound);
289 void playPlayerJump()
291 if (m_player_jump_timer <= 0.0f) {
292 m_player_jump_timer = 0.2f;
293 m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f));
297 static void viewBobbingStep(MtEvent *e, void *data)
299 SoundMaker *sm = (SoundMaker *)data;
300 sm->playPlayerStep();
303 static void playerRegainGround(MtEvent *e, void *data)
305 SoundMaker *sm = (SoundMaker *)data;
306 sm->playPlayerStep();
309 static void playerJump(MtEvent *e, void *data)
311 SoundMaker *sm = (SoundMaker *)data;
312 sm->playPlayerJump();
315 static void cameraPunchLeft(MtEvent *e, void *data)
317 SoundMaker *sm = (SoundMaker *)data;
318 sm->m_sound->playSound(sm->m_player_leftpunch_sound);
319 sm->m_sound->playSound(sm->m_player_leftpunch_sound2);
322 static void cameraPunchRight(MtEvent *e, void *data)
324 SoundMaker *sm = (SoundMaker *)data;
325 sm->m_sound->playSound(sm->m_player_rightpunch_sound);
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);
335 static void playerDamage(MtEvent *e, void *data)
337 SoundMaker *sm = (SoundMaker *)data;
338 sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5));
341 static void playerFallingDamage(MtEvent *e, void *data)
343 SoundMaker *sm = (SoundMaker *)data;
344 sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5));
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 typedef s32 SamplerLayer_t;
406 class GameGlobalShaderConstantSetter : public IShaderConstantSetter
410 bool *m_force_fog_off;
413 CachedPixelShaderSetting<float, 4> m_sky_bg_color;
414 CachedPixelShaderSetting<float> m_fog_distance;
415 CachedVertexShaderSetting<float> m_animation_timer_vertex;
416 CachedPixelShaderSetting<float> m_animation_timer_pixel;
417 CachedPixelShaderSetting<float, 3> m_day_light;
418 CachedPixelShaderSetting<float, 4> m_star_color;
419 CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
420 CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
421 CachedPixelShaderSetting<float, 3> m_minimap_yaw;
422 CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
423 CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
424 CachedPixelShaderSetting<SamplerLayer_t> m_texture0;
425 CachedPixelShaderSetting<SamplerLayer_t> m_texture1;
426 CachedPixelShaderSetting<SamplerLayer_t> m_texture2;
427 CachedPixelShaderSetting<SamplerLayer_t> m_texture3;
428 CachedPixelShaderSetting<float, 2> m_texel_size0;
429 std::array<float, 2> m_texel_size0_values;
430 CachedPixelShaderSetting<float> m_exposure_factor_pixel;
431 float m_user_exposure_factor;
432 bool m_bloom_enabled;
433 CachedPixelShaderSetting<float> m_bloom_intensity_pixel;
434 float m_bloom_intensity;
435 CachedPixelShaderSetting<float> m_bloom_radius_pixel;
436 float m_bloom_radius;
439 void onSettingsChange(const std::string &name)
441 if (name == "enable_fog")
442 m_fog_enabled = g_settings->getBool("enable_fog");
443 if (name == "exposure_factor")
444 m_user_exposure_factor = g_settings->getFloat("exposure_factor", 0.1f, 10.0f);
445 if (name == "bloom_intensity")
446 m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f);
447 if (name == "bloom_radius")
448 m_bloom_radius = g_settings->getFloat("bloom_radius", 1.0f, 64.0f);
451 static void settingsCallback(const std::string &name, void *userdata)
453 reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
456 void setSky(Sky *sky) { m_sky = sky; }
458 GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
459 f32 *fog_range, Client *client) :
462 m_force_fog_off(force_fog_off),
463 m_fog_range(fog_range),
464 m_sky_bg_color("skyBgColor"),
465 m_fog_distance("fogDistance"),
466 m_animation_timer_vertex("animationTimer"),
467 m_animation_timer_pixel("animationTimer"),
468 m_day_light("dayLight"),
469 m_star_color("starColor"),
470 m_eye_position_pixel("eyePosition"),
471 m_eye_position_vertex("eyePosition"),
472 m_minimap_yaw("yawVec"),
473 m_camera_offset_pixel("cameraOffset"),
474 m_camera_offset_vertex("cameraOffset"),
475 m_texture0("texture0"),
476 m_texture1("texture1"),
477 m_texture2("texture2"),
478 m_texture3("texture3"),
479 m_texel_size0("texelSize0"),
480 m_exposure_factor_pixel("exposureFactor"),
481 m_bloom_intensity_pixel("bloomIntensity"),
482 m_bloom_radius_pixel("bloomRadius")
484 g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
485 g_settings->registerChangedCallback("exposure_factor", settingsCallback, this);
486 g_settings->registerChangedCallback("bloom_intensity", settingsCallback, this);
487 g_settings->registerChangedCallback("bloom_radius", settingsCallback, this);
488 m_fog_enabled = g_settings->getBool("enable_fog");
489 m_user_exposure_factor = g_settings->getFloat("exposure_factor", 0.1f, 10.0f);
490 m_bloom_enabled = g_settings->getBool("enable_bloom");
491 m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f);
492 m_bloom_radius = g_settings->getFloat("bloom_radius", 1.0f, 64.0f);
495 ~GameGlobalShaderConstantSetter()
497 g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
500 void onSetConstants(video::IMaterialRendererServices *services) override
503 video::SColor bgcolor = m_sky->getBgColor();
504 video::SColorf bgcolorf(bgcolor);
505 float bgcolorfa[4] = {
511 m_sky_bg_color.set(bgcolorfa, services);
514 float fog_distance = 10000 * BS;
516 if (m_fog_enabled && !*m_force_fog_off)
517 fog_distance = *m_fog_range;
519 m_fog_distance.set(&fog_distance, services);
521 u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
522 video::SColorf sunlight;
523 get_sunlight_color(&sunlight, daynight_ratio);
528 m_day_light.set(dnc, services);
530 video::SColorf star_color = m_sky->getCurrentStarColor();
531 float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a};
532 m_star_color.set(clr, services);
534 u32 animation_timer = m_client->getEnv().getFrameTime() % 1000000;
535 float animation_timer_f = (float)animation_timer / 100000.f;
536 m_animation_timer_vertex.set(&animation_timer_f, services);
537 m_animation_timer_pixel.set(&animation_timer_f, services);
539 float eye_position_array[3];
540 v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
541 epos.getAs3Values(eye_position_array);
542 m_eye_position_pixel.set(eye_position_array, services);
543 m_eye_position_vertex.set(eye_position_array, services);
545 if (m_client->getMinimap()) {
546 float minimap_yaw_array[3];
547 v3f minimap_yaw = m_client->getMinimap()->getYawVec();
548 minimap_yaw.getAs3Values(minimap_yaw_array);
549 m_minimap_yaw.set(minimap_yaw_array, services);
552 float camera_offset_array[3];
553 v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
554 offset.getAs3Values(camera_offset_array);
555 m_camera_offset_pixel.set(camera_offset_array, services);
556 m_camera_offset_vertex.set(camera_offset_array, services);
558 SamplerLayer_t tex_id;
560 m_texture0.set(&tex_id, services);
562 m_texture1.set(&tex_id, services);
564 m_texture2.set(&tex_id, services);
566 m_texture3.set(&tex_id, services);
568 m_texel_size0.set(m_texel_size0_values.data(), services);
570 float exposure_factor = RenderingEngine::DEFAULT_EXPOSURE_FACTOR * m_user_exposure_factor;
571 if (std::isnan(exposure_factor))
572 exposure_factor = RenderingEngine::DEFAULT_EXPOSURE_FACTOR;
573 m_exposure_factor_pixel.set(&exposure_factor, services);
575 if (m_bloom_enabled) {
576 m_bloom_intensity_pixel.set(&m_bloom_intensity, services);
577 m_bloom_radius_pixel.set(&m_bloom_radius, services);
581 void onSetMaterial(const video::SMaterial &material)
583 video::ITexture *texture = material.getTexture(0);
585 core::dimension2du size = texture->getSize();
586 m_texel_size0_values[0] = 1.f / size.Width;
587 m_texel_size0_values[1] = 1.f / size.Height;
590 m_texel_size0_values[0] = 0.f;
591 m_texel_size0_values[1] = 0.f;
597 class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
600 bool *m_force_fog_off;
603 std::vector<GameGlobalShaderConstantSetter *> created_nosky;
605 GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
606 f32 *fog_range, Client *client) :
608 m_force_fog_off(force_fog_off),
609 m_fog_range(fog_range),
613 void setSky(Sky *sky) {
615 for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
616 ggscs->setSky(m_sky);
618 created_nosky.clear();
621 virtual IShaderConstantSetter* create()
623 auto *scs = new GameGlobalShaderConstantSetter(
624 m_sky, m_force_fog_off, m_fog_range, m_client);
626 created_nosky.push_back(scs);
631 #ifdef HAVE_TOUCHSCREENGUI
632 #define SIZE_TAG "size[11,5.5]"
634 #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
637 /****************************************************************************
638 ****************************************************************************/
640 const static float object_hit_delay = 0.2;
643 FpsControl() : last_time(0), busy_time(0), sleep_time(0) {}
647 void limit(IrrlichtDevice *device, f32 *dtime);
649 u32 getBusyMs() const { return busy_time / 1000; }
651 // all values in microseconds (us)
652 u64 last_time, busy_time, sleep_time;
656 /* The reason the following structs are not anonymous structs within the
657 * class is that they are not used by the majority of member functions and
658 * many functions that do require objects of thse types do not modify them
659 * (so they can be passed as a const qualified parameter)
665 PointedThing pointed_old;
668 bool btn_down_for_dig;
670 bool digging_blocked;
671 bool reset_jump_timer;
672 float nodig_delay_timer;
674 float dig_time_complete;
675 float repeat_place_timer;
676 float object_hit_delay_timer;
677 float time_from_last_punch;
678 ClientActiveObject *selected_object;
680 float jump_timer_up; // from key up until key down
681 float jump_timer_down; // since last key down
682 float jump_timer_down_before; // from key down until key down again
685 float update_draw_list_timer;
689 v3f update_draw_list_last_cam_dir;
691 float time_of_day_smooth;
696 struct ClientEventHandler
698 void (Game::*handler)(ClientEvent *, CameraOrientation *);
701 /****************************************************************************
703 ****************************************************************************/
705 using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
707 /* This is not intended to be a public class. If a public class becomes
708 * desirable then it may be better to create another 'wrapper' class that
709 * hides most of the stuff in this class (nothing in this class is required
710 * by any other file) but exposes the public methods/data only.
717 bool startup(bool *kill,
719 RenderingEngine *rendering_engine,
720 const GameStartData &game_params,
721 std::string &error_message,
723 ChatBackend *chat_backend);
730 // Basic initialisation
731 bool init(const std::string &map_dir, const std::string &address,
732 u16 port, const SubgameSpec &gamespec);
734 bool createSingleplayerServer(const std::string &map_dir,
735 const SubgameSpec &gamespec, u16 port);
738 bool createClient(const GameStartData &start_data);
742 bool connectToServer(const GameStartData &start_data,
743 bool *connect_ok, bool *aborted);
744 bool getServerContent(bool *aborted);
748 void updateInteractTimers(f32 dtime);
749 bool checkConnection();
750 bool handleCallbacks();
751 void processQueues();
752 void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
753 void updateDebugState();
754 void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
755 void updateProfilerGraphs(ProfilerGraph *graph);
758 void processUserInput(f32 dtime);
759 void processKeyInput();
760 void processItemSelection(u16 *new_playeritem);
762 void dropSelectedItem(bool single_item = false);
763 void openInventory();
764 void openConsole(float scale, const wchar_t *line=NULL);
765 void toggleFreeMove();
766 void toggleFreeMoveAlt();
767 void togglePitchMove();
770 void toggleCinematic();
771 void toggleBlockBounds();
772 void toggleAutoforward();
774 void toggleMinimap(bool shift_pressed);
777 void toggleUpdateCamera();
779 void increaseViewRange();
780 void decreaseViewRange();
781 void toggleFullViewRange();
782 void checkZoomEnabled();
784 void updateCameraDirection(CameraOrientation *cam, float dtime);
785 void updateCameraOrientation(CameraOrientation *cam, float dtime);
786 void updatePlayerControl(const CameraOrientation &cam);
787 void step(f32 dtime);
788 void processClientEvents(CameraOrientation *cam);
789 void updateCamera(f32 dtime);
790 void updateSound(f32 dtime);
791 void processPlayerInteraction(f32 dtime, bool show_hud);
793 * Returns the object or node the player is pointing at.
794 * Also updates the selected thing in the Hud.
796 * @param[in] shootline the shootline, starting from
797 * the camera position. This also gives the maximal distance
799 * @param[in] liquids_pointable if false, liquids are ignored
800 * @param[in] look_for_object if false, objects are ignored
801 * @param[in] camera_offset offset of the camera
802 * @param[out] selected_object the selected object or
805 PointedThing updatePointedThing(
806 const core::line3d<f32> &shootline, bool liquids_pointable,
807 bool look_for_object, const v3s16 &camera_offset);
808 void handlePointingAtNothing(const ItemStack &playerItem);
809 void handlePointingAtNode(const PointedThing &pointed,
810 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
811 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
812 const v3f &player_position, bool show_debug);
813 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
814 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
815 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
816 const CameraOrientation &cam);
817 void updateShadows();
820 void showOverlayMessage(const char *msg, float dtime, int percent,
821 bool draw_clouds = true);
823 static void settingChangedCallback(const std::string &setting_name, void *data);
826 inline bool isKeyDown(GameKeyType k)
828 return input->isKeyDown(k);
830 inline bool wasKeyDown(GameKeyType k)
832 return input->wasKeyDown(k);
834 inline bool wasKeyPressed(GameKeyType k)
836 return input->wasKeyPressed(k);
838 inline bool wasKeyReleased(GameKeyType k)
840 return input->wasKeyReleased(k);
844 void handleAndroidChatInput();
849 bool force_fog_off = false;
850 bool disable_camera_update = false;
853 void showDeathFormspec();
854 void showPauseMenu();
856 void pauseAnimation();
857 void resumeAnimation();
859 // ClientEvent handlers
860 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
861 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
862 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
863 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
864 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
865 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
866 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
867 CameraOrientation *cam);
868 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
869 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
870 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
871 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
872 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
873 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
874 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
875 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
876 CameraOrientation *cam);
877 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
879 void updateChat(f32 dtime);
881 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
882 const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
883 const NodeMetadata *meta);
884 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
886 f32 getSensitivityScaleFactor() const;
888 InputHandler *input = nullptr;
890 Client *client = nullptr;
891 Server *server = nullptr;
893 IWritableTextureSource *texture_src = nullptr;
894 IWritableShaderSource *shader_src = nullptr;
896 // When created, these will be filled with data received from the server
897 IWritableItemDefManager *itemdef_manager = nullptr;
898 NodeDefManager *nodedef_manager = nullptr;
900 GameOnDemandSoundFetcher soundfetcher; // useful when testing
901 ISoundManager *sound = nullptr;
902 bool sound_is_dummy = false;
903 SoundMaker *soundmaker = nullptr;
905 ChatBackend *chat_backend = nullptr;
906 LogOutputBuffer m_chat_log_buf;
908 EventManager *eventmgr = nullptr;
909 QuicktuneShortcutter *quicktune = nullptr;
911 std::unique_ptr<GameUI> m_game_ui;
912 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
913 MapDrawControl *draw_control = nullptr;
914 Camera *camera = nullptr;
915 Clouds *clouds = nullptr; // Free using ->Drop()
916 Sky *sky = nullptr; // Free using ->Drop()
918 Minimap *mapper = nullptr;
920 // Map server hud ids to client hud ids
921 std::unordered_map<u32, u32> m_hud_server_to_client;
927 This class does take ownership/responsibily for cleaning up etc of any of
928 these items (e.g. device)
930 IrrlichtDevice *device;
931 RenderingEngine *m_rendering_engine;
932 video::IVideoDriver *driver;
933 scene::ISceneManager *smgr;
935 std::string *error_message;
936 bool *reconnect_requested;
937 scene::ISceneNode *skybox;
938 PausedNodesList paused_animated_nodes;
940 bool simple_singleplayer_mode;
943 /* Pre-calculated values
945 int crack_animation_length;
947 IntervalLimiter profiler_interval;
950 * TODO: Local caching of settings is not optimal and should at some stage
951 * be updated to use a global settings object for getting thse values
952 * (as opposed to the this local caching). This can be addressed in
955 bool m_cache_doubletap_jump;
956 bool m_cache_enable_clouds;
957 bool m_cache_enable_joysticks;
958 bool m_cache_enable_particles;
959 bool m_cache_enable_fog;
960 bool m_cache_enable_noclip;
961 bool m_cache_enable_free_move;
962 f32 m_cache_mouse_sensitivity;
963 f32 m_cache_joystick_frustum_sensitivity;
964 f32 m_repeat_place_time;
965 f32 m_cache_cam_smoothing;
966 f32 m_cache_fog_start;
968 bool m_invert_mouse = false;
969 bool m_first_loop_after_window_activation = false;
970 bool m_camera_offset_changed = false;
972 bool m_does_lost_focus_pause_game = false;
974 // if true, (almost) the whole game is paused
975 // this happens in pause menu in singleplayer
976 bool m_is_paused = false;
978 #if IRRLICHT_VERSION_MT_REVISION < 5
979 int m_reset_HW_buffer_counter = 0;
982 #ifdef HAVE_TOUCHSCREENGUI
983 bool m_cache_hold_aux1;
984 bool m_touch_use_crosshair;
985 inline bool isNoCrosshairAllowed() {
986 return !m_touch_use_crosshair && camera->getCameraMode() == CAMERA_MODE_FIRST;
990 bool m_android_chat_open;
995 m_chat_log_buf(g_logger),
996 m_game_ui(new GameUI())
998 g_settings->registerChangedCallback("doubletap_jump",
999 &settingChangedCallback, this);
1000 g_settings->registerChangedCallback("enable_clouds",
1001 &settingChangedCallback, this);
1002 g_settings->registerChangedCallback("doubletap_joysticks",
1003 &settingChangedCallback, this);
1004 g_settings->registerChangedCallback("enable_particles",
1005 &settingChangedCallback, this);
1006 g_settings->registerChangedCallback("enable_fog",
1007 &settingChangedCallback, this);
1008 g_settings->registerChangedCallback("mouse_sensitivity",
1009 &settingChangedCallback, this);
1010 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
1011 &settingChangedCallback, this);
1012 g_settings->registerChangedCallback("repeat_place_time",
1013 &settingChangedCallback, this);
1014 g_settings->registerChangedCallback("noclip",
1015 &settingChangedCallback, this);
1016 g_settings->registerChangedCallback("free_move",
1017 &settingChangedCallback, this);
1018 g_settings->registerChangedCallback("cinematic",
1019 &settingChangedCallback, this);
1020 g_settings->registerChangedCallback("cinematic_camera_smoothing",
1021 &settingChangedCallback, this);
1022 g_settings->registerChangedCallback("camera_smoothing",
1023 &settingChangedCallback, this);
1027 #ifdef HAVE_TOUCHSCREENGUI
1028 m_cache_hold_aux1 = false; // This is initialised properly later
1035 /****************************************************************************
1037 ****************************************************************************/
1043 if (!sound_is_dummy)
1046 delete server; // deleted first to stop all server threads
1054 delete nodedef_manager;
1055 delete itemdef_manager;
1056 delete draw_control;
1058 clearTextureNameCache();
1060 g_settings->deregisterChangedCallback("doubletap_jump",
1061 &settingChangedCallback, this);
1062 g_settings->deregisterChangedCallback("enable_clouds",
1063 &settingChangedCallback, this);
1064 g_settings->deregisterChangedCallback("enable_particles",
1065 &settingChangedCallback, this);
1066 g_settings->deregisterChangedCallback("enable_fog",
1067 &settingChangedCallback, this);
1068 g_settings->deregisterChangedCallback("mouse_sensitivity",
1069 &settingChangedCallback, this);
1070 g_settings->deregisterChangedCallback("repeat_place_time",
1071 &settingChangedCallback, this);
1072 g_settings->deregisterChangedCallback("noclip",
1073 &settingChangedCallback, this);
1074 g_settings->deregisterChangedCallback("free_move",
1075 &settingChangedCallback, this);
1076 g_settings->deregisterChangedCallback("cinematic",
1077 &settingChangedCallback, this);
1078 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
1079 &settingChangedCallback, this);
1080 g_settings->deregisterChangedCallback("camera_smoothing",
1081 &settingChangedCallback, this);
1084 bool Game::startup(bool *kill,
1085 InputHandler *input,
1086 RenderingEngine *rendering_engine,
1087 const GameStartData &start_data,
1088 std::string &error_message,
1090 ChatBackend *chat_backend)
1094 m_rendering_engine = rendering_engine;
1095 device = m_rendering_engine->get_raw_device();
1097 this->error_message = &error_message;
1098 reconnect_requested = reconnect;
1099 this->input = input;
1100 this->chat_backend = chat_backend;
1101 simple_singleplayer_mode = start_data.isSinglePlayer();
1103 input->keycache.populate();
1105 driver = device->getVideoDriver();
1106 smgr = m_rendering_engine->get_scene_manager();
1108 smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1111 runData = GameRunData();
1112 runData.time_from_last_punch = 10.0;
1114 m_game_ui->initFlags();
1116 m_invert_mouse = g_settings->getBool("invert_mouse");
1117 m_first_loop_after_window_activation = true;
1119 #ifdef HAVE_TOUCHSCREENGUI
1120 m_touch_use_crosshair = g_settings->getBool("touch_use_crosshair");
1123 g_client_translations->clear();
1125 // address can change if simple_singleplayer_mode
1126 if (!init(start_data.world_spec.path, start_data.address,
1127 start_data.socket_port, start_data.game_spec))
1130 if (!createClient(start_data))
1133 m_rendering_engine->initialize(client, hud);
1141 ProfilerGraph graph;
1142 RunStats stats = {};
1143 CameraOrientation cam_view_target = {};
1144 CameraOrientation cam_view = {};
1145 FpsControl draw_times;
1146 f32 dtime; // in seconds
1148 /* Clear the profiler */
1149 Profiler::GraphValues dummyvalues;
1150 g_profiler->graphGet(dummyvalues);
1154 set_light_table(g_settings->getFloat("display_gamma"));
1156 #ifdef HAVE_TOUCHSCREENGUI
1157 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1158 && client->checkPrivilege("fast");
1161 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1162 g_settings->getU16("screen_h"));
1164 while (m_rendering_engine->run()
1165 && !(*kill || g_gamecallback->shutdown_requested
1166 || (server && server->isShutdownRequested()))) {
1168 const irr::core::dimension2d<u32> ¤t_screen_size =
1169 m_rendering_engine->get_video_driver()->getScreenSize();
1170 // Verify if window size has changed and save it if it's the case
1171 // Ensure evaluating settings->getBool after verifying screensize
1172 // First condition is cheaper
1173 if (previous_screen_size != current_screen_size &&
1174 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1175 g_settings->getBool("autosave_screensize")) {
1176 g_settings->setU16("screen_w", current_screen_size.Width);
1177 g_settings->setU16("screen_h", current_screen_size.Height);
1178 previous_screen_size = current_screen_size;
1181 // Calculate dtime =
1182 // m_rendering_engine->run() from this iteration
1183 // + Sleep time until the wanted FPS are reached
1184 draw_times.limit(device, &dtime);
1186 // Prepare render data for next iteration
1188 updateStats(&stats, draw_times, dtime);
1189 updateInteractTimers(dtime);
1191 if (!checkConnection())
1193 if (!handleCallbacks())
1198 m_game_ui->clearInfoText();
1199 hud->resizeHotbar();
1202 updateProfilers(stats, draw_times, dtime);
1203 processUserInput(dtime);
1204 // Update camera before player movement to avoid camera lag of one frame
1205 updateCameraDirection(&cam_view_target, dtime);
1206 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1207 cam_view.camera_yaw) * m_cache_cam_smoothing;
1208 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1209 cam_view.camera_pitch) * m_cache_cam_smoothing;
1210 updatePlayerControl(cam_view);
1213 bool was_paused = m_is_paused;
1214 m_is_paused = simple_singleplayer_mode && g_menumgr.pausesGame();
1218 if (!was_paused && m_is_paused)
1220 else if (was_paused && !m_is_paused)
1226 processClientEvents(&cam_view_target);
1228 updateCamera(dtime);
1230 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
1231 updateFrame(&graph, &stats, dtime, cam_view);
1232 updateProfilerGraphs(&graph);
1234 // Update if minimap has been disabled by the server
1235 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1237 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1244 void Game::shutdown()
1246 m_rendering_engine->finalize();
1247 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1248 if (g_settings->get("3d_mode") == "pageflip") {
1249 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1252 auto formspec = m_game_ui->getFormspecGUI();
1254 formspec->quitMenu();
1256 #ifdef HAVE_TOUCHSCREENGUI
1257 g_touchscreengui->hide();
1260 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1265 if (gui_chat_console)
1266 gui_chat_console->drop();
1272 while (g_menumgr.menuCount() > 0) {
1273 g_menumgr.m_stack.front()->setVisible(false);
1274 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1277 m_game_ui->deleteFormspec();
1279 chat_backend->addMessage(L"", L"# Disconnected.");
1280 chat_backend->addMessage(L"", L"");
1281 m_chat_log_buf.clear();
1285 while (!client->isShutdown()) {
1286 assert(texture_src != NULL);
1287 assert(shader_src != NULL);
1288 texture_src->processQueue();
1289 shader_src->processQueue();
1296 /****************************************************************************/
1297 /****************************************************************************
1299 ****************************************************************************/
1300 /****************************************************************************/
1303 const std::string &map_dir,
1304 const std::string &address,
1306 const SubgameSpec &gamespec)
1308 texture_src = createTextureSource();
1310 showOverlayMessage(N_("Loading..."), 0, 0);
1312 shader_src = createShaderSource();
1314 itemdef_manager = createItemDefManager();
1315 nodedef_manager = createNodeDefManager();
1317 eventmgr = new EventManager();
1318 quicktune = new QuicktuneShortcutter();
1320 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1321 && eventmgr && quicktune))
1327 // Create a server if not connecting to an existing one
1328 if (address.empty()) {
1329 if (!createSingleplayerServer(map_dir, gamespec, port))
1336 bool Game::initSound()
1339 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1340 infostream << "Attempting to use OpenAL audio" << std::endl;
1341 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1343 infostream << "Failed to initialize OpenAL audio" << std::endl;
1345 infostream << "Sound disabled." << std::endl;
1349 infostream << "Using dummy audio." << std::endl;
1350 sound = &dummySoundManager;
1351 sound_is_dummy = true;
1354 soundmaker = new SoundMaker(sound, nodedef_manager);
1358 soundmaker->registerReceiver(eventmgr);
1363 bool Game::createSingleplayerServer(const std::string &map_dir,
1364 const SubgameSpec &gamespec, u16 port)
1366 showOverlayMessage(N_("Creating server..."), 0, 5);
1368 std::string bind_str = g_settings->get("bind_address");
1369 Address bind_addr(0, 0, 0, 0, port);
1371 if (g_settings->getBool("ipv6_server")) {
1372 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1376 bind_addr.Resolve(bind_str.c_str());
1377 } catch (ResolveError &e) {
1378 infostream << "Resolving bind address \"" << bind_str
1379 << "\" failed: " << e.what()
1380 << " -- Listening on all addresses." << std::endl;
1383 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1384 *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
1385 bind_addr.serializeString().c_str());
1386 errorstream << *error_message << std::endl;
1390 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1391 false, nullptr, error_message);
1397 bool Game::createClient(const GameStartData &start_data)
1399 showOverlayMessage(N_("Creating client..."), 0, 10);
1401 draw_control = new MapDrawControl();
1405 bool could_connect, connect_aborted;
1406 #ifdef HAVE_TOUCHSCREENGUI
1407 if (g_touchscreengui) {
1408 g_touchscreengui->init(texture_src);
1409 g_touchscreengui->hide();
1412 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1415 if (!could_connect) {
1416 if (error_message->empty() && !connect_aborted) {
1417 // Should not happen if error messages are set properly
1418 *error_message = gettext("Connection failed for unknown reason");
1419 errorstream << *error_message << std::endl;
1424 if (!getServerContent(&connect_aborted)) {
1425 if (error_message->empty() && !connect_aborted) {
1426 // Should not happen if error messages are set properly
1427 *error_message = gettext("Connection failed for unknown reason");
1428 errorstream << *error_message << std::endl;
1433 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1434 &m_flags.force_fog_off, &runData.fog_range, client);
1435 shader_src->addShaderConstantSetterFactory(scsf);
1437 // Update cached textures, meshes and materials
1438 client->afterContentReceived();
1442 camera = new Camera(*draw_control, client, m_rendering_engine);
1443 if (client->modsLoaded())
1444 client->getScript()->on_camera_ready(camera);
1445 client->setCamera(camera);
1449 if (m_cache_enable_clouds)
1450 clouds = new Clouds(smgr, -1, time(0));
1454 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
1456 skybox = NULL; // This is used/set later on in the main run loop
1458 /* Pre-calculated values
1460 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1462 v2u32 size = t->getOriginalSize();
1463 crack_animation_length = size.Y / size.X;
1465 crack_animation_length = 5;
1471 /* Set window caption
1473 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1475 str += utf8_to_wide(g_version_hash);
1477 const wchar_t *text = nullptr;
1478 if (simple_singleplayer_mode)
1479 text = wgettext("Singleplayer");
1481 text = wgettext("Multiplayer");
1488 str += driver->getName();
1491 device->setWindowCaption(str.c_str());
1493 LocalPlayer *player = client->getEnv().getLocalPlayer();
1494 player->hurt_tilt_timer = 0;
1495 player->hurt_tilt_strength = 0;
1497 hud = new Hud(client, player, &player->inventory);
1499 mapper = client->getMinimap();
1501 if (mapper && client->modsLoaded())
1502 client->getScript()->on_minimap_ready(mapper);
1507 bool Game::initGui()
1511 // Remove stale "recent" chat messages from previous connections
1512 chat_backend->clearRecentChat();
1514 // Make sure the size of the recent messages buffer is right
1515 chat_backend->applySettings();
1517 // Chat backend and console
1518 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1519 -1, chat_backend, client, &g_menumgr);
1521 #ifdef HAVE_TOUCHSCREENGUI
1523 if (g_touchscreengui)
1524 g_touchscreengui->show();
1531 bool Game::connectToServer(const GameStartData &start_data,
1532 bool *connect_ok, bool *connection_aborted)
1534 *connect_ok = false; // Let's not be overly optimistic
1535 *connection_aborted = false;
1536 bool local_server_mode = false;
1538 showOverlayMessage(N_("Resolving address..."), 0, 15);
1540 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1543 connect_address.Resolve(start_data.address.c_str());
1545 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1546 if (connect_address.isIPv6()) {
1547 IPv6AddressBytes addr_bytes;
1548 addr_bytes.bytes[15] = 1;
1549 connect_address.setAddress(&addr_bytes);
1551 connect_address.setAddress(127, 0, 0, 1);
1553 local_server_mode = true;
1555 } catch (ResolveError &e) {
1556 *error_message = fmtgettext("Couldn't resolve address: %s", e.what());
1558 errorstream << *error_message << std::endl;
1562 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1563 *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
1564 errorstream << *error_message << std::endl;
1569 client = new Client(start_data.name.c_str(),
1570 start_data.password, start_data.address,
1571 *draw_control, texture_src, shader_src,
1572 itemdef_manager, nodedef_manager, sound, eventmgr,
1573 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get(),
1574 start_data.allow_login_or_register);
1575 client->migrateModStorage();
1576 } catch (const BaseException &e) {
1577 *error_message = fmtgettext("Error creating client: %s", e.what());
1578 errorstream << *error_message << std::endl;
1582 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1584 infostream << "Connecting to server at ";
1585 connect_address.print(infostream);
1586 infostream << std::endl;
1588 client->connect(connect_address,
1589 simple_singleplayer_mode || local_server_mode);
1592 Wait for server to accept connection
1598 FpsControl fps_control;
1600 f32 wait_time = 0; // in seconds
1602 fps_control.reset();
1604 while (m_rendering_engine->run()) {
1606 fps_control.limit(device, &dtime);
1608 // Update client and server
1609 client->step(dtime);
1612 server->step(dtime);
1615 if (client->getState() == LC_Init) {
1621 if (*connection_aborted)
1624 if (client->accessDenied()) {
1625 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1626 *reconnect_requested = client->reconnectRequested();
1627 errorstream << *error_message << std::endl;
1631 if (input->cancelPressed()) {
1632 *connection_aborted = true;
1633 infostream << "Connect aborted [Escape]" << std::endl;
1638 // Only time out if we aren't waiting for the server we started
1639 if (!start_data.address.empty() && wait_time > 10) {
1640 *error_message = gettext("Connection timed out.");
1641 errorstream << *error_message << std::endl;
1646 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1648 } catch (con::PeerNotFoundException &e) {
1649 // TODO: Should something be done here? At least an info/error
1657 bool Game::getServerContent(bool *aborted)
1661 FpsControl fps_control;
1662 f32 dtime; // in seconds
1664 fps_control.reset();
1666 while (m_rendering_engine->run()) {
1668 fps_control.limit(device, &dtime);
1670 // Update client and server
1671 client->step(dtime);
1674 server->step(dtime);
1677 if (client->mediaReceived() && client->itemdefReceived() &&
1678 client->nodedefReceived()) {
1683 if (!checkConnection())
1686 if (client->getState() < LC_Init) {
1687 *error_message = gettext("Client disconnected");
1688 errorstream << *error_message << std::endl;
1692 if (input->cancelPressed()) {
1694 infostream << "Connect aborted [Escape]" << std::endl;
1701 if (!client->itemdefReceived()) {
1702 const wchar_t *text = wgettext("Item definitions...");
1704 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1707 } else if (!client->nodedefReceived()) {
1708 const wchar_t *text = wgettext("Node definitions...");
1710 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1714 std::ostringstream message;
1715 std::fixed(message);
1716 message.precision(0);
1717 float receive = client->mediaReceiveProgress() * 100;
1718 message << gettext("Media...");
1720 message << " " << receive << "%";
1721 message.precision(2);
1723 if ((USE_CURL == 0) ||
1724 (!g_settings->getBool("enable_remote_media_server"))) {
1725 float cur = client->getCurRate();
1726 std::string cur_unit = gettext("KiB/s");
1730 cur_unit = gettext("MiB/s");
1733 message << " (" << cur << ' ' << cur_unit << ")";
1736 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1737 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
1738 texture_src, dtime, progress);
1746 /****************************************************************************/
1747 /****************************************************************************
1749 ****************************************************************************/
1750 /****************************************************************************/
1752 inline void Game::updateInteractTimers(f32 dtime)
1754 if (runData.nodig_delay_timer >= 0)
1755 runData.nodig_delay_timer -= dtime;
1757 if (runData.object_hit_delay_timer >= 0)
1758 runData.object_hit_delay_timer -= dtime;
1760 runData.time_from_last_punch += dtime;
1764 /* returns false if game should exit, otherwise true
1766 inline bool Game::checkConnection()
1768 if (client->accessDenied()) {
1769 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1770 *reconnect_requested = client->reconnectRequested();
1771 errorstream << *error_message << std::endl;
1779 /* returns false if game should exit, otherwise true
1781 inline bool Game::handleCallbacks()
1783 if (g_gamecallback->disconnect_requested) {
1784 g_gamecallback->disconnect_requested = false;
1788 if (g_gamecallback->changepassword_requested) {
1789 (new GUIPasswordChange(guienv, guiroot, -1,
1790 &g_menumgr, client, texture_src))->drop();
1791 g_gamecallback->changepassword_requested = false;
1794 if (g_gamecallback->changevolume_requested) {
1795 (new GUIVolumeChange(guienv, guiroot, -1,
1796 &g_menumgr, texture_src))->drop();
1797 g_gamecallback->changevolume_requested = false;
1800 if (g_gamecallback->keyconfig_requested) {
1801 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1802 &g_menumgr, texture_src))->drop();
1803 g_gamecallback->keyconfig_requested = false;
1806 if (g_gamecallback->keyconfig_changed) {
1807 input->keycache.populate(); // update the cache with new settings
1808 g_gamecallback->keyconfig_changed = false;
1815 void Game::processQueues()
1817 texture_src->processQueue();
1818 itemdef_manager->processQueue(client);
1819 shader_src->processQueue();
1822 void Game::updateDebugState()
1824 LocalPlayer *player = client->getEnv().getLocalPlayer();
1826 // debug UI and wireframe
1827 bool has_debug = client->checkPrivilege("debug");
1828 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
1830 if (m_game_ui->m_flags.show_basic_debug) {
1831 if (!has_basic_debug)
1832 m_game_ui->m_flags.show_basic_debug = false;
1833 } else if (m_game_ui->m_flags.show_minimal_debug) {
1834 if (has_basic_debug)
1835 m_game_ui->m_flags.show_basic_debug = true;
1837 if (!has_basic_debug)
1838 hud->disableBlockBounds();
1840 draw_control->show_wireframe = false;
1843 draw_control->allow_noclip = m_cache_enable_noclip && client->checkPrivilege("noclip");
1846 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1849 float profiler_print_interval =
1850 g_settings->getFloat("profiler_print_interval");
1851 bool print_to_log = true;
1853 if (profiler_print_interval == 0) {
1854 print_to_log = false;
1855 profiler_print_interval = 3;
1858 if (profiler_interval.step(dtime, profiler_print_interval)) {
1860 infostream << "Profiler:" << std::endl;
1861 g_profiler->print(infostream);
1864 m_game_ui->updateProfiler();
1865 g_profiler->clear();
1868 // Update update graphs
1869 g_profiler->graphAdd("Time non-rendering [us]",
1870 draw_times.busy_time - stats.drawtime);
1872 g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
1873 g_profiler->graphAdd("FPS", 1.0f / dtime);
1876 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1883 /* Time average and jitter calculation
1885 jp = &stats->dtime_jitter;
1886 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1888 jitter = dtime - jp->avg;
1890 if (jitter > jp->max)
1893 jp->counter += dtime;
1895 if (jp->counter > 0.0) {
1897 jp->max_sample = jp->max;
1898 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1902 /* Busytime average and jitter calculation
1904 jp = &stats->busy_time_jitter;
1905 jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
1907 jitter = draw_times.getBusyMs() - jp->avg;
1909 if (jitter > jp->max)
1911 if (jitter < jp->min)
1914 jp->counter += dtime;
1916 if (jp->counter > 0.0) {
1918 jp->max_sample = jp->max;
1919 jp->min_sample = jp->min;
1927 /****************************************************************************
1929 ****************************************************************************/
1931 void Game::processUserInput(f32 dtime)
1933 // Reset input if window not active or some menu is active
1934 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1936 #ifdef HAVE_TOUCHSCREENGUI
1937 g_touchscreengui->hide();
1940 #ifdef HAVE_TOUCHSCREENGUI
1941 else if (g_touchscreengui) {
1942 /* on touchscreengui step may generate own input events which ain't
1943 * what we want in case we just did clear them */
1944 g_touchscreengui->show();
1945 g_touchscreengui->step(dtime);
1949 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1950 gui_chat_console->closeConsoleAtOnce();
1953 // Input handler step() (used by the random input generator)
1957 auto formspec = m_game_ui->getFormspecGUI();
1959 formspec->getAndroidUIInput();
1961 handleAndroidChatInput();
1964 // Increase timer for double tap of "keymap_jump"
1965 if (m_cache_doubletap_jump && runData.jump_timer_up <= 0.2f)
1966 runData.jump_timer_up += dtime;
1967 if (m_cache_doubletap_jump && runData.jump_timer_down <= 0.4f)
1968 runData.jump_timer_down += dtime;
1971 processItemSelection(&runData.new_playeritem);
1975 void Game::processKeyInput()
1977 if (wasKeyDown(KeyType::DROP)) {
1978 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1979 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1980 toggleAutoforward();
1981 } else if (wasKeyDown(KeyType::BACKWARD)) {
1982 if (g_settings->getBool("continuous_forward"))
1983 toggleAutoforward();
1984 } else if (wasKeyDown(KeyType::INVENTORY)) {
1986 } else if (input->cancelPressed()) {
1988 m_android_chat_open = false;
1990 if (!gui_chat_console->isOpenInhibited()) {
1993 } else if (wasKeyDown(KeyType::CHAT)) {
1994 openConsole(0.2, L"");
1995 } else if (wasKeyDown(KeyType::CMD)) {
1996 openConsole(0.2, L"/");
1997 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1998 if (client->modsLoaded())
1999 openConsole(0.2, L".");
2001 m_game_ui->showTranslatedStatusText("Client side scripting is disabled");
2002 } else if (wasKeyDown(KeyType::CONSOLE)) {
2003 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
2004 } else if (wasKeyDown(KeyType::FREEMOVE)) {
2006 } else if (wasKeyDown(KeyType::JUMP)) {
2007 toggleFreeMoveAlt();
2008 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
2010 } else if (wasKeyDown(KeyType::FASTMOVE)) {
2012 } else if (wasKeyDown(KeyType::NOCLIP)) {
2015 } else if (wasKeyDown(KeyType::MUTE)) {
2016 if (g_settings->getBool("enable_sound")) {
2017 bool new_mute_sound = !g_settings->getBool("mute_sound");
2018 g_settings->setBool("mute_sound", new_mute_sound);
2020 m_game_ui->showTranslatedStatusText("Sound muted");
2022 m_game_ui->showTranslatedStatusText("Sound unmuted");
2024 m_game_ui->showTranslatedStatusText("Sound system is disabled");
2026 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
2027 if (g_settings->getBool("enable_sound")) {
2028 float new_volume = g_settings->getFloat("sound_volume", 0.0f, 0.9f) + 0.1f;
2029 g_settings->setFloat("sound_volume", new_volume);
2030 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
2031 m_game_ui->showStatusText(msg);
2033 m_game_ui->showTranslatedStatusText("Sound system is disabled");
2035 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
2036 if (g_settings->getBool("enable_sound")) {
2037 float new_volume = g_settings->getFloat("sound_volume", 0.1f, 1.0f) - 0.1f;
2038 g_settings->setFloat("sound_volume", new_volume);
2039 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
2040 m_game_ui->showStatusText(msg);
2042 m_game_ui->showTranslatedStatusText("Sound system is disabled");
2045 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
2046 || wasKeyDown(KeyType::DEC_VOLUME)) {
2047 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
2049 } else if (wasKeyDown(KeyType::CINEMATIC)) {
2051 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
2052 client->makeScreenshot();
2053 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
2054 toggleBlockBounds();
2055 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
2056 m_game_ui->toggleHud();
2057 } else if (wasKeyDown(KeyType::MINIMAP)) {
2058 toggleMinimap(isKeyDown(KeyType::SNEAK));
2059 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
2060 m_game_ui->toggleChat();
2061 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
2063 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
2064 toggleUpdateCamera();
2065 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
2067 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
2068 m_game_ui->toggleProfiler();
2069 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
2070 increaseViewRange();
2071 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
2072 decreaseViewRange();
2073 } else if (wasKeyDown(KeyType::RANGESELECT)) {
2074 toggleFullViewRange();
2075 } else if (wasKeyDown(KeyType::ZOOM)) {
2077 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
2079 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
2081 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
2083 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
2087 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
2088 runData.reset_jump_timer = false;
2089 runData.jump_timer_up = 0.0f;
2092 if (quicktune->hasMessage()) {
2093 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2097 void Game::processItemSelection(u16 *new_playeritem)
2099 LocalPlayer *player = client->getEnv().getLocalPlayer();
2101 /* Item selection using mouse wheel
2103 *new_playeritem = player->getWieldIndex();
2105 s32 wheel = input->getMouseWheel();
2106 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2107 player->hud_hotbar_itemcount - 1);
2111 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2114 if (wasKeyDown(KeyType::HOTBAR_PREV))
2118 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2120 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2123 /* Item selection using hotbar slot keys
2125 for (u16 i = 0; i <= max_item; i++) {
2126 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2127 *new_playeritem = i;
2134 void Game::dropSelectedItem(bool single_item)
2136 IDropAction *a = new IDropAction();
2137 a->count = single_item ? 1 : 0;
2138 a->from_inv.setCurrentPlayer();
2139 a->from_list = "main";
2140 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2141 client->inventoryAction(a);
2145 void Game::openInventory()
2148 * Don't permit to open inventory is CAO or player doesn't exists.
2149 * This prevent showing an empty inventory at player load
2152 LocalPlayer *player = client->getEnv().getLocalPlayer();
2153 if (!player || !player->getCAO())
2156 infostream << "Game: Launching inventory" << std::endl;
2158 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2160 InventoryLocation inventoryloc;
2161 inventoryloc.setCurrentPlayer();
2163 if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2168 if (fs_src->getForm().empty()) {
2173 TextDest *txt_dst = new TextDestPlayerInventory(client);
2174 auto *&formspec = m_game_ui->updateFormspec("");
2175 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2176 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2178 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2182 void Game::openConsole(float scale, const wchar_t *line)
2184 assert(scale > 0.0f && scale <= 1.0f);
2187 porting::showInputDialog(gettext("ok"), "", "", 2);
2188 m_android_chat_open = true;
2190 if (gui_chat_console->isOpenInhibited())
2192 gui_chat_console->openConsole(scale);
2194 gui_chat_console->setCloseOnEnter(true);
2195 gui_chat_console->replaceAndAddToHistory(line);
2201 void Game::handleAndroidChatInput()
2203 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2204 std::string text = porting::getInputDialogValue();
2205 client->typeChatMessage(utf8_to_wide(text));
2206 m_android_chat_open = false;
2212 void Game::toggleFreeMove()
2214 bool free_move = !g_settings->getBool("free_move");
2215 g_settings->set("free_move", bool_to_cstr(free_move));
2218 if (client->checkPrivilege("fly")) {
2219 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2221 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2224 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2228 void Game::toggleFreeMoveAlt()
2230 if (!runData.reset_jump_timer) {
2231 runData.jump_timer_down_before = runData.jump_timer_down;
2232 runData.jump_timer_down = 0.0f;
2235 // key down (0.2 s max.), then key up (0.2 s max.), then key down
2236 if (m_cache_doubletap_jump && runData.jump_timer_up < 0.2f &&
2237 runData.jump_timer_down_before < 0.4f) // 0.2 + 0.2
2240 runData.reset_jump_timer = true;
2244 void Game::togglePitchMove()
2246 bool pitch_move = !g_settings->getBool("pitch_move");
2247 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2250 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2252 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2257 void Game::toggleFast()
2259 bool fast_move = !g_settings->getBool("fast_move");
2260 bool has_fast_privs = client->checkPrivilege("fast");
2261 g_settings->set("fast_move", bool_to_cstr(fast_move));
2264 if (has_fast_privs) {
2265 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2267 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2270 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2273 #ifdef HAVE_TOUCHSCREENGUI
2274 m_cache_hold_aux1 = fast_move && has_fast_privs;
2279 void Game::toggleNoClip()
2281 bool noclip = !g_settings->getBool("noclip");
2282 g_settings->set("noclip", bool_to_cstr(noclip));
2285 if (client->checkPrivilege("noclip")) {
2286 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2288 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2291 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2295 void Game::toggleCinematic()
2297 bool cinematic = !g_settings->getBool("cinematic");
2298 g_settings->set("cinematic", bool_to_cstr(cinematic));
2301 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2303 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2306 void Game::toggleBlockBounds()
2308 LocalPlayer *player = client->getEnv().getLocalPlayer();
2309 if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
2310 m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
2313 enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
2315 case Hud::BLOCK_BOUNDS_OFF:
2316 m_game_ui->showTranslatedStatusText("Block bounds hidden");
2318 case Hud::BLOCK_BOUNDS_CURRENT:
2319 m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
2321 case Hud::BLOCK_BOUNDS_NEAR:
2322 m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
2324 case Hud::BLOCK_BOUNDS_MAX:
2325 m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
2332 // Autoforward by toggling continuous forward.
2333 void Game::toggleAutoforward()
2335 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2336 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2338 if (autorun_enabled)
2339 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2341 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2344 void Game::toggleMinimap(bool shift_pressed)
2346 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2350 mapper->toggleMinimapShape();
2354 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2356 // Not so satisying code to keep compatibility with old fixed mode system
2358 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2360 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2361 m_game_ui->m_flags.show_minimap = false;
2364 // If radar is disabled, try to find a non radar mode or fall back to 0
2365 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2366 while (mapper->getModeIndex() &&
2367 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2370 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2374 // End of 'not so satifying code'
2375 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2376 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2377 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2379 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2382 void Game::toggleFog()
2384 bool fog_enabled = g_settings->getBool("enable_fog");
2385 g_settings->setBool("enable_fog", !fog_enabled);
2387 m_game_ui->showTranslatedStatusText("Fog disabled");
2389 m_game_ui->showTranslatedStatusText("Fog enabled");
2393 void Game::toggleDebug()
2395 LocalPlayer *player = client->getEnv().getLocalPlayer();
2396 bool has_debug = client->checkPrivilege("debug");
2397 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
2398 // Initial: No debug info
2399 // 1x toggle: Debug text
2400 // 2x toggle: Debug text with profiler graph
2401 // 3x toggle: Debug text and wireframe (needs "debug" priv)
2402 // Next toggle: Back to initial
2404 // The debug text can be in 2 modes: minimal and basic.
2405 // * Minimal: Only technical client info that not gameplay-relevant
2406 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
2407 // Basic mode is used when player has the debug HUD flag set,
2408 // otherwise the Minimal mode is used.
2409 if (!m_game_ui->m_flags.show_minimal_debug) {
2410 m_game_ui->m_flags.show_minimal_debug = true;
2411 if (has_basic_debug)
2412 m_game_ui->m_flags.show_basic_debug = true;
2413 m_game_ui->m_flags.show_profiler_graph = false;
2414 draw_control->show_wireframe = false;
2415 m_game_ui->showTranslatedStatusText("Debug info shown");
2416 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2417 if (has_basic_debug)
2418 m_game_ui->m_flags.show_basic_debug = true;
2419 m_game_ui->m_flags.show_profiler_graph = true;
2420 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2421 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2422 if (has_basic_debug)
2423 m_game_ui->m_flags.show_basic_debug = true;
2424 m_game_ui->m_flags.show_profiler_graph = false;
2425 draw_control->show_wireframe = true;
2426 m_game_ui->showTranslatedStatusText("Wireframe shown");
2428 m_game_ui->m_flags.show_minimal_debug = false;
2429 m_game_ui->m_flags.show_basic_debug = false;
2430 m_game_ui->m_flags.show_profiler_graph = false;
2431 draw_control->show_wireframe = false;
2433 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2435 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2441 void Game::toggleUpdateCamera()
2443 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2444 if (m_flags.disable_camera_update)
2445 m_game_ui->showTranslatedStatusText("Camera update disabled");
2447 m_game_ui->showTranslatedStatusText("Camera update enabled");
2451 void Game::increaseViewRange()
2453 s16 range = g_settings->getS16("viewing_range");
2454 s16 range_new = range + 10;
2456 if (range_new > 4000) {
2458 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
2459 m_game_ui->showStatusText(msg);
2461 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2462 m_game_ui->showStatusText(msg);
2464 g_settings->set("viewing_range", itos(range_new));
2468 void Game::decreaseViewRange()
2470 s16 range = g_settings->getS16("viewing_range");
2471 s16 range_new = range - 10;
2473 if (range_new < 20) {
2475 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
2476 m_game_ui->showStatusText(msg);
2478 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2479 m_game_ui->showStatusText(msg);
2481 g_settings->set("viewing_range", itos(range_new));
2485 void Game::toggleFullViewRange()
2487 draw_control->range_all = !draw_control->range_all;
2488 if (draw_control->range_all)
2489 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2491 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2495 void Game::checkZoomEnabled()
2497 LocalPlayer *player = client->getEnv().getLocalPlayer();
2498 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2499 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2502 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2504 if ((device->isWindowActive() && device->isWindowFocused()
2505 && !isMenuActive()) || input->isRandom()) {
2508 if (!input->isRandom()) {
2509 // Mac OSX gets upset if this is set every frame
2510 if (device->getCursorControl()->isVisible())
2511 device->getCursorControl()->setVisible(false);
2515 if (m_first_loop_after_window_activation) {
2516 m_first_loop_after_window_activation = false;
2518 input->setMousePos(driver->getScreenSize().Width / 2,
2519 driver->getScreenSize().Height / 2);
2521 updateCameraOrientation(cam, dtime);
2527 // Mac OSX gets upset if this is set every frame
2528 if (!device->getCursorControl()->isVisible())
2529 device->getCursorControl()->setVisible(true);
2532 m_first_loop_after_window_activation = true;
2537 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2538 // responsiveness independently of FOV.
2539 f32 Game::getSensitivityScaleFactor() const
2541 f32 fov_y = client->getCamera()->getFovY();
2543 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2544 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2546 return tan(fov_y / 2.0f) * 1.3763818698f;
2549 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2551 #ifdef HAVE_TOUCHSCREENGUI
2552 if (g_touchscreengui) {
2553 cam->camera_yaw += g_touchscreengui->getYawChange();
2554 cam->camera_pitch = g_touchscreengui->getPitch();
2557 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2558 v2s32 dist = input->getMousePos() - center;
2560 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2564 f32 sens_scale = getSensitivityScaleFactor();
2565 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2566 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2568 if (dist.X != 0 || dist.Y != 0)
2569 input->setMousePos(center.X, center.Y);
2570 #ifdef HAVE_TOUCHSCREENGUI
2574 if (m_cache_enable_joysticks) {
2575 f32 sens_scale = getSensitivityScaleFactor();
2576 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
2577 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2578 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2581 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2585 void Game::updatePlayerControl(const CameraOrientation &cam)
2587 LocalPlayer *player = client->getEnv().getLocalPlayer();
2589 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2591 PlayerControl control(
2592 isKeyDown(KeyType::FORWARD),
2593 isKeyDown(KeyType::BACKWARD),
2594 isKeyDown(KeyType::LEFT),
2595 isKeyDown(KeyType::RIGHT),
2596 isKeyDown(KeyType::JUMP) || player->getAutojump(),
2597 isKeyDown(KeyType::AUX1),
2598 isKeyDown(KeyType::SNEAK),
2599 isKeyDown(KeyType::ZOOM),
2600 isKeyDown(KeyType::DIG),
2601 isKeyDown(KeyType::PLACE),
2604 input->getMovementSpeed(),
2605 input->getMovementDirection()
2608 // autoforward if set: move at maximum speed
2609 if (player->getPlayerSettings().continuous_forward &&
2610 client->activeObjectsReceived() && !player->isDead()) {
2611 control.movement_speed = 1.0f;
2612 // sideways movement only
2613 float dx = sin(control.movement_direction);
2614 control.movement_direction = atan2(dx, 1.0f);
2617 #ifdef HAVE_TOUCHSCREENGUI
2618 /* For touch, simulate holding down AUX1 (fast move) if the user has
2619 * the fast_move setting toggled on. If there is an aux1 key defined for
2620 * touch then its meaning is inverted (i.e. holding aux1 means walk and
2623 if (m_cache_hold_aux1) {
2624 control.aux1 = control.aux1 ^ true;
2628 client->setPlayerControl(control);
2634 inline void Game::step(f32 dtime)
2637 server->step(dtime);
2639 client->step(dtime);
2642 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2645 for (auto &&child: node->getChildren())
2646 pauseNodeAnimation(paused, child);
2647 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2649 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2650 float speed = animated_node->getAnimationSpeed();
2653 paused.push_back({grab(animated_node), speed});
2654 animated_node->setAnimationSpeed(0.0f);
2657 void Game::pauseAnimation()
2659 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2662 void Game::resumeAnimation()
2664 for (auto &&pair: paused_animated_nodes)
2665 pair.first->setAnimationSpeed(pair.second);
2666 paused_animated_nodes.clear();
2669 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2670 {&Game::handleClientEvent_None},
2671 {&Game::handleClientEvent_PlayerDamage},
2672 {&Game::handleClientEvent_PlayerForceMove},
2673 {&Game::handleClientEvent_Deathscreen},
2674 {&Game::handleClientEvent_ShowFormSpec},
2675 {&Game::handleClientEvent_ShowLocalFormSpec},
2676 {&Game::handleClientEvent_HandleParticleEvent},
2677 {&Game::handleClientEvent_HandleParticleEvent},
2678 {&Game::handleClientEvent_HandleParticleEvent},
2679 {&Game::handleClientEvent_HudAdd},
2680 {&Game::handleClientEvent_HudRemove},
2681 {&Game::handleClientEvent_HudChange},
2682 {&Game::handleClientEvent_SetSky},
2683 {&Game::handleClientEvent_SetSun},
2684 {&Game::handleClientEvent_SetMoon},
2685 {&Game::handleClientEvent_SetStars},
2686 {&Game::handleClientEvent_OverrideDayNigthRatio},
2687 {&Game::handleClientEvent_CloudParams},
2690 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2692 FATAL_ERROR("ClientEvent type None received");
2695 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2697 if (client->modsLoaded())
2698 client->getScript()->on_damage_taken(event->player_damage.amount);
2700 if (!event->player_damage.effect)
2703 // Damage flash and hurt tilt are not used at death
2704 if (client->getHP() > 0) {
2705 LocalPlayer *player = client->getEnv().getLocalPlayer();
2707 f32 hp_max = player->getCAO() ?
2708 player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2709 f32 damage_ratio = event->player_damage.amount / hp_max;
2711 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2712 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2714 player->hurt_tilt_timer = 1.5f;
2715 player->hurt_tilt_strength =
2716 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2719 // Play damage sound
2720 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2723 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2725 cam->camera_yaw = event->player_force_move.yaw;
2726 cam->camera_pitch = event->player_force_move.pitch;
2729 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2731 // If client scripting is enabled, deathscreen is handled by CSM code in
2732 // builtin/client/init.lua
2733 if (client->modsLoaded())
2734 client->getScript()->on_death();
2736 showDeathFormspec();
2738 /* Handle visualization */
2739 LocalPlayer *player = client->getEnv().getLocalPlayer();
2740 runData.damage_flash = 0;
2741 player->hurt_tilt_timer = 0;
2742 player->hurt_tilt_strength = 0;
2745 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2747 if (event->show_formspec.formspec->empty()) {
2748 auto formspec = m_game_ui->getFormspecGUI();
2749 if (formspec && (event->show_formspec.formname->empty()
2750 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2751 formspec->quitMenu();
2754 FormspecFormSource *fs_src =
2755 new FormspecFormSource(*(event->show_formspec.formspec));
2756 TextDestPlayerInventory *txt_dst =
2757 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2759 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2760 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2761 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2764 delete event->show_formspec.formspec;
2765 delete event->show_formspec.formname;
2768 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2770 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2771 LocalFormspecHandler *txt_dst =
2772 new LocalFormspecHandler(*event->show_formspec.formname, client);
2773 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2774 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2776 delete event->show_formspec.formspec;
2777 delete event->show_formspec.formname;
2780 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2781 CameraOrientation *cam)
2783 LocalPlayer *player = client->getEnv().getLocalPlayer();
2784 client->getParticleManager()->handleParticleEvent(event, client, player);
2787 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2789 LocalPlayer *player = client->getEnv().getLocalPlayer();
2791 u32 server_id = event->hudadd->server_id;
2792 // ignore if we already have a HUD with that ID
2793 auto i = m_hud_server_to_client.find(server_id);
2794 if (i != m_hud_server_to_client.end()) {
2795 delete event->hudadd;
2799 HudElement *e = new HudElement;
2800 e->type = static_cast<HudElementType>(event->hudadd->type);
2801 e->pos = event->hudadd->pos;
2802 e->name = event->hudadd->name;
2803 e->scale = event->hudadd->scale;
2804 e->text = event->hudadd->text;
2805 e->number = event->hudadd->number;
2806 e->item = event->hudadd->item;
2807 e->dir = event->hudadd->dir;
2808 e->align = event->hudadd->align;
2809 e->offset = event->hudadd->offset;
2810 e->world_pos = event->hudadd->world_pos;
2811 e->size = event->hudadd->size;
2812 e->z_index = event->hudadd->z_index;
2813 e->text2 = event->hudadd->text2;
2814 e->style = event->hudadd->style;
2815 m_hud_server_to_client[server_id] = player->addHud(e);
2817 delete event->hudadd;
2820 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2822 LocalPlayer *player = client->getEnv().getLocalPlayer();
2824 auto i = m_hud_server_to_client.find(event->hudrm.id);
2825 if (i != m_hud_server_to_client.end()) {
2826 HudElement *e = player->removeHud(i->second);
2828 m_hud_server_to_client.erase(i);
2833 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2835 LocalPlayer *player = client->getEnv().getLocalPlayer();
2837 HudElement *e = nullptr;
2839 auto i = m_hud_server_to_client.find(event->hudchange->id);
2840 if (i != m_hud_server_to_client.end()) {
2841 e = player->getHud(i->second);
2845 delete event->hudchange;
2849 #define CASE_SET(statval, prop, dataprop) \
2851 e->prop = event->hudchange->dataprop; \
2854 switch (event->hudchange->stat) {
2855 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2857 CASE_SET(HUD_STAT_NAME, name, sdata);
2859 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2861 CASE_SET(HUD_STAT_TEXT, text, sdata);
2863 CASE_SET(HUD_STAT_NUMBER, number, data);
2865 CASE_SET(HUD_STAT_ITEM, item, data);
2867 CASE_SET(HUD_STAT_DIR, dir, data);
2869 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2871 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2873 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2875 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2877 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2879 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2881 CASE_SET(HUD_STAT_STYLE, style, data);
2886 delete event->hudchange;
2889 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2891 sky->setVisible(false);
2892 // Whether clouds are visible in front of a custom skybox.
2893 sky->setCloudsEnabled(event->set_sky->clouds);
2899 // Clear the old textures out in case we switch rendering type.
2900 sky->clearSkyboxTextures();
2901 // Handle according to type
2902 if (event->set_sky->type == "regular") {
2903 // Shows the mesh skybox
2904 sky->setVisible(true);
2905 // Update mesh based skybox colours if applicable.
2906 sky->setSkyColors(event->set_sky->sky_color);
2907 sky->setHorizonTint(
2908 event->set_sky->fog_sun_tint,
2909 event->set_sky->fog_moon_tint,
2910 event->set_sky->fog_tint_type
2912 } else if (event->set_sky->type == "skybox" &&
2913 event->set_sky->textures.size() == 6) {
2914 // Disable the dyanmic mesh skybox:
2915 sky->setVisible(false);
2917 sky->setFallbackBgColor(event->set_sky->bgcolor);
2918 // Set sunrise and sunset fog tinting:
2919 sky->setHorizonTint(
2920 event->set_sky->fog_sun_tint,
2921 event->set_sky->fog_moon_tint,
2922 event->set_sky->fog_tint_type
2924 // Add textures to skybox.
2925 for (int i = 0; i < 6; i++)
2926 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2928 // Handle everything else as plain color.
2929 if (event->set_sky->type != "plain")
2930 infostream << "Unknown sky type: "
2931 << (event->set_sky->type) << std::endl;
2932 sky->setVisible(false);
2933 sky->setFallbackBgColor(event->set_sky->bgcolor);
2934 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2935 sky->setHorizonTint(
2936 event->set_sky->bgcolor,
2937 event->set_sky->bgcolor,
2942 delete event->set_sky;
2945 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2947 sky->setSunVisible(event->sun_params->visible);
2948 sky->setSunTexture(event->sun_params->texture,
2949 event->sun_params->tonemap, texture_src);
2950 sky->setSunScale(event->sun_params->scale);
2951 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2952 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2953 delete event->sun_params;
2956 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2958 sky->setMoonVisible(event->moon_params->visible);
2959 sky->setMoonTexture(event->moon_params->texture,
2960 event->moon_params->tonemap, texture_src);
2961 sky->setMoonScale(event->moon_params->scale);
2962 delete event->moon_params;
2965 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2967 sky->setStarsVisible(event->star_params->visible);
2968 sky->setStarCount(event->star_params->count);
2969 sky->setStarColor(event->star_params->starcolor);
2970 sky->setStarScale(event->star_params->scale);
2971 sky->setStarDayOpacity(event->star_params->day_opacity);
2972 delete event->star_params;
2975 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2976 CameraOrientation *cam)
2978 client->getEnv().setDayNightRatioOverride(
2979 event->override_day_night_ratio.do_override,
2980 event->override_day_night_ratio.ratio_f * 1000.0f);
2983 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2988 clouds->setDensity(event->cloud_params.density);
2989 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2990 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2991 clouds->setHeight(event->cloud_params.height);
2992 clouds->setThickness(event->cloud_params.thickness);
2993 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2996 void Game::processClientEvents(CameraOrientation *cam)
2998 while (client->hasClientEvents()) {
2999 std::unique_ptr<ClientEvent> event(client->getClientEvent());
3000 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
3001 const ClientEventHandler& evHandler = clientEventHandler[event->type];
3002 (this->*evHandler.handler)(event.get(), cam);
3006 void Game::updateChat(f32 dtime)
3008 // Get new messages from error log buffer
3009 while (!m_chat_log_buf.empty())
3010 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
3012 // Get new messages from client
3013 std::wstring message;
3014 while (client->getChatMessage(message)) {
3015 chat_backend->addUnparsedMessage(message);
3018 // Remove old messages
3019 chat_backend->step(dtime);
3021 // Display all messages in a static text element
3022 auto &buf = chat_backend->getRecentBuffer();
3023 if (buf.getLinesModified()) {
3024 buf.resetLinesModified();
3025 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
3028 // Make sure that the size is still correct
3029 m_game_ui->updateChatSize();
3032 void Game::updateCamera(f32 dtime)
3034 LocalPlayer *player = client->getEnv().getLocalPlayer();
3037 For interaction purposes, get info about the held item
3039 - Is it a usable item?
3040 - Can it point to liquids?
3042 ItemStack playeritem;
3044 ItemStack selected, hand;
3045 playeritem = player->getWieldedItem(&selected, &hand);
3048 ToolCapabilities playeritem_toolcap =
3049 playeritem.getToolCapabilities(itemdef_manager);
3051 v3s16 old_camera_offset = camera->getOffset();
3053 if (wasKeyDown(KeyType::CAMERA_MODE)) {
3054 GenericCAO *playercao = player->getCAO();
3056 // If playercao not loaded, don't change camera
3060 camera->toggleCameraMode();
3062 #ifdef HAVE_TOUCHSCREENGUI
3063 if (g_touchscreengui)
3064 g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed());
3067 // Make the player visible depending on camera mode.
3068 playercao->updateMeshCulling();
3069 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
3072 float full_punch_interval = playeritem_toolcap.full_punch_interval;
3073 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
3075 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
3076 camera->update(player, dtime, tool_reload_ratio);
3077 camera->step(dtime);
3079 f32 camera_fov = camera->getFovMax();
3080 v3s16 camera_offset = camera->getOffset();
3082 m_camera_offset_changed = (camera_offset != old_camera_offset);
3084 if (!m_flags.disable_camera_update) {
3085 v3f camera_position = camera->getPosition();
3086 v3f camera_direction = camera->getDirection();
3088 client->getEnv().getClientMap().updateCamera(camera_position,
3089 camera_direction, camera_fov, camera_offset);
3091 if (m_camera_offset_changed) {
3092 client->updateCameraOffset(camera_offset);
3093 client->getEnv().updateCameraOffset(camera_offset);
3096 clouds->updateCameraOffset(camera_offset);
3102 void Game::updateSound(f32 dtime)
3104 // Update sound listener
3105 v3s16 camera_offset = camera->getOffset();
3106 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3107 v3f(0, 0, 0), // velocity
3108 camera->getDirection(),
3109 camera->getCameraNode()->getUpVector());
3111 bool mute_sound = g_settings->getBool("mute_sound");
3113 sound->setListenerGain(0.0f);
3115 // Check if volume is in the proper range, else fix it.
3116 float old_volume = g_settings->getFloat("sound_volume");
3117 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3118 sound->setListenerGain(new_volume);
3120 if (old_volume != new_volume) {
3121 g_settings->setFloat("sound_volume", new_volume);
3125 LocalPlayer *player = client->getEnv().getLocalPlayer();
3127 // Tell the sound maker whether to make footstep sounds
3128 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3130 // Update sound maker
3131 if (player->makes_footstep_sound)
3132 soundmaker->step(dtime);
3134 ClientMap &map = client->getEnv().getClientMap();
3135 MapNode n = map.getNode(player->getFootstepNodePos());
3136 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3140 void Game::processPlayerInteraction(f32 dtime, bool show_hud)
3142 LocalPlayer *player = client->getEnv().getLocalPlayer();
3144 const v3f camera_direction = camera->getDirection();
3145 const v3s16 camera_offset = camera->getOffset();
3148 Calculate what block is the crosshair pointing to
3151 ItemStack selected_item, hand_item;
3152 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3154 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3155 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3157 core::line3d<f32> shootline;
3159 switch (camera->getCameraMode()) {
3160 case CAMERA_MODE_FIRST:
3161 // Shoot from camera position, with bobbing
3162 shootline.start = camera->getPosition();
3164 case CAMERA_MODE_THIRD:
3165 // Shoot from player head, no bobbing
3166 shootline.start = camera->getHeadPosition();
3168 case CAMERA_MODE_THIRD_FRONT:
3169 shootline.start = camera->getHeadPosition();
3170 // prevent player pointing anything in front-view
3174 shootline.end = shootline.start + camera_direction * BS * d;
3176 #ifdef HAVE_TOUCHSCREENGUI
3177 if (g_touchscreengui && isNoCrosshairAllowed()) {
3178 shootline = g_touchscreengui->getShootline();
3179 // Scale shootline to the acual distance the player can reach
3180 shootline.end = shootline.start +
3181 shootline.getVector().normalize() * BS * d;
3182 shootline.start += intToFloat(camera_offset, BS);
3183 shootline.end += intToFloat(camera_offset, BS);
3187 PointedThing pointed = updatePointedThing(shootline,
3188 selected_def.liquids_pointable,
3189 !runData.btn_down_for_dig,
3192 if (pointed != runData.pointed_old)
3193 infostream << "Pointing at " << pointed.dump() << std::endl;
3195 // Note that updating the selection mesh every frame is not particularly efficient,
3196 // but the halo rendering code is already inefficient so there's no point in optimizing it here
3197 hud->updateSelectionMesh(camera_offset);
3199 // Allow digging again if button is not pressed
3200 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3201 runData.digging_blocked = false;
3205 - releasing dig button
3206 - pointing away from node
3208 if (runData.digging) {
3209 if (wasKeyReleased(KeyType::DIG)) {
3210 infostream << "Dig button released (stopped digging)" << std::endl;
3211 runData.digging = false;
3212 } else if (pointed != runData.pointed_old) {
3213 if (pointed.type == POINTEDTHING_NODE
3214 && runData.pointed_old.type == POINTEDTHING_NODE
3215 && pointed.node_undersurface
3216 == runData.pointed_old.node_undersurface) {
3217 // Still pointing to the same node, but a different face.
3220 infostream << "Pointing away from node (stopped digging)" << std::endl;
3221 runData.digging = false;
3222 hud->updateSelectionMesh(camera_offset);
3226 if (!runData.digging) {
3227 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3228 client->setCrack(-1, v3s16(0, 0, 0));
3229 runData.dig_time = 0.0;
3231 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3232 // Remove e.g. torches faster when clicking instead of holding dig button
3233 runData.nodig_delay_timer = 0;
3234 runData.dig_instantly = false;
3237 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3238 runData.btn_down_for_dig = false;
3240 runData.punching = false;
3242 soundmaker->m_player_leftpunch_sound = SimpleSoundSpec();
3243 soundmaker->m_player_leftpunch_sound2 = pointed.type != POINTEDTHING_NOTHING ?
3244 selected_def.sound_use : selected_def.sound_use_air;
3246 // Prepare for repeating, unless we're not supposed to
3247 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3248 runData.repeat_place_timer += dtime;
3250 runData.repeat_place_timer = 0;
3252 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3253 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3254 !client->getScript()->on_item_use(selected_item, pointed)))
3255 client->interact(INTERACT_USE, pointed);
3256 } else if (pointed.type == POINTEDTHING_NODE) {
3257 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3258 } else if (pointed.type == POINTEDTHING_OBJECT) {
3259 v3f player_position = player->getPosition();
3260 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
3261 handlePointingAtObject(pointed, tool_item, player_position,
3262 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
3263 } else if (isKeyDown(KeyType::DIG)) {
3264 // When button is held down in air, show continuous animation
3265 runData.punching = true;
3266 // Run callback even though item is not usable
3267 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3268 client->getScript()->on_item_use(selected_item, pointed);
3269 } else if (wasKeyPressed(KeyType::PLACE)) {
3270 handlePointingAtNothing(selected_item);
3273 runData.pointed_old = pointed;
3275 if (runData.punching || wasKeyPressed(KeyType::DIG))
3276 camera->setDigging(0); // dig animation
3278 input->clearWasKeyPressed();
3279 input->clearWasKeyReleased();
3280 // Ensure DIG & PLACE are marked as handled
3281 wasKeyDown(KeyType::DIG);
3282 wasKeyDown(KeyType::PLACE);
3284 input->joystick.clearWasKeyPressed(KeyType::DIG);
3285 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3287 input->joystick.clearWasKeyReleased(KeyType::DIG);
3288 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3292 PointedThing Game::updatePointedThing(
3293 const core::line3d<f32> &shootline,
3294 bool liquids_pointable,
3295 bool look_for_object,
3296 const v3s16 &camera_offset)
3298 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3299 selectionboxes->clear();
3300 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3301 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3302 "show_entity_selectionbox");
3304 ClientEnvironment &env = client->getEnv();
3305 ClientMap &map = env.getClientMap();
3306 const NodeDefManager *nodedef = map.getNodeDefManager();
3308 runData.selected_object = NULL;
3309 hud->pointing_at_object = false;
3311 RaycastState s(shootline, look_for_object, liquids_pointable);
3312 PointedThing result;
3313 env.continueRaycast(&s, &result);
3314 if (result.type == POINTEDTHING_OBJECT) {
3315 hud->pointing_at_object = true;
3317 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3318 aabb3f selection_box;
3319 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3320 runData.selected_object->getSelectionBox(&selection_box)) {
3321 v3f pos = runData.selected_object->getPosition();
3322 selectionboxes->push_back(aabb3f(selection_box));
3323 hud->setSelectionPos(pos, camera_offset);
3325 } else if (result.type == POINTEDTHING_NODE) {
3326 // Update selection boxes
3327 MapNode n = map.getNode(result.node_undersurface);
3328 std::vector<aabb3f> boxes;
3329 n.getSelectionBoxes(nodedef, &boxes,
3330 n.getNeighbors(result.node_undersurface, &map));
3333 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3334 i != boxes.end(); ++i) {
3336 box.MinEdge -= v3f(d, d, d);
3337 box.MaxEdge += v3f(d, d, d);
3338 selectionboxes->push_back(box);
3340 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3342 hud->setSelectedFaceNormal(v3f(
3343 result.intersection_normal.X,
3344 result.intersection_normal.Y,
3345 result.intersection_normal.Z));
3348 // Update selection mesh light level and vertex colors
3349 if (!selectionboxes->empty()) {
3350 v3f pf = hud->getSelectionPos();
3351 v3s16 p = floatToInt(pf, BS);
3353 // Get selection mesh light level
3354 MapNode n = map.getNode(p);
3355 u16 node_light = getInteriorLight(n, -1, nodedef);
3356 u16 light_level = node_light;
3358 for (const v3s16 &dir : g_6dirs) {
3359 n = map.getNode(p + dir);
3360 node_light = getInteriorLight(n, -1, nodedef);
3361 if (node_light > light_level)
3362 light_level = node_light;
3365 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3367 final_color_blend(&c, light_level, daynight_ratio);
3369 // Modify final color a bit with time
3370 u32 timer = client->getEnv().getFrameTime() % 5000;
3371 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3372 float sin_r = 0.08f * std::sin(timerf);
3373 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3374 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3375 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3376 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3377 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3379 // Set mesh final color
3380 hud->setSelectionMeshColor(c);
3386 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3388 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3389 PointedThing fauxPointed;
3390 fauxPointed.type = POINTEDTHING_NOTHING;
3391 client->interact(INTERACT_ACTIVATE, fauxPointed);
3395 void Game::handlePointingAtNode(const PointedThing &pointed,
3396 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3398 v3s16 nodepos = pointed.node_undersurface;
3399 v3s16 neighbourpos = pointed.node_abovesurface;
3402 Check information text of node
3405 ClientMap &map = client->getEnv().getClientMap();
3407 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3408 && !runData.digging_blocked
3409 && client->checkPrivilege("interact")) {
3410 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3413 // This should be done after digging handling
3414 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3417 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3418 meta->getString("infotext"))));
3420 MapNode n = map.getNode(nodepos);
3422 if (nodedef_manager->get(n).name == "unknown") {
3423 m_game_ui->setInfoText(L"Unknown node");
3427 if ((wasKeyPressed(KeyType::PLACE) ||
3428 runData.repeat_place_timer >= m_repeat_place_time) &&
3429 client->checkPrivilege("interact")) {
3430 runData.repeat_place_timer = 0;
3431 infostream << "Place button pressed while looking at ground" << std::endl;
3433 // Placing animation (always shown for feedback)
3434 camera->setDigging(1);
3436 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3438 // If the wielded item has node placement prediction,
3440 // And also set the sound and send the interact
3441 // But first check for meta formspec and rightclickable
3442 auto &def = selected_item.getDefinition(itemdef_manager);
3443 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3446 if (placed && client->modsLoaded())
3447 client->getScript()->on_placenode(pointed, def);
3451 bool Game::nodePlacement(const ItemDefinition &selected_def,
3452 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3453 const PointedThing &pointed, const NodeMetadata *meta)
3455 const auto &prediction = selected_def.node_placement_prediction;
3457 const NodeDefManager *nodedef = client->ndef();
3458 ClientMap &map = client->getEnv().getClientMap();
3460 bool is_valid_position;
3462 node = map.getNode(nodepos, &is_valid_position);
3463 if (!is_valid_position) {
3464 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3469 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3470 && !isKeyDown(KeyType::SNEAK)) {
3471 // on_rightclick callbacks are called anyway
3472 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3473 client->interact(INTERACT_PLACE, pointed);
3475 infostream << "Launching custom inventory view" << std::endl;
3477 InventoryLocation inventoryloc;
3478 inventoryloc.setNodeMeta(nodepos);
3480 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3481 &client->getEnv().getClientMap(), nodepos);
3482 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3484 auto *&formspec = m_game_ui->updateFormspec("");
3485 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3486 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3488 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3492 // on_rightclick callback
3493 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3494 !isKeyDown(KeyType::SNEAK))) {
3496 client->interact(INTERACT_PLACE, pointed);
3500 verbosestream << "Node placement prediction for "
3501 << selected_def.name << " is " << prediction << std::endl;
3502 v3s16 p = neighbourpos;
3504 // Place inside node itself if buildable_to
3505 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3506 if (is_valid_position) {
3507 if (nodedef->get(n_under).buildable_to) {
3510 node = map.getNode(p, &is_valid_position);
3511 if (is_valid_position && !nodedef->get(node).buildable_to) {
3512 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3514 client->interact(INTERACT_PLACE, pointed);
3520 // Find id of predicted node
3522 bool found = nodedef->getId(prediction, id);
3525 errorstream << "Node placement prediction failed for "
3526 << selected_def.name << " (places " << prediction
3527 << ") - Name not known" << std::endl;
3528 // Handle this as if prediction was empty
3530 client->interact(INTERACT_PLACE, pointed);
3534 const ContentFeatures &predicted_f = nodedef->get(id);
3536 // Predict param2 for facedir and wallmounted nodes
3537 // Compare core.item_place_node() for what the server does
3540 const u8 place_param2 = selected_def.place_param2;
3543 param2 = place_param2;
3544 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3545 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3546 v3s16 dir = nodepos - neighbourpos;
3548 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3549 param2 = dir.Y < 0 ? 1 : 0;
3550 } else if (abs(dir.X) > abs(dir.Z)) {
3551 param2 = dir.X < 0 ? 3 : 2;
3553 param2 = dir.Z < 0 ? 5 : 4;
3555 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3556 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR ||
3557 predicted_f.param_type_2 == CPT2_4DIR ||
3558 predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3559 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3561 if (abs(dir.X) > abs(dir.Z)) {
3562 param2 = dir.X < 0 ? 3 : 1;
3564 param2 = dir.Z < 0 ? 2 : 0;
3568 // Check attachment if node is in group attached_node
3569 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3570 const static v3s16 wallmounted_dirs[8] = {
3580 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3581 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3582 pp = p + wallmounted_dirs[param2];
3584 pp = p + v3s16(0, -1, 0);
3586 if (!nodedef->get(map.getNode(pp)).walkable) {
3587 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3589 client->interact(INTERACT_PLACE, pointed);
3595 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3596 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3597 || predicted_f.param_type_2 == CPT2_COLORED_4DIR
3598 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3599 const auto &indexstr = selected_item.metadata.
3600 getString("palette_index", 0);
3601 if (!indexstr.empty()) {
3602 s32 index = mystoi(indexstr);
3603 if (predicted_f.param_type_2 == CPT2_COLOR) {
3605 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3606 // param2 = pure palette index + other
3607 param2 = (index & 0xf8) | (param2 & 0x07);
3608 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3609 // param2 = pure palette index + other
3610 param2 = (index & 0xe0) | (param2 & 0x1f);
3611 } else if (predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3612 // param2 = pure palette index + other
3613 param2 = (index & 0xfc) | (param2 & 0x03);
3618 // Add node to client map
3619 MapNode n(id, 0, param2);
3622 LocalPlayer *player = client->getEnv().getLocalPlayer();
3624 // Dont place node when player would be inside new node
3625 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3626 if (!nodedef->get(n).walkable ||
3627 g_settings->getBool("enable_build_where_you_stand") ||
3628 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3629 (nodedef->get(n).walkable &&
3630 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3631 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3632 // This triggers the required mesh update too
3633 client->addNode(p, n);
3635 client->interact(INTERACT_PLACE, pointed);
3636 // A node is predicted, also play a sound
3637 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3640 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3643 } catch (const InvalidPositionException &e) {
3644 errorstream << "Node placement prediction failed for "
3645 << selected_def.name << " (places "
3646 << prediction << ") - Position not loaded" << std::endl;
3647 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3652 void Game::handlePointingAtObject(const PointedThing &pointed,
3653 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3655 std::wstring infotext = unescape_translate(
3656 utf8_to_wide(runData.selected_object->infoText()));
3659 if (!infotext.empty()) {
3662 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3665 m_game_ui->setInfoText(infotext);
3667 if (isKeyDown(KeyType::DIG)) {
3668 bool do_punch = false;
3669 bool do_punch_damage = false;
3671 if (runData.object_hit_delay_timer <= 0.0) {
3673 do_punch_damage = true;
3674 runData.object_hit_delay_timer = object_hit_delay;
3677 if (wasKeyPressed(KeyType::DIG))
3681 infostream << "Punched object" << std::endl;
3682 runData.punching = true;
3685 if (do_punch_damage) {
3686 // Report direct punch
3687 v3f objpos = runData.selected_object->getPosition();
3688 v3f dir = (objpos - player_position).normalize();
3690 bool disable_send = runData.selected_object->directReportPunch(
3691 dir, &tool_item, runData.time_from_last_punch);
3692 runData.time_from_last_punch = 0;
3695 client->interact(INTERACT_START_DIGGING, pointed);
3697 } else if (wasKeyDown(KeyType::PLACE)) {
3698 infostream << "Pressed place button while pointing at object" << std::endl;
3699 client->interact(INTERACT_PLACE, pointed); // place
3704 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3705 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3707 // See also: serverpackethandle.cpp, action == 2
3708 LocalPlayer *player = client->getEnv().getLocalPlayer();
3709 ClientMap &map = client->getEnv().getClientMap();
3710 MapNode n = map.getNode(nodepos);
3711 const auto &features = nodedef_manager->get(n);
3713 // NOTE: Similar piece of code exists on the server side for
3715 // Get digging parameters
3716 DigParams params = getDigParams(features.groups,
3717 &selected_item.getToolCapabilities(itemdef_manager),
3718 selected_item.wear);
3720 // If can't dig, try hand
3721 if (!params.diggable) {
3722 params = getDigParams(features.groups,
3723 &hand_item.getToolCapabilities(itemdef_manager));
3726 if (!params.diggable) {
3727 // I guess nobody will wait for this long
3728 runData.dig_time_complete = 10000000.0;
3730 runData.dig_time_complete = params.time;
3732 if (m_cache_enable_particles) {
3733 client->getParticleManager()->addNodeParticle(client,
3734 player, nodepos, n, features);
3738 if (!runData.digging) {
3739 infostream << "Started digging" << std::endl;
3740 runData.dig_instantly = runData.dig_time_complete == 0;
3741 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3744 client->interact(INTERACT_START_DIGGING, pointed);
3745 runData.digging = true;
3746 runData.btn_down_for_dig = true;
3749 if (!runData.dig_instantly) {
3750 runData.dig_index = (float)crack_animation_length
3752 / runData.dig_time_complete;
3754 // This is for e.g. torches
3755 runData.dig_index = crack_animation_length;
3758 const auto &sound_dig = features.sound_dig;
3760 if (sound_dig.exists() && params.diggable) {
3761 if (sound_dig.name == "__group") {
3762 if (!params.main_group.empty()) {
3763 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3764 soundmaker->m_player_leftpunch_sound.name =
3765 std::string("default_dig_") +
3769 soundmaker->m_player_leftpunch_sound = sound_dig;
3773 // Don't show cracks if not diggable
3774 if (runData.dig_time_complete >= 100000.0) {
3775 } else if (runData.dig_index < crack_animation_length) {
3776 client->setCrack(runData.dig_index, nodepos);
3778 infostream << "Digging completed" << std::endl;
3779 client->setCrack(-1, v3s16(0, 0, 0));
3781 runData.dig_time = 0;
3782 runData.digging = false;
3783 // we successfully dug, now block it from repeating if we want to be safe
3784 if (g_settings->getBool("safe_dig_and_place"))
3785 runData.digging_blocked = true;
3787 runData.nodig_delay_timer =
3788 runData.dig_time_complete / (float)crack_animation_length;
3790 // We don't want a corresponding delay to very time consuming nodes
3791 // and nodes without digging time (e.g. torches) get a fixed delay.
3792 if (runData.nodig_delay_timer > 0.3)
3793 runData.nodig_delay_timer = 0.3;
3794 else if (runData.dig_instantly)
3795 runData.nodig_delay_timer = 0.15;
3797 if (client->modsLoaded() &&
3798 client->getScript()->on_dignode(nodepos, n)) {
3802 if (features.node_dig_prediction == "air") {
3803 client->removeNode(nodepos);
3804 } else if (!features.node_dig_prediction.empty()) {
3806 bool found = nodedef_manager->getId(features.node_dig_prediction, id);
3808 client->addNode(nodepos, id, true);
3810 // implicit else: no prediction
3812 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3814 if (m_cache_enable_particles) {
3815 client->getParticleManager()->addDiggingParticles(client,
3816 player, nodepos, n, features);
3820 // Send event to trigger sound
3821 client->getEventManager()->put(new NodeDugEvent(nodepos, n));
3824 if (runData.dig_time_complete < 100000.0) {
3825 runData.dig_time += dtime;
3827 runData.dig_time = 0;
3828 client->setCrack(-1, nodepos);
3831 camera->setDigging(0); // Dig animation
3834 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3835 const CameraOrientation &cam)
3837 TimeTaker tt_update("Game::updateFrame()");
3838 LocalPlayer *player = client->getEnv().getLocalPlayer();
3844 client->getEnv().updateFrameTime(m_is_paused);
3850 if (draw_control->range_all) {
3851 runData.fog_range = 100000 * BS;
3853 runData.fog_range = draw_control->wanted_range * BS;
3857 Calculate general brightness
3859 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3860 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3861 float direct_brightness;
3864 // When in noclip mode force same sky brightness as above ground so you
3866 if (draw_control->allow_noclip && m_cache_enable_free_move &&
3867 client->checkPrivilege("fly")) {
3868 direct_brightness = time_brightness;
3869 sunlight_seen = true;
3871 float old_brightness = sky->getBrightness();
3872 direct_brightness = client->getEnv().getClientMap()
3873 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3874 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3878 float time_of_day_smooth = runData.time_of_day_smooth;
3879 float time_of_day = client->getEnv().getTimeOfDayF();
3881 static const float maxsm = 0.05f;
3882 static const float todsm = 0.05f;
3884 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3885 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3886 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3887 time_of_day_smooth = time_of_day;
3889 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3890 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3891 + (time_of_day + 1.0) * todsm;
3893 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3894 + time_of_day * todsm;
3896 runData.time_of_day_smooth = time_of_day_smooth;
3898 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3899 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3900 player->getPitch());
3906 if (sky->getCloudsVisible()) {
3907 clouds->setVisible(true);
3908 clouds->step(dtime);
3909 // camera->getPosition is not enough for 3rd person views
3910 v3f camera_node_position = camera->getCameraNode()->getPosition();
3911 v3s16 camera_offset = camera->getOffset();
3912 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3913 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3914 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3915 clouds->update(camera_node_position,
3916 sky->getCloudColor());
3917 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3918 // if inside clouds, and fog enabled, use that as sky
3920 video::SColor clouds_dark = clouds->getColor()
3921 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3922 sky->overrideColors(clouds_dark, clouds->getColor());
3923 sky->setInClouds(true);
3924 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3925 // do not draw clouds after all
3926 clouds->setVisible(false);
3929 clouds->setVisible(false);
3936 client->getParticleManager()->step(dtime);
3942 if (m_cache_enable_fog) {
3945 video::EFT_FOG_LINEAR,
3946 runData.fog_range * m_cache_fog_start,
3947 runData.fog_range * 1.0,
3955 video::EFT_FOG_LINEAR,
3967 if (player->hurt_tilt_timer > 0.0f) {
3968 player->hurt_tilt_timer -= dtime * 6.0f;
3970 if (player->hurt_tilt_timer < 0.0f)
3971 player->hurt_tilt_strength = 0.0f;
3975 Update minimap pos and rotation
3977 if (mapper && m_game_ui->m_flags.show_hud) {
3978 mapper->setPos(floatToInt(player->getPosition(), BS));
3979 mapper->setAngle(player->getYaw());
3983 Get chat messages from client
3992 if (player->getWieldIndex() != runData.new_playeritem)
3993 client->setPlayerItem(runData.new_playeritem);
3995 if (client->updateWieldedItem()) {
3996 // Update wielded tool
3997 ItemStack selected_item, hand_item;
3998 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3999 camera->wield(tool_item);
4003 Update block draw list every 200ms or when camera direction has
4006 runData.update_draw_list_timer += dtime;
4008 float update_draw_list_delta = 0.2f;
4010 v3f camera_direction = camera->getDirection();
4011 if (runData.update_draw_list_timer >= update_draw_list_delta
4012 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
4013 || m_camera_offset_changed
4014 || client->getEnv().getClientMap().needsUpdateDrawList()) {
4015 runData.update_draw_list_timer = 0;
4016 client->getEnv().getClientMap().updateDrawList();
4017 runData.update_draw_list_last_cam_dir = camera_direction;
4020 if (RenderingEngine::get_shadow_renderer()) {
4024 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
4027 make sure menu is on top
4028 1. Delete formspec menu reference if menu was removed
4029 2. Else, make sure formspec menu is on top
4031 auto formspec = m_game_ui->getFormspecGUI();
4032 do { // breakable. only runs for one iteration
4036 if (formspec->getReferenceCount() == 1) {
4037 m_game_ui->deleteFormspec();
4041 auto &loc = formspec->getFormspecLocation();
4042 if (loc.type == InventoryLocation::NODEMETA) {
4043 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
4044 if (!meta || meta->getString("formspec").empty()) {
4045 formspec->quitMenu();
4051 guiroot->bringToFront(formspec);
4055 ==================== Drawing begins ====================
4057 const video::SColor skycolor = sky->getSkyColor();
4059 TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
4060 driver->beginScene(true, true, skycolor);
4062 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
4063 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
4064 (camera->getCameraMode() == CAMERA_MODE_FIRST));
4065 bool draw_crosshair = (
4066 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
4067 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
4068 #ifdef HAVE_TOUCHSCREENGUI
4069 if (isNoCrosshairAllowed())
4070 draw_crosshair = false;
4072 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
4073 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
4078 v2u32 screensize = driver->getScreenSize();
4080 if (m_game_ui->m_flags.show_profiler_graph)
4081 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
4086 if (runData.damage_flash > 0.0f) {
4087 video::SColor color(runData.damage_flash, 180, 0, 0);
4088 driver->draw2DRectangle(color,
4089 core::rect<s32>(0, 0, screensize.X, screensize.Y),
4092 runData.damage_flash -= 384.0f * dtime;
4096 ==================== End scene ====================
4098 #if IRRLICHT_VERSION_MT_REVISION < 5
4099 if (++m_reset_HW_buffer_counter > 500) {
4101 Periodically remove all mesh HW buffers.
4103 Work around for a quirk in Irrlicht where a HW buffer is only
4104 released after 20000 iterations (triggered from endScene()).
4106 Without this, all loaded but unused meshes will retain their HW
4107 buffers for at least 5 minutes, at which point looking up the HW buffers
4108 becomes a bottleneck and the framerate drops (as much as 30%).
4110 Tests showed that numbers between 50 and 1000 are good, so picked 500.
4111 There are no other public Irrlicht APIs that allow interacting with the
4112 HW buffers without tracking the status of every individual mesh.
4114 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4116 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4117 driver->removeAllHardwareBuffers();
4118 m_reset_HW_buffer_counter = 0;
4124 stats->drawtime = tt_draw.stop(true);
4125 g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
4126 g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
4129 /* Log times and stuff for visualization */
4130 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4132 Profiler::GraphValues values;
4133 g_profiler->graphGet(values);
4137 /****************************************************************************
4139 *****************************************************************************/
4140 void Game::updateShadows()
4142 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4146 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4148 float timeoftheday = getWickedTimeOfDay(in_timeofday);
4149 bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
4150 bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
4151 shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
4153 timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
4154 const float offset_constant = 10000.0f;
4156 v3f light = is_day ? sky->getSunDirection() : sky->getMoonDirection();
4158 v3f sun_pos = light * offset_constant;
4160 if (shadow->getDirectionalLightCount() == 0)
4161 shadow->addDirectionalLight();
4162 shadow->getDirectionalLight().setDirection(sun_pos);
4163 shadow->setTimeOfDay(in_timeofday);
4165 shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4168 /****************************************************************************
4170 ****************************************************************************/
4172 void FpsControl::reset()
4174 last_time = porting::getTimeUs();
4178 * On some computers framerate doesn't seem to be automatically limited
4180 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
4182 const float fps_limit = (device->isWindowFocused() && !g_menumgr.pausesGame())
4183 ? g_settings->getFloat("fps_max")
4184 : g_settings->getFloat("fps_max_unfocused");
4185 const u64 frametime_min = 1000000.0f / std::max(fps_limit, 1.0f);
4187 u64 time = porting::getTimeUs();
4189 if (time > last_time) // Make sure time hasn't overflowed
4190 busy_time = time - last_time;
4194 if (busy_time < frametime_min) {
4195 sleep_time = frametime_min - busy_time;
4196 if (sleep_time > 1000)
4197 sleep_ms(sleep_time / 1000);
4202 // Read the timer again to accurately determine how long we actually slept,
4203 // rather than calculating it by adding sleep_time to time.
4204 time = porting::getTimeUs();
4206 if (time > last_time) // Make sure last_time hasn't overflowed
4207 *dtime = (time - last_time) / 1000000.0f;
4214 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4216 const wchar_t *wmsg = wgettext(msg);
4217 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4222 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4224 ((Game *)data)->readSettings();
4227 void Game::readSettings()
4229 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4230 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4231 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4232 m_cache_enable_particles = g_settings->getBool("enable_particles");
4233 m_cache_enable_fog = g_settings->getBool("enable_fog");
4234 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f);
4235 m_cache_joystick_frustum_sensitivity = std::max(g_settings->getFloat("joystick_frustum_sensitivity"), 0.001f);
4236 m_repeat_place_time = g_settings->getFloat("repeat_place_time", 0.25f, 2.0);
4238 m_cache_enable_noclip = g_settings->getBool("noclip");
4239 m_cache_enable_free_move = g_settings->getBool("free_move");
4241 m_cache_fog_start = g_settings->getFloat("fog_start");
4243 m_cache_cam_smoothing = 0;
4244 if (g_settings->getBool("cinematic"))
4245 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4247 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4249 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4250 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4251 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4253 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4256 /****************************************************************************/
4257 /****************************************************************************
4259 ****************************************************************************/
4260 /****************************************************************************/
4262 void Game::showDeathFormspec()
4264 static std::string formspec_str =
4265 std::string("formspec_version[1]") +
4267 "bgcolor[#320000b4;true]"
4268 "label[4.85,1.35;" + gettext("You died") + "]"
4269 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4273 /* Note: FormspecFormSource and LocalFormspecHandler *
4274 * are deleted by guiFormSpecMenu */
4275 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4276 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4278 auto *&formspec = m_game_ui->getFormspecGUI();
4279 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4280 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4281 formspec->setFocus("btn_respawn");
4284 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4285 void Game::showPauseMenu()
4287 #ifdef HAVE_TOUCHSCREENGUI
4288 static const std::string control_text = strgettext("Default Controls:\n"
4289 "No menu visible:\n"
4290 "- single tap: button activate\n"
4291 "- double tap: place/use\n"
4292 "- slide finger: look around\n"
4293 "Menu/Inventory visible:\n"
4294 "- double tap (outside):\n"
4296 "- touch stack, touch slot:\n"
4298 "- touch&drag, tap 2nd finger\n"
4299 " --> place single item to slot\n"
4302 static const std::string control_text_template = strgettext("Controls:\n"
4303 "- %s: move forwards\n"
4304 "- %s: move backwards\n"
4306 "- %s: move right\n"
4307 "- %s: jump/climb up\n"
4310 "- %s: sneak/climb down\n"
4313 "- Mouse: turn/look\n"
4314 "- Mouse wheel: select item\n"
4318 char control_text_buf[600];
4320 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4321 GET_KEY_NAME(keymap_forward),
4322 GET_KEY_NAME(keymap_backward),
4323 GET_KEY_NAME(keymap_left),
4324 GET_KEY_NAME(keymap_right),
4325 GET_KEY_NAME(keymap_jump),
4326 GET_KEY_NAME(keymap_dig),
4327 GET_KEY_NAME(keymap_place),
4328 GET_KEY_NAME(keymap_sneak),
4329 GET_KEY_NAME(keymap_drop),
4330 GET_KEY_NAME(keymap_inventory),
4331 GET_KEY_NAME(keymap_chat)
4334 std::string control_text = std::string(control_text_buf);
4335 str_formspec_escape(control_text);
4338 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4339 std::ostringstream os;
4341 os << "formspec_version[1]" << SIZE_TAG
4342 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4343 << strgettext("Continue") << "]";
4345 if (!simple_singleplayer_mode) {
4346 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4347 << strgettext("Change Password") << "]";
4349 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4354 if (g_settings->getBool("enable_sound")) {
4355 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4356 << strgettext("Sound Volume") << "]";
4359 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4360 << strgettext("Change Keys") << "]";
4362 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4363 << strgettext("Exit to Menu") << "]";
4364 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4365 << strgettext("Exit to OS") << "]"
4366 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4367 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4369 << strgettext("Game info:") << "\n";
4370 const std::string &address = client->getAddressName();
4371 static const std::string mode = strgettext("- Mode: ");
4372 if (!simple_singleplayer_mode) {
4373 Address serverAddress = client->getServerAddress();
4374 if (!address.empty()) {
4375 os << mode << strgettext("Remote server") << "\n"
4376 << strgettext("- Address: ") << address;
4378 os << mode << strgettext("Hosting server");
4380 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4382 os << mode << strgettext("Singleplayer") << "\n";
4384 if (simple_singleplayer_mode || address.empty()) {
4385 static const std::string on = strgettext("On");
4386 static const std::string off = strgettext("Off");
4387 // Note: Status of enable_damage and creative_mode settings is intentionally
4388 // NOT shown here because the game might roll its own damage system and/or do
4389 // a per-player Creative Mode, in which case writing it here would mislead.
4390 bool damage = g_settings->getBool("enable_damage");
4391 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4392 if (!simple_singleplayer_mode) {
4394 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4395 //~ PvP = Player versus Player
4396 os << strgettext("- PvP: ") << pvp << "\n";
4398 os << strgettext("- Public: ") << announced << "\n";
4399 std::string server_name = g_settings->get("server_name");
4400 str_formspec_escape(server_name);
4401 if (announced == on && !server_name.empty())
4402 os << strgettext("- Server Name: ") << server_name;
4409 /* Note: FormspecFormSource and LocalFormspecHandler *
4410 * are deleted by guiFormSpecMenu */
4411 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4412 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4414 auto *&formspec = m_game_ui->getFormspecGUI();
4415 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4416 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4417 formspec->setFocus("btn_continue");
4418 // game will be paused in next step, if in singleplayer (see m_is_paused)
4419 formspec->doPause = true;
4422 /****************************************************************************/
4423 /****************************************************************************
4424 extern function for launching the game
4425 ****************************************************************************/
4426 /****************************************************************************/
4428 void the_game(bool *kill,
4429 InputHandler *input,
4430 RenderingEngine *rendering_engine,
4431 const GameStartData &start_data,
4432 std::string &error_message,
4433 ChatBackend &chat_backend,
4434 bool *reconnect_requested) // Used for local game
4438 /* Make a copy of the server address because if a local singleplayer server
4439 * is created then this is updated and we don't want to change the value
4440 * passed to us by the calling function
4445 if (game.startup(kill, input, rendering_engine, start_data,
4446 error_message, reconnect_requested, &chat_backend)) {
4450 } catch (SerializationError &e) {
4451 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
4452 error_message = strgettext("A serialization error occurred:") +"\n"
4453 + e.what() + "\n\n" + ver_err;
4454 errorstream << error_message << std::endl;
4455 } catch (ServerError &e) {
4456 error_message = e.what();
4457 errorstream << "ServerError: " << error_message << std::endl;
4458 } catch (ModError &e) {
4459 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
4460 error_message = std::string("ModError: ") + e.what() +
4461 strgettext("\nCheck debug.txt for details.");
4462 errorstream << error_message << std::endl;