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 SimpleSoundSpec m_player_rightpunch_sound;
269 SoundMaker(ISoundManager *sound, const NodeDefManager *ndef):
272 makes_footstep_sound(true),
273 m_player_step_timer(0.0f),
274 m_player_jump_timer(0.0f)
278 void playPlayerStep()
280 if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
281 m_player_step_timer = 0.03;
282 if (makes_footstep_sound)
283 m_sound->playSound(m_player_step_sound);
287 void playPlayerJump()
289 if (m_player_jump_timer <= 0.0f) {
290 m_player_jump_timer = 0.2f;
291 m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f));
295 static void viewBobbingStep(MtEvent *e, void *data)
297 SoundMaker *sm = (SoundMaker *)data;
298 sm->playPlayerStep();
301 static void playerRegainGround(MtEvent *e, void *data)
303 SoundMaker *sm = (SoundMaker *)data;
304 sm->playPlayerStep();
307 static void playerJump(MtEvent *e, void *data)
309 SoundMaker *sm = (SoundMaker *)data;
310 sm->playPlayerJump();
313 static void cameraPunchLeft(MtEvent *e, void *data)
315 SoundMaker *sm = (SoundMaker *)data;
316 sm->m_sound->playSound(sm->m_player_leftpunch_sound);
319 static void cameraPunchRight(MtEvent *e, void *data)
321 SoundMaker *sm = (SoundMaker *)data;
322 sm->m_sound->playSound(sm->m_player_rightpunch_sound);
325 static void nodeDug(MtEvent *e, void *data)
327 SoundMaker *sm = (SoundMaker *)data;
328 NodeDugEvent *nde = (NodeDugEvent *)e;
329 sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug);
332 static void playerDamage(MtEvent *e, void *data)
334 SoundMaker *sm = (SoundMaker *)data;
335 sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5));
338 static void playerFallingDamage(MtEvent *e, void *data)
340 SoundMaker *sm = (SoundMaker *)data;
341 sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5));
344 void registerReceiver(MtEventManager *mgr)
346 mgr->reg(MtEvent::VIEW_BOBBING_STEP, SoundMaker::viewBobbingStep, this);
347 mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, this);
348 mgr->reg(MtEvent::PLAYER_JUMP, SoundMaker::playerJump, this);
349 mgr->reg(MtEvent::CAMERA_PUNCH_LEFT, SoundMaker::cameraPunchLeft, this);
350 mgr->reg(MtEvent::CAMERA_PUNCH_RIGHT, SoundMaker::cameraPunchRight, this);
351 mgr->reg(MtEvent::NODE_DUG, SoundMaker::nodeDug, this);
352 mgr->reg(MtEvent::PLAYER_DAMAGE, SoundMaker::playerDamage, this);
353 mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, this);
356 void step(float dtime)
358 m_player_step_timer -= dtime;
359 m_player_jump_timer -= dtime;
363 // Locally stored sounds don't need to be preloaded because of this
364 class GameOnDemandSoundFetcher: public OnDemandSoundFetcher
366 std::set<std::string> m_fetched;
368 void paths_insert(std::set<std::string> &dst_paths,
369 const std::string &base,
370 const std::string &name)
372 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg");
373 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg");
374 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg");
375 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg");
376 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg");
377 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg");
378 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg");
379 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg");
380 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg");
381 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg");
382 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg");
385 void fetchSounds(const std::string &name,
386 std::set<std::string> &dst_paths,
387 std::set<std::string> &dst_datas)
389 if (m_fetched.count(name))
392 m_fetched.insert(name);
394 paths_insert(dst_paths, porting::path_share, name);
395 paths_insert(dst_paths, porting::path_user, name);
400 typedef s32 SamplerLayer_t;
403 class GameGlobalShaderConstantSetter : public IShaderConstantSetter
407 bool *m_force_fog_off;
410 CachedPixelShaderSetting<float, 4> m_sky_bg_color;
411 CachedPixelShaderSetting<float> m_fog_distance;
412 CachedVertexShaderSetting<float> m_animation_timer_vertex;
413 CachedPixelShaderSetting<float> m_animation_timer_pixel;
414 CachedPixelShaderSetting<float, 3> m_day_light;
415 CachedPixelShaderSetting<float, 4> m_star_color;
416 CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
417 CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
418 CachedPixelShaderSetting<float, 3> m_minimap_yaw;
419 CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
420 CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
421 CachedPixelShaderSetting<SamplerLayer_t> m_texture0;
422 CachedPixelShaderSetting<SamplerLayer_t> m_texture1;
423 CachedPixelShaderSetting<SamplerLayer_t> m_texture2;
424 CachedPixelShaderSetting<SamplerLayer_t> m_texture3;
425 CachedPixelShaderSetting<float, 2> m_texel_size0;
426 std::array<float, 2> m_texel_size0_values;
427 CachedPixelShaderSetting<float> m_exposure_factor_pixel;
428 float m_user_exposure_factor;
429 bool m_bloom_enabled;
430 CachedPixelShaderSetting<float> m_bloom_intensity_pixel;
431 float m_bloom_intensity;
432 CachedPixelShaderSetting<float> m_bloom_radius_pixel;
433 float m_bloom_radius;
436 void onSettingsChange(const std::string &name)
438 if (name == "enable_fog")
439 m_fog_enabled = g_settings->getBool("enable_fog");
440 if (name == "exposure_factor")
441 m_user_exposure_factor = g_settings->getFloat("exposure_factor", 0.1f, 10.0f);
442 if (name == "bloom_intensity")
443 m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f);
444 if (name == "bloom_radius")
445 m_bloom_radius = g_settings->getFloat("bloom_radius", 1.0f, 64.0f);
448 static void settingsCallback(const std::string &name, void *userdata)
450 reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
453 void setSky(Sky *sky) { m_sky = sky; }
455 GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
456 f32 *fog_range, Client *client) :
459 m_force_fog_off(force_fog_off),
460 m_fog_range(fog_range),
461 m_sky_bg_color("skyBgColor"),
462 m_fog_distance("fogDistance"),
463 m_animation_timer_vertex("animationTimer"),
464 m_animation_timer_pixel("animationTimer"),
465 m_day_light("dayLight"),
466 m_star_color("starColor"),
467 m_eye_position_pixel("eyePosition"),
468 m_eye_position_vertex("eyePosition"),
469 m_minimap_yaw("yawVec"),
470 m_camera_offset_pixel("cameraOffset"),
471 m_camera_offset_vertex("cameraOffset"),
472 m_texture0("texture0"),
473 m_texture1("texture1"),
474 m_texture2("texture2"),
475 m_texture3("texture3"),
476 m_texel_size0("texelSize0"),
477 m_exposure_factor_pixel("exposureFactor"),
478 m_bloom_intensity_pixel("bloomIntensity"),
479 m_bloom_radius_pixel("bloomRadius")
481 g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
482 g_settings->registerChangedCallback("exposure_factor", settingsCallback, this);
483 g_settings->registerChangedCallback("bloom_intensity", settingsCallback, this);
484 g_settings->registerChangedCallback("bloom_radius", settingsCallback, this);
485 m_fog_enabled = g_settings->getBool("enable_fog");
486 m_user_exposure_factor = g_settings->getFloat("exposure_factor", 0.1f, 10.0f);
487 m_bloom_enabled = g_settings->getBool("enable_bloom");
488 m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f);
489 m_bloom_radius = g_settings->getFloat("bloom_radius", 1.0f, 64.0f);
492 ~GameGlobalShaderConstantSetter()
494 g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
497 void onSetConstants(video::IMaterialRendererServices *services) override
500 video::SColor bgcolor = m_sky->getBgColor();
501 video::SColorf bgcolorf(bgcolor);
502 float bgcolorfa[4] = {
508 m_sky_bg_color.set(bgcolorfa, services);
511 float fog_distance = 10000 * BS;
513 if (m_fog_enabled && !*m_force_fog_off)
514 fog_distance = *m_fog_range;
516 m_fog_distance.set(&fog_distance, services);
518 u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
519 video::SColorf sunlight;
520 get_sunlight_color(&sunlight, daynight_ratio);
525 m_day_light.set(dnc, services);
527 video::SColorf star_color = m_sky->getCurrentStarColor();
528 float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a};
529 m_star_color.set(clr, services);
531 u32 animation_timer = m_client->getEnv().getFrameTime() % 1000000;
532 float animation_timer_f = (float)animation_timer / 100000.f;
533 m_animation_timer_vertex.set(&animation_timer_f, services);
534 m_animation_timer_pixel.set(&animation_timer_f, services);
536 float eye_position_array[3];
537 v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
538 epos.getAs3Values(eye_position_array);
539 m_eye_position_pixel.set(eye_position_array, services);
540 m_eye_position_vertex.set(eye_position_array, services);
542 if (m_client->getMinimap()) {
543 float minimap_yaw_array[3];
544 v3f minimap_yaw = m_client->getMinimap()->getYawVec();
545 minimap_yaw.getAs3Values(minimap_yaw_array);
546 m_minimap_yaw.set(minimap_yaw_array, services);
549 float camera_offset_array[3];
550 v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
551 offset.getAs3Values(camera_offset_array);
552 m_camera_offset_pixel.set(camera_offset_array, services);
553 m_camera_offset_vertex.set(camera_offset_array, services);
555 SamplerLayer_t tex_id;
557 m_texture0.set(&tex_id, services);
559 m_texture1.set(&tex_id, services);
561 m_texture2.set(&tex_id, services);
563 m_texture3.set(&tex_id, services);
565 m_texel_size0.set(m_texel_size0_values.data(), services);
567 float exposure_factor = RenderingEngine::DEFAULT_EXPOSURE_FACTOR * m_user_exposure_factor;
568 if (std::isnan(exposure_factor))
569 exposure_factor = RenderingEngine::DEFAULT_EXPOSURE_FACTOR;
570 m_exposure_factor_pixel.set(&exposure_factor, services);
572 if (m_bloom_enabled) {
573 m_bloom_intensity_pixel.set(&m_bloom_intensity, services);
574 m_bloom_radius_pixel.set(&m_bloom_radius, services);
578 void onSetMaterial(const video::SMaterial &material)
580 video::ITexture *texture = material.getTexture(0);
582 core::dimension2du size = texture->getSize();
583 m_texel_size0_values[0] = 1.f / size.Width;
584 m_texel_size0_values[1] = 1.f / size.Height;
587 m_texel_size0_values[0] = 0.f;
588 m_texel_size0_values[1] = 0.f;
594 class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
597 bool *m_force_fog_off;
600 std::vector<GameGlobalShaderConstantSetter *> created_nosky;
602 GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
603 f32 *fog_range, Client *client) :
605 m_force_fog_off(force_fog_off),
606 m_fog_range(fog_range),
610 void setSky(Sky *sky) {
612 for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
613 ggscs->setSky(m_sky);
615 created_nosky.clear();
618 virtual IShaderConstantSetter* create()
620 auto *scs = new GameGlobalShaderConstantSetter(
621 m_sky, m_force_fog_off, m_fog_range, m_client);
623 created_nosky.push_back(scs);
628 #ifdef HAVE_TOUCHSCREENGUI
629 #define SIZE_TAG "size[11,5.5]"
631 #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
634 /****************************************************************************
635 ****************************************************************************/
637 const static float object_hit_delay = 0.2;
640 FpsControl() : last_time(0), busy_time(0), sleep_time(0) {}
644 void limit(IrrlichtDevice *device, f32 *dtime);
646 u32 getBusyMs() const { return busy_time / 1000; }
648 // all values in microseconds (us)
649 u64 last_time, busy_time, sleep_time;
653 /* The reason the following structs are not anonymous structs within the
654 * class is that they are not used by the majority of member functions and
655 * many functions that do require objects of thse types do not modify them
656 * (so they can be passed as a const qualified parameter)
662 PointedThing pointed_old;
665 bool btn_down_for_dig;
667 bool digging_blocked;
668 bool reset_jump_timer;
669 float nodig_delay_timer;
671 float dig_time_complete;
672 float repeat_place_timer;
673 float object_hit_delay_timer;
674 float time_from_last_punch;
675 ClientActiveObject *selected_object;
677 float jump_timer_up; // from key up until key down
678 float jump_timer_down; // since last key down
679 float jump_timer_down_before; // from key down until key down again
682 float update_draw_list_timer;
686 v3f update_draw_list_last_cam_dir;
688 float time_of_day_smooth;
693 struct ClientEventHandler
695 void (Game::*handler)(ClientEvent *, CameraOrientation *);
698 /****************************************************************************
700 ****************************************************************************/
702 using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
704 /* This is not intended to be a public class. If a public class becomes
705 * desirable then it may be better to create another 'wrapper' class that
706 * hides most of the stuff in this class (nothing in this class is required
707 * by any other file) but exposes the public methods/data only.
714 bool startup(bool *kill,
716 RenderingEngine *rendering_engine,
717 const GameStartData &game_params,
718 std::string &error_message,
720 ChatBackend *chat_backend);
727 // Basic initialisation
728 bool init(const std::string &map_dir, const std::string &address,
729 u16 port, const SubgameSpec &gamespec);
731 bool createSingleplayerServer(const std::string &map_dir,
732 const SubgameSpec &gamespec, u16 port);
735 bool createClient(const GameStartData &start_data);
739 bool connectToServer(const GameStartData &start_data,
740 bool *connect_ok, bool *aborted);
741 bool getServerContent(bool *aborted);
745 void updateInteractTimers(f32 dtime);
746 bool checkConnection();
747 bool handleCallbacks();
748 void processQueues();
749 void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
750 void updateDebugState();
751 void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
752 void updateProfilerGraphs(ProfilerGraph *graph);
755 void processUserInput(f32 dtime);
756 void processKeyInput();
757 void processItemSelection(u16 *new_playeritem);
759 void dropSelectedItem(bool single_item = false);
760 void openInventory();
761 void openConsole(float scale, const wchar_t *line=NULL);
762 void toggleFreeMove();
763 void toggleFreeMoveAlt();
764 void togglePitchMove();
767 void toggleCinematic();
768 void toggleBlockBounds();
769 void toggleAutoforward();
771 void toggleMinimap(bool shift_pressed);
774 void toggleUpdateCamera();
776 void increaseViewRange();
777 void decreaseViewRange();
778 void toggleFullViewRange();
779 void checkZoomEnabled();
781 void updateCameraDirection(CameraOrientation *cam, float dtime);
782 void updateCameraOrientation(CameraOrientation *cam, float dtime);
783 void updatePlayerControl(const CameraOrientation &cam);
784 void step(f32 dtime);
785 void processClientEvents(CameraOrientation *cam);
786 void updateCamera(f32 dtime);
787 void updateSound(f32 dtime);
788 void processPlayerInteraction(f32 dtime, bool show_hud);
790 * Returns the object or node the player is pointing at.
791 * Also updates the selected thing in the Hud.
793 * @param[in] shootline the shootline, starting from
794 * the camera position. This also gives the maximal distance
796 * @param[in] liquids_pointable if false, liquids are ignored
797 * @param[in] look_for_object if false, objects are ignored
798 * @param[in] camera_offset offset of the camera
799 * @param[out] selected_object the selected object or
802 PointedThing updatePointedThing(
803 const core::line3d<f32> &shootline, bool liquids_pointable,
804 bool look_for_object, const v3s16 &camera_offset);
805 void handlePointingAtNothing(const ItemStack &playerItem);
806 void handlePointingAtNode(const PointedThing &pointed,
807 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
808 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
809 const v3f &player_position, bool show_debug);
810 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
811 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
812 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
813 const CameraOrientation &cam);
814 void updateShadows();
817 void showOverlayMessage(const char *msg, float dtime, int percent,
818 bool draw_clouds = true);
820 static void settingChangedCallback(const std::string &setting_name, void *data);
823 inline bool isKeyDown(GameKeyType k)
825 return input->isKeyDown(k);
827 inline bool wasKeyDown(GameKeyType k)
829 return input->wasKeyDown(k);
831 inline bool wasKeyPressed(GameKeyType k)
833 return input->wasKeyPressed(k);
835 inline bool wasKeyReleased(GameKeyType k)
837 return input->wasKeyReleased(k);
841 void handleAndroidChatInput();
846 bool force_fog_off = false;
847 bool disable_camera_update = false;
850 void showDeathFormspec();
851 void showPauseMenu();
853 void pauseAnimation();
854 void resumeAnimation();
856 // ClientEvent handlers
857 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
858 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
859 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
860 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
861 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
862 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
863 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
864 CameraOrientation *cam);
865 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
866 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
867 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
868 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
869 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
870 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
871 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
872 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
873 CameraOrientation *cam);
874 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
876 void updateChat(f32 dtime);
878 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
879 const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
880 const NodeMetadata *meta);
881 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
883 f32 getSensitivityScaleFactor() const;
885 InputHandler *input = nullptr;
887 Client *client = nullptr;
888 Server *server = nullptr;
890 IWritableTextureSource *texture_src = nullptr;
891 IWritableShaderSource *shader_src = nullptr;
893 // When created, these will be filled with data received from the server
894 IWritableItemDefManager *itemdef_manager = nullptr;
895 NodeDefManager *nodedef_manager = nullptr;
897 GameOnDemandSoundFetcher soundfetcher; // useful when testing
898 ISoundManager *sound = nullptr;
899 bool sound_is_dummy = false;
900 SoundMaker *soundmaker = nullptr;
902 ChatBackend *chat_backend = nullptr;
903 LogOutputBuffer m_chat_log_buf;
905 EventManager *eventmgr = nullptr;
906 QuicktuneShortcutter *quicktune = nullptr;
908 std::unique_ptr<GameUI> m_game_ui;
909 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
910 MapDrawControl *draw_control = nullptr;
911 Camera *camera = nullptr;
912 Clouds *clouds = nullptr; // Free using ->Drop()
913 Sky *sky = nullptr; // Free using ->Drop()
915 Minimap *mapper = nullptr;
917 // Map server hud ids to client hud ids
918 std::unordered_map<u32, u32> m_hud_server_to_client;
924 This class does take ownership/responsibily for cleaning up etc of any of
925 these items (e.g. device)
927 IrrlichtDevice *device;
928 RenderingEngine *m_rendering_engine;
929 video::IVideoDriver *driver;
930 scene::ISceneManager *smgr;
932 std::string *error_message;
933 bool *reconnect_requested;
934 scene::ISceneNode *skybox;
935 PausedNodesList paused_animated_nodes;
937 bool simple_singleplayer_mode;
940 /* Pre-calculated values
942 int crack_animation_length;
944 IntervalLimiter profiler_interval;
947 * TODO: Local caching of settings is not optimal and should at some stage
948 * be updated to use a global settings object for getting thse values
949 * (as opposed to the this local caching). This can be addressed in
952 bool m_cache_doubletap_jump;
953 bool m_cache_enable_clouds;
954 bool m_cache_enable_joysticks;
955 bool m_cache_enable_particles;
956 bool m_cache_enable_fog;
957 bool m_cache_enable_noclip;
958 bool m_cache_enable_free_move;
959 f32 m_cache_mouse_sensitivity;
960 f32 m_cache_joystick_frustum_sensitivity;
961 f32 m_repeat_place_time;
962 f32 m_cache_cam_smoothing;
963 f32 m_cache_fog_start;
965 bool m_invert_mouse = false;
966 bool m_first_loop_after_window_activation = false;
967 bool m_camera_offset_changed = false;
969 bool m_does_lost_focus_pause_game = false;
971 // if true, (almost) the whole game is paused
972 // this happens in pause menu in singleplayer
973 bool m_is_paused = false;
975 #if IRRLICHT_VERSION_MT_REVISION < 5
976 int m_reset_HW_buffer_counter = 0;
979 #ifdef HAVE_TOUCHSCREENGUI
980 bool m_cache_hold_aux1;
981 bool m_touch_use_crosshair;
982 inline bool isNoCrosshairAllowed() {
983 return !m_touch_use_crosshair && camera->getCameraMode() == CAMERA_MODE_FIRST;
987 bool m_android_chat_open;
992 m_chat_log_buf(g_logger),
993 m_game_ui(new GameUI())
995 g_settings->registerChangedCallback("doubletap_jump",
996 &settingChangedCallback, this);
997 g_settings->registerChangedCallback("enable_clouds",
998 &settingChangedCallback, this);
999 g_settings->registerChangedCallback("doubletap_joysticks",
1000 &settingChangedCallback, this);
1001 g_settings->registerChangedCallback("enable_particles",
1002 &settingChangedCallback, this);
1003 g_settings->registerChangedCallback("enable_fog",
1004 &settingChangedCallback, this);
1005 g_settings->registerChangedCallback("mouse_sensitivity",
1006 &settingChangedCallback, this);
1007 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
1008 &settingChangedCallback, this);
1009 g_settings->registerChangedCallback("repeat_place_time",
1010 &settingChangedCallback, this);
1011 g_settings->registerChangedCallback("noclip",
1012 &settingChangedCallback, this);
1013 g_settings->registerChangedCallback("free_move",
1014 &settingChangedCallback, this);
1015 g_settings->registerChangedCallback("cinematic",
1016 &settingChangedCallback, this);
1017 g_settings->registerChangedCallback("cinematic_camera_smoothing",
1018 &settingChangedCallback, this);
1019 g_settings->registerChangedCallback("camera_smoothing",
1020 &settingChangedCallback, this);
1024 #ifdef HAVE_TOUCHSCREENGUI
1025 m_cache_hold_aux1 = false; // This is initialised properly later
1032 /****************************************************************************
1034 ****************************************************************************/
1040 if (!sound_is_dummy)
1043 delete server; // deleted first to stop all server threads
1051 delete nodedef_manager;
1052 delete itemdef_manager;
1053 delete draw_control;
1055 clearTextureNameCache();
1057 g_settings->deregisterChangedCallback("doubletap_jump",
1058 &settingChangedCallback, this);
1059 g_settings->deregisterChangedCallback("enable_clouds",
1060 &settingChangedCallback, this);
1061 g_settings->deregisterChangedCallback("enable_particles",
1062 &settingChangedCallback, this);
1063 g_settings->deregisterChangedCallback("enable_fog",
1064 &settingChangedCallback, this);
1065 g_settings->deregisterChangedCallback("mouse_sensitivity",
1066 &settingChangedCallback, this);
1067 g_settings->deregisterChangedCallback("repeat_place_time",
1068 &settingChangedCallback, this);
1069 g_settings->deregisterChangedCallback("noclip",
1070 &settingChangedCallback, this);
1071 g_settings->deregisterChangedCallback("free_move",
1072 &settingChangedCallback, this);
1073 g_settings->deregisterChangedCallback("cinematic",
1074 &settingChangedCallback, this);
1075 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
1076 &settingChangedCallback, this);
1077 g_settings->deregisterChangedCallback("camera_smoothing",
1078 &settingChangedCallback, this);
1081 bool Game::startup(bool *kill,
1082 InputHandler *input,
1083 RenderingEngine *rendering_engine,
1084 const GameStartData &start_data,
1085 std::string &error_message,
1087 ChatBackend *chat_backend)
1091 m_rendering_engine = rendering_engine;
1092 device = m_rendering_engine->get_raw_device();
1094 this->error_message = &error_message;
1095 reconnect_requested = reconnect;
1096 this->input = input;
1097 this->chat_backend = chat_backend;
1098 simple_singleplayer_mode = start_data.isSinglePlayer();
1100 input->keycache.populate();
1102 driver = device->getVideoDriver();
1103 smgr = m_rendering_engine->get_scene_manager();
1105 smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1108 runData = GameRunData();
1109 runData.time_from_last_punch = 10.0;
1111 m_game_ui->initFlags();
1113 m_invert_mouse = g_settings->getBool("invert_mouse");
1114 m_first_loop_after_window_activation = true;
1116 #ifdef HAVE_TOUCHSCREENGUI
1117 m_touch_use_crosshair = g_settings->getBool("touch_use_crosshair");
1120 g_client_translations->clear();
1122 // address can change if simple_singleplayer_mode
1123 if (!init(start_data.world_spec.path, start_data.address,
1124 start_data.socket_port, start_data.game_spec))
1127 if (!createClient(start_data))
1130 m_rendering_engine->initialize(client, hud);
1138 ProfilerGraph graph;
1139 RunStats stats = {};
1140 CameraOrientation cam_view_target = {};
1141 CameraOrientation cam_view = {};
1142 FpsControl draw_times;
1143 f32 dtime; // in seconds
1145 /* Clear the profiler */
1146 Profiler::GraphValues dummyvalues;
1147 g_profiler->graphGet(dummyvalues);
1151 set_light_table(g_settings->getFloat("display_gamma"));
1153 #ifdef HAVE_TOUCHSCREENGUI
1154 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1155 && client->checkPrivilege("fast");
1158 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1159 g_settings->getU16("screen_h"));
1161 while (m_rendering_engine->run()
1162 && !(*kill || g_gamecallback->shutdown_requested
1163 || (server && server->isShutdownRequested()))) {
1165 const irr::core::dimension2d<u32> ¤t_screen_size =
1166 m_rendering_engine->get_video_driver()->getScreenSize();
1167 // Verify if window size has changed and save it if it's the case
1168 // Ensure evaluating settings->getBool after verifying screensize
1169 // First condition is cheaper
1170 if (previous_screen_size != current_screen_size &&
1171 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1172 g_settings->getBool("autosave_screensize")) {
1173 g_settings->setU16("screen_w", current_screen_size.Width);
1174 g_settings->setU16("screen_h", current_screen_size.Height);
1175 previous_screen_size = current_screen_size;
1178 // Calculate dtime =
1179 // m_rendering_engine->run() from this iteration
1180 // + Sleep time until the wanted FPS are reached
1181 draw_times.limit(device, &dtime);
1183 // Prepare render data for next iteration
1185 updateStats(&stats, draw_times, dtime);
1186 updateInteractTimers(dtime);
1188 if (!checkConnection())
1190 if (!handleCallbacks())
1195 m_game_ui->clearInfoText();
1196 hud->resizeHotbar();
1199 updateProfilers(stats, draw_times, dtime);
1200 processUserInput(dtime);
1201 // Update camera before player movement to avoid camera lag of one frame
1202 updateCameraDirection(&cam_view_target, dtime);
1203 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1204 cam_view.camera_yaw) * m_cache_cam_smoothing;
1205 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1206 cam_view.camera_pitch) * m_cache_cam_smoothing;
1207 updatePlayerControl(cam_view);
1210 bool was_paused = m_is_paused;
1211 m_is_paused = simple_singleplayer_mode && g_menumgr.pausesGame();
1215 if (!was_paused && m_is_paused)
1217 else if (was_paused && !m_is_paused)
1223 processClientEvents(&cam_view_target);
1225 updateCamera(dtime);
1227 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
1228 updateFrame(&graph, &stats, dtime, cam_view);
1229 updateProfilerGraphs(&graph);
1231 // Update if minimap has been disabled by the server
1232 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1234 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1241 void Game::shutdown()
1243 m_rendering_engine->finalize();
1244 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1245 if (g_settings->get("3d_mode") == "pageflip") {
1246 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1249 auto formspec = m_game_ui->getFormspecGUI();
1251 formspec->quitMenu();
1253 #ifdef HAVE_TOUCHSCREENGUI
1254 g_touchscreengui->hide();
1257 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1262 if (gui_chat_console)
1263 gui_chat_console->drop();
1269 while (g_menumgr.menuCount() > 0) {
1270 g_menumgr.m_stack.front()->setVisible(false);
1271 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1274 m_game_ui->deleteFormspec();
1276 chat_backend->addMessage(L"", L"# Disconnected.");
1277 chat_backend->addMessage(L"", L"");
1278 m_chat_log_buf.clear();
1282 while (!client->isShutdown()) {
1283 assert(texture_src != NULL);
1284 assert(shader_src != NULL);
1285 texture_src->processQueue();
1286 shader_src->processQueue();
1293 /****************************************************************************/
1294 /****************************************************************************
1296 ****************************************************************************/
1297 /****************************************************************************/
1300 const std::string &map_dir,
1301 const std::string &address,
1303 const SubgameSpec &gamespec)
1305 texture_src = createTextureSource();
1307 showOverlayMessage(N_("Loading..."), 0, 0);
1309 shader_src = createShaderSource();
1311 itemdef_manager = createItemDefManager();
1312 nodedef_manager = createNodeDefManager();
1314 eventmgr = new EventManager();
1315 quicktune = new QuicktuneShortcutter();
1317 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1318 && eventmgr && quicktune))
1324 // Create a server if not connecting to an existing one
1325 if (address.empty()) {
1326 if (!createSingleplayerServer(map_dir, gamespec, port))
1333 bool Game::initSound()
1336 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1337 infostream << "Attempting to use OpenAL audio" << std::endl;
1338 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1340 infostream << "Failed to initialize OpenAL audio" << std::endl;
1342 infostream << "Sound disabled." << std::endl;
1346 infostream << "Using dummy audio." << std::endl;
1347 sound = &dummySoundManager;
1348 sound_is_dummy = true;
1351 soundmaker = new SoundMaker(sound, nodedef_manager);
1355 soundmaker->registerReceiver(eventmgr);
1360 bool Game::createSingleplayerServer(const std::string &map_dir,
1361 const SubgameSpec &gamespec, u16 port)
1363 showOverlayMessage(N_("Creating server..."), 0, 5);
1365 std::string bind_str = g_settings->get("bind_address");
1366 Address bind_addr(0, 0, 0, 0, port);
1368 if (g_settings->getBool("ipv6_server")) {
1369 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1373 bind_addr.Resolve(bind_str.c_str());
1374 } catch (ResolveError &e) {
1375 infostream << "Resolving bind address \"" << bind_str
1376 << "\" failed: " << e.what()
1377 << " -- Listening on all addresses." << std::endl;
1380 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1381 *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
1382 bind_addr.serializeString().c_str());
1383 errorstream << *error_message << std::endl;
1387 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1388 false, nullptr, error_message);
1394 bool Game::createClient(const GameStartData &start_data)
1396 showOverlayMessage(N_("Creating client..."), 0, 10);
1398 draw_control = new MapDrawControl();
1402 bool could_connect, connect_aborted;
1403 #ifdef HAVE_TOUCHSCREENGUI
1404 if (g_touchscreengui) {
1405 g_touchscreengui->init(texture_src);
1406 g_touchscreengui->hide();
1409 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1412 if (!could_connect) {
1413 if (error_message->empty() && !connect_aborted) {
1414 // Should not happen if error messages are set properly
1415 *error_message = gettext("Connection failed for unknown reason");
1416 errorstream << *error_message << std::endl;
1421 if (!getServerContent(&connect_aborted)) {
1422 if (error_message->empty() && !connect_aborted) {
1423 // Should not happen if error messages are set properly
1424 *error_message = gettext("Connection failed for unknown reason");
1425 errorstream << *error_message << std::endl;
1430 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1431 &m_flags.force_fog_off, &runData.fog_range, client);
1432 shader_src->addShaderConstantSetterFactory(scsf);
1434 // Update cached textures, meshes and materials
1435 client->afterContentReceived();
1439 camera = new Camera(*draw_control, client, m_rendering_engine);
1440 if (client->modsLoaded())
1441 client->getScript()->on_camera_ready(camera);
1442 client->setCamera(camera);
1446 if (m_cache_enable_clouds)
1447 clouds = new Clouds(smgr, -1, time(0));
1451 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
1453 skybox = NULL; // This is used/set later on in the main run loop
1455 /* Pre-calculated values
1457 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1459 v2u32 size = t->getOriginalSize();
1460 crack_animation_length = size.Y / size.X;
1462 crack_animation_length = 5;
1468 /* Set window caption
1470 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1472 str += utf8_to_wide(g_version_hash);
1474 const wchar_t *text = nullptr;
1475 if (simple_singleplayer_mode)
1476 text = wgettext("Singleplayer");
1478 text = wgettext("Multiplayer");
1485 str += driver->getName();
1488 device->setWindowCaption(str.c_str());
1490 LocalPlayer *player = client->getEnv().getLocalPlayer();
1491 player->hurt_tilt_timer = 0;
1492 player->hurt_tilt_strength = 0;
1494 hud = new Hud(client, player, &player->inventory);
1496 mapper = client->getMinimap();
1498 if (mapper && client->modsLoaded())
1499 client->getScript()->on_minimap_ready(mapper);
1504 bool Game::initGui()
1508 // Remove stale "recent" chat messages from previous connections
1509 chat_backend->clearRecentChat();
1511 // Make sure the size of the recent messages buffer is right
1512 chat_backend->applySettings();
1514 // Chat backend and console
1515 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1516 -1, chat_backend, client, &g_menumgr);
1518 #ifdef HAVE_TOUCHSCREENGUI
1520 if (g_touchscreengui)
1521 g_touchscreengui->show();
1528 bool Game::connectToServer(const GameStartData &start_data,
1529 bool *connect_ok, bool *connection_aborted)
1531 *connect_ok = false; // Let's not be overly optimistic
1532 *connection_aborted = false;
1533 bool local_server_mode = false;
1535 showOverlayMessage(N_("Resolving address..."), 0, 15);
1537 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1540 connect_address.Resolve(start_data.address.c_str());
1542 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1543 if (connect_address.isIPv6()) {
1544 IPv6AddressBytes addr_bytes;
1545 addr_bytes.bytes[15] = 1;
1546 connect_address.setAddress(&addr_bytes);
1548 connect_address.setAddress(127, 0, 0, 1);
1550 local_server_mode = true;
1552 } catch (ResolveError &e) {
1553 *error_message = fmtgettext("Couldn't resolve address: %s", e.what());
1555 errorstream << *error_message << std::endl;
1559 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1560 *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
1561 errorstream << *error_message << std::endl;
1566 client = new Client(start_data.name.c_str(),
1567 start_data.password, start_data.address,
1568 *draw_control, texture_src, shader_src,
1569 itemdef_manager, nodedef_manager, sound, eventmgr,
1570 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get(),
1571 start_data.allow_login_or_register);
1572 client->migrateModStorage();
1573 } catch (const BaseException &e) {
1574 *error_message = fmtgettext("Error creating client: %s", e.what());
1575 errorstream << *error_message << std::endl;
1579 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1581 infostream << "Connecting to server at ";
1582 connect_address.print(infostream);
1583 infostream << std::endl;
1585 client->connect(connect_address,
1586 simple_singleplayer_mode || local_server_mode);
1589 Wait for server to accept connection
1595 FpsControl fps_control;
1597 f32 wait_time = 0; // in seconds
1599 fps_control.reset();
1601 while (m_rendering_engine->run()) {
1603 fps_control.limit(device, &dtime);
1605 // Update client and server
1606 client->step(dtime);
1609 server->step(dtime);
1612 if (client->getState() == LC_Init) {
1618 if (*connection_aborted)
1621 if (client->accessDenied()) {
1622 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1623 *reconnect_requested = client->reconnectRequested();
1624 errorstream << *error_message << std::endl;
1628 if (input->cancelPressed()) {
1629 *connection_aborted = true;
1630 infostream << "Connect aborted [Escape]" << std::endl;
1635 // Only time out if we aren't waiting for the server we started
1636 if (!start_data.address.empty() && wait_time > 10) {
1637 *error_message = gettext("Connection timed out.");
1638 errorstream << *error_message << std::endl;
1643 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1645 } catch (con::PeerNotFoundException &e) {
1646 // TODO: Should something be done here? At least an info/error
1654 bool Game::getServerContent(bool *aborted)
1658 FpsControl fps_control;
1659 f32 dtime; // in seconds
1661 fps_control.reset();
1663 while (m_rendering_engine->run()) {
1665 fps_control.limit(device, &dtime);
1667 // Update client and server
1668 client->step(dtime);
1671 server->step(dtime);
1674 if (client->mediaReceived() && client->itemdefReceived() &&
1675 client->nodedefReceived()) {
1680 if (!checkConnection())
1683 if (client->getState() < LC_Init) {
1684 *error_message = gettext("Client disconnected");
1685 errorstream << *error_message << std::endl;
1689 if (input->cancelPressed()) {
1691 infostream << "Connect aborted [Escape]" << std::endl;
1698 if (!client->itemdefReceived()) {
1699 const wchar_t *text = wgettext("Item definitions...");
1701 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1704 } else if (!client->nodedefReceived()) {
1705 const wchar_t *text = wgettext("Node definitions...");
1707 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1711 std::ostringstream message;
1712 std::fixed(message);
1713 message.precision(0);
1714 float receive = client->mediaReceiveProgress() * 100;
1715 message << gettext("Media...");
1717 message << " " << receive << "%";
1718 message.precision(2);
1720 if ((USE_CURL == 0) ||
1721 (!g_settings->getBool("enable_remote_media_server"))) {
1722 float cur = client->getCurRate();
1723 std::string cur_unit = gettext("KiB/s");
1727 cur_unit = gettext("MiB/s");
1730 message << " (" << cur << ' ' << cur_unit << ")";
1733 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1734 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
1735 texture_src, dtime, progress);
1743 /****************************************************************************/
1744 /****************************************************************************
1746 ****************************************************************************/
1747 /****************************************************************************/
1749 inline void Game::updateInteractTimers(f32 dtime)
1751 if (runData.nodig_delay_timer >= 0)
1752 runData.nodig_delay_timer -= dtime;
1754 if (runData.object_hit_delay_timer >= 0)
1755 runData.object_hit_delay_timer -= dtime;
1757 runData.time_from_last_punch += dtime;
1761 /* returns false if game should exit, otherwise true
1763 inline bool Game::checkConnection()
1765 if (client->accessDenied()) {
1766 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1767 *reconnect_requested = client->reconnectRequested();
1768 errorstream << *error_message << std::endl;
1776 /* returns false if game should exit, otherwise true
1778 inline bool Game::handleCallbacks()
1780 if (g_gamecallback->disconnect_requested) {
1781 g_gamecallback->disconnect_requested = false;
1785 if (g_gamecallback->changepassword_requested) {
1786 (new GUIPasswordChange(guienv, guiroot, -1,
1787 &g_menumgr, client, texture_src))->drop();
1788 g_gamecallback->changepassword_requested = false;
1791 if (g_gamecallback->changevolume_requested) {
1792 (new GUIVolumeChange(guienv, guiroot, -1,
1793 &g_menumgr, texture_src))->drop();
1794 g_gamecallback->changevolume_requested = false;
1797 if (g_gamecallback->keyconfig_requested) {
1798 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1799 &g_menumgr, texture_src))->drop();
1800 g_gamecallback->keyconfig_requested = false;
1803 if (g_gamecallback->keyconfig_changed) {
1804 input->keycache.populate(); // update the cache with new settings
1805 g_gamecallback->keyconfig_changed = false;
1812 void Game::processQueues()
1814 texture_src->processQueue();
1815 itemdef_manager->processQueue(client);
1816 shader_src->processQueue();
1819 void Game::updateDebugState()
1821 LocalPlayer *player = client->getEnv().getLocalPlayer();
1823 // debug UI and wireframe
1824 bool has_debug = client->checkPrivilege("debug");
1825 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
1827 if (m_game_ui->m_flags.show_basic_debug) {
1828 if (!has_basic_debug)
1829 m_game_ui->m_flags.show_basic_debug = false;
1830 } else if (m_game_ui->m_flags.show_minimal_debug) {
1831 if (has_basic_debug)
1832 m_game_ui->m_flags.show_basic_debug = true;
1834 if (!has_basic_debug)
1835 hud->disableBlockBounds();
1837 draw_control->show_wireframe = false;
1840 draw_control->allow_noclip = m_cache_enable_noclip && client->checkPrivilege("noclip");
1843 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1846 float profiler_print_interval =
1847 g_settings->getFloat("profiler_print_interval");
1848 bool print_to_log = true;
1850 if (profiler_print_interval == 0) {
1851 print_to_log = false;
1852 profiler_print_interval = 3;
1855 if (profiler_interval.step(dtime, profiler_print_interval)) {
1857 infostream << "Profiler:" << std::endl;
1858 g_profiler->print(infostream);
1861 m_game_ui->updateProfiler();
1862 g_profiler->clear();
1865 // Update update graphs
1866 g_profiler->graphAdd("Time non-rendering [us]",
1867 draw_times.busy_time - stats.drawtime);
1869 g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
1870 g_profiler->graphAdd("FPS", 1.0f / dtime);
1873 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1880 /* Time average and jitter calculation
1882 jp = &stats->dtime_jitter;
1883 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1885 jitter = dtime - jp->avg;
1887 if (jitter > jp->max)
1890 jp->counter += dtime;
1892 if (jp->counter > 0.0) {
1894 jp->max_sample = jp->max;
1895 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1899 /* Busytime average and jitter calculation
1901 jp = &stats->busy_time_jitter;
1902 jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
1904 jitter = draw_times.getBusyMs() - jp->avg;
1906 if (jitter > jp->max)
1908 if (jitter < jp->min)
1911 jp->counter += dtime;
1913 if (jp->counter > 0.0) {
1915 jp->max_sample = jp->max;
1916 jp->min_sample = jp->min;
1924 /****************************************************************************
1926 ****************************************************************************/
1928 void Game::processUserInput(f32 dtime)
1930 // Reset input if window not active or some menu is active
1931 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1933 #ifdef HAVE_TOUCHSCREENGUI
1934 g_touchscreengui->hide();
1937 #ifdef HAVE_TOUCHSCREENGUI
1938 else if (g_touchscreengui) {
1939 /* on touchscreengui step may generate own input events which ain't
1940 * what we want in case we just did clear them */
1941 g_touchscreengui->show();
1942 g_touchscreengui->step(dtime);
1946 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1947 gui_chat_console->closeConsoleAtOnce();
1950 // Input handler step() (used by the random input generator)
1954 auto formspec = m_game_ui->getFormspecGUI();
1956 formspec->getAndroidUIInput();
1958 handleAndroidChatInput();
1961 // Increase timer for double tap of "keymap_jump"
1962 if (m_cache_doubletap_jump && runData.jump_timer_up <= 0.2f)
1963 runData.jump_timer_up += dtime;
1964 if (m_cache_doubletap_jump && runData.jump_timer_down <= 0.4f)
1965 runData.jump_timer_down += dtime;
1968 processItemSelection(&runData.new_playeritem);
1972 void Game::processKeyInput()
1974 if (wasKeyDown(KeyType::DROP)) {
1975 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1976 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1977 toggleAutoforward();
1978 } else if (wasKeyDown(KeyType::BACKWARD)) {
1979 if (g_settings->getBool("continuous_forward"))
1980 toggleAutoforward();
1981 } else if (wasKeyDown(KeyType::INVENTORY)) {
1983 } else if (input->cancelPressed()) {
1985 m_android_chat_open = false;
1987 if (!gui_chat_console->isOpenInhibited()) {
1990 } else if (wasKeyDown(KeyType::CHAT)) {
1991 openConsole(0.2, L"");
1992 } else if (wasKeyDown(KeyType::CMD)) {
1993 openConsole(0.2, L"/");
1994 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1995 if (client->modsLoaded())
1996 openConsole(0.2, L".");
1998 m_game_ui->showTranslatedStatusText("Client side scripting is disabled");
1999 } else if (wasKeyDown(KeyType::CONSOLE)) {
2000 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
2001 } else if (wasKeyDown(KeyType::FREEMOVE)) {
2003 } else if (wasKeyDown(KeyType::JUMP)) {
2004 toggleFreeMoveAlt();
2005 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
2007 } else if (wasKeyDown(KeyType::FASTMOVE)) {
2009 } else if (wasKeyDown(KeyType::NOCLIP)) {
2012 } else if (wasKeyDown(KeyType::MUTE)) {
2013 if (g_settings->getBool("enable_sound")) {
2014 bool new_mute_sound = !g_settings->getBool("mute_sound");
2015 g_settings->setBool("mute_sound", new_mute_sound);
2017 m_game_ui->showTranslatedStatusText("Sound muted");
2019 m_game_ui->showTranslatedStatusText("Sound unmuted");
2021 m_game_ui->showTranslatedStatusText("Sound system is disabled");
2023 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
2024 if (g_settings->getBool("enable_sound")) {
2025 float new_volume = g_settings->getFloat("sound_volume", 0.0f, 0.9f) + 0.1f;
2026 g_settings->setFloat("sound_volume", new_volume);
2027 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
2028 m_game_ui->showStatusText(msg);
2030 m_game_ui->showTranslatedStatusText("Sound system is disabled");
2032 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
2033 if (g_settings->getBool("enable_sound")) {
2034 float new_volume = g_settings->getFloat("sound_volume", 0.1f, 1.0f) - 0.1f;
2035 g_settings->setFloat("sound_volume", new_volume);
2036 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
2037 m_game_ui->showStatusText(msg);
2039 m_game_ui->showTranslatedStatusText("Sound system is disabled");
2042 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
2043 || wasKeyDown(KeyType::DEC_VOLUME)) {
2044 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
2046 } else if (wasKeyDown(KeyType::CINEMATIC)) {
2048 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
2049 client->makeScreenshot();
2050 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
2051 toggleBlockBounds();
2052 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
2053 m_game_ui->toggleHud();
2054 } else if (wasKeyDown(KeyType::MINIMAP)) {
2055 toggleMinimap(isKeyDown(KeyType::SNEAK));
2056 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
2057 m_game_ui->toggleChat();
2058 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
2060 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
2061 toggleUpdateCamera();
2062 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
2064 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
2065 m_game_ui->toggleProfiler();
2066 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
2067 increaseViewRange();
2068 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
2069 decreaseViewRange();
2070 } else if (wasKeyDown(KeyType::RANGESELECT)) {
2071 toggleFullViewRange();
2072 } else if (wasKeyDown(KeyType::ZOOM)) {
2074 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
2076 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
2078 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
2080 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
2084 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
2085 runData.reset_jump_timer = false;
2086 runData.jump_timer_up = 0.0f;
2089 if (quicktune->hasMessage()) {
2090 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2094 void Game::processItemSelection(u16 *new_playeritem)
2096 LocalPlayer *player = client->getEnv().getLocalPlayer();
2098 /* Item selection using mouse wheel
2100 *new_playeritem = player->getWieldIndex();
2102 s32 wheel = input->getMouseWheel();
2103 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2104 player->hud_hotbar_itemcount - 1);
2108 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2111 if (wasKeyDown(KeyType::HOTBAR_PREV))
2115 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2117 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2120 /* Item selection using hotbar slot keys
2122 for (u16 i = 0; i <= max_item; i++) {
2123 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2124 *new_playeritem = i;
2131 void Game::dropSelectedItem(bool single_item)
2133 IDropAction *a = new IDropAction();
2134 a->count = single_item ? 1 : 0;
2135 a->from_inv.setCurrentPlayer();
2136 a->from_list = "main";
2137 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2138 client->inventoryAction(a);
2142 void Game::openInventory()
2145 * Don't permit to open inventory is CAO or player doesn't exists.
2146 * This prevent showing an empty inventory at player load
2149 LocalPlayer *player = client->getEnv().getLocalPlayer();
2150 if (!player || !player->getCAO())
2153 infostream << "Game: Launching inventory" << std::endl;
2155 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2157 InventoryLocation inventoryloc;
2158 inventoryloc.setCurrentPlayer();
2160 if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2165 if (fs_src->getForm().empty()) {
2170 TextDest *txt_dst = new TextDestPlayerInventory(client);
2171 auto *&formspec = m_game_ui->updateFormspec("");
2172 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2173 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2175 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2179 void Game::openConsole(float scale, const wchar_t *line)
2181 assert(scale > 0.0f && scale <= 1.0f);
2184 porting::showInputDialog(gettext("ok"), "", "", 2);
2185 m_android_chat_open = true;
2187 if (gui_chat_console->isOpenInhibited())
2189 gui_chat_console->openConsole(scale);
2191 gui_chat_console->setCloseOnEnter(true);
2192 gui_chat_console->replaceAndAddToHistory(line);
2198 void Game::handleAndroidChatInput()
2200 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2201 std::string text = porting::getInputDialogValue();
2202 client->typeChatMessage(utf8_to_wide(text));
2203 m_android_chat_open = false;
2209 void Game::toggleFreeMove()
2211 bool free_move = !g_settings->getBool("free_move");
2212 g_settings->set("free_move", bool_to_cstr(free_move));
2215 if (client->checkPrivilege("fly")) {
2216 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2218 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2221 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2225 void Game::toggleFreeMoveAlt()
2227 if (!runData.reset_jump_timer) {
2228 runData.jump_timer_down_before = runData.jump_timer_down;
2229 runData.jump_timer_down = 0.0f;
2232 // key down (0.2 s max.), then key up (0.2 s max.), then key down
2233 if (m_cache_doubletap_jump && runData.jump_timer_up < 0.2f &&
2234 runData.jump_timer_down_before < 0.4f) // 0.2 + 0.2
2237 runData.reset_jump_timer = true;
2241 void Game::togglePitchMove()
2243 bool pitch_move = !g_settings->getBool("pitch_move");
2244 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2247 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2249 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2254 void Game::toggleFast()
2256 bool fast_move = !g_settings->getBool("fast_move");
2257 bool has_fast_privs = client->checkPrivilege("fast");
2258 g_settings->set("fast_move", bool_to_cstr(fast_move));
2261 if (has_fast_privs) {
2262 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2264 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2267 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2270 #ifdef HAVE_TOUCHSCREENGUI
2271 m_cache_hold_aux1 = fast_move && has_fast_privs;
2276 void Game::toggleNoClip()
2278 bool noclip = !g_settings->getBool("noclip");
2279 g_settings->set("noclip", bool_to_cstr(noclip));
2282 if (client->checkPrivilege("noclip")) {
2283 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2285 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2288 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2292 void Game::toggleCinematic()
2294 bool cinematic = !g_settings->getBool("cinematic");
2295 g_settings->set("cinematic", bool_to_cstr(cinematic));
2298 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2300 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2303 void Game::toggleBlockBounds()
2305 LocalPlayer *player = client->getEnv().getLocalPlayer();
2306 if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
2307 m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
2310 enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
2312 case Hud::BLOCK_BOUNDS_OFF:
2313 m_game_ui->showTranslatedStatusText("Block bounds hidden");
2315 case Hud::BLOCK_BOUNDS_CURRENT:
2316 m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
2318 case Hud::BLOCK_BOUNDS_NEAR:
2319 m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
2321 case Hud::BLOCK_BOUNDS_MAX:
2322 m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
2329 // Autoforward by toggling continuous forward.
2330 void Game::toggleAutoforward()
2332 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2333 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2335 if (autorun_enabled)
2336 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2338 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2341 void Game::toggleMinimap(bool shift_pressed)
2343 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2347 mapper->toggleMinimapShape();
2351 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2353 // Not so satisying code to keep compatibility with old fixed mode system
2355 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2357 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2358 m_game_ui->m_flags.show_minimap = false;
2361 // If radar is disabled, try to find a non radar mode or fall back to 0
2362 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2363 while (mapper->getModeIndex() &&
2364 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2367 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2371 // End of 'not so satifying code'
2372 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2373 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2374 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2376 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2379 void Game::toggleFog()
2381 bool fog_enabled = g_settings->getBool("enable_fog");
2382 g_settings->setBool("enable_fog", !fog_enabled);
2384 m_game_ui->showTranslatedStatusText("Fog disabled");
2386 m_game_ui->showTranslatedStatusText("Fog enabled");
2390 void Game::toggleDebug()
2392 LocalPlayer *player = client->getEnv().getLocalPlayer();
2393 bool has_debug = client->checkPrivilege("debug");
2394 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
2395 // Initial: No debug info
2396 // 1x toggle: Debug text
2397 // 2x toggle: Debug text with profiler graph
2398 // 3x toggle: Debug text and wireframe (needs "debug" priv)
2399 // Next toggle: Back to initial
2401 // The debug text can be in 2 modes: minimal and basic.
2402 // * Minimal: Only technical client info that not gameplay-relevant
2403 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
2404 // Basic mode is used when player has the debug HUD flag set,
2405 // otherwise the Minimal mode is used.
2406 if (!m_game_ui->m_flags.show_minimal_debug) {
2407 m_game_ui->m_flags.show_minimal_debug = true;
2408 if (has_basic_debug)
2409 m_game_ui->m_flags.show_basic_debug = true;
2410 m_game_ui->m_flags.show_profiler_graph = false;
2411 draw_control->show_wireframe = false;
2412 m_game_ui->showTranslatedStatusText("Debug info shown");
2413 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2414 if (has_basic_debug)
2415 m_game_ui->m_flags.show_basic_debug = true;
2416 m_game_ui->m_flags.show_profiler_graph = true;
2417 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2418 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2419 if (has_basic_debug)
2420 m_game_ui->m_flags.show_basic_debug = true;
2421 m_game_ui->m_flags.show_profiler_graph = false;
2422 draw_control->show_wireframe = true;
2423 m_game_ui->showTranslatedStatusText("Wireframe shown");
2425 m_game_ui->m_flags.show_minimal_debug = false;
2426 m_game_ui->m_flags.show_basic_debug = false;
2427 m_game_ui->m_flags.show_profiler_graph = false;
2428 draw_control->show_wireframe = false;
2430 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2432 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2438 void Game::toggleUpdateCamera()
2440 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2441 if (m_flags.disable_camera_update)
2442 m_game_ui->showTranslatedStatusText("Camera update disabled");
2444 m_game_ui->showTranslatedStatusText("Camera update enabled");
2448 void Game::increaseViewRange()
2450 s16 range = g_settings->getS16("viewing_range");
2451 s16 range_new = range + 10;
2453 if (range_new > 4000) {
2455 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
2456 m_game_ui->showStatusText(msg);
2458 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2459 m_game_ui->showStatusText(msg);
2461 g_settings->set("viewing_range", itos(range_new));
2465 void Game::decreaseViewRange()
2467 s16 range = g_settings->getS16("viewing_range");
2468 s16 range_new = range - 10;
2470 if (range_new < 20) {
2472 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
2473 m_game_ui->showStatusText(msg);
2475 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2476 m_game_ui->showStatusText(msg);
2478 g_settings->set("viewing_range", itos(range_new));
2482 void Game::toggleFullViewRange()
2484 draw_control->range_all = !draw_control->range_all;
2485 if (draw_control->range_all)
2486 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2488 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2492 void Game::checkZoomEnabled()
2494 LocalPlayer *player = client->getEnv().getLocalPlayer();
2495 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2496 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2499 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2501 if ((device->isWindowActive() && device->isWindowFocused()
2502 && !isMenuActive()) || input->isRandom()) {
2505 if (!input->isRandom()) {
2506 // Mac OSX gets upset if this is set every frame
2507 if (device->getCursorControl()->isVisible())
2508 device->getCursorControl()->setVisible(false);
2512 if (m_first_loop_after_window_activation) {
2513 m_first_loop_after_window_activation = false;
2515 input->setMousePos(driver->getScreenSize().Width / 2,
2516 driver->getScreenSize().Height / 2);
2518 updateCameraOrientation(cam, dtime);
2524 // Mac OSX gets upset if this is set every frame
2525 if (!device->getCursorControl()->isVisible())
2526 device->getCursorControl()->setVisible(true);
2529 m_first_loop_after_window_activation = true;
2534 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2535 // responsiveness independently of FOV.
2536 f32 Game::getSensitivityScaleFactor() const
2538 f32 fov_y = client->getCamera()->getFovY();
2540 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2541 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2543 return tan(fov_y / 2.0f) * 1.3763818698f;
2546 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2548 #ifdef HAVE_TOUCHSCREENGUI
2549 if (g_touchscreengui) {
2550 cam->camera_yaw += g_touchscreengui->getYawChange();
2551 cam->camera_pitch = g_touchscreengui->getPitch();
2554 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2555 v2s32 dist = input->getMousePos() - center;
2557 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2561 f32 sens_scale = getSensitivityScaleFactor();
2562 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2563 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2565 if (dist.X != 0 || dist.Y != 0)
2566 input->setMousePos(center.X, center.Y);
2567 #ifdef HAVE_TOUCHSCREENGUI
2571 if (m_cache_enable_joysticks) {
2572 f32 sens_scale = getSensitivityScaleFactor();
2573 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
2574 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2575 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2578 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2582 void Game::updatePlayerControl(const CameraOrientation &cam)
2584 LocalPlayer *player = client->getEnv().getLocalPlayer();
2586 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2588 PlayerControl control(
2589 isKeyDown(KeyType::FORWARD),
2590 isKeyDown(KeyType::BACKWARD),
2591 isKeyDown(KeyType::LEFT),
2592 isKeyDown(KeyType::RIGHT),
2593 isKeyDown(KeyType::JUMP) || player->getAutojump(),
2594 isKeyDown(KeyType::AUX1),
2595 isKeyDown(KeyType::SNEAK),
2596 isKeyDown(KeyType::ZOOM),
2597 isKeyDown(KeyType::DIG),
2598 isKeyDown(KeyType::PLACE),
2601 input->getMovementSpeed(),
2602 input->getMovementDirection()
2605 // autoforward if set: move at maximum speed
2606 if (player->getPlayerSettings().continuous_forward &&
2607 client->activeObjectsReceived() && !player->isDead()) {
2608 control.movement_speed = 1.0f;
2609 // sideways movement only
2610 float dx = sin(control.movement_direction);
2611 control.movement_direction = atan2(dx, 1.0f);
2614 #ifdef HAVE_TOUCHSCREENGUI
2615 /* For touch, simulate holding down AUX1 (fast move) if the user has
2616 * the fast_move setting toggled on. If there is an aux1 key defined for
2617 * touch then its meaning is inverted (i.e. holding aux1 means walk and
2620 if (m_cache_hold_aux1) {
2621 control.aux1 = control.aux1 ^ true;
2625 client->setPlayerControl(control);
2631 inline void Game::step(f32 dtime)
2634 server->step(dtime);
2636 client->step(dtime);
2639 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2642 for (auto &&child: node->getChildren())
2643 pauseNodeAnimation(paused, child);
2644 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2646 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2647 float speed = animated_node->getAnimationSpeed();
2650 paused.push_back({grab(animated_node), speed});
2651 animated_node->setAnimationSpeed(0.0f);
2654 void Game::pauseAnimation()
2656 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2659 void Game::resumeAnimation()
2661 for (auto &&pair: paused_animated_nodes)
2662 pair.first->setAnimationSpeed(pair.second);
2663 paused_animated_nodes.clear();
2666 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2667 {&Game::handleClientEvent_None},
2668 {&Game::handleClientEvent_PlayerDamage},
2669 {&Game::handleClientEvent_PlayerForceMove},
2670 {&Game::handleClientEvent_Deathscreen},
2671 {&Game::handleClientEvent_ShowFormSpec},
2672 {&Game::handleClientEvent_ShowLocalFormSpec},
2673 {&Game::handleClientEvent_HandleParticleEvent},
2674 {&Game::handleClientEvent_HandleParticleEvent},
2675 {&Game::handleClientEvent_HandleParticleEvent},
2676 {&Game::handleClientEvent_HudAdd},
2677 {&Game::handleClientEvent_HudRemove},
2678 {&Game::handleClientEvent_HudChange},
2679 {&Game::handleClientEvent_SetSky},
2680 {&Game::handleClientEvent_SetSun},
2681 {&Game::handleClientEvent_SetMoon},
2682 {&Game::handleClientEvent_SetStars},
2683 {&Game::handleClientEvent_OverrideDayNigthRatio},
2684 {&Game::handleClientEvent_CloudParams},
2687 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2689 FATAL_ERROR("ClientEvent type None received");
2692 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2694 if (client->modsLoaded())
2695 client->getScript()->on_damage_taken(event->player_damage.amount);
2697 if (!event->player_damage.effect)
2700 // Damage flash and hurt tilt are not used at death
2701 if (client->getHP() > 0) {
2702 LocalPlayer *player = client->getEnv().getLocalPlayer();
2704 f32 hp_max = player->getCAO() ?
2705 player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2706 f32 damage_ratio = event->player_damage.amount / hp_max;
2708 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2709 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2711 player->hurt_tilt_timer = 1.5f;
2712 player->hurt_tilt_strength =
2713 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2716 // Play damage sound
2717 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2720 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2722 cam->camera_yaw = event->player_force_move.yaw;
2723 cam->camera_pitch = event->player_force_move.pitch;
2726 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2728 // If client scripting is enabled, deathscreen is handled by CSM code in
2729 // builtin/client/init.lua
2730 if (client->modsLoaded())
2731 client->getScript()->on_death();
2733 showDeathFormspec();
2735 /* Handle visualization */
2736 LocalPlayer *player = client->getEnv().getLocalPlayer();
2737 runData.damage_flash = 0;
2738 player->hurt_tilt_timer = 0;
2739 player->hurt_tilt_strength = 0;
2742 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2744 if (event->show_formspec.formspec->empty()) {
2745 auto formspec = m_game_ui->getFormspecGUI();
2746 if (formspec && (event->show_formspec.formname->empty()
2747 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2748 formspec->quitMenu();
2751 FormspecFormSource *fs_src =
2752 new FormspecFormSource(*(event->show_formspec.formspec));
2753 TextDestPlayerInventory *txt_dst =
2754 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2756 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2757 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2758 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2761 delete event->show_formspec.formspec;
2762 delete event->show_formspec.formname;
2765 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2767 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2768 LocalFormspecHandler *txt_dst =
2769 new LocalFormspecHandler(*event->show_formspec.formname, client);
2770 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2771 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2773 delete event->show_formspec.formspec;
2774 delete event->show_formspec.formname;
2777 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2778 CameraOrientation *cam)
2780 LocalPlayer *player = client->getEnv().getLocalPlayer();
2781 client->getParticleManager()->handleParticleEvent(event, client, player);
2784 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2786 LocalPlayer *player = client->getEnv().getLocalPlayer();
2788 u32 server_id = event->hudadd->server_id;
2789 // ignore if we already have a HUD with that ID
2790 auto i = m_hud_server_to_client.find(server_id);
2791 if (i != m_hud_server_to_client.end()) {
2792 delete event->hudadd;
2796 HudElement *e = new HudElement;
2797 e->type = static_cast<HudElementType>(event->hudadd->type);
2798 e->pos = event->hudadd->pos;
2799 e->name = event->hudadd->name;
2800 e->scale = event->hudadd->scale;
2801 e->text = event->hudadd->text;
2802 e->number = event->hudadd->number;
2803 e->item = event->hudadd->item;
2804 e->dir = event->hudadd->dir;
2805 e->align = event->hudadd->align;
2806 e->offset = event->hudadd->offset;
2807 e->world_pos = event->hudadd->world_pos;
2808 e->size = event->hudadd->size;
2809 e->z_index = event->hudadd->z_index;
2810 e->text2 = event->hudadd->text2;
2811 e->style = event->hudadd->style;
2812 m_hud_server_to_client[server_id] = player->addHud(e);
2814 delete event->hudadd;
2817 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2819 LocalPlayer *player = client->getEnv().getLocalPlayer();
2821 auto i = m_hud_server_to_client.find(event->hudrm.id);
2822 if (i != m_hud_server_to_client.end()) {
2823 HudElement *e = player->removeHud(i->second);
2825 m_hud_server_to_client.erase(i);
2830 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2832 LocalPlayer *player = client->getEnv().getLocalPlayer();
2834 HudElement *e = nullptr;
2836 auto i = m_hud_server_to_client.find(event->hudchange->id);
2837 if (i != m_hud_server_to_client.end()) {
2838 e = player->getHud(i->second);
2842 delete event->hudchange;
2846 #define CASE_SET(statval, prop, dataprop) \
2848 e->prop = event->hudchange->dataprop; \
2851 switch (event->hudchange->stat) {
2852 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2854 CASE_SET(HUD_STAT_NAME, name, sdata);
2856 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2858 CASE_SET(HUD_STAT_TEXT, text, sdata);
2860 CASE_SET(HUD_STAT_NUMBER, number, data);
2862 CASE_SET(HUD_STAT_ITEM, item, data);
2864 CASE_SET(HUD_STAT_DIR, dir, data);
2866 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2868 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2870 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2872 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2874 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2876 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2878 CASE_SET(HUD_STAT_STYLE, style, data);
2883 delete event->hudchange;
2886 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2888 sky->setVisible(false);
2889 // Whether clouds are visible in front of a custom skybox.
2890 sky->setCloudsEnabled(event->set_sky->clouds);
2896 // Clear the old textures out in case we switch rendering type.
2897 sky->clearSkyboxTextures();
2898 // Handle according to type
2899 if (event->set_sky->type == "regular") {
2900 // Shows the mesh skybox
2901 sky->setVisible(true);
2902 // Update mesh based skybox colours if applicable.
2903 sky->setSkyColors(event->set_sky->sky_color);
2904 sky->setHorizonTint(
2905 event->set_sky->fog_sun_tint,
2906 event->set_sky->fog_moon_tint,
2907 event->set_sky->fog_tint_type
2909 } else if (event->set_sky->type == "skybox" &&
2910 event->set_sky->textures.size() == 6) {
2911 // Disable the dyanmic mesh skybox:
2912 sky->setVisible(false);
2914 sky->setFallbackBgColor(event->set_sky->bgcolor);
2915 // Set sunrise and sunset fog tinting:
2916 sky->setHorizonTint(
2917 event->set_sky->fog_sun_tint,
2918 event->set_sky->fog_moon_tint,
2919 event->set_sky->fog_tint_type
2921 // Add textures to skybox.
2922 for (int i = 0; i < 6; i++)
2923 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2925 // Handle everything else as plain color.
2926 if (event->set_sky->type != "plain")
2927 infostream << "Unknown sky type: "
2928 << (event->set_sky->type) << std::endl;
2929 sky->setVisible(false);
2930 sky->setFallbackBgColor(event->set_sky->bgcolor);
2931 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2932 sky->setHorizonTint(
2933 event->set_sky->bgcolor,
2934 event->set_sky->bgcolor,
2939 delete event->set_sky;
2942 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2944 sky->setSunVisible(event->sun_params->visible);
2945 sky->setSunTexture(event->sun_params->texture,
2946 event->sun_params->tonemap, texture_src);
2947 sky->setSunScale(event->sun_params->scale);
2948 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2949 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2950 delete event->sun_params;
2953 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2955 sky->setMoonVisible(event->moon_params->visible);
2956 sky->setMoonTexture(event->moon_params->texture,
2957 event->moon_params->tonemap, texture_src);
2958 sky->setMoonScale(event->moon_params->scale);
2959 delete event->moon_params;
2962 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2964 sky->setStarsVisible(event->star_params->visible);
2965 sky->setStarCount(event->star_params->count);
2966 sky->setStarColor(event->star_params->starcolor);
2967 sky->setStarScale(event->star_params->scale);
2968 sky->setStarDayOpacity(event->star_params->day_opacity);
2969 delete event->star_params;
2972 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2973 CameraOrientation *cam)
2975 client->getEnv().setDayNightRatioOverride(
2976 event->override_day_night_ratio.do_override,
2977 event->override_day_night_ratio.ratio_f * 1000.0f);
2980 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2985 clouds->setDensity(event->cloud_params.density);
2986 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2987 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2988 clouds->setHeight(event->cloud_params.height);
2989 clouds->setThickness(event->cloud_params.thickness);
2990 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2993 void Game::processClientEvents(CameraOrientation *cam)
2995 while (client->hasClientEvents()) {
2996 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2997 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2998 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2999 (this->*evHandler.handler)(event.get(), cam);
3003 void Game::updateChat(f32 dtime)
3005 // Get new messages from error log buffer
3006 while (!m_chat_log_buf.empty())
3007 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
3009 // Get new messages from client
3010 std::wstring message;
3011 while (client->getChatMessage(message)) {
3012 chat_backend->addUnparsedMessage(message);
3015 // Remove old messages
3016 chat_backend->step(dtime);
3018 // Display all messages in a static text element
3019 auto &buf = chat_backend->getRecentBuffer();
3020 if (buf.getLinesModified()) {
3021 buf.resetLinesModified();
3022 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
3025 // Make sure that the size is still correct
3026 m_game_ui->updateChatSize();
3029 void Game::updateCamera(f32 dtime)
3031 LocalPlayer *player = client->getEnv().getLocalPlayer();
3034 For interaction purposes, get info about the held item
3036 - Is it a usable item?
3037 - Can it point to liquids?
3039 ItemStack playeritem;
3041 ItemStack selected, hand;
3042 playeritem = player->getWieldedItem(&selected, &hand);
3045 ToolCapabilities playeritem_toolcap =
3046 playeritem.getToolCapabilities(itemdef_manager);
3048 v3s16 old_camera_offset = camera->getOffset();
3050 if (wasKeyDown(KeyType::CAMERA_MODE)) {
3051 GenericCAO *playercao = player->getCAO();
3053 // If playercao not loaded, don't change camera
3057 camera->toggleCameraMode();
3059 #ifdef HAVE_TOUCHSCREENGUI
3060 if (g_touchscreengui)
3061 g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed());
3064 // Make the player visible depending on camera mode.
3065 playercao->updateMeshCulling();
3066 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
3069 float full_punch_interval = playeritem_toolcap.full_punch_interval;
3070 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
3072 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
3073 camera->update(player, dtime, tool_reload_ratio);
3074 camera->step(dtime);
3076 f32 camera_fov = camera->getFovMax();
3077 v3s16 camera_offset = camera->getOffset();
3079 m_camera_offset_changed = (camera_offset != old_camera_offset);
3081 if (!m_flags.disable_camera_update) {
3082 v3f camera_position = camera->getPosition();
3083 v3f camera_direction = camera->getDirection();
3085 client->getEnv().getClientMap().updateCamera(camera_position,
3086 camera_direction, camera_fov, camera_offset);
3088 if (m_camera_offset_changed) {
3089 client->updateCameraOffset(camera_offset);
3090 client->getEnv().updateCameraOffset(camera_offset);
3093 clouds->updateCameraOffset(camera_offset);
3099 void Game::updateSound(f32 dtime)
3101 // Update sound listener
3102 v3s16 camera_offset = camera->getOffset();
3103 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3104 v3f(0, 0, 0), // velocity
3105 camera->getDirection(),
3106 camera->getCameraNode()->getUpVector());
3108 bool mute_sound = g_settings->getBool("mute_sound");
3110 sound->setListenerGain(0.0f);
3112 // Check if volume is in the proper range, else fix it.
3113 float old_volume = g_settings->getFloat("sound_volume");
3114 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3115 sound->setListenerGain(new_volume);
3117 if (old_volume != new_volume) {
3118 g_settings->setFloat("sound_volume", new_volume);
3122 LocalPlayer *player = client->getEnv().getLocalPlayer();
3124 // Tell the sound maker whether to make footstep sounds
3125 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3127 // Update sound maker
3128 if (player->makes_footstep_sound)
3129 soundmaker->step(dtime);
3131 ClientMap &map = client->getEnv().getClientMap();
3132 MapNode n = map.getNode(player->getFootstepNodePos());
3133 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3137 void Game::processPlayerInteraction(f32 dtime, bool show_hud)
3139 LocalPlayer *player = client->getEnv().getLocalPlayer();
3141 const v3f camera_direction = camera->getDirection();
3142 const v3s16 camera_offset = camera->getOffset();
3145 Calculate what block is the crosshair pointing to
3148 ItemStack selected_item, hand_item;
3149 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3151 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3152 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3154 core::line3d<f32> shootline;
3156 switch (camera->getCameraMode()) {
3157 case CAMERA_MODE_FIRST:
3158 // Shoot from camera position, with bobbing
3159 shootline.start = camera->getPosition();
3161 case CAMERA_MODE_THIRD:
3162 // Shoot from player head, no bobbing
3163 shootline.start = camera->getHeadPosition();
3165 case CAMERA_MODE_THIRD_FRONT:
3166 shootline.start = camera->getHeadPosition();
3167 // prevent player pointing anything in front-view
3171 shootline.end = shootline.start + camera_direction * BS * d;
3173 #ifdef HAVE_TOUCHSCREENGUI
3174 if (g_touchscreengui && isNoCrosshairAllowed()) {
3175 shootline = g_touchscreengui->getShootline();
3176 // Scale shootline to the acual distance the player can reach
3177 shootline.end = shootline.start +
3178 shootline.getVector().normalize() * BS * d;
3179 shootline.start += intToFloat(camera_offset, BS);
3180 shootline.end += intToFloat(camera_offset, BS);
3184 PointedThing pointed = updatePointedThing(shootline,
3185 selected_def.liquids_pointable,
3186 !runData.btn_down_for_dig,
3189 if (pointed != runData.pointed_old)
3190 infostream << "Pointing at " << pointed.dump() << std::endl;
3192 // Note that updating the selection mesh every frame is not particularly efficient,
3193 // but the halo rendering code is already inefficient so there's no point in optimizing it here
3194 hud->updateSelectionMesh(camera_offset);
3196 // Allow digging again if button is not pressed
3197 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3198 runData.digging_blocked = false;
3202 - releasing dig button
3203 - pointing away from node
3205 if (runData.digging) {
3206 if (wasKeyReleased(KeyType::DIG)) {
3207 infostream << "Dig button released (stopped digging)" << std::endl;
3208 runData.digging = false;
3209 } else if (pointed != runData.pointed_old) {
3210 if (pointed.type == POINTEDTHING_NODE
3211 && runData.pointed_old.type == POINTEDTHING_NODE
3212 && pointed.node_undersurface
3213 == runData.pointed_old.node_undersurface) {
3214 // Still pointing to the same node, but a different face.
3217 infostream << "Pointing away from node (stopped digging)" << std::endl;
3218 runData.digging = false;
3219 hud->updateSelectionMesh(camera_offset);
3223 if (!runData.digging) {
3224 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3225 client->setCrack(-1, v3s16(0, 0, 0));
3226 runData.dig_time = 0.0;
3228 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3229 // Remove e.g. torches faster when clicking instead of holding dig button
3230 runData.nodig_delay_timer = 0;
3231 runData.dig_instantly = false;
3234 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3235 runData.btn_down_for_dig = false;
3237 runData.punching = false;
3239 soundmaker->m_player_leftpunch_sound.name.clear();
3241 // Prepare for repeating, unless we're not supposed to
3242 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3243 runData.repeat_place_timer += dtime;
3245 runData.repeat_place_timer = 0;
3247 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3248 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3249 !client->getScript()->on_item_use(selected_item, pointed)))
3250 client->interact(INTERACT_USE, pointed);
3251 } else if (pointed.type == POINTEDTHING_NODE) {
3252 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3253 } else if (pointed.type == POINTEDTHING_OBJECT) {
3254 v3f player_position = player->getPosition();
3255 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
3256 handlePointingAtObject(pointed, tool_item, player_position,
3257 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
3258 } else if (isKeyDown(KeyType::DIG)) {
3259 // When button is held down in air, show continuous animation
3260 runData.punching = true;
3261 // Run callback even though item is not usable
3262 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3263 client->getScript()->on_item_use(selected_item, pointed);
3264 } else if (wasKeyPressed(KeyType::PLACE)) {
3265 handlePointingAtNothing(selected_item);
3268 runData.pointed_old = pointed;
3270 if (runData.punching || wasKeyPressed(KeyType::DIG))
3271 camera->setDigging(0); // dig animation
3273 input->clearWasKeyPressed();
3274 input->clearWasKeyReleased();
3275 // Ensure DIG & PLACE are marked as handled
3276 wasKeyDown(KeyType::DIG);
3277 wasKeyDown(KeyType::PLACE);
3279 input->joystick.clearWasKeyPressed(KeyType::DIG);
3280 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3282 input->joystick.clearWasKeyReleased(KeyType::DIG);
3283 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3287 PointedThing Game::updatePointedThing(
3288 const core::line3d<f32> &shootline,
3289 bool liquids_pointable,
3290 bool look_for_object,
3291 const v3s16 &camera_offset)
3293 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3294 selectionboxes->clear();
3295 hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3296 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3297 "show_entity_selectionbox");
3299 ClientEnvironment &env = client->getEnv();
3300 ClientMap &map = env.getClientMap();
3301 const NodeDefManager *nodedef = map.getNodeDefManager();
3303 runData.selected_object = NULL;
3304 hud->pointing_at_object = false;
3306 RaycastState s(shootline, look_for_object, liquids_pointable);
3307 PointedThing result;
3308 env.continueRaycast(&s, &result);
3309 if (result.type == POINTEDTHING_OBJECT) {
3310 hud->pointing_at_object = true;
3312 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3313 aabb3f selection_box;
3314 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3315 runData.selected_object->getSelectionBox(&selection_box)) {
3316 v3f pos = runData.selected_object->getPosition();
3317 selectionboxes->push_back(aabb3f(selection_box));
3318 hud->setSelectionPos(pos, camera_offset);
3320 } else if (result.type == POINTEDTHING_NODE) {
3321 // Update selection boxes
3322 MapNode n = map.getNode(result.node_undersurface);
3323 std::vector<aabb3f> boxes;
3324 n.getSelectionBoxes(nodedef, &boxes,
3325 n.getNeighbors(result.node_undersurface, &map));
3328 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3329 i != boxes.end(); ++i) {
3331 box.MinEdge -= v3f(d, d, d);
3332 box.MaxEdge += v3f(d, d, d);
3333 selectionboxes->push_back(box);
3335 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3337 hud->setSelectedFaceNormal(v3f(
3338 result.intersection_normal.X,
3339 result.intersection_normal.Y,
3340 result.intersection_normal.Z));
3343 // Update selection mesh light level and vertex colors
3344 if (!selectionboxes->empty()) {
3345 v3f pf = hud->getSelectionPos();
3346 v3s16 p = floatToInt(pf, BS);
3348 // Get selection mesh light level
3349 MapNode n = map.getNode(p);
3350 u16 node_light = getInteriorLight(n, -1, nodedef);
3351 u16 light_level = node_light;
3353 for (const v3s16 &dir : g_6dirs) {
3354 n = map.getNode(p + dir);
3355 node_light = getInteriorLight(n, -1, nodedef);
3356 if (node_light > light_level)
3357 light_level = node_light;
3360 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3362 final_color_blend(&c, light_level, daynight_ratio);
3364 // Modify final color a bit with time
3365 u32 timer = client->getEnv().getFrameTime() % 5000;
3366 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3367 float sin_r = 0.08f * std::sin(timerf);
3368 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3369 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3370 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3371 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3372 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3374 // Set mesh final color
3375 hud->setSelectionMeshColor(c);
3381 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3383 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3384 PointedThing fauxPointed;
3385 fauxPointed.type = POINTEDTHING_NOTHING;
3386 client->interact(INTERACT_ACTIVATE, fauxPointed);
3390 void Game::handlePointingAtNode(const PointedThing &pointed,
3391 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3393 v3s16 nodepos = pointed.node_undersurface;
3394 v3s16 neighbourpos = pointed.node_abovesurface;
3397 Check information text of node
3400 ClientMap &map = client->getEnv().getClientMap();
3402 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3403 && !runData.digging_blocked
3404 && client->checkPrivilege("interact")) {
3405 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3408 // This should be done after digging handling
3409 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3412 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3413 meta->getString("infotext"))));
3415 MapNode n = map.getNode(nodepos);
3417 if (nodedef_manager->get(n).name == "unknown") {
3418 m_game_ui->setInfoText(L"Unknown node");
3422 if ((wasKeyPressed(KeyType::PLACE) ||
3423 runData.repeat_place_timer >= m_repeat_place_time) &&
3424 client->checkPrivilege("interact")) {
3425 runData.repeat_place_timer = 0;
3426 infostream << "Place button pressed while looking at ground" << std::endl;
3428 // Placing animation (always shown for feedback)
3429 camera->setDigging(1);
3431 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3433 // If the wielded item has node placement prediction,
3435 // And also set the sound and send the interact
3436 // But first check for meta formspec and rightclickable
3437 auto &def = selected_item.getDefinition(itemdef_manager);
3438 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3441 if (placed && client->modsLoaded())
3442 client->getScript()->on_placenode(pointed, def);
3446 bool Game::nodePlacement(const ItemDefinition &selected_def,
3447 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3448 const PointedThing &pointed, const NodeMetadata *meta)
3450 const auto &prediction = selected_def.node_placement_prediction;
3452 const NodeDefManager *nodedef = client->ndef();
3453 ClientMap &map = client->getEnv().getClientMap();
3455 bool is_valid_position;
3457 node = map.getNode(nodepos, &is_valid_position);
3458 if (!is_valid_position) {
3459 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3464 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3465 && !isKeyDown(KeyType::SNEAK)) {
3466 // on_rightclick callbacks are called anyway
3467 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3468 client->interact(INTERACT_PLACE, pointed);
3470 infostream << "Launching custom inventory view" << std::endl;
3472 InventoryLocation inventoryloc;
3473 inventoryloc.setNodeMeta(nodepos);
3475 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3476 &client->getEnv().getClientMap(), nodepos);
3477 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3479 auto *&formspec = m_game_ui->updateFormspec("");
3480 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3481 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3483 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3487 // on_rightclick callback
3488 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3489 !isKeyDown(KeyType::SNEAK))) {
3491 client->interact(INTERACT_PLACE, pointed);
3495 verbosestream << "Node placement prediction for "
3496 << selected_def.name << " is " << prediction << std::endl;
3497 v3s16 p = neighbourpos;
3499 // Place inside node itself if buildable_to
3500 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3501 if (is_valid_position) {
3502 if (nodedef->get(n_under).buildable_to) {
3505 node = map.getNode(p, &is_valid_position);
3506 if (is_valid_position && !nodedef->get(node).buildable_to) {
3507 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3509 client->interact(INTERACT_PLACE, pointed);
3515 // Find id of predicted node
3517 bool found = nodedef->getId(prediction, id);
3520 errorstream << "Node placement prediction failed for "
3521 << selected_def.name << " (places " << prediction
3522 << ") - Name not known" << std::endl;
3523 // Handle this as if prediction was empty
3525 client->interact(INTERACT_PLACE, pointed);
3529 const ContentFeatures &predicted_f = nodedef->get(id);
3531 // Predict param2 for facedir and wallmounted nodes
3532 // Compare core.item_place_node() for what the server does
3535 const u8 place_param2 = selected_def.place_param2;
3538 param2 = place_param2;
3539 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3540 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3541 v3s16 dir = nodepos - neighbourpos;
3543 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3544 param2 = dir.Y < 0 ? 1 : 0;
3545 } else if (abs(dir.X) > abs(dir.Z)) {
3546 param2 = dir.X < 0 ? 3 : 2;
3548 param2 = dir.Z < 0 ? 5 : 4;
3550 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3551 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR ||
3552 predicted_f.param_type_2 == CPT2_4DIR ||
3553 predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3554 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3556 if (abs(dir.X) > abs(dir.Z)) {
3557 param2 = dir.X < 0 ? 3 : 1;
3559 param2 = dir.Z < 0 ? 2 : 0;
3563 // Check attachment if node is in group attached_node
3564 if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3565 const static v3s16 wallmounted_dirs[8] = {
3575 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3576 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3577 pp = p + wallmounted_dirs[param2];
3579 pp = p + v3s16(0, -1, 0);
3581 if (!nodedef->get(map.getNode(pp)).walkable) {
3582 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3584 client->interact(INTERACT_PLACE, pointed);
3590 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3591 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3592 || predicted_f.param_type_2 == CPT2_COLORED_4DIR
3593 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3594 const auto &indexstr = selected_item.metadata.
3595 getString("palette_index", 0);
3596 if (!indexstr.empty()) {
3597 s32 index = mystoi(indexstr);
3598 if (predicted_f.param_type_2 == CPT2_COLOR) {
3600 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3601 // param2 = pure palette index + other
3602 param2 = (index & 0xf8) | (param2 & 0x07);
3603 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3604 // param2 = pure palette index + other
3605 param2 = (index & 0xe0) | (param2 & 0x1f);
3606 } else if (predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3607 // param2 = pure palette index + other
3608 param2 = (index & 0xfc) | (param2 & 0x03);
3613 // Add node to client map
3614 MapNode n(id, 0, param2);
3617 LocalPlayer *player = client->getEnv().getLocalPlayer();
3619 // Dont place node when player would be inside new node
3620 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3621 if (!nodedef->get(n).walkable ||
3622 g_settings->getBool("enable_build_where_you_stand") ||
3623 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3624 (nodedef->get(n).walkable &&
3625 neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3626 neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3627 // This triggers the required mesh update too
3628 client->addNode(p, n);
3630 client->interact(INTERACT_PLACE, pointed);
3631 // A node is predicted, also play a sound
3632 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3635 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3638 } catch (const InvalidPositionException &e) {
3639 errorstream << "Node placement prediction failed for "
3640 << selected_def.name << " (places "
3641 << prediction << ") - Position not loaded" << std::endl;
3642 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3647 void Game::handlePointingAtObject(const PointedThing &pointed,
3648 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3650 std::wstring infotext = unescape_translate(
3651 utf8_to_wide(runData.selected_object->infoText()));
3654 if (!infotext.empty()) {
3657 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3660 m_game_ui->setInfoText(infotext);
3662 if (isKeyDown(KeyType::DIG)) {
3663 bool do_punch = false;
3664 bool do_punch_damage = false;
3666 if (runData.object_hit_delay_timer <= 0.0) {
3668 do_punch_damage = true;
3669 runData.object_hit_delay_timer = object_hit_delay;
3672 if (wasKeyPressed(KeyType::DIG))
3676 infostream << "Punched object" << std::endl;
3677 runData.punching = true;
3680 if (do_punch_damage) {
3681 // Report direct punch
3682 v3f objpos = runData.selected_object->getPosition();
3683 v3f dir = (objpos - player_position).normalize();
3685 bool disable_send = runData.selected_object->directReportPunch(
3686 dir, &tool_item, runData.time_from_last_punch);
3687 runData.time_from_last_punch = 0;
3690 client->interact(INTERACT_START_DIGGING, pointed);
3692 } else if (wasKeyDown(KeyType::PLACE)) {
3693 infostream << "Pressed place button while pointing at object" << std::endl;
3694 client->interact(INTERACT_PLACE, pointed); // place
3699 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3700 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3702 // See also: serverpackethandle.cpp, action == 2
3703 LocalPlayer *player = client->getEnv().getLocalPlayer();
3704 ClientMap &map = client->getEnv().getClientMap();
3705 MapNode n = map.getNode(nodepos);
3706 const auto &features = nodedef_manager->get(n);
3708 // NOTE: Similar piece of code exists on the server side for
3710 // Get digging parameters
3711 DigParams params = getDigParams(features.groups,
3712 &selected_item.getToolCapabilities(itemdef_manager),
3713 selected_item.wear);
3715 // If can't dig, try hand
3716 if (!params.diggable) {
3717 params = getDigParams(features.groups,
3718 &hand_item.getToolCapabilities(itemdef_manager));
3721 if (!params.diggable) {
3722 // I guess nobody will wait for this long
3723 runData.dig_time_complete = 10000000.0;
3725 runData.dig_time_complete = params.time;
3727 if (m_cache_enable_particles) {
3728 client->getParticleManager()->addNodeParticle(client,
3729 player, nodepos, n, features);
3733 if (!runData.digging) {
3734 infostream << "Started digging" << std::endl;
3735 runData.dig_instantly = runData.dig_time_complete == 0;
3736 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3739 client->interact(INTERACT_START_DIGGING, pointed);
3740 runData.digging = true;
3741 runData.btn_down_for_dig = true;
3744 if (!runData.dig_instantly) {
3745 runData.dig_index = (float)crack_animation_length
3747 / runData.dig_time_complete;
3749 // This is for e.g. torches
3750 runData.dig_index = crack_animation_length;
3753 const auto &sound_dig = features.sound_dig;
3755 if (sound_dig.exists() && params.diggable) {
3756 if (sound_dig.name == "__group") {
3757 if (!params.main_group.empty()) {
3758 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3759 soundmaker->m_player_leftpunch_sound.name =
3760 std::string("default_dig_") +
3764 soundmaker->m_player_leftpunch_sound = sound_dig;
3768 // Don't show cracks if not diggable
3769 if (runData.dig_time_complete >= 100000.0) {
3770 } else if (runData.dig_index < crack_animation_length) {
3771 client->setCrack(runData.dig_index, nodepos);
3773 infostream << "Digging completed" << std::endl;
3774 client->setCrack(-1, v3s16(0, 0, 0));
3776 runData.dig_time = 0;
3777 runData.digging = false;
3778 // we successfully dug, now block it from repeating if we want to be safe
3779 if (g_settings->getBool("safe_dig_and_place"))
3780 runData.digging_blocked = true;
3782 runData.nodig_delay_timer =
3783 runData.dig_time_complete / (float)crack_animation_length;
3785 // We don't want a corresponding delay to very time consuming nodes
3786 // and nodes without digging time (e.g. torches) get a fixed delay.
3787 if (runData.nodig_delay_timer > 0.3)
3788 runData.nodig_delay_timer = 0.3;
3789 else if (runData.dig_instantly)
3790 runData.nodig_delay_timer = 0.15;
3792 if (client->modsLoaded() &&
3793 client->getScript()->on_dignode(nodepos, n)) {
3797 if (features.node_dig_prediction == "air") {
3798 client->removeNode(nodepos);
3799 } else if (!features.node_dig_prediction.empty()) {
3801 bool found = nodedef_manager->getId(features.node_dig_prediction, id);
3803 client->addNode(nodepos, id, true);
3805 // implicit else: no prediction
3807 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3809 if (m_cache_enable_particles) {
3810 client->getParticleManager()->addDiggingParticles(client,
3811 player, nodepos, n, features);
3815 // Send event to trigger sound
3816 client->getEventManager()->put(new NodeDugEvent(nodepos, n));
3819 if (runData.dig_time_complete < 100000.0) {
3820 runData.dig_time += dtime;
3822 runData.dig_time = 0;
3823 client->setCrack(-1, nodepos);
3826 camera->setDigging(0); // Dig animation
3829 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3830 const CameraOrientation &cam)
3832 TimeTaker tt_update("Game::updateFrame()");
3833 LocalPlayer *player = client->getEnv().getLocalPlayer();
3839 client->getEnv().updateFrameTime(m_is_paused);
3845 if (draw_control->range_all) {
3846 runData.fog_range = 100000 * BS;
3848 runData.fog_range = draw_control->wanted_range * BS;
3852 Calculate general brightness
3854 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3855 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3856 float direct_brightness;
3859 // When in noclip mode force same sky brightness as above ground so you
3861 if (draw_control->allow_noclip && m_cache_enable_free_move &&
3862 client->checkPrivilege("fly")) {
3863 direct_brightness = time_brightness;
3864 sunlight_seen = true;
3866 float old_brightness = sky->getBrightness();
3867 direct_brightness = client->getEnv().getClientMap()
3868 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3869 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3873 float time_of_day_smooth = runData.time_of_day_smooth;
3874 float time_of_day = client->getEnv().getTimeOfDayF();
3876 static const float maxsm = 0.05f;
3877 static const float todsm = 0.05f;
3879 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3880 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3881 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3882 time_of_day_smooth = time_of_day;
3884 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3885 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3886 + (time_of_day + 1.0) * todsm;
3888 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3889 + time_of_day * todsm;
3891 runData.time_of_day_smooth = time_of_day_smooth;
3893 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3894 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3895 player->getPitch());
3901 if (sky->getCloudsVisible()) {
3902 clouds->setVisible(true);
3903 clouds->step(dtime);
3904 // camera->getPosition is not enough for 3rd person views
3905 v3f camera_node_position = camera->getCameraNode()->getPosition();
3906 v3s16 camera_offset = camera->getOffset();
3907 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3908 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3909 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3910 clouds->update(camera_node_position,
3911 sky->getCloudColor());
3912 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3913 // if inside clouds, and fog enabled, use that as sky
3915 video::SColor clouds_dark = clouds->getColor()
3916 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3917 sky->overrideColors(clouds_dark, clouds->getColor());
3918 sky->setInClouds(true);
3919 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3920 // do not draw clouds after all
3921 clouds->setVisible(false);
3924 clouds->setVisible(false);
3931 client->getParticleManager()->step(dtime);
3937 if (m_cache_enable_fog) {
3940 video::EFT_FOG_LINEAR,
3941 runData.fog_range * m_cache_fog_start,
3942 runData.fog_range * 1.0,
3950 video::EFT_FOG_LINEAR,
3962 if (player->hurt_tilt_timer > 0.0f) {
3963 player->hurt_tilt_timer -= dtime * 6.0f;
3965 if (player->hurt_tilt_timer < 0.0f)
3966 player->hurt_tilt_strength = 0.0f;
3970 Update minimap pos and rotation
3972 if (mapper && m_game_ui->m_flags.show_hud) {
3973 mapper->setPos(floatToInt(player->getPosition(), BS));
3974 mapper->setAngle(player->getYaw());
3978 Get chat messages from client
3987 if (player->getWieldIndex() != runData.new_playeritem)
3988 client->setPlayerItem(runData.new_playeritem);
3990 if (client->updateWieldedItem()) {
3991 // Update wielded tool
3992 ItemStack selected_item, hand_item;
3993 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3994 camera->wield(tool_item);
3998 Update block draw list every 200ms or when camera direction has
4001 runData.update_draw_list_timer += dtime;
4003 float update_draw_list_delta = 0.2f;
4005 v3f camera_direction = camera->getDirection();
4006 if (runData.update_draw_list_timer >= update_draw_list_delta
4007 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
4008 || m_camera_offset_changed
4009 || client->getEnv().getClientMap().needsUpdateDrawList()) {
4010 runData.update_draw_list_timer = 0;
4011 client->getEnv().getClientMap().updateDrawList();
4012 runData.update_draw_list_last_cam_dir = camera_direction;
4015 if (RenderingEngine::get_shadow_renderer()) {
4019 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
4022 make sure menu is on top
4023 1. Delete formspec menu reference if menu was removed
4024 2. Else, make sure formspec menu is on top
4026 auto formspec = m_game_ui->getFormspecGUI();
4027 do { // breakable. only runs for one iteration
4031 if (formspec->getReferenceCount() == 1) {
4032 m_game_ui->deleteFormspec();
4036 auto &loc = formspec->getFormspecLocation();
4037 if (loc.type == InventoryLocation::NODEMETA) {
4038 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
4039 if (!meta || meta->getString("formspec").empty()) {
4040 formspec->quitMenu();
4046 guiroot->bringToFront(formspec);
4050 ==================== Drawing begins ====================
4052 const video::SColor skycolor = sky->getSkyColor();
4054 TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
4055 driver->beginScene(true, true, skycolor);
4057 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
4058 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
4059 (camera->getCameraMode() == CAMERA_MODE_FIRST));
4060 bool draw_crosshair = (
4061 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
4062 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
4063 #ifdef HAVE_TOUCHSCREENGUI
4064 if (isNoCrosshairAllowed())
4065 draw_crosshair = false;
4067 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
4068 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
4073 v2u32 screensize = driver->getScreenSize();
4075 if (m_game_ui->m_flags.show_profiler_graph)
4076 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
4081 if (runData.damage_flash > 0.0f) {
4082 video::SColor color(runData.damage_flash, 180, 0, 0);
4083 driver->draw2DRectangle(color,
4084 core::rect<s32>(0, 0, screensize.X, screensize.Y),
4087 runData.damage_flash -= 384.0f * dtime;
4091 ==================== End scene ====================
4093 #if IRRLICHT_VERSION_MT_REVISION < 5
4094 if (++m_reset_HW_buffer_counter > 500) {
4096 Periodically remove all mesh HW buffers.
4098 Work around for a quirk in Irrlicht where a HW buffer is only
4099 released after 20000 iterations (triggered from endScene()).
4101 Without this, all loaded but unused meshes will retain their HW
4102 buffers for at least 5 minutes, at which point looking up the HW buffers
4103 becomes a bottleneck and the framerate drops (as much as 30%).
4105 Tests showed that numbers between 50 and 1000 are good, so picked 500.
4106 There are no other public Irrlicht APIs that allow interacting with the
4107 HW buffers without tracking the status of every individual mesh.
4109 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4111 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4112 driver->removeAllHardwareBuffers();
4113 m_reset_HW_buffer_counter = 0;
4119 stats->drawtime = tt_draw.stop(true);
4120 g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
4121 g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
4124 /* Log times and stuff for visualization */
4125 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4127 Profiler::GraphValues values;
4128 g_profiler->graphGet(values);
4132 /****************************************************************************
4134 *****************************************************************************/
4135 void Game::updateShadows()
4137 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4141 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4143 float timeoftheday = getWickedTimeOfDay(in_timeofday);
4144 bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
4145 bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
4146 shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
4148 timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
4149 const float offset_constant = 10000.0f;
4151 v3f light = is_day ? sky->getSunDirection() : sky->getMoonDirection();
4153 v3f sun_pos = light * offset_constant;
4155 if (shadow->getDirectionalLightCount() == 0)
4156 shadow->addDirectionalLight();
4157 shadow->getDirectionalLight().setDirection(sun_pos);
4158 shadow->setTimeOfDay(in_timeofday);
4160 shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4163 /****************************************************************************
4165 ****************************************************************************/
4167 void FpsControl::reset()
4169 last_time = porting::getTimeUs();
4173 * On some computers framerate doesn't seem to be automatically limited
4175 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
4177 const float fps_limit = (device->isWindowFocused() && !g_menumgr.pausesGame())
4178 ? g_settings->getFloat("fps_max")
4179 : g_settings->getFloat("fps_max_unfocused");
4180 const u64 frametime_min = 1000000.0f / std::max(fps_limit, 1.0f);
4182 u64 time = porting::getTimeUs();
4184 if (time > last_time) // Make sure time hasn't overflowed
4185 busy_time = time - last_time;
4189 if (busy_time < frametime_min) {
4190 sleep_time = frametime_min - busy_time;
4191 if (sleep_time > 1000)
4192 sleep_ms(sleep_time / 1000);
4197 // Read the timer again to accurately determine how long we actually slept,
4198 // rather than calculating it by adding sleep_time to time.
4199 time = porting::getTimeUs();
4201 if (time > last_time) // Make sure last_time hasn't overflowed
4202 *dtime = (time - last_time) / 1000000.0f;
4209 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4211 const wchar_t *wmsg = wgettext(msg);
4212 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4217 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4219 ((Game *)data)->readSettings();
4222 void Game::readSettings()
4224 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4225 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4226 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4227 m_cache_enable_particles = g_settings->getBool("enable_particles");
4228 m_cache_enable_fog = g_settings->getBool("enable_fog");
4229 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f);
4230 m_cache_joystick_frustum_sensitivity = std::max(g_settings->getFloat("joystick_frustum_sensitivity"), 0.001f);
4231 m_repeat_place_time = g_settings->getFloat("repeat_place_time", 0.25f, 2.0);
4233 m_cache_enable_noclip = g_settings->getBool("noclip");
4234 m_cache_enable_free_move = g_settings->getBool("free_move");
4236 m_cache_fog_start = g_settings->getFloat("fog_start");
4238 m_cache_cam_smoothing = 0;
4239 if (g_settings->getBool("cinematic"))
4240 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4242 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4244 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4245 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4246 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4248 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4251 /****************************************************************************/
4252 /****************************************************************************
4254 ****************************************************************************/
4255 /****************************************************************************/
4257 void Game::showDeathFormspec()
4259 static std::string formspec_str =
4260 std::string("formspec_version[1]") +
4262 "bgcolor[#320000b4;true]"
4263 "label[4.85,1.35;" + gettext("You died") + "]"
4264 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4268 /* Note: FormspecFormSource and LocalFormspecHandler *
4269 * are deleted by guiFormSpecMenu */
4270 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4271 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4273 auto *&formspec = m_game_ui->getFormspecGUI();
4274 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4275 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4276 formspec->setFocus("btn_respawn");
4279 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4280 void Game::showPauseMenu()
4282 #ifdef HAVE_TOUCHSCREENGUI
4283 static const std::string control_text = strgettext("Default Controls:\n"
4284 "No menu visible:\n"
4285 "- single tap: button activate\n"
4286 "- double tap: place/use\n"
4287 "- slide finger: look around\n"
4288 "Menu/Inventory visible:\n"
4289 "- double tap (outside):\n"
4291 "- touch stack, touch slot:\n"
4293 "- touch&drag, tap 2nd finger\n"
4294 " --> place single item to slot\n"
4297 static const std::string control_text_template = strgettext("Controls:\n"
4298 "- %s: move forwards\n"
4299 "- %s: move backwards\n"
4301 "- %s: move right\n"
4302 "- %s: jump/climb up\n"
4305 "- %s: sneak/climb down\n"
4308 "- Mouse: turn/look\n"
4309 "- Mouse wheel: select item\n"
4313 char control_text_buf[600];
4315 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4316 GET_KEY_NAME(keymap_forward),
4317 GET_KEY_NAME(keymap_backward),
4318 GET_KEY_NAME(keymap_left),
4319 GET_KEY_NAME(keymap_right),
4320 GET_KEY_NAME(keymap_jump),
4321 GET_KEY_NAME(keymap_dig),
4322 GET_KEY_NAME(keymap_place),
4323 GET_KEY_NAME(keymap_sneak),
4324 GET_KEY_NAME(keymap_drop),
4325 GET_KEY_NAME(keymap_inventory),
4326 GET_KEY_NAME(keymap_chat)
4329 std::string control_text = std::string(control_text_buf);
4330 str_formspec_escape(control_text);
4333 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4334 std::ostringstream os;
4336 os << "formspec_version[1]" << SIZE_TAG
4337 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4338 << strgettext("Continue") << "]";
4340 if (!simple_singleplayer_mode) {
4341 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4342 << strgettext("Change Password") << "]";
4344 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4349 if (g_settings->getBool("enable_sound")) {
4350 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4351 << strgettext("Sound Volume") << "]";
4354 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4355 << strgettext("Change Keys") << "]";
4357 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4358 << strgettext("Exit to Menu") << "]";
4359 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4360 << strgettext("Exit to OS") << "]"
4361 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4362 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4364 << strgettext("Game info:") << "\n";
4365 const std::string &address = client->getAddressName();
4366 static const std::string mode = strgettext("- Mode: ");
4367 if (!simple_singleplayer_mode) {
4368 Address serverAddress = client->getServerAddress();
4369 if (!address.empty()) {
4370 os << mode << strgettext("Remote server") << "\n"
4371 << strgettext("- Address: ") << address;
4373 os << mode << strgettext("Hosting server");
4375 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4377 os << mode << strgettext("Singleplayer") << "\n";
4379 if (simple_singleplayer_mode || address.empty()) {
4380 static const std::string on = strgettext("On");
4381 static const std::string off = strgettext("Off");
4382 // Note: Status of enable_damage and creative_mode settings is intentionally
4383 // NOT shown here because the game might roll its own damage system and/or do
4384 // a per-player Creative Mode, in which case writing it here would mislead.
4385 bool damage = g_settings->getBool("enable_damage");
4386 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4387 if (!simple_singleplayer_mode) {
4389 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4390 //~ PvP = Player versus Player
4391 os << strgettext("- PvP: ") << pvp << "\n";
4393 os << strgettext("- Public: ") << announced << "\n";
4394 std::string server_name = g_settings->get("server_name");
4395 str_formspec_escape(server_name);
4396 if (announced == on && !server_name.empty())
4397 os << strgettext("- Server Name: ") << server_name;
4404 /* Note: FormspecFormSource and LocalFormspecHandler *
4405 * are deleted by guiFormSpecMenu */
4406 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4407 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4409 auto *&formspec = m_game_ui->getFormspecGUI();
4410 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4411 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4412 formspec->setFocus("btn_continue");
4413 // game will be paused in next step, if in singleplayer (see m_is_paused)
4414 formspec->doPause = true;
4417 /****************************************************************************/
4418 /****************************************************************************
4419 extern function for launching the game
4420 ****************************************************************************/
4421 /****************************************************************************/
4423 void the_game(bool *kill,
4424 InputHandler *input,
4425 RenderingEngine *rendering_engine,
4426 const GameStartData &start_data,
4427 std::string &error_message,
4428 ChatBackend &chat_backend,
4429 bool *reconnect_requested) // Used for local game
4433 /* Make a copy of the server address because if a local singleplayer server
4434 * is created then this is updated and we don't want to change the value
4435 * passed to us by the calling function
4440 if (game.startup(kill, input, rendering_engine, start_data,
4441 error_message, reconnect_requested, &chat_backend)) {
4445 } catch (SerializationError &e) {
4446 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
4447 error_message = strgettext("A serialization error occurred:") +"\n"
4448 + e.what() + "\n\n" + ver_err;
4449 errorstream << error_message << std::endl;
4450 } catch (ServerError &e) {
4451 error_message = e.what();
4452 errorstream << "ServerError: " << error_message << std::endl;
4453 } catch (ModError &e) {
4454 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
4455 error_message = std::string("ModError: ") + e.what() +
4456 strgettext("\nCheck debug.txt for details.");
4457 errorstream << error_message << std::endl;