3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "client/renderingengine.h"
27 #include "client/clientevent.h"
28 #include "client/gameui.h"
29 #include "client/inputhandler.h"
30 #include "client/tile.h" // For TextureSource
31 #include "client/keys.h"
32 #include "client/joystick_controller.h"
33 #include "clientmap.h"
36 #include "content_cao.h"
37 #include "content/subgames.h"
38 #include "client/event_manager.h"
39 #include "fontengine.h"
43 #include "gameparams.h"
45 #include "gui/guiChatConsole.h"
46 #include "gui/guiFormSpecMenu.h"
47 #include "gui/guiKeyChangeMenu.h"
48 #include "gui/guiPasswordChange.h"
49 #include "gui/guiVolumeChange.h"
50 #include "gui/mainmenumanager.h"
51 #include "gui/profilergraph.h"
54 #include "nodedef.h" // Needed for determining pointing to nodes
55 #include "nodemetadata.h"
56 #include "particles.h"
64 #include "translation.h"
65 #include "util/basic_macros.h"
66 #include "util/directiontables.h"
67 #include "util/pointedthing.h"
68 #include "util/quicktune_shortcutter.h"
69 #include "irrlicht_changes/static_text.h"
72 #include "script/scripting_client.h"
76 #include "client/sound_openal.h"
78 #include "client/sound.h"
84 struct TextDestNodeMetadata : public TextDest
86 TextDestNodeMetadata(v3s16 p, Client *client)
91 // This is deprecated I guess? -celeron55
92 void gotText(const std::wstring &text)
94 std::string ntext = wide_to_utf8(text);
95 infostream << "Submitting 'text' field of node at (" << m_p.X << ","
96 << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
98 fields["text"] = ntext;
99 m_client->sendNodemetaFields(m_p, "", fields);
101 void gotText(const StringMap &fields)
103 m_client->sendNodemetaFields(m_p, "", fields);
110 struct TextDestPlayerInventory : public TextDest
112 TextDestPlayerInventory(Client *client)
117 TextDestPlayerInventory(Client *client, const std::string &formname)
120 m_formname = formname;
122 void gotText(const StringMap &fields)
124 m_client->sendInventoryFields(m_formname, fields);
130 struct LocalFormspecHandler : public TextDest
132 LocalFormspecHandler(const std::string &formname)
134 m_formname = formname;
137 LocalFormspecHandler(const std::string &formname, Client *client):
140 m_formname = formname;
143 void gotText(const StringMap &fields)
145 if (m_formname == "MT_PAUSE_MENU") {
146 if (fields.find("btn_sound") != fields.end()) {
147 g_gamecallback->changeVolume();
151 if (fields.find("btn_key_config") != fields.end()) {
152 g_gamecallback->keyConfig();
156 if (fields.find("btn_exit_menu") != fields.end()) {
157 g_gamecallback->disconnect();
161 if (fields.find("btn_exit_os") != fields.end()) {
162 g_gamecallback->exitToOS();
164 RenderingEngine::get_raw_device()->closeDevice();
169 if (fields.find("btn_change_password") != fields.end()) {
170 g_gamecallback->changePassword();
177 if (m_formname == "MT_DEATH_SCREEN") {
178 assert(m_client != 0);
179 m_client->sendRespawn();
183 if (m_client->modsLoaded())
184 m_client->getScript()->on_formspec_input(m_formname, fields);
187 Client *m_client = nullptr;
190 /* Form update callback */
192 class NodeMetadataFormSource: public IFormSource
195 NodeMetadataFormSource(ClientMap *map, v3s16 p):
200 const std::string &getForm() const
202 static const std::string empty_string = "";
203 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
208 return meta->getString("formspec");
211 virtual std::string resolveText(const std::string &str)
213 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
218 return meta->resolveString(str);
225 class PlayerInventoryFormSource: public IFormSource
228 PlayerInventoryFormSource(Client *client):
233 const std::string &getForm() const
235 LocalPlayer *player = m_client->getEnv().getLocalPlayer();
236 return player->inventory_formspec;
242 class NodeDugEvent : public MtEvent
248 NodeDugEvent(v3s16 p, MapNode n):
252 Type getType() const { return NODE_DUG; }
257 ISoundManager *m_sound;
258 const NodeDefManager *m_ndef;
261 bool makes_footstep_sound;
262 float m_player_step_timer;
263 float m_player_jump_timer;
265 SimpleSoundSpec m_player_step_sound;
266 SimpleSoundSpec m_player_leftpunch_sound;
267 // Second sound made on left punch, currently used for item 'use' sound
268 SimpleSoundSpec m_player_leftpunch_sound2;
269 SimpleSoundSpec m_player_rightpunch_sound;
271 SoundMaker(ISoundManager *sound, const NodeDefManager *ndef):
274 makes_footstep_sound(true),
275 m_player_step_timer(0.0f),
276 m_player_jump_timer(0.0f)
280 void playPlayerStep()
282 if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
283 m_player_step_timer = 0.03;
284 if (makes_footstep_sound)
285 m_sound->playSound(m_player_step_sound);
289 void playPlayerJump()
291 if (m_player_jump_timer <= 0.0f) {
292 m_player_jump_timer = 0.2f;
293 m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f));
297 static void viewBobbingStep(MtEvent *e, void *data)
299 SoundMaker *sm = (SoundMaker *)data;
300 sm->playPlayerStep();
303 static void playerRegainGround(MtEvent *e, void *data)
305 SoundMaker *sm = (SoundMaker *)data;
306 sm->playPlayerStep();
309 static void playerJump(MtEvent *e, void *data)
311 SoundMaker *sm = (SoundMaker *)data;
312 sm->playPlayerJump();
315 static void cameraPunchLeft(MtEvent *e, void *data)
317 SoundMaker *sm = (SoundMaker *)data;
318 sm->m_sound->playSound(sm->m_player_leftpunch_sound);
319 sm->m_sound->playSound(sm->m_player_leftpunch_sound2);
322 static void cameraPunchRight(MtEvent *e, void *data)
324 SoundMaker *sm = (SoundMaker *)data;
325 sm->m_sound->playSound(sm->m_player_rightpunch_sound);
328 static void nodeDug(MtEvent *e, void *data)
330 SoundMaker *sm = (SoundMaker *)data;
331 NodeDugEvent *nde = (NodeDugEvent *)e;
332 sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug);
335 static void playerDamage(MtEvent *e, void *data)
337 SoundMaker *sm = (SoundMaker *)data;
338 sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5));
341 static void playerFallingDamage(MtEvent *e, void *data)
343 SoundMaker *sm = (SoundMaker *)data;
344 sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5));
347 void registerReceiver(MtEventManager *mgr)
349 mgr->reg(MtEvent::VIEW_BOBBING_STEP, SoundMaker::viewBobbingStep, this);
350 mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, this);
351 mgr->reg(MtEvent::PLAYER_JUMP, SoundMaker::playerJump, this);
352 mgr->reg(MtEvent::CAMERA_PUNCH_LEFT, SoundMaker::cameraPunchLeft, this);
353 mgr->reg(MtEvent::CAMERA_PUNCH_RIGHT, SoundMaker::cameraPunchRight, this);
354 mgr->reg(MtEvent::NODE_DUG, SoundMaker::nodeDug, this);
355 mgr->reg(MtEvent::PLAYER_DAMAGE, SoundMaker::playerDamage, this);
356 mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, this);
359 void step(float dtime)
361 m_player_step_timer -= dtime;
362 m_player_jump_timer -= dtime;
366 // Locally stored sounds don't need to be preloaded because of this
367 class GameOnDemandSoundFetcher: public OnDemandSoundFetcher
369 std::set<std::string> m_fetched;
371 void paths_insert(std::set<std::string> &dst_paths,
372 const std::string &base,
373 const std::string &name)
375 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg");
376 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg");
377 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg");
378 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg");
379 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg");
380 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg");
381 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg");
382 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg");
383 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg");
384 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg");
385 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg");
388 void fetchSounds(const std::string &name,
389 std::set<std::string> &dst_paths,
390 std::set<std::string> &dst_datas)
392 if (m_fetched.count(name))
395 m_fetched.insert(name);
397 paths_insert(dst_paths, porting::path_share, name);
398 paths_insert(dst_paths, porting::path_user, name);
403 typedef s32 SamplerLayer_t;
406 class GameGlobalShaderConstantSetter : public IShaderConstantSetter
410 bool *m_force_fog_off;
413 CachedPixelShaderSetting<float, 4> m_sky_bg_color;
414 CachedPixelShaderSetting<float> m_fog_distance;
415 CachedVertexShaderSetting<float> m_animation_timer_vertex;
416 CachedPixelShaderSetting<float> m_animation_timer_pixel;
417 CachedPixelShaderSetting<float, 3> m_day_light;
418 CachedPixelShaderSetting<float, 4> m_star_color;
419 CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
420 CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
421 CachedPixelShaderSetting<float, 3> m_minimap_yaw;
422 CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
423 CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
424 CachedPixelShaderSetting<SamplerLayer_t> m_texture0;
425 CachedPixelShaderSetting<SamplerLayer_t> m_texture1;
426 CachedPixelShaderSetting<SamplerLayer_t> m_texture2;
427 CachedPixelShaderSetting<SamplerLayer_t> m_texture3;
428 CachedPixelShaderSetting<float, 2> m_texel_size0;
429 std::array<float, 2> m_texel_size0_values;
430 CachedPixelShaderSetting<float> m_exposure_factor_pixel;
431 float m_user_exposure_factor;
432 bool m_bloom_enabled;
433 CachedPixelShaderSetting<float> m_bloom_intensity_pixel;
434 float m_bloom_intensity;
435 CachedPixelShaderSetting<float> m_bloom_strength_pixel;
436 float m_bloom_strength;
437 CachedPixelShaderSetting<float> m_bloom_radius_pixel;
438 float m_bloom_radius;
440 CachedPixelShaderSetting<float> m_saturation_pixel;
443 void onSettingsChange(const std::string &name)
445 if (name == "enable_fog")
446 m_fog_enabled = g_settings->getBool("enable_fog");
447 if (name == "exposure_factor")
448 m_user_exposure_factor = g_settings->getFloat("exposure_factor", 0.1f, 10.0f);
449 if (name == "bloom_intensity")
450 m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f);
451 if (name == "bloom_strength_factor")
452 m_bloom_strength = RenderingEngine::BASE_BLOOM_STRENGTH * g_settings->getFloat("bloom_strength_factor", 0.1f, 10.0f);
453 if (name == "bloom_radius")
454 m_bloom_radius = g_settings->getFloat("bloom_radius", 0.1f, 8.0f);
455 if (name == "saturation")
456 m_saturation = g_settings->getFloat("saturation", 0.0f, 5.0f);
459 static void settingsCallback(const std::string &name, void *userdata)
461 reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
464 void setSky(Sky *sky) { m_sky = sky; }
466 GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
467 f32 *fog_range, Client *client) :
470 m_force_fog_off(force_fog_off),
471 m_fog_range(fog_range),
472 m_sky_bg_color("skyBgColor"),
473 m_fog_distance("fogDistance"),
474 m_animation_timer_vertex("animationTimer"),
475 m_animation_timer_pixel("animationTimer"),
476 m_day_light("dayLight"),
477 m_star_color("starColor"),
478 m_eye_position_pixel("eyePosition"),
479 m_eye_position_vertex("eyePosition"),
480 m_minimap_yaw("yawVec"),
481 m_camera_offset_pixel("cameraOffset"),
482 m_camera_offset_vertex("cameraOffset"),
483 m_texture0("texture0"),
484 m_texture1("texture1"),
485 m_texture2("texture2"),
486 m_texture3("texture3"),
487 m_texel_size0("texelSize0"),
488 m_exposure_factor_pixel("exposureFactor"),
489 m_bloom_intensity_pixel("bloomIntensity"),
490 m_bloom_strength_pixel("bloomStrength"),
491 m_bloom_radius_pixel("bloomRadius"),
492 m_saturation_pixel("saturation")
494 g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
495 g_settings->registerChangedCallback("exposure_factor", settingsCallback, this);
496 g_settings->registerChangedCallback("bloom_intensity", settingsCallback, this);
497 g_settings->registerChangedCallback("bloom_strength_factor", settingsCallback, this);
498 g_settings->registerChangedCallback("bloom_radius", settingsCallback, this);
499 g_settings->registerChangedCallback("saturation", settingsCallback, this);
500 m_fog_enabled = g_settings->getBool("enable_fog");
501 m_user_exposure_factor = g_settings->getFloat("exposure_factor", 0.1f, 10.0f);
502 m_bloom_enabled = g_settings->getBool("enable_bloom");
503 m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f);
504 m_bloom_strength = RenderingEngine::BASE_BLOOM_STRENGTH * g_settings->getFloat("bloom_strength_factor", 0.1f, 10.0f);
505 m_bloom_radius = g_settings->getFloat("bloom_radius", 0.1f, 8.0f);
506 m_saturation = g_settings->getFloat("saturation", 0.0f, 5.0f);
509 ~GameGlobalShaderConstantSetter()
511 g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
514 void onSetConstants(video::IMaterialRendererServices *services) override
517 video::SColor bgcolor = m_sky->getBgColor();
518 video::SColorf bgcolorf(bgcolor);
519 float bgcolorfa[4] = {
525 m_sky_bg_color.set(bgcolorfa, services);
528 float fog_distance = 10000 * BS;
530 if (m_fog_enabled && !*m_force_fog_off)
531 fog_distance = *m_fog_range;
533 m_fog_distance.set(&fog_distance, services);
535 u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
536 video::SColorf sunlight;
537 get_sunlight_color(&sunlight, daynight_ratio);
542 m_day_light.set(dnc, services);
544 video::SColorf star_color = m_sky->getCurrentStarColor();
545 float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a};
546 m_star_color.set(clr, services);
548 u32 animation_timer = m_client->getEnv().getFrameTime() % 1000000;
549 float animation_timer_f = (float)animation_timer / 100000.f;
550 m_animation_timer_vertex.set(&animation_timer_f, services);
551 m_animation_timer_pixel.set(&animation_timer_f, services);
553 float eye_position_array[3];
554 v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
555 epos.getAs3Values(eye_position_array);
556 m_eye_position_pixel.set(eye_position_array, services);
557 m_eye_position_vertex.set(eye_position_array, services);
559 if (m_client->getMinimap()) {
560 float minimap_yaw_array[3];
561 v3f minimap_yaw = m_client->getMinimap()->getYawVec();
562 minimap_yaw.getAs3Values(minimap_yaw_array);
563 m_minimap_yaw.set(minimap_yaw_array, services);
566 float camera_offset_array[3];
567 v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
568 offset.getAs3Values(camera_offset_array);
569 m_camera_offset_pixel.set(camera_offset_array, services);
570 m_camera_offset_vertex.set(camera_offset_array, services);
572 SamplerLayer_t tex_id;
574 m_texture0.set(&tex_id, services);
576 m_texture1.set(&tex_id, services);
578 m_texture2.set(&tex_id, services);
580 m_texture3.set(&tex_id, services);
582 m_texel_size0.set(m_texel_size0_values.data(), services);
584 float exposure_factor = m_user_exposure_factor;
585 if (std::isnan(exposure_factor))
586 exposure_factor = 1.0f;
587 m_exposure_factor_pixel.set(&exposure_factor, services);
589 if (m_bloom_enabled) {
590 m_bloom_intensity_pixel.set(&m_bloom_intensity, services);
591 m_bloom_radius_pixel.set(&m_bloom_radius, services);
592 m_bloom_strength_pixel.set(&m_bloom_strength, services);
594 m_saturation_pixel.set(&m_saturation, services);
597 void onSetMaterial(const video::SMaterial &material)
599 video::ITexture *texture = material.getTexture(0);
601 core::dimension2du size = texture->getSize();
602 m_texel_size0_values[0] = 1.f / size.Width;
603 m_texel_size0_values[1] = 1.f / size.Height;
606 m_texel_size0_values[0] = 0.f;
607 m_texel_size0_values[1] = 0.f;
613 class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
616 bool *m_force_fog_off;
619 std::vector<GameGlobalShaderConstantSetter *> created_nosky;
621 GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
622 f32 *fog_range, Client *client) :
624 m_force_fog_off(force_fog_off),
625 m_fog_range(fog_range),
629 void setSky(Sky *sky) {
631 for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
632 ggscs->setSky(m_sky);
634 created_nosky.clear();
637 virtual IShaderConstantSetter* create()
639 auto *scs = new GameGlobalShaderConstantSetter(
640 m_sky, m_force_fog_off, m_fog_range, m_client);
642 created_nosky.push_back(scs);
647 #ifdef HAVE_TOUCHSCREENGUI
648 #define SIZE_TAG "size[11,5.5]"
650 #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
653 /****************************************************************************
654 ****************************************************************************/
656 const static float object_hit_delay = 0.2;
659 FpsControl() : last_time(0), busy_time(0), sleep_time(0) {}
663 void limit(IrrlichtDevice *device, f32 *dtime);
665 u32 getBusyMs() const { return busy_time / 1000; }
667 // all values in microseconds (us)
668 u64 last_time, busy_time, sleep_time;
672 /* The reason the following structs are not anonymous structs within the
673 * class is that they are not used by the majority of member functions and
674 * many functions that do require objects of thse types do not modify them
675 * (so they can be passed as a const qualified parameter)
681 PointedThing pointed_old;
684 bool btn_down_for_dig;
686 bool digging_blocked;
687 bool reset_jump_timer;
688 float nodig_delay_timer;
690 float dig_time_complete;
691 float repeat_place_timer;
692 float object_hit_delay_timer;
693 float time_from_last_punch;
694 ClientActiveObject *selected_object;
696 float jump_timer_up; // from key up until key down
697 float jump_timer_down; // since last key down
698 float jump_timer_down_before; // from key down until key down again
701 float update_draw_list_timer;
705 v3f update_draw_list_last_cam_dir;
707 float time_of_day_smooth;
712 struct ClientEventHandler
714 void (Game::*handler)(ClientEvent *, CameraOrientation *);
717 /****************************************************************************
719 ****************************************************************************/
721 using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
723 /* This is not intended to be a public class. If a public class becomes
724 * desirable then it may be better to create another 'wrapper' class that
725 * hides most of the stuff in this class (nothing in this class is required
726 * by any other file) but exposes the public methods/data only.
733 bool startup(bool *kill,
735 RenderingEngine *rendering_engine,
736 const GameStartData &game_params,
737 std::string &error_message,
739 ChatBackend *chat_backend);
746 // Basic initialisation
747 bool init(const std::string &map_dir, const std::string &address,
748 u16 port, const SubgameSpec &gamespec);
750 bool createSingleplayerServer(const std::string &map_dir,
751 const SubgameSpec &gamespec, u16 port);
754 bool createClient(const GameStartData &start_data);
758 bool connectToServer(const GameStartData &start_data,
759 bool *connect_ok, bool *aborted);
760 bool getServerContent(bool *aborted);
764 void updateInteractTimers(f32 dtime);
765 bool checkConnection();
766 bool handleCallbacks();
767 void processQueues();
768 void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
769 void updateDebugState();
770 void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
771 void updateProfilerGraphs(ProfilerGraph *graph);
774 void processUserInput(f32 dtime);
775 void processKeyInput();
776 void processItemSelection(u16 *new_playeritem);
778 void dropSelectedItem(bool single_item = false);
779 void openInventory();
780 void openConsole(float scale, const wchar_t *line=NULL);
781 void toggleFreeMove();
782 void toggleFreeMoveAlt();
783 void togglePitchMove();
786 void toggleCinematic();
787 void toggleBlockBounds();
788 void toggleAutoforward();
790 void toggleMinimap(bool shift_pressed);
793 void toggleUpdateCamera();
795 void increaseViewRange();
796 void decreaseViewRange();
797 void toggleFullViewRange();
798 void checkZoomEnabled();
800 void updateCameraDirection(CameraOrientation *cam, float dtime);
801 void updateCameraOrientation(CameraOrientation *cam, float dtime);
802 void updatePlayerControl(const CameraOrientation &cam);
803 void step(f32 dtime);
804 void processClientEvents(CameraOrientation *cam);
805 void updateCamera(f32 dtime);
806 void updateSound(f32 dtime);
807 void processPlayerInteraction(f32 dtime, bool show_hud);
809 * Returns the object or node the player is pointing at.
810 * Also updates the selected thing in the Hud.
812 * @param[in] shootline the shootline, starting from
813 * the camera position. This also gives the maximal distance
815 * @param[in] liquids_pointable if false, liquids are ignored
816 * @param[in] look_for_object if false, objects are ignored
817 * @param[in] camera_offset offset of the camera
818 * @param[out] selected_object the selected object or
821 PointedThing updatePointedThing(
822 const core::line3d<f32> &shootline, bool liquids_pointable,
823 bool look_for_object, const v3s16 &camera_offset);
824 void handlePointingAtNothing(const ItemStack &playerItem);
825 void handlePointingAtNode(const PointedThing &pointed,
826 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
827 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
828 const v3f &player_position, bool show_debug);
829 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
830 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
831 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
832 const CameraOrientation &cam);
833 void updateShadows();
836 void showOverlayMessage(const char *msg, float dtime, int percent,
837 bool draw_clouds = true);
839 static void settingChangedCallback(const std::string &setting_name, void *data);
842 inline bool isKeyDown(GameKeyType k)
844 return input->isKeyDown(k);
846 inline bool wasKeyDown(GameKeyType k)
848 return input->wasKeyDown(k);
850 inline bool wasKeyPressed(GameKeyType k)
852 return input->wasKeyPressed(k);
854 inline bool wasKeyReleased(GameKeyType k)
856 return input->wasKeyReleased(k);
860 void handleAndroidChatInput();
865 bool force_fog_off = false;
866 bool disable_camera_update = false;
869 void showDeathFormspec();
870 void showPauseMenu();
872 void pauseAnimation();
873 void resumeAnimation();
875 // ClientEvent handlers
876 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
877 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
878 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
879 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
880 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
881 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
882 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
883 CameraOrientation *cam);
884 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
885 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
886 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
887 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
888 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
889 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
890 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
891 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
892 CameraOrientation *cam);
893 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
895 void updateChat(f32 dtime);
897 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
898 const v3s16 &nodepos, const v3s16 &neighborpos, const PointedThing &pointed,
899 const NodeMetadata *meta);
900 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
902 f32 getSensitivityScaleFactor() const;
904 InputHandler *input = nullptr;
906 Client *client = nullptr;
907 Server *server = nullptr;
909 IWritableTextureSource *texture_src = nullptr;
910 IWritableShaderSource *shader_src = nullptr;
912 // When created, these will be filled with data received from the server
913 IWritableItemDefManager *itemdef_manager = nullptr;
914 NodeDefManager *nodedef_manager = nullptr;
916 GameOnDemandSoundFetcher soundfetcher; // useful when testing
917 ISoundManager *sound = nullptr;
918 bool sound_is_dummy = false;
919 SoundMaker *soundmaker = nullptr;
921 ChatBackend *chat_backend = nullptr;
922 LogOutputBuffer m_chat_log_buf;
924 EventManager *eventmgr = nullptr;
925 QuicktuneShortcutter *quicktune = nullptr;
927 std::unique_ptr<GameUI> m_game_ui;
928 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
929 MapDrawControl *draw_control = nullptr;
930 Camera *camera = nullptr;
931 Clouds *clouds = nullptr; // Free using ->Drop()
932 Sky *sky = nullptr; // Free using ->Drop()
934 Minimap *mapper = nullptr;
936 // Map server hud ids to client hud ids
937 std::unordered_map<u32, u32> m_hud_server_to_client;
943 This class does take ownership/responsibily for cleaning up etc of any of
944 these items (e.g. device)
946 IrrlichtDevice *device;
947 RenderingEngine *m_rendering_engine;
948 video::IVideoDriver *driver;
949 scene::ISceneManager *smgr;
951 std::string *error_message;
952 bool *reconnect_requested;
953 scene::ISceneNode *skybox;
954 PausedNodesList paused_animated_nodes;
956 bool simple_singleplayer_mode;
959 /* Pre-calculated values
961 int crack_animation_length;
963 IntervalLimiter profiler_interval;
966 * TODO: Local caching of settings is not optimal and should at some stage
967 * be updated to use a global settings object for getting thse values
968 * (as opposed to the this local caching). This can be addressed in
971 bool m_cache_doubletap_jump;
972 bool m_cache_enable_clouds;
973 bool m_cache_enable_joysticks;
974 bool m_cache_enable_particles;
975 bool m_cache_enable_fog;
976 bool m_cache_enable_noclip;
977 bool m_cache_enable_free_move;
978 f32 m_cache_mouse_sensitivity;
979 f32 m_cache_joystick_frustum_sensitivity;
980 f32 m_repeat_place_time;
981 f32 m_cache_cam_smoothing;
982 f32 m_cache_fog_start;
984 bool m_invert_mouse = false;
985 bool m_first_loop_after_window_activation = false;
986 bool m_camera_offset_changed = false;
988 bool m_does_lost_focus_pause_game = false;
990 // if true, (almost) the whole game is paused
991 // this happens in pause menu in singleplayer
992 bool m_is_paused = false;
994 #if IRRLICHT_VERSION_MT_REVISION < 5
995 int m_reset_HW_buffer_counter = 0;
998 #ifdef HAVE_TOUCHSCREENGUI
999 bool m_cache_hold_aux1;
1000 bool m_touch_use_crosshair;
1001 inline bool isNoCrosshairAllowed() {
1002 return !m_touch_use_crosshair && camera->getCameraMode() == CAMERA_MODE_FIRST;
1006 bool m_android_chat_open;
1011 m_chat_log_buf(g_logger),
1012 m_game_ui(new GameUI())
1014 g_settings->registerChangedCallback("doubletap_jump",
1015 &settingChangedCallback, this);
1016 g_settings->registerChangedCallback("enable_clouds",
1017 &settingChangedCallback, this);
1018 g_settings->registerChangedCallback("doubletap_joysticks",
1019 &settingChangedCallback, this);
1020 g_settings->registerChangedCallback("enable_particles",
1021 &settingChangedCallback, this);
1022 g_settings->registerChangedCallback("enable_fog",
1023 &settingChangedCallback, this);
1024 g_settings->registerChangedCallback("mouse_sensitivity",
1025 &settingChangedCallback, this);
1026 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
1027 &settingChangedCallback, this);
1028 g_settings->registerChangedCallback("repeat_place_time",
1029 &settingChangedCallback, this);
1030 g_settings->registerChangedCallback("noclip",
1031 &settingChangedCallback, this);
1032 g_settings->registerChangedCallback("free_move",
1033 &settingChangedCallback, this);
1034 g_settings->registerChangedCallback("cinematic",
1035 &settingChangedCallback, this);
1036 g_settings->registerChangedCallback("cinematic_camera_smoothing",
1037 &settingChangedCallback, this);
1038 g_settings->registerChangedCallback("camera_smoothing",
1039 &settingChangedCallback, this);
1043 #ifdef HAVE_TOUCHSCREENGUI
1044 m_cache_hold_aux1 = false; // This is initialised properly later
1051 /****************************************************************************
1053 ****************************************************************************/
1059 if (!sound_is_dummy)
1062 delete server; // deleted first to stop all server threads
1070 delete nodedef_manager;
1071 delete itemdef_manager;
1072 delete draw_control;
1074 clearTextureNameCache();
1076 g_settings->deregisterChangedCallback("doubletap_jump",
1077 &settingChangedCallback, this);
1078 g_settings->deregisterChangedCallback("enable_clouds",
1079 &settingChangedCallback, this);
1080 g_settings->deregisterChangedCallback("enable_particles",
1081 &settingChangedCallback, this);
1082 g_settings->deregisterChangedCallback("enable_fog",
1083 &settingChangedCallback, this);
1084 g_settings->deregisterChangedCallback("mouse_sensitivity",
1085 &settingChangedCallback, this);
1086 g_settings->deregisterChangedCallback("repeat_place_time",
1087 &settingChangedCallback, this);
1088 g_settings->deregisterChangedCallback("noclip",
1089 &settingChangedCallback, this);
1090 g_settings->deregisterChangedCallback("free_move",
1091 &settingChangedCallback, this);
1092 g_settings->deregisterChangedCallback("cinematic",
1093 &settingChangedCallback, this);
1094 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
1095 &settingChangedCallback, this);
1096 g_settings->deregisterChangedCallback("camera_smoothing",
1097 &settingChangedCallback, this);
1100 bool Game::startup(bool *kill,
1101 InputHandler *input,
1102 RenderingEngine *rendering_engine,
1103 const GameStartData &start_data,
1104 std::string &error_message,
1106 ChatBackend *chat_backend)
1110 m_rendering_engine = rendering_engine;
1111 device = m_rendering_engine->get_raw_device();
1113 this->error_message = &error_message;
1114 reconnect_requested = reconnect;
1115 this->input = input;
1116 this->chat_backend = chat_backend;
1117 simple_singleplayer_mode = start_data.isSinglePlayer();
1119 input->keycache.populate();
1121 driver = device->getVideoDriver();
1122 smgr = m_rendering_engine->get_scene_manager();
1124 smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1127 runData = GameRunData();
1128 runData.time_from_last_punch = 10.0;
1130 m_game_ui->initFlags();
1132 m_invert_mouse = g_settings->getBool("invert_mouse");
1133 m_first_loop_after_window_activation = true;
1135 #ifdef HAVE_TOUCHSCREENGUI
1136 m_touch_use_crosshair = g_settings->getBool("touch_use_crosshair");
1139 g_client_translations->clear();
1141 // address can change if simple_singleplayer_mode
1142 if (!init(start_data.world_spec.path, start_data.address,
1143 start_data.socket_port, start_data.game_spec))
1146 if (!createClient(start_data))
1149 m_rendering_engine->initialize(client, hud);
1157 ProfilerGraph graph;
1158 RunStats stats = {};
1159 CameraOrientation cam_view_target = {};
1160 CameraOrientation cam_view = {};
1161 FpsControl draw_times;
1162 f32 dtime; // in seconds
1164 /* Clear the profiler */
1165 Profiler::GraphValues dummyvalues;
1166 g_profiler->graphGet(dummyvalues);
1170 set_light_table(g_settings->getFloat("display_gamma"));
1172 #ifdef HAVE_TOUCHSCREENGUI
1173 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1174 && client->checkPrivilege("fast");
1177 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1178 g_settings->getU16("screen_h"));
1180 while (m_rendering_engine->run()
1181 && !(*kill || g_gamecallback->shutdown_requested
1182 || (server && server->isShutdownRequested()))) {
1184 const irr::core::dimension2d<u32> ¤t_screen_size =
1185 m_rendering_engine->get_video_driver()->getScreenSize();
1186 // Verify if window size has changed and save it if it's the case
1187 // Ensure evaluating settings->getBool after verifying screensize
1188 // First condition is cheaper
1189 if (previous_screen_size != current_screen_size &&
1190 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1191 g_settings->getBool("autosave_screensize")) {
1192 g_settings->setU16("screen_w", current_screen_size.Width);
1193 g_settings->setU16("screen_h", current_screen_size.Height);
1194 previous_screen_size = current_screen_size;
1197 // Calculate dtime =
1198 // m_rendering_engine->run() from this iteration
1199 // + Sleep time until the wanted FPS are reached
1200 draw_times.limit(device, &dtime);
1202 // Prepare render data for next iteration
1204 updateStats(&stats, draw_times, dtime);
1205 updateInteractTimers(dtime);
1207 if (!checkConnection())
1209 if (!handleCallbacks())
1214 m_game_ui->clearInfoText();
1216 updateProfilers(stats, draw_times, dtime);
1217 processUserInput(dtime);
1218 // Update camera before player movement to avoid camera lag of one frame
1219 updateCameraDirection(&cam_view_target, dtime);
1220 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1221 cam_view.camera_yaw) * m_cache_cam_smoothing;
1222 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1223 cam_view.camera_pitch) * m_cache_cam_smoothing;
1224 updatePlayerControl(cam_view);
1227 bool was_paused = m_is_paused;
1228 m_is_paused = simple_singleplayer_mode && g_menumgr.pausesGame();
1232 if (!was_paused && m_is_paused)
1234 else if (was_paused && !m_is_paused)
1240 processClientEvents(&cam_view_target);
1242 updateCamera(dtime);
1244 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
1245 updateFrame(&graph, &stats, dtime, cam_view);
1246 updateProfilerGraphs(&graph);
1248 // Update if minimap has been disabled by the server
1249 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1251 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1258 void Game::shutdown()
1260 m_rendering_engine->finalize();
1262 auto formspec = m_game_ui->getFormspecGUI();
1264 formspec->quitMenu();
1266 #ifdef HAVE_TOUCHSCREENGUI
1267 g_touchscreengui->hide();
1270 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1275 if (gui_chat_console)
1276 gui_chat_console->drop();
1282 while (g_menumgr.menuCount() > 0) {
1283 g_menumgr.m_stack.front()->setVisible(false);
1284 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1287 m_game_ui->deleteFormspec();
1289 chat_backend->addMessage(L"", L"# Disconnected.");
1290 chat_backend->addMessage(L"", L"");
1291 m_chat_log_buf.clear();
1295 while (!client->isShutdown()) {
1296 assert(texture_src != NULL);
1297 assert(shader_src != NULL);
1298 texture_src->processQueue();
1299 shader_src->processQueue();
1306 /****************************************************************************/
1307 /****************************************************************************
1309 ****************************************************************************/
1310 /****************************************************************************/
1313 const std::string &map_dir,
1314 const std::string &address,
1316 const SubgameSpec &gamespec)
1318 texture_src = createTextureSource();
1320 showOverlayMessage(N_("Loading..."), 0, 0);
1322 shader_src = createShaderSource();
1324 itemdef_manager = createItemDefManager();
1325 nodedef_manager = createNodeDefManager();
1327 eventmgr = new EventManager();
1328 quicktune = new QuicktuneShortcutter();
1330 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1331 && eventmgr && quicktune))
1337 // Create a server if not connecting to an existing one
1338 if (address.empty()) {
1339 if (!createSingleplayerServer(map_dir, gamespec, port))
1346 bool Game::initSound()
1349 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1350 infostream << "Attempting to use OpenAL audio" << std::endl;
1351 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1353 infostream << "Failed to initialize OpenAL audio" << std::endl;
1355 infostream << "Sound disabled." << std::endl;
1359 infostream << "Using dummy audio." << std::endl;
1360 sound = &dummySoundManager;
1361 sound_is_dummy = true;
1364 soundmaker = new SoundMaker(sound, nodedef_manager);
1368 soundmaker->registerReceiver(eventmgr);
1373 bool Game::createSingleplayerServer(const std::string &map_dir,
1374 const SubgameSpec &gamespec, u16 port)
1376 showOverlayMessage(N_("Creating server..."), 0, 5);
1378 std::string bind_str = g_settings->get("bind_address");
1379 Address bind_addr(0, 0, 0, 0, port);
1381 if (g_settings->getBool("ipv6_server")) {
1382 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1386 bind_addr.Resolve(bind_str.c_str());
1387 } catch (ResolveError &e) {
1388 infostream << "Resolving bind address \"" << bind_str
1389 << "\" failed: " << e.what()
1390 << " -- Listening on all addresses." << std::endl;
1393 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1394 *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
1395 bind_addr.serializeString().c_str());
1396 errorstream << *error_message << std::endl;
1400 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1401 false, nullptr, error_message);
1407 bool Game::createClient(const GameStartData &start_data)
1409 showOverlayMessage(N_("Creating client..."), 0, 10);
1411 draw_control = new MapDrawControl();
1415 bool could_connect, connect_aborted;
1416 #ifdef HAVE_TOUCHSCREENGUI
1417 if (g_touchscreengui) {
1418 g_touchscreengui->init(texture_src);
1419 g_touchscreengui->hide();
1422 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1425 if (!could_connect) {
1426 if (error_message->empty() && !connect_aborted) {
1427 // Should not happen if error messages are set properly
1428 *error_message = gettext("Connection failed for unknown reason");
1429 errorstream << *error_message << std::endl;
1434 if (!getServerContent(&connect_aborted)) {
1435 if (error_message->empty() && !connect_aborted) {
1436 // Should not happen if error messages are set properly
1437 *error_message = gettext("Connection failed for unknown reason");
1438 errorstream << *error_message << std::endl;
1443 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1444 &m_flags.force_fog_off, &runData.fog_range, client);
1445 shader_src->addShaderConstantSetterFactory(scsf);
1447 // Update cached textures, meshes and materials
1448 client->afterContentReceived();
1452 camera = new Camera(*draw_control, client, m_rendering_engine);
1453 if (client->modsLoaded())
1454 client->getScript()->on_camera_ready(camera);
1455 client->setCamera(camera);
1456 #ifdef HAVE_TOUCHSCREENGUI
1457 if (g_touchscreengui) {
1458 g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed());
1464 if (m_cache_enable_clouds)
1465 clouds = new Clouds(smgr, -1, time(0));
1469 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
1471 skybox = NULL; // This is used/set later on in the main run loop
1473 /* Pre-calculated values
1475 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1477 v2u32 size = t->getOriginalSize();
1478 crack_animation_length = size.Y / size.X;
1480 crack_animation_length = 5;
1486 /* Set window caption
1488 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1490 str += utf8_to_wide(g_version_hash);
1492 const wchar_t *text = nullptr;
1493 if (simple_singleplayer_mode)
1494 text = wgettext("Singleplayer");
1496 text = wgettext("Multiplayer");
1503 str += driver->getName();
1506 device->setWindowCaption(str.c_str());
1508 LocalPlayer *player = client->getEnv().getLocalPlayer();
1509 player->hurt_tilt_timer = 0;
1510 player->hurt_tilt_strength = 0;
1512 hud = new Hud(client, player, &player->inventory);
1514 mapper = client->getMinimap();
1516 if (mapper && client->modsLoaded())
1517 client->getScript()->on_minimap_ready(mapper);
1522 bool Game::initGui()
1526 // Remove stale "recent" chat messages from previous connections
1527 chat_backend->clearRecentChat();
1529 // Make sure the size of the recent messages buffer is right
1530 chat_backend->applySettings();
1532 // Chat backend and console
1533 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1534 -1, chat_backend, client, &g_menumgr);
1536 #ifdef HAVE_TOUCHSCREENGUI
1538 if (g_touchscreengui)
1539 g_touchscreengui->show();
1546 bool Game::connectToServer(const GameStartData &start_data,
1547 bool *connect_ok, bool *connection_aborted)
1549 *connect_ok = false; // Let's not be overly optimistic
1550 *connection_aborted = false;
1551 bool local_server_mode = false;
1553 showOverlayMessage(N_("Resolving address..."), 0, 15);
1555 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1558 connect_address.Resolve(start_data.address.c_str());
1560 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1561 if (connect_address.isIPv6()) {
1562 IPv6AddressBytes addr_bytes;
1563 addr_bytes.bytes[15] = 1;
1564 connect_address.setAddress(&addr_bytes);
1566 connect_address.setAddress(127, 0, 0, 1);
1568 local_server_mode = true;
1570 } catch (ResolveError &e) {
1571 *error_message = fmtgettext("Couldn't resolve address: %s", e.what());
1573 errorstream << *error_message << std::endl;
1577 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1578 *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
1579 errorstream << *error_message << std::endl;
1584 client = new Client(start_data.name.c_str(),
1585 start_data.password, start_data.address,
1586 *draw_control, texture_src, shader_src,
1587 itemdef_manager, nodedef_manager, sound, eventmgr,
1588 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get(),
1589 start_data.allow_login_or_register);
1590 client->migrateModStorage();
1591 } catch (const BaseException &e) {
1592 *error_message = fmtgettext("Error creating client: %s", e.what());
1593 errorstream << *error_message << std::endl;
1597 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1599 infostream << "Connecting to server at ";
1600 connect_address.print(infostream);
1601 infostream << std::endl;
1603 client->connect(connect_address,
1604 simple_singleplayer_mode || local_server_mode);
1607 Wait for server to accept connection
1613 FpsControl fps_control;
1615 f32 wait_time = 0; // in seconds
1617 fps_control.reset();
1619 while (m_rendering_engine->run()) {
1621 fps_control.limit(device, &dtime);
1623 // Update client and server
1624 client->step(dtime);
1627 server->step(dtime);
1630 if (client->getState() == LC_Init) {
1636 if (*connection_aborted)
1639 if (client->accessDenied()) {
1640 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1641 *reconnect_requested = client->reconnectRequested();
1642 errorstream << *error_message << std::endl;
1646 if (input->cancelPressed()) {
1647 *connection_aborted = true;
1648 infostream << "Connect aborted [Escape]" << std::endl;
1653 // Only time out if we aren't waiting for the server we started
1654 if (!start_data.address.empty() && wait_time > 10) {
1655 *error_message = gettext("Connection timed out.");
1656 errorstream << *error_message << std::endl;
1661 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1663 } catch (con::PeerNotFoundException &e) {
1664 // TODO: Should something be done here? At least an info/error
1672 bool Game::getServerContent(bool *aborted)
1676 FpsControl fps_control;
1677 f32 dtime; // in seconds
1679 fps_control.reset();
1681 while (m_rendering_engine->run()) {
1683 fps_control.limit(device, &dtime);
1685 // Update client and server
1686 client->step(dtime);
1689 server->step(dtime);
1692 if (client->mediaReceived() && client->itemdefReceived() &&
1693 client->nodedefReceived()) {
1698 if (!checkConnection())
1701 if (client->getState() < LC_Init) {
1702 *error_message = gettext("Client disconnected");
1703 errorstream << *error_message << std::endl;
1707 if (input->cancelPressed()) {
1709 infostream << "Connect aborted [Escape]" << std::endl;
1716 if (!client->itemdefReceived()) {
1717 const wchar_t *text = wgettext("Item definitions...");
1719 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1722 } else if (!client->nodedefReceived()) {
1723 const wchar_t *text = wgettext("Node definitions...");
1725 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1729 std::ostringstream message;
1730 std::fixed(message);
1731 message.precision(0);
1732 float receive = client->mediaReceiveProgress() * 100;
1733 message << gettext("Media...");
1735 message << " " << receive << "%";
1736 message.precision(2);
1738 if ((USE_CURL == 0) ||
1739 (!g_settings->getBool("enable_remote_media_server"))) {
1740 float cur = client->getCurRate();
1741 std::string cur_unit = gettext("KiB/s");
1745 cur_unit = gettext("MiB/s");
1748 message << " (" << cur << ' ' << cur_unit << ")";
1751 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1752 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
1753 texture_src, dtime, progress);
1761 /****************************************************************************/
1762 /****************************************************************************
1764 ****************************************************************************/
1765 /****************************************************************************/
1767 inline void Game::updateInteractTimers(f32 dtime)
1769 if (runData.nodig_delay_timer >= 0)
1770 runData.nodig_delay_timer -= dtime;
1772 if (runData.object_hit_delay_timer >= 0)
1773 runData.object_hit_delay_timer -= dtime;
1775 runData.time_from_last_punch += dtime;
1779 /* returns false if game should exit, otherwise true
1781 inline bool Game::checkConnection()
1783 if (client->accessDenied()) {
1784 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1785 *reconnect_requested = client->reconnectRequested();
1786 errorstream << *error_message << std::endl;
1794 /* returns false if game should exit, otherwise true
1796 inline bool Game::handleCallbacks()
1798 if (g_gamecallback->disconnect_requested) {
1799 g_gamecallback->disconnect_requested = false;
1803 if (g_gamecallback->changepassword_requested) {
1804 (new GUIPasswordChange(guienv, guiroot, -1,
1805 &g_menumgr, client, texture_src))->drop();
1806 g_gamecallback->changepassword_requested = false;
1809 if (g_gamecallback->changevolume_requested) {
1810 (new GUIVolumeChange(guienv, guiroot, -1,
1811 &g_menumgr, texture_src))->drop();
1812 g_gamecallback->changevolume_requested = false;
1815 if (g_gamecallback->keyconfig_requested) {
1816 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1817 &g_menumgr, texture_src))->drop();
1818 g_gamecallback->keyconfig_requested = false;
1821 if (g_gamecallback->keyconfig_changed) {
1822 input->keycache.populate(); // update the cache with new settings
1823 g_gamecallback->keyconfig_changed = false;
1830 void Game::processQueues()
1832 texture_src->processQueue();
1833 itemdef_manager->processQueue(client);
1834 shader_src->processQueue();
1837 void Game::updateDebugState()
1839 LocalPlayer *player = client->getEnv().getLocalPlayer();
1841 // debug UI and wireframe
1842 bool has_debug = client->checkPrivilege("debug");
1843 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
1845 if (m_game_ui->m_flags.show_basic_debug) {
1846 if (!has_basic_debug)
1847 m_game_ui->m_flags.show_basic_debug = false;
1848 } else if (m_game_ui->m_flags.show_minimal_debug) {
1849 if (has_basic_debug)
1850 m_game_ui->m_flags.show_basic_debug = true;
1852 if (!has_basic_debug)
1853 hud->disableBlockBounds();
1855 draw_control->show_wireframe = false;
1858 draw_control->allow_noclip = m_cache_enable_noclip && client->checkPrivilege("noclip");
1861 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1864 float profiler_print_interval =
1865 g_settings->getFloat("profiler_print_interval");
1866 bool print_to_log = true;
1868 if (profiler_print_interval == 0) {
1869 print_to_log = false;
1870 profiler_print_interval = 3;
1873 if (profiler_interval.step(dtime, profiler_print_interval)) {
1875 infostream << "Profiler:" << std::endl;
1876 g_profiler->print(infostream);
1879 m_game_ui->updateProfiler();
1880 g_profiler->clear();
1883 // Update update graphs
1884 g_profiler->graphAdd("Time non-rendering [us]",
1885 draw_times.busy_time - stats.drawtime);
1887 g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
1888 g_profiler->graphAdd("FPS", 1.0f / dtime);
1891 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1898 /* Time average and jitter calculation
1900 jp = &stats->dtime_jitter;
1901 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1903 jitter = dtime - jp->avg;
1905 if (jitter > jp->max)
1908 jp->counter += dtime;
1910 if (jp->counter > 0.0) {
1912 jp->max_sample = jp->max;
1913 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1917 /* Busytime average and jitter calculation
1919 jp = &stats->busy_time_jitter;
1920 jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
1922 jitter = draw_times.getBusyMs() - jp->avg;
1924 if (jitter > jp->max)
1926 if (jitter < jp->min)
1929 jp->counter += dtime;
1931 if (jp->counter > 0.0) {
1933 jp->max_sample = jp->max;
1934 jp->min_sample = jp->min;
1942 /****************************************************************************
1944 ****************************************************************************/
1946 void Game::processUserInput(f32 dtime)
1948 // Reset input if window not active or some menu is active
1949 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1951 #ifdef HAVE_TOUCHSCREENGUI
1952 g_touchscreengui->hide();
1955 #ifdef HAVE_TOUCHSCREENGUI
1956 else if (g_touchscreengui) {
1957 /* on touchscreengui step may generate own input events which ain't
1958 * what we want in case we just did clear them */
1959 g_touchscreengui->show();
1960 g_touchscreengui->step(dtime);
1964 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1965 gui_chat_console->closeConsoleAtOnce();
1968 // Input handler step() (used by the random input generator)
1972 auto formspec = m_game_ui->getFormspecGUI();
1974 formspec->getAndroidUIInput();
1976 handleAndroidChatInput();
1979 // Increase timer for double tap of "keymap_jump"
1980 if (m_cache_doubletap_jump && runData.jump_timer_up <= 0.2f)
1981 runData.jump_timer_up += dtime;
1982 if (m_cache_doubletap_jump && runData.jump_timer_down <= 0.4f)
1983 runData.jump_timer_down += dtime;
1986 processItemSelection(&runData.new_playeritem);
1990 void Game::processKeyInput()
1992 if (wasKeyDown(KeyType::DROP)) {
1993 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1994 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1995 toggleAutoforward();
1996 } else if (wasKeyDown(KeyType::BACKWARD)) {
1997 if (g_settings->getBool("continuous_forward"))
1998 toggleAutoforward();
1999 } else if (wasKeyDown(KeyType::INVENTORY)) {
2001 } else if (input->cancelPressed()) {
2003 m_android_chat_open = false;
2005 if (!gui_chat_console->isOpenInhibited()) {
2008 } else if (wasKeyDown(KeyType::CHAT)) {
2009 openConsole(0.2, L"");
2010 } else if (wasKeyDown(KeyType::CMD)) {
2011 openConsole(0.2, L"/");
2012 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
2013 if (client->modsLoaded())
2014 openConsole(0.2, L".");
2016 m_game_ui->showTranslatedStatusText("Client side scripting is disabled");
2017 } else if (wasKeyDown(KeyType::CONSOLE)) {
2018 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
2019 } else if (wasKeyDown(KeyType::FREEMOVE)) {
2021 } else if (wasKeyDown(KeyType::JUMP)) {
2022 toggleFreeMoveAlt();
2023 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
2025 } else if (wasKeyDown(KeyType::FASTMOVE)) {
2027 } else if (wasKeyDown(KeyType::NOCLIP)) {
2030 } else if (wasKeyDown(KeyType::MUTE)) {
2031 if (g_settings->getBool("enable_sound")) {
2032 bool new_mute_sound = !g_settings->getBool("mute_sound");
2033 g_settings->setBool("mute_sound", new_mute_sound);
2035 m_game_ui->showTranslatedStatusText("Sound muted");
2037 m_game_ui->showTranslatedStatusText("Sound unmuted");
2039 m_game_ui->showTranslatedStatusText("Sound system is disabled");
2041 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
2042 if (g_settings->getBool("enable_sound")) {
2043 float new_volume = g_settings->getFloat("sound_volume", 0.0f, 0.9f) + 0.1f;
2044 g_settings->setFloat("sound_volume", new_volume);
2045 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
2046 m_game_ui->showStatusText(msg);
2048 m_game_ui->showTranslatedStatusText("Sound system is disabled");
2050 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
2051 if (g_settings->getBool("enable_sound")) {
2052 float new_volume = g_settings->getFloat("sound_volume", 0.1f, 1.0f) - 0.1f;
2053 g_settings->setFloat("sound_volume", new_volume);
2054 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
2055 m_game_ui->showStatusText(msg);
2057 m_game_ui->showTranslatedStatusText("Sound system is disabled");
2060 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
2061 || wasKeyDown(KeyType::DEC_VOLUME)) {
2062 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
2064 } else if (wasKeyDown(KeyType::CINEMATIC)) {
2066 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
2067 client->makeScreenshot();
2068 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
2069 toggleBlockBounds();
2070 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
2071 m_game_ui->toggleHud();
2072 } else if (wasKeyDown(KeyType::MINIMAP)) {
2073 toggleMinimap(isKeyDown(KeyType::SNEAK));
2074 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
2075 m_game_ui->toggleChat();
2076 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
2078 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
2079 toggleUpdateCamera();
2080 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
2082 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
2083 m_game_ui->toggleProfiler();
2084 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
2085 increaseViewRange();
2086 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
2087 decreaseViewRange();
2088 } else if (wasKeyDown(KeyType::RANGESELECT)) {
2089 toggleFullViewRange();
2090 } else if (wasKeyDown(KeyType::ZOOM)) {
2092 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
2094 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
2096 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
2098 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
2102 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
2103 runData.reset_jump_timer = false;
2104 runData.jump_timer_up = 0.0f;
2107 if (quicktune->hasMessage()) {
2108 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2112 void Game::processItemSelection(u16 *new_playeritem)
2114 LocalPlayer *player = client->getEnv().getLocalPlayer();
2116 /* Item selection using mouse wheel
2118 *new_playeritem = player->getWieldIndex();
2120 s32 wheel = input->getMouseWheel();
2121 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2122 player->hud_hotbar_itemcount - 1);
2126 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2129 if (wasKeyDown(KeyType::HOTBAR_PREV))
2133 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2135 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2138 /* Item selection using hotbar slot keys
2140 for (u16 i = 0; i <= max_item; i++) {
2141 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2142 *new_playeritem = i;
2149 void Game::dropSelectedItem(bool single_item)
2151 IDropAction *a = new IDropAction();
2152 a->count = single_item ? 1 : 0;
2153 a->from_inv.setCurrentPlayer();
2154 a->from_list = "main";
2155 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2156 client->inventoryAction(a);
2160 void Game::openInventory()
2163 * Don't permit to open inventory is CAO or player doesn't exists.
2164 * This prevent showing an empty inventory at player load
2167 LocalPlayer *player = client->getEnv().getLocalPlayer();
2168 if (!player || !player->getCAO())
2171 infostream << "Game: Launching inventory" << std::endl;
2173 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2175 InventoryLocation inventoryloc;
2176 inventoryloc.setCurrentPlayer();
2178 if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2183 if (fs_src->getForm().empty()) {
2188 TextDest *txt_dst = new TextDestPlayerInventory(client);
2189 auto *&formspec = m_game_ui->updateFormspec("");
2190 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2191 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2193 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2197 void Game::openConsole(float scale, const wchar_t *line)
2199 assert(scale > 0.0f && scale <= 1.0f);
2202 porting::showInputDialog(gettext("ok"), "", "", 2);
2203 m_android_chat_open = true;
2205 if (gui_chat_console->isOpenInhibited())
2207 gui_chat_console->openConsole(scale);
2209 gui_chat_console->setCloseOnEnter(true);
2210 gui_chat_console->replaceAndAddToHistory(line);
2216 void Game::handleAndroidChatInput()
2218 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2219 std::string text = porting::getInputDialogValue();
2220 client->typeChatMessage(utf8_to_wide(text));
2221 m_android_chat_open = false;
2227 void Game::toggleFreeMove()
2229 bool free_move = !g_settings->getBool("free_move");
2230 g_settings->set("free_move", bool_to_cstr(free_move));
2233 if (client->checkPrivilege("fly")) {
2234 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2236 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2239 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2243 void Game::toggleFreeMoveAlt()
2245 if (!runData.reset_jump_timer) {
2246 runData.jump_timer_down_before = runData.jump_timer_down;
2247 runData.jump_timer_down = 0.0f;
2250 // key down (0.2 s max.), then key up (0.2 s max.), then key down
2251 if (m_cache_doubletap_jump && runData.jump_timer_up < 0.2f &&
2252 runData.jump_timer_down_before < 0.4f) // 0.2 + 0.2
2255 runData.reset_jump_timer = true;
2259 void Game::togglePitchMove()
2261 bool pitch_move = !g_settings->getBool("pitch_move");
2262 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2265 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2267 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2272 void Game::toggleFast()
2274 bool fast_move = !g_settings->getBool("fast_move");
2275 bool has_fast_privs = client->checkPrivilege("fast");
2276 g_settings->set("fast_move", bool_to_cstr(fast_move));
2279 if (has_fast_privs) {
2280 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2282 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2285 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2288 #ifdef HAVE_TOUCHSCREENGUI
2289 m_cache_hold_aux1 = fast_move && has_fast_privs;
2294 void Game::toggleNoClip()
2296 bool noclip = !g_settings->getBool("noclip");
2297 g_settings->set("noclip", bool_to_cstr(noclip));
2300 if (client->checkPrivilege("noclip")) {
2301 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2303 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2306 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2310 void Game::toggleCinematic()
2312 bool cinematic = !g_settings->getBool("cinematic");
2313 g_settings->set("cinematic", bool_to_cstr(cinematic));
2316 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2318 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2321 void Game::toggleBlockBounds()
2323 LocalPlayer *player = client->getEnv().getLocalPlayer();
2324 if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
2325 m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
2328 enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
2330 case Hud::BLOCK_BOUNDS_OFF:
2331 m_game_ui->showTranslatedStatusText("Block bounds hidden");
2333 case Hud::BLOCK_BOUNDS_CURRENT:
2334 m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
2336 case Hud::BLOCK_BOUNDS_NEAR:
2337 m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
2339 case Hud::BLOCK_BOUNDS_MAX:
2340 m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
2347 // Autoforward by toggling continuous forward.
2348 void Game::toggleAutoforward()
2350 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2351 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2353 if (autorun_enabled)
2354 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2356 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2359 void Game::toggleMinimap(bool shift_pressed)
2361 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2365 mapper->toggleMinimapShape();
2369 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2371 // Not so satisying code to keep compatibility with old fixed mode system
2373 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2375 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2376 m_game_ui->m_flags.show_minimap = false;
2379 // If radar is disabled, try to find a non radar mode or fall back to 0
2380 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2381 while (mapper->getModeIndex() &&
2382 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2385 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2389 // End of 'not so satifying code'
2390 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2391 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2392 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2394 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2397 void Game::toggleFog()
2399 bool fog_enabled = g_settings->getBool("enable_fog");
2400 g_settings->setBool("enable_fog", !fog_enabled);
2402 m_game_ui->showTranslatedStatusText("Fog disabled");
2404 m_game_ui->showTranslatedStatusText("Fog enabled");
2408 void Game::toggleDebug()
2410 LocalPlayer *player = client->getEnv().getLocalPlayer();
2411 bool has_debug = client->checkPrivilege("debug");
2412 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
2413 // Initial: No debug info
2414 // 1x toggle: Debug text
2415 // 2x toggle: Debug text with profiler graph
2416 // 3x toggle: Debug text and wireframe (needs "debug" priv)
2417 // Next toggle: Back to initial
2419 // The debug text can be in 2 modes: minimal and basic.
2420 // * Minimal: Only technical client info that not gameplay-relevant
2421 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
2422 // Basic mode is used when player has the debug HUD flag set,
2423 // otherwise the Minimal mode is used.
2424 if (!m_game_ui->m_flags.show_minimal_debug) {
2425 m_game_ui->m_flags.show_minimal_debug = true;
2426 if (has_basic_debug)
2427 m_game_ui->m_flags.show_basic_debug = true;
2428 m_game_ui->m_flags.show_profiler_graph = false;
2429 draw_control->show_wireframe = false;
2430 m_game_ui->showTranslatedStatusText("Debug info shown");
2431 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2432 if (has_basic_debug)
2433 m_game_ui->m_flags.show_basic_debug = true;
2434 m_game_ui->m_flags.show_profiler_graph = true;
2435 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2436 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2437 if (has_basic_debug)
2438 m_game_ui->m_flags.show_basic_debug = true;
2439 m_game_ui->m_flags.show_profiler_graph = false;
2440 draw_control->show_wireframe = true;
2441 m_game_ui->showTranslatedStatusText("Wireframe shown");
2443 m_game_ui->m_flags.show_minimal_debug = false;
2444 m_game_ui->m_flags.show_basic_debug = false;
2445 m_game_ui->m_flags.show_profiler_graph = false;
2446 draw_control->show_wireframe = false;
2448 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2450 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2456 void Game::toggleUpdateCamera()
2458 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2459 if (m_flags.disable_camera_update)
2460 m_game_ui->showTranslatedStatusText("Camera update disabled");
2462 m_game_ui->showTranslatedStatusText("Camera update enabled");
2466 void Game::increaseViewRange()
2468 s16 range = g_settings->getS16("viewing_range");
2469 s16 range_new = range + 10;
2471 if (range_new > 4000) {
2473 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
2474 m_game_ui->showStatusText(msg);
2476 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2477 m_game_ui->showStatusText(msg);
2479 g_settings->set("viewing_range", itos(range_new));
2483 void Game::decreaseViewRange()
2485 s16 range = g_settings->getS16("viewing_range");
2486 s16 range_new = range - 10;
2488 if (range_new < 20) {
2490 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
2491 m_game_ui->showStatusText(msg);
2493 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2494 m_game_ui->showStatusText(msg);
2496 g_settings->set("viewing_range", itos(range_new));
2500 void Game::toggleFullViewRange()
2502 draw_control->range_all = !draw_control->range_all;
2503 if (draw_control->range_all)
2504 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2506 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2510 void Game::checkZoomEnabled()
2512 LocalPlayer *player = client->getEnv().getLocalPlayer();
2513 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2514 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2517 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2519 #if IRRLICHT_VERSION_MT_REVISION >= 9
2521 device->getCursorControl()->setRelativeMode(false);
2523 device->getCursorControl()->setRelativeMode(true);
2525 if ((device->isWindowActive() && device->isWindowFocused()
2526 && !isMenuActive()) || input->isRandom()) {
2529 if (!input->isRandom()) {
2530 // Mac OSX gets upset if this is set every frame
2531 if (device->getCursorControl()->isVisible())
2532 device->getCursorControl()->setVisible(false);
2536 if (m_first_loop_after_window_activation) {
2537 m_first_loop_after_window_activation = false;
2539 input->setMousePos(driver->getScreenSize().Width / 2,
2540 driver->getScreenSize().Height / 2);
2542 updateCameraOrientation(cam, dtime);
2548 // Mac OSX gets upset if this is set every frame
2549 if (!device->getCursorControl()->isVisible())
2550 device->getCursorControl()->setVisible(true);
2553 m_first_loop_after_window_activation = true;
2558 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2559 // responsiveness independently of FOV.
2560 f32 Game::getSensitivityScaleFactor() const
2562 f32 fov_y = client->getCamera()->getFovY();
2564 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2565 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2567 return tan(fov_y / 2.0f) * 1.3763818698f;
2570 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2572 #ifdef HAVE_TOUCHSCREENGUI
2573 if (g_touchscreengui) {
2574 cam->camera_yaw += g_touchscreengui->getYawChange();
2575 cam->camera_pitch = g_touchscreengui->getPitch();
2578 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2579 v2s32 dist = input->getMousePos() - center;
2581 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2585 f32 sens_scale = getSensitivityScaleFactor();
2586 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2587 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2589 if (dist.X != 0 || dist.Y != 0)
2590 input->setMousePos(center.X, center.Y);
2591 #ifdef HAVE_TOUCHSCREENGUI
2595 if (m_cache_enable_joysticks) {
2596 f32 sens_scale = getSensitivityScaleFactor();
2597 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
2598 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2599 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2602 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2606 void Game::updatePlayerControl(const CameraOrientation &cam)
2608 LocalPlayer *player = client->getEnv().getLocalPlayer();
2610 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2612 PlayerControl control(
2613 isKeyDown(KeyType::FORWARD),
2614 isKeyDown(KeyType::BACKWARD),
2615 isKeyDown(KeyType::LEFT),
2616 isKeyDown(KeyType::RIGHT),
2617 isKeyDown(KeyType::JUMP) || player->getAutojump(),
2618 isKeyDown(KeyType::AUX1),
2619 isKeyDown(KeyType::SNEAK),
2620 isKeyDown(KeyType::ZOOM),
2621 isKeyDown(KeyType::DIG),
2622 isKeyDown(KeyType::PLACE),
2625 input->getMovementSpeed(),
2626 input->getMovementDirection()
2629 // autoforward if set: move at maximum speed
2630 if (player->getPlayerSettings().continuous_forward &&
2631 client->activeObjectsReceived() && !player->isDead()) {
2632 control.movement_speed = 1.0f;
2633 // sideways movement only
2634 float dx = sin(control.movement_direction);
2635 control.movement_direction = atan2(dx, 1.0f);
2638 #ifdef HAVE_TOUCHSCREENGUI
2639 /* For touch, simulate holding down AUX1 (fast move) if the user has
2640 * the fast_move setting toggled on. If there is an aux1 key defined for
2641 * touch then its meaning is inverted (i.e. holding aux1 means walk and
2644 if (m_cache_hold_aux1) {
2645 control.aux1 = control.aux1 ^ true;
2649 client->setPlayerControl(control);
2655 inline void Game::step(f32 dtime)
2658 server->step(dtime);
2660 client->step(dtime);
2663 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2666 for (auto &&child: node->getChildren())
2667 pauseNodeAnimation(paused, child);
2668 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2670 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2671 float speed = animated_node->getAnimationSpeed();
2674 paused.push_back({grab(animated_node), speed});
2675 animated_node->setAnimationSpeed(0.0f);
2678 void Game::pauseAnimation()
2680 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2683 void Game::resumeAnimation()
2685 for (auto &&pair: paused_animated_nodes)
2686 pair.first->setAnimationSpeed(pair.second);
2687 paused_animated_nodes.clear();
2690 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2691 {&Game::handleClientEvent_None},
2692 {&Game::handleClientEvent_PlayerDamage},
2693 {&Game::handleClientEvent_PlayerForceMove},
2694 {&Game::handleClientEvent_Deathscreen},
2695 {&Game::handleClientEvent_ShowFormSpec},
2696 {&Game::handleClientEvent_ShowLocalFormSpec},
2697 {&Game::handleClientEvent_HandleParticleEvent},
2698 {&Game::handleClientEvent_HandleParticleEvent},
2699 {&Game::handleClientEvent_HandleParticleEvent},
2700 {&Game::handleClientEvent_HudAdd},
2701 {&Game::handleClientEvent_HudRemove},
2702 {&Game::handleClientEvent_HudChange},
2703 {&Game::handleClientEvent_SetSky},
2704 {&Game::handleClientEvent_SetSun},
2705 {&Game::handleClientEvent_SetMoon},
2706 {&Game::handleClientEvent_SetStars},
2707 {&Game::handleClientEvent_OverrideDayNigthRatio},
2708 {&Game::handleClientEvent_CloudParams},
2711 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2713 FATAL_ERROR("ClientEvent type None received");
2716 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2718 if (client->modsLoaded())
2719 client->getScript()->on_damage_taken(event->player_damage.amount);
2721 if (!event->player_damage.effect)
2724 // Damage flash and hurt tilt are not used at death
2725 if (client->getHP() > 0) {
2726 LocalPlayer *player = client->getEnv().getLocalPlayer();
2728 f32 hp_max = player->getCAO() ?
2729 player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2730 f32 damage_ratio = event->player_damage.amount / hp_max;
2732 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2733 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2735 player->hurt_tilt_timer = 1.5f;
2736 player->hurt_tilt_strength =
2737 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2740 // Play damage sound
2741 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2744 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2746 cam->camera_yaw = event->player_force_move.yaw;
2747 cam->camera_pitch = event->player_force_move.pitch;
2750 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2752 // If client scripting is enabled, deathscreen is handled by CSM code in
2753 // builtin/client/init.lua
2754 if (client->modsLoaded())
2755 client->getScript()->on_death();
2757 showDeathFormspec();
2759 /* Handle visualization */
2760 LocalPlayer *player = client->getEnv().getLocalPlayer();
2761 runData.damage_flash = 0;
2762 player->hurt_tilt_timer = 0;
2763 player->hurt_tilt_strength = 0;
2766 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2768 if (event->show_formspec.formspec->empty()) {
2769 auto formspec = m_game_ui->getFormspecGUI();
2770 if (formspec && (event->show_formspec.formname->empty()
2771 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2772 formspec->quitMenu();
2775 FormspecFormSource *fs_src =
2776 new FormspecFormSource(*(event->show_formspec.formspec));
2777 TextDestPlayerInventory *txt_dst =
2778 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2780 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2781 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2782 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2785 delete event->show_formspec.formspec;
2786 delete event->show_formspec.formname;
2789 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2791 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2792 LocalFormspecHandler *txt_dst =
2793 new LocalFormspecHandler(*event->show_formspec.formname, client);
2794 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2795 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2797 delete event->show_formspec.formspec;
2798 delete event->show_formspec.formname;
2801 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2802 CameraOrientation *cam)
2804 LocalPlayer *player = client->getEnv().getLocalPlayer();
2805 client->getParticleManager()->handleParticleEvent(event, client, player);
2808 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2810 LocalPlayer *player = client->getEnv().getLocalPlayer();
2812 u32 server_id = event->hudadd->server_id;
2813 // ignore if we already have a HUD with that ID
2814 auto i = m_hud_server_to_client.find(server_id);
2815 if (i != m_hud_server_to_client.end()) {
2816 delete event->hudadd;
2820 HudElement *e = new HudElement;
2821 e->type = static_cast<HudElementType>(event->hudadd->type);
2822 e->pos = event->hudadd->pos;
2823 e->name = event->hudadd->name;
2824 e->scale = event->hudadd->scale;
2825 e->text = event->hudadd->text;
2826 e->number = event->hudadd->number;
2827 e->item = event->hudadd->item;
2828 e->dir = event->hudadd->dir;
2829 e->align = event->hudadd->align;
2830 e->offset = event->hudadd->offset;
2831 e->world_pos = event->hudadd->world_pos;
2832 e->size = event->hudadd->size;
2833 e->z_index = event->hudadd->z_index;
2834 e->text2 = event->hudadd->text2;
2835 e->style = event->hudadd->style;
2836 m_hud_server_to_client[server_id] = player->addHud(e);
2838 delete event->hudadd;
2841 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2843 LocalPlayer *player = client->getEnv().getLocalPlayer();
2845 auto i = m_hud_server_to_client.find(event->hudrm.id);
2846 if (i != m_hud_server_to_client.end()) {
2847 HudElement *e = player->removeHud(i->second);
2849 m_hud_server_to_client.erase(i);
2854 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2856 LocalPlayer *player = client->getEnv().getLocalPlayer();
2858 HudElement *e = nullptr;
2860 auto i = m_hud_server_to_client.find(event->hudchange->id);
2861 if (i != m_hud_server_to_client.end()) {
2862 e = player->getHud(i->second);
2866 delete event->hudchange;
2870 #define CASE_SET(statval, prop, dataprop) \
2872 e->prop = event->hudchange->dataprop; \
2875 switch (event->hudchange->stat) {
2876 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2878 CASE_SET(HUD_STAT_NAME, name, sdata);
2880 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2882 CASE_SET(HUD_STAT_TEXT, text, sdata);
2884 CASE_SET(HUD_STAT_NUMBER, number, data);
2886 CASE_SET(HUD_STAT_ITEM, item, data);
2888 CASE_SET(HUD_STAT_DIR, dir, data);
2890 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2892 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2894 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2896 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2898 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2900 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2902 CASE_SET(HUD_STAT_STYLE, style, data);
2907 delete event->hudchange;
2910 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2912 sky->setVisible(false);
2913 // Whether clouds are visible in front of a custom skybox.
2914 sky->setCloudsEnabled(event->set_sky->clouds);
2920 // Clear the old textures out in case we switch rendering type.
2921 sky->clearSkyboxTextures();
2922 // Handle according to type
2923 if (event->set_sky->type == "regular") {
2924 // Shows the mesh skybox
2925 sky->setVisible(true);
2926 // Update mesh based skybox colours if applicable.
2927 sky->setSkyColors(event->set_sky->sky_color);
2928 sky->setHorizonTint(
2929 event->set_sky->fog_sun_tint,
2930 event->set_sky->fog_moon_tint,
2931 event->set_sky->fog_tint_type
2933 } else if (event->set_sky->type == "skybox" &&
2934 event->set_sky->textures.size() == 6) {
2935 // Disable the dyanmic mesh skybox:
2936 sky->setVisible(false);
2938 sky->setFallbackBgColor(event->set_sky->bgcolor);
2939 // Set sunrise and sunset fog tinting:
2940 sky->setHorizonTint(
2941 event->set_sky->fog_sun_tint,
2942 event->set_sky->fog_moon_tint,
2943 event->set_sky->fog_tint_type
2945 // Add textures to skybox.
2946 for (int i = 0; i < 6; i++)
2947 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2949 // Handle everything else as plain color.
2950 if (event->set_sky->type != "plain")
2951 infostream << "Unknown sky type: "
2952 << (event->set_sky->type) << std::endl;
2953 sky->setVisible(false);
2954 sky->setFallbackBgColor(event->set_sky->bgcolor);
2955 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2956 sky->setHorizonTint(
2957 event->set_sky->bgcolor,
2958 event->set_sky->bgcolor,
2963 delete event->set_sky;
2966 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2968 sky->setSunVisible(event->sun_params->visible);
2969 sky->setSunTexture(event->sun_params->texture,
2970 event->sun_params->tonemap, texture_src);
2971 sky->setSunScale(event->sun_params->scale);
2972 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2973 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2974 delete event->sun_params;
2977 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2979 sky->setMoonVisible(event->moon_params->visible);
2980 sky->setMoonTexture(event->moon_params->texture,
2981 event->moon_params->tonemap, texture_src);
2982 sky->setMoonScale(event->moon_params->scale);
2983 delete event->moon_params;
2986 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2988 sky->setStarsVisible(event->star_params->visible);
2989 sky->setStarCount(event->star_params->count);
2990 sky->setStarColor(event->star_params->starcolor);
2991 sky->setStarScale(event->star_params->scale);
2992 sky->setStarDayOpacity(event->star_params->day_opacity);
2993 delete event->star_params;
2996 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2997 CameraOrientation *cam)
2999 client->getEnv().setDayNightRatioOverride(
3000 event->override_day_night_ratio.do_override,
3001 event->override_day_night_ratio.ratio_f * 1000.0f);
3004 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
3009 clouds->setDensity(event->cloud_params.density);
3010 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
3011 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
3012 clouds->setHeight(event->cloud_params.height);
3013 clouds->setThickness(event->cloud_params.thickness);
3014 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
3017 void Game::processClientEvents(CameraOrientation *cam)
3019 while (client->hasClientEvents()) {
3020 std::unique_ptr<ClientEvent> event(client->getClientEvent());
3021 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
3022 const ClientEventHandler& evHandler = clientEventHandler[event->type];
3023 (this->*evHandler.handler)(event.get(), cam);
3027 void Game::updateChat(f32 dtime)
3029 // Get new messages from error log buffer
3030 while (!m_chat_log_buf.empty())
3031 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
3033 // Get new messages from client
3034 std::wstring message;
3035 while (client->getChatMessage(message)) {
3036 chat_backend->addUnparsedMessage(message);
3039 // Remove old messages
3040 chat_backend->step(dtime);
3042 // Display all messages in a static text element
3043 auto &buf = chat_backend->getRecentBuffer();
3044 if (buf.getLinesModified()) {
3045 buf.resetLinesModified();
3046 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
3049 // Make sure that the size is still correct
3050 m_game_ui->updateChatSize();
3053 void Game::updateCamera(f32 dtime)
3055 LocalPlayer *player = client->getEnv().getLocalPlayer();
3058 For interaction purposes, get info about the held item
3060 - Is it a usable item?
3061 - Can it point to liquids?
3063 ItemStack playeritem;
3065 ItemStack selected, hand;
3066 playeritem = player->getWieldedItem(&selected, &hand);
3069 ToolCapabilities playeritem_toolcap =
3070 playeritem.getToolCapabilities(itemdef_manager);
3072 v3s16 old_camera_offset = camera->getOffset();
3074 if (wasKeyDown(KeyType::CAMERA_MODE)) {
3075 GenericCAO *playercao = player->getCAO();
3077 // If playercao not loaded, don't change camera
3081 camera->toggleCameraMode();
3083 #ifdef HAVE_TOUCHSCREENGUI
3084 if (g_touchscreengui)
3085 g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed());
3088 // Make the player visible depending on camera mode.
3089 playercao->updateMeshCulling();
3090 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
3093 float full_punch_interval = playeritem_toolcap.full_punch_interval;
3094 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
3096 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
3097 camera->update(player, dtime, tool_reload_ratio);
3098 camera->step(dtime);
3100 f32 camera_fov = camera->getFovMax();
3101 v3s16 camera_offset = camera->getOffset();
3103 m_camera_offset_changed = (camera_offset != old_camera_offset);
3105 if (!m_flags.disable_camera_update) {
3106 v3f camera_position = camera->getPosition();
3107 v3f camera_direction = camera->getDirection();
3109 client->getEnv().getClientMap().updateCamera(camera_position,
3110 camera_direction, camera_fov, camera_offset);
3112 if (m_camera_offset_changed) {
3113 client->updateCameraOffset(camera_offset);
3114 client->getEnv().updateCameraOffset(camera_offset);
3117 clouds->updateCameraOffset(camera_offset);
3123 void Game::updateSound(f32 dtime)
3125 // Update sound listener
3126 v3s16 camera_offset = camera->getOffset();
3127 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3128 v3f(0, 0, 0), // velocity
3129 camera->getDirection(),
3130 camera->getCameraNode()->getUpVector());
3132 bool mute_sound = g_settings->getBool("mute_sound");
3134 sound->setListenerGain(0.0f);
3136 // Check if volume is in the proper range, else fix it.
3137 float old_volume = g_settings->getFloat("sound_volume");
3138 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3139 sound->setListenerGain(new_volume);
3141 if (old_volume != new_volume) {
3142 g_settings->setFloat("sound_volume", new_volume);
3146 LocalPlayer *player = client->getEnv().getLocalPlayer();
3148 // Tell the sound maker whether to make footstep sounds
3149 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3151 // Update sound maker
3152 if (player->makes_footstep_sound)
3153 soundmaker->step(dtime);
3155 ClientMap &map = client->getEnv().getClientMap();
3156 MapNode n = map.getNode(player->getFootstepNodePos());
3157 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3161 void Game::processPlayerInteraction(f32 dtime, bool show_hud)
3163 LocalPlayer *player = client->getEnv().getLocalPlayer();
3165 const v3f camera_direction = camera->getDirection();
3166 const v3s16 camera_offset = camera->getOffset();
3169 Calculate what block is the crosshair pointing to
3172 ItemStack selected_item, hand_item;
3173 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3175 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3176 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3178 core::line3d<f32> shootline;
3180 switch (camera->getCameraMode()) {
3181 case CAMERA_MODE_FIRST:
3182 // Shoot from camera position, with bobbing
3183 shootline.start = camera->getPosition();
3185 case CAMERA_MODE_THIRD:
3186 // Shoot from player head, no bobbing
3187 shootline.start = camera->getHeadPosition();
3189 case CAMERA_MODE_THIRD_FRONT:
3190 shootline.start = camera->getHeadPosition();
3191 // prevent player pointing anything in front-view
3195 shootline.end = shootline.start + camera_direction * BS * d;
3197 #ifdef HAVE_TOUCHSCREENGUI
3198 if (g_touchscreengui && isNoCrosshairAllowed()) {
3199 shootline = g_touchscreengui->getShootline();
3200 // Scale shootline to the acual distance the player can reach
3201 shootline.end = shootline.start +
3202 shootline.getVector().normalize() * BS * d;
3203 shootline.start += intToFloat(camera_offset, BS);
3204 shootline.end += intToFloat(camera_offset, BS);
3208 PointedThing pointed = updatePointedThing(shootline,
3209 selected_def.liquids_pointable,
3210 !runData.btn_down_for_dig,
3213 if (pointed != runData.pointed_old)
3214 infostream << "Pointing at " << pointed.dump() << std::endl;
3216 // Note that updating the selection mesh every frame is not particularly efficient,
3217 // but the halo rendering code is already inefficient so there's no point in optimizing it here
3218 hud->updateSelectionMesh(camera_offset);
3220 // Allow digging again if button is not pressed
3221 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3222 runData.digging_blocked = false;
3226 - releasing dig button
3227 - pointing away from node
3229 if (runData.digging) {
3230 if (wasKeyReleased(KeyType::DIG)) {
3231 infostream << "Dig button released (stopped digging)" << std::endl;
3232 runData.digging = false;
3233 } else if (pointed != runData.pointed_old) {
3234 if (pointed.type == POINTEDTHING_NODE
3235 && runData.pointed_old.type == POINTEDTHING_NODE
3236 && pointed.node_undersurface
3237 == runData.pointed_old.node_undersurface) {
3238 // Still pointing to the same node, but a different face.
3241 infostream << "Pointing away from node (stopped digging)" << std::endl;
3242 runData.digging = false;
3243 hud->updateSelectionMesh(camera_offset);
3247 if (!runData.digging) {
3248 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3249 client->setCrack(-1, v3s16(0, 0, 0));
3250 runData.dig_time = 0.0;
3252 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3253 // Remove e.g. torches faster when clicking instead of holding dig button
3254 runData.nodig_delay_timer = 0;
3255 runData.dig_instantly = false;
3258 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3259 runData.btn_down_for_dig = false;
3261 runData.punching = false;
3263 soundmaker->m_player_leftpunch_sound = SimpleSoundSpec();
3264 soundmaker->m_player_leftpunch_sound2 = pointed.type != POINTEDTHING_NOTHING ?
3265 selected_def.sound_use : selected_def.sound_use_air;
3267 // Prepare for repeating, unless we're not supposed to
3268 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3269 runData.repeat_place_timer += dtime;
3271 runData.repeat_place_timer = 0;
3273 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3274 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3275 !client->getScript()->on_item_use(selected_item, pointed)))
3276 client->interact(INTERACT_USE, pointed);
3277 } else if (pointed.type == POINTEDTHING_NODE) {
3278 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3279 } else if (pointed.type == POINTEDTHING_OBJECT) {
3280 v3f player_position = player->getPosition();
3281 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
3282 handlePointingAtObject(pointed, tool_item, player_position,
3283 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
3284 } else if (isKeyDown(KeyType::DIG)) {
3285 // When button is held down in air, show continuous animation
3286 runData.punching = true;
3287 // Run callback even though item is not usable
3288 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3289 client->getScript()->on_item_use(selected_item, pointed);
3290 } else if (wasKeyPressed(KeyType::PLACE)) {
3291 handlePointingAtNothing(selected_item);
3294 runData.pointed_old = pointed;
3296 if (runData.punching || wasKeyPressed(KeyType::DIG))
3297 camera->setDigging(0); // dig animation
3299 input->clearWasKeyPressed();
3300 input->clearWasKeyReleased();
3301 // Ensure DIG & PLACE are marked as handled
3302 wasKeyDown(KeyType::DIG);
3303 wasKeyDown(KeyType::PLACE);
3305 input->joystick.clearWasKeyPressed(KeyType::DIG);
3306 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3308 input->joystick.clearWasKeyReleased(KeyType::DIG);
3309 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3313 PointedThing Game::updatePointedThing(
3314 const core::line3d<f32> &shootline,
3315 bool liquids_pointable,
3316 bool look_for_object,
3317 const v3s16 &camera_offset)
3319 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3320 selectionboxes->clear();
3321 hud->setSelectedFaceNormal(v3f());
3322 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3323 "show_entity_selectionbox");
3325 ClientEnvironment &env = client->getEnv();
3326 ClientMap &map = env.getClientMap();
3327 const NodeDefManager *nodedef = map.getNodeDefManager();
3329 runData.selected_object = NULL;
3330 hud->pointing_at_object = false;
3332 RaycastState s(shootline, look_for_object, liquids_pointable);
3333 PointedThing result;
3334 env.continueRaycast(&s, &result);
3335 if (result.type == POINTEDTHING_OBJECT) {
3336 hud->pointing_at_object = true;
3338 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3339 aabb3f selection_box;
3340 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3341 runData.selected_object->getSelectionBox(&selection_box)) {
3342 v3f pos = runData.selected_object->getPosition();
3343 selectionboxes->push_back(aabb3f(selection_box));
3344 hud->setSelectionPos(pos, camera_offset);
3345 GenericCAO* gcao = dynamic_cast<GenericCAO*>(runData.selected_object);
3346 if (gcao != nullptr && gcao->getProperties().rotate_selectionbox)
3347 hud->setSelectionRotation(gcao->getSceneNode()->getAbsoluteTransformation().getRotationDegrees());
3349 hud->setSelectionRotation(v3f());
3351 hud->setSelectedFaceNormal(result.raw_intersection_normal);
3352 } else if (result.type == POINTEDTHING_NODE) {
3353 // Update selection boxes
3354 MapNode n = map.getNode(result.node_undersurface);
3355 std::vector<aabb3f> boxes;
3356 n.getSelectionBoxes(nodedef, &boxes,
3357 n.getNeighbors(result.node_undersurface, &map));
3360 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3361 i != boxes.end(); ++i) {
3363 box.MinEdge -= v3f(d, d, d);
3364 box.MaxEdge += v3f(d, d, d);
3365 selectionboxes->push_back(box);
3367 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3369 hud->setSelectionRotation(v3f());
3370 hud->setSelectedFaceNormal(result.intersection_normal);
3373 // Update selection mesh light level and vertex colors
3374 if (!selectionboxes->empty()) {
3375 v3f pf = hud->getSelectionPos();
3376 v3s16 p = floatToInt(pf, BS);
3378 // Get selection mesh light level
3379 MapNode n = map.getNode(p);
3380 u16 node_light = getInteriorLight(n, -1, nodedef);
3381 u16 light_level = node_light;
3383 for (const v3s16 &dir : g_6dirs) {
3384 n = map.getNode(p + dir);
3385 node_light = getInteriorLight(n, -1, nodedef);
3386 if (node_light > light_level)
3387 light_level = node_light;
3390 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3392 final_color_blend(&c, light_level, daynight_ratio);
3394 // Modify final color a bit with time
3395 u32 timer = client->getEnv().getFrameTime() % 5000;
3396 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3397 float sin_r = 0.08f * std::sin(timerf);
3398 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3399 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3400 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3401 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3402 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3404 // Set mesh final color
3405 hud->setSelectionMeshColor(c);
3411 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3413 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3414 PointedThing fauxPointed;
3415 fauxPointed.type = POINTEDTHING_NOTHING;
3416 client->interact(INTERACT_ACTIVATE, fauxPointed);
3420 void Game::handlePointingAtNode(const PointedThing &pointed,
3421 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3423 v3s16 nodepos = pointed.node_undersurface;
3424 v3s16 neighborpos = pointed.node_abovesurface;
3427 Check information text of node
3430 ClientMap &map = client->getEnv().getClientMap();
3432 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3433 && !runData.digging_blocked
3434 && client->checkPrivilege("interact")) {
3435 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3438 // This should be done after digging handling
3439 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3442 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3443 meta->getString("infotext"))));
3445 MapNode n = map.getNode(nodepos);
3447 if (nodedef_manager->get(n).name == "unknown") {
3448 m_game_ui->setInfoText(L"Unknown node");
3452 if ((wasKeyPressed(KeyType::PLACE) ||
3453 runData.repeat_place_timer >= m_repeat_place_time) &&
3454 client->checkPrivilege("interact")) {
3455 runData.repeat_place_timer = 0;
3456 infostream << "Place button pressed while looking at ground" << std::endl;
3458 // Placing animation (always shown for feedback)
3459 camera->setDigging(1);
3461 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3463 // If the wielded item has node placement prediction,
3465 // And also set the sound and send the interact
3466 // But first check for meta formspec and rightclickable
3467 auto &def = selected_item.getDefinition(itemdef_manager);
3468 bool placed = nodePlacement(def, selected_item, nodepos, neighborpos,
3471 if (placed && client->modsLoaded())
3472 client->getScript()->on_placenode(pointed, def);
3476 bool Game::nodePlacement(const ItemDefinition &selected_def,
3477 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighborpos,
3478 const PointedThing &pointed, const NodeMetadata *meta)
3480 const auto &prediction = selected_def.node_placement_prediction;
3482 const NodeDefManager *nodedef = client->ndef();
3483 ClientMap &map = client->getEnv().getClientMap();
3485 bool is_valid_position;
3487 node = map.getNode(nodepos, &is_valid_position);
3488 if (!is_valid_position) {
3489 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3494 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3495 && !isKeyDown(KeyType::SNEAK)) {
3496 // on_rightclick callbacks are called anyway
3497 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3498 client->interact(INTERACT_PLACE, pointed);
3500 infostream << "Launching custom inventory view" << std::endl;
3502 InventoryLocation inventoryloc;
3503 inventoryloc.setNodeMeta(nodepos);
3505 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3506 &client->getEnv().getClientMap(), nodepos);
3507 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3509 auto *&formspec = m_game_ui->updateFormspec("");
3510 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3511 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3513 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3517 // on_rightclick callback
3518 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3519 !isKeyDown(KeyType::SNEAK))) {
3521 client->interact(INTERACT_PLACE, pointed);
3525 verbosestream << "Node placement prediction for "
3526 << selected_def.name << " is " << prediction << std::endl;
3527 v3s16 p = neighborpos;
3529 // Place inside node itself if buildable_to
3530 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3531 if (is_valid_position) {
3532 if (nodedef->get(n_under).buildable_to) {
3535 node = map.getNode(p, &is_valid_position);
3536 if (is_valid_position && !nodedef->get(node).buildable_to) {
3537 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3539 client->interact(INTERACT_PLACE, pointed);
3545 // Find id of predicted node
3547 bool found = nodedef->getId(prediction, id);
3550 errorstream << "Node placement prediction failed for "
3551 << selected_def.name << " (places " << prediction
3552 << ") - Name not known" << std::endl;
3553 // Handle this as if prediction was empty
3555 client->interact(INTERACT_PLACE, pointed);
3559 const ContentFeatures &predicted_f = nodedef->get(id);
3561 // Compare core.item_place_node() for what the server does with param2
3562 MapNode predicted_node(id, 0, 0);
3564 const u8 place_param2 = selected_def.place_param2;
3567 predicted_node.setParam2(place_param2);
3568 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3569 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3570 v3s16 dir = nodepos - neighborpos;
3572 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3573 predicted_node.setParam2(dir.Y < 0 ? 1 : 0);
3574 } else if (abs(dir.X) > abs(dir.Z)) {
3575 predicted_node.setParam2(dir.X < 0 ? 3 : 2);
3577 predicted_node.setParam2(dir.Z < 0 ? 5 : 4);
3579 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3580 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR ||
3581 predicted_f.param_type_2 == CPT2_4DIR ||
3582 predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3583 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3585 if (abs(dir.X) > abs(dir.Z)) {
3586 predicted_node.setParam2(dir.X < 0 ? 3 : 1);
3588 predicted_node.setParam2(dir.Z < 0 ? 2 : 0);
3592 // Check attachment if node is in group attached_node
3593 int an = itemgroup_get(predicted_f.groups, "attached_node");
3598 pp = p + v3s16(0, -1, 0);
3599 } else if (an == 4) {
3600 pp = p + v3s16(0, 1, 0);
3601 } else if (an == 2) {
3602 if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3603 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR ||
3604 predicted_f.param_type_2 == CPT2_4DIR ||
3605 predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3606 pp = p + facedir_dirs[predicted_node.getFaceDir(nodedef)];
3610 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3611 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3612 pp = p + predicted_node.getWallMountedDir(nodedef);
3614 pp = p + v3s16(0, -1, 0);
3617 if (!nodedef->get(map.getNode(pp)).walkable) {
3618 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3620 client->interact(INTERACT_PLACE, pointed);
3626 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3627 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3628 || predicted_f.param_type_2 == CPT2_COLORED_4DIR
3629 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3630 const auto &indexstr = selected_item.metadata.
3631 getString("palette_index", 0);
3632 if (!indexstr.empty()) {
3633 s32 index = mystoi(indexstr);
3634 if (predicted_f.param_type_2 == CPT2_COLOR) {
3635 predicted_node.setParam2(index);
3636 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3637 // param2 = pure palette index + other
3638 predicted_node.setParam2((index & 0xf8) | (predicted_node.getParam2() & 0x07));
3639 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3640 // param2 = pure palette index + other
3641 predicted_node.setParam2((index & 0xe0) | (predicted_node.getParam2() & 0x1f));
3642 } else if (predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3643 // param2 = pure palette index + other
3644 predicted_node.setParam2((index & 0xfc) | (predicted_node.getParam2() & 0x03));
3649 // Add node to client map
3651 LocalPlayer *player = client->getEnv().getLocalPlayer();
3653 // Don't place node when player would be inside new node
3654 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3655 if (!predicted_f.walkable ||
3656 g_settings->getBool("enable_build_where_you_stand") ||
3657 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3658 (predicted_f.walkable &&
3659 neighborpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3660 neighborpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3661 // This triggers the required mesh update too
3662 client->addNode(p, predicted_node);
3664 client->interact(INTERACT_PLACE, pointed);
3665 // A node is predicted, also play a sound
3666 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3669 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3672 } catch (const InvalidPositionException &e) {
3673 errorstream << "Node placement prediction failed for "
3674 << selected_def.name << " (places "
3675 << prediction << ") - Position not loaded" << std::endl;
3676 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3681 void Game::handlePointingAtObject(const PointedThing &pointed,
3682 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3684 std::wstring infotext = unescape_translate(
3685 utf8_to_wide(runData.selected_object->infoText()));
3688 if (!infotext.empty()) {
3691 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3694 m_game_ui->setInfoText(infotext);
3696 if (isKeyDown(KeyType::DIG)) {
3697 bool do_punch = false;
3698 bool do_punch_damage = false;
3700 if (runData.object_hit_delay_timer <= 0.0) {
3702 do_punch_damage = true;
3703 runData.object_hit_delay_timer = object_hit_delay;
3706 if (wasKeyPressed(KeyType::DIG))
3710 infostream << "Punched object" << std::endl;
3711 runData.punching = true;
3714 if (do_punch_damage) {
3715 // Report direct punch
3716 v3f objpos = runData.selected_object->getPosition();
3717 v3f dir = (objpos - player_position).normalize();
3719 bool disable_send = runData.selected_object->directReportPunch(
3720 dir, &tool_item, runData.time_from_last_punch);
3721 runData.time_from_last_punch = 0;
3724 client->interact(INTERACT_START_DIGGING, pointed);
3726 } else if (wasKeyDown(KeyType::PLACE)) {
3727 infostream << "Pressed place button while pointing at object" << std::endl;
3728 client->interact(INTERACT_PLACE, pointed); // place
3733 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3734 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3736 // See also: serverpackethandle.cpp, action == 2
3737 LocalPlayer *player = client->getEnv().getLocalPlayer();
3738 ClientMap &map = client->getEnv().getClientMap();
3739 MapNode n = map.getNode(nodepos);
3740 const auto &features = nodedef_manager->get(n);
3742 // NOTE: Similar piece of code exists on the server side for
3744 // Get digging parameters
3745 DigParams params = getDigParams(features.groups,
3746 &selected_item.getToolCapabilities(itemdef_manager),
3747 selected_item.wear);
3749 // If can't dig, try hand
3750 if (!params.diggable) {
3751 params = getDigParams(features.groups,
3752 &hand_item.getToolCapabilities(itemdef_manager));
3755 if (!params.diggable) {
3756 // I guess nobody will wait for this long
3757 runData.dig_time_complete = 10000000.0;
3759 runData.dig_time_complete = params.time;
3761 if (m_cache_enable_particles) {
3762 client->getParticleManager()->addNodeParticle(client,
3763 player, nodepos, n, features);
3767 if (!runData.digging) {
3768 infostream << "Started digging" << std::endl;
3769 runData.dig_instantly = runData.dig_time_complete == 0;
3770 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3773 client->interact(INTERACT_START_DIGGING, pointed);
3774 runData.digging = true;
3775 runData.btn_down_for_dig = true;
3778 if (!runData.dig_instantly) {
3779 runData.dig_index = (float)crack_animation_length
3781 / runData.dig_time_complete;
3783 // This is for e.g. torches
3784 runData.dig_index = crack_animation_length;
3787 const auto &sound_dig = features.sound_dig;
3789 if (sound_dig.exists() && params.diggable) {
3790 if (sound_dig.name == "__group") {
3791 if (!params.main_group.empty()) {
3792 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3793 soundmaker->m_player_leftpunch_sound.name =
3794 std::string("default_dig_") +
3798 soundmaker->m_player_leftpunch_sound = sound_dig;
3802 // Don't show cracks if not diggable
3803 if (runData.dig_time_complete >= 100000.0) {
3804 } else if (runData.dig_index < crack_animation_length) {
3805 client->setCrack(runData.dig_index, nodepos);
3807 infostream << "Digging completed" << std::endl;
3808 client->setCrack(-1, v3s16(0, 0, 0));
3810 runData.dig_time = 0;
3811 runData.digging = false;
3812 // we successfully dug, now block it from repeating if we want to be safe
3813 if (g_settings->getBool("safe_dig_and_place"))
3814 runData.digging_blocked = true;
3816 runData.nodig_delay_timer =
3817 runData.dig_time_complete / (float)crack_animation_length;
3819 // We don't want a corresponding delay to very time consuming nodes
3820 // and nodes without digging time (e.g. torches) get a fixed delay.
3821 if (runData.nodig_delay_timer > 0.3)
3822 runData.nodig_delay_timer = 0.3;
3823 else if (runData.dig_instantly)
3824 runData.nodig_delay_timer = 0.15;
3826 if (client->modsLoaded() &&
3827 client->getScript()->on_dignode(nodepos, n)) {
3831 if (features.node_dig_prediction == "air") {
3832 client->removeNode(nodepos);
3833 } else if (!features.node_dig_prediction.empty()) {
3835 bool found = nodedef_manager->getId(features.node_dig_prediction, id);
3837 client->addNode(nodepos, id, true);
3839 // implicit else: no prediction
3841 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3843 if (m_cache_enable_particles) {
3844 client->getParticleManager()->addDiggingParticles(client,
3845 player, nodepos, n, features);
3849 // Send event to trigger sound
3850 client->getEventManager()->put(new NodeDugEvent(nodepos, n));
3853 if (runData.dig_time_complete < 100000.0) {
3854 runData.dig_time += dtime;
3856 runData.dig_time = 0;
3857 client->setCrack(-1, nodepos);
3860 camera->setDigging(0); // Dig animation
3863 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3864 const CameraOrientation &cam)
3866 TimeTaker tt_update("Game::updateFrame()");
3867 LocalPlayer *player = client->getEnv().getLocalPlayer();
3873 client->getEnv().updateFrameTime(m_is_paused);
3879 if (draw_control->range_all) {
3880 runData.fog_range = 100000 * BS;
3882 runData.fog_range = draw_control->wanted_range * BS;
3886 Calculate general brightness
3888 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3889 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3890 float direct_brightness;
3893 // When in noclip mode force same sky brightness as above ground so you
3895 if (draw_control->allow_noclip && m_cache_enable_free_move &&
3896 client->checkPrivilege("fly")) {
3897 direct_brightness = time_brightness;
3898 sunlight_seen = true;
3900 float old_brightness = sky->getBrightness();
3901 direct_brightness = client->getEnv().getClientMap()
3902 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3903 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3907 float time_of_day_smooth = runData.time_of_day_smooth;
3908 float time_of_day = client->getEnv().getTimeOfDayF();
3910 static const float maxsm = 0.05f;
3911 static const float todsm = 0.05f;
3913 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3914 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3915 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3916 time_of_day_smooth = time_of_day;
3918 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3919 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3920 + (time_of_day + 1.0) * todsm;
3922 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3923 + time_of_day * todsm;
3925 runData.time_of_day_smooth = time_of_day_smooth;
3927 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3928 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3929 player->getPitch());
3935 if (sky->getCloudsVisible()) {
3936 clouds->setVisible(true);
3937 clouds->step(dtime);
3938 // camera->getPosition is not enough for 3rd person views
3939 v3f camera_node_position = camera->getCameraNode()->getPosition();
3940 v3s16 camera_offset = camera->getOffset();
3941 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3942 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3943 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3944 clouds->update(camera_node_position,
3945 sky->getCloudColor());
3946 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3947 // if inside clouds, and fog enabled, use that as sky
3949 video::SColor clouds_dark = clouds->getColor()
3950 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3951 sky->overrideColors(clouds_dark, clouds->getColor());
3952 sky->setInClouds(true);
3953 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3954 // do not draw clouds after all
3955 clouds->setVisible(false);
3958 clouds->setVisible(false);
3965 client->getParticleManager()->step(dtime);
3971 if (m_cache_enable_fog) {
3974 video::EFT_FOG_LINEAR,
3975 runData.fog_range * m_cache_fog_start,
3976 runData.fog_range * 1.0,
3984 video::EFT_FOG_LINEAR,
3996 if (player->hurt_tilt_timer > 0.0f) {
3997 player->hurt_tilt_timer -= dtime * 6.0f;
3999 if (player->hurt_tilt_timer < 0.0f)
4000 player->hurt_tilt_strength = 0.0f;
4004 Update minimap pos and rotation
4006 if (mapper && m_game_ui->m_flags.show_hud) {
4007 mapper->setPos(floatToInt(player->getPosition(), BS));
4008 mapper->setAngle(player->getYaw());
4012 Get chat messages from client
4021 if (player->getWieldIndex() != runData.new_playeritem)
4022 client->setPlayerItem(runData.new_playeritem);
4024 if (client->updateWieldedItem()) {
4025 // Update wielded tool
4026 ItemStack selected_item, hand_item;
4027 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
4028 camera->wield(tool_item);
4032 Update block draw list every 200ms or when camera direction has
4035 runData.update_draw_list_timer += dtime;
4037 float update_draw_list_delta = 0.2f;
4039 v3f camera_direction = camera->getDirection();
4040 if (runData.update_draw_list_timer >= update_draw_list_delta
4041 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
4042 || m_camera_offset_changed
4043 || client->getEnv().getClientMap().needsUpdateDrawList()) {
4044 runData.update_draw_list_timer = 0;
4045 client->getEnv().getClientMap().updateDrawList();
4046 runData.update_draw_list_last_cam_dir = camera_direction;
4049 if (RenderingEngine::get_shadow_renderer()) {
4053 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
4056 make sure menu is on top
4057 1. Delete formspec menu reference if menu was removed
4058 2. Else, make sure formspec menu is on top
4060 auto formspec = m_game_ui->getFormspecGUI();
4061 do { // breakable. only runs for one iteration
4065 if (formspec->getReferenceCount() == 1) {
4066 m_game_ui->deleteFormspec();
4070 auto &loc = formspec->getFormspecLocation();
4071 if (loc.type == InventoryLocation::NODEMETA) {
4072 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
4073 if (!meta || meta->getString("formspec").empty()) {
4074 formspec->quitMenu();
4080 guiroot->bringToFront(formspec);
4084 ==================== Drawing begins ====================
4086 const video::SColor skycolor = sky->getSkyColor();
4088 TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
4089 driver->beginScene(true, true, skycolor);
4091 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
4092 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
4093 (camera->getCameraMode() == CAMERA_MODE_FIRST));
4094 bool draw_crosshair = (
4095 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
4096 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
4097 #ifdef HAVE_TOUCHSCREENGUI
4098 if (isNoCrosshairAllowed())
4099 draw_crosshair = false;
4101 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
4102 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
4107 v2u32 screensize = driver->getScreenSize();
4109 if (m_game_ui->m_flags.show_profiler_graph)
4110 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
4115 if (runData.damage_flash > 0.0f) {
4116 video::SColor color(runData.damage_flash, 180, 0, 0);
4117 driver->draw2DRectangle(color,
4118 core::rect<s32>(0, 0, screensize.X, screensize.Y),
4121 runData.damage_flash -= 384.0f * dtime;
4125 ==================== End scene ====================
4127 #if IRRLICHT_VERSION_MT_REVISION < 5
4128 if (++m_reset_HW_buffer_counter > 500) {
4130 Periodically remove all mesh HW buffers.
4132 Work around for a quirk in Irrlicht where a HW buffer is only
4133 released after 20000 iterations (triggered from endScene()).
4135 Without this, all loaded but unused meshes will retain their HW
4136 buffers for at least 5 minutes, at which point looking up the HW buffers
4137 becomes a bottleneck and the framerate drops (as much as 30%).
4139 Tests showed that numbers between 50 and 1000 are good, so picked 500.
4140 There are no other public Irrlicht APIs that allow interacting with the
4141 HW buffers without tracking the status of every individual mesh.
4143 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4145 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4146 driver->removeAllHardwareBuffers();
4147 m_reset_HW_buffer_counter = 0;
4153 stats->drawtime = tt_draw.stop(true);
4154 g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
4155 g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
4158 /* Log times and stuff for visualization */
4159 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4161 Profiler::GraphValues values;
4162 g_profiler->graphGet(values);
4166 /****************************************************************************
4168 *****************************************************************************/
4169 void Game::updateShadows()
4171 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4175 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4177 float timeoftheday = getWickedTimeOfDay(in_timeofday);
4178 bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
4179 bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
4180 shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
4182 timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
4183 const float offset_constant = 10000.0f;
4185 v3f light = is_day ? sky->getSunDirection() : sky->getMoonDirection();
4187 v3f sun_pos = light * offset_constant;
4189 if (shadow->getDirectionalLightCount() == 0)
4190 shadow->addDirectionalLight();
4191 shadow->getDirectionalLight().setDirection(sun_pos);
4192 shadow->setTimeOfDay(in_timeofday);
4194 shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4197 /****************************************************************************
4199 ****************************************************************************/
4201 void FpsControl::reset()
4203 last_time = porting::getTimeUs();
4207 * On some computers framerate doesn't seem to be automatically limited
4209 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
4211 const float fps_limit = (device->isWindowFocused() && !g_menumgr.pausesGame())
4212 ? g_settings->getFloat("fps_max")
4213 : g_settings->getFloat("fps_max_unfocused");
4214 const u64 frametime_min = 1000000.0f / std::max(fps_limit, 1.0f);
4216 u64 time = porting::getTimeUs();
4218 if (time > last_time) // Make sure time hasn't overflowed
4219 busy_time = time - last_time;
4223 if (busy_time < frametime_min) {
4224 sleep_time = frametime_min - busy_time;
4225 if (sleep_time > 1000)
4226 sleep_ms(sleep_time / 1000);
4231 // Read the timer again to accurately determine how long we actually slept,
4232 // rather than calculating it by adding sleep_time to time.
4233 time = porting::getTimeUs();
4235 if (time > last_time) // Make sure last_time hasn't overflowed
4236 *dtime = (time - last_time) / 1000000.0f;
4243 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4245 const wchar_t *wmsg = wgettext(msg);
4246 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4251 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4253 ((Game *)data)->readSettings();
4256 void Game::readSettings()
4258 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4259 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4260 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4261 m_cache_enable_particles = g_settings->getBool("enable_particles");
4262 m_cache_enable_fog = g_settings->getBool("enable_fog");
4263 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f);
4264 m_cache_joystick_frustum_sensitivity = std::max(g_settings->getFloat("joystick_frustum_sensitivity"), 0.001f);
4265 m_repeat_place_time = g_settings->getFloat("repeat_place_time", 0.25f, 2.0);
4267 m_cache_enable_noclip = g_settings->getBool("noclip");
4268 m_cache_enable_free_move = g_settings->getBool("free_move");
4270 m_cache_fog_start = g_settings->getFloat("fog_start");
4272 m_cache_cam_smoothing = 0;
4273 if (g_settings->getBool("cinematic"))
4274 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4276 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4278 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4279 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4280 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4282 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4285 /****************************************************************************/
4286 /****************************************************************************
4288 ****************************************************************************/
4289 /****************************************************************************/
4291 void Game::showDeathFormspec()
4293 static std::string formspec_str =
4294 std::string("formspec_version[1]") +
4296 "bgcolor[#320000b4;true]"
4297 "label[4.85,1.35;" + gettext("You died") + "]"
4298 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4302 /* Note: FormspecFormSource and LocalFormspecHandler *
4303 * are deleted by guiFormSpecMenu */
4304 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4305 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4307 auto *&formspec = m_game_ui->getFormspecGUI();
4308 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4309 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4310 formspec->setFocus("btn_respawn");
4313 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4314 void Game::showPauseMenu()
4316 #ifdef HAVE_TOUCHSCREENGUI
4317 static const std::string control_text = strgettext("Default Controls:\n"
4318 "No menu visible:\n"
4319 "- single tap: button activate\n"
4320 "- double tap: place/use\n"
4321 "- slide finger: look around\n"
4322 "Menu/Inventory visible:\n"
4323 "- double tap (outside):\n"
4325 "- touch stack, touch slot:\n"
4327 "- touch&drag, tap 2nd finger\n"
4328 " --> place single item to slot\n"
4331 static const std::string control_text_template = strgettext("Controls:\n"
4332 "- %s: move forwards\n"
4333 "- %s: move backwards\n"
4335 "- %s: move right\n"
4336 "- %s: jump/climb up\n"
4339 "- %s: sneak/climb down\n"
4342 "- Mouse: turn/look\n"
4343 "- Mouse wheel: select item\n"
4347 char control_text_buf[600];
4349 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4350 GET_KEY_NAME(keymap_forward),
4351 GET_KEY_NAME(keymap_backward),
4352 GET_KEY_NAME(keymap_left),
4353 GET_KEY_NAME(keymap_right),
4354 GET_KEY_NAME(keymap_jump),
4355 GET_KEY_NAME(keymap_dig),
4356 GET_KEY_NAME(keymap_place),
4357 GET_KEY_NAME(keymap_sneak),
4358 GET_KEY_NAME(keymap_drop),
4359 GET_KEY_NAME(keymap_inventory),
4360 GET_KEY_NAME(keymap_chat)
4363 std::string control_text = std::string(control_text_buf);
4364 str_formspec_escape(control_text);
4367 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4368 std::ostringstream os;
4370 os << "formspec_version[1]" << SIZE_TAG
4371 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4372 << strgettext("Continue") << "]";
4374 if (!simple_singleplayer_mode) {
4375 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4376 << strgettext("Change Password") << "]";
4378 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4383 if (g_settings->getBool("enable_sound")) {
4384 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4385 << strgettext("Sound Volume") << "]";
4388 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4389 << strgettext("Change Keys") << "]";
4391 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4392 << strgettext("Exit to Menu") << "]";
4393 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4394 << strgettext("Exit to OS") << "]"
4395 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4396 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4398 << strgettext("Game info:") << "\n";
4399 const std::string &address = client->getAddressName();
4400 static const std::string mode = strgettext("- Mode: ");
4401 if (!simple_singleplayer_mode) {
4402 Address serverAddress = client->getServerAddress();
4403 if (!address.empty()) {
4404 os << mode << strgettext("Remote server") << "\n"
4405 << strgettext("- Address: ") << address;
4407 os << mode << strgettext("Hosting server");
4409 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4411 os << mode << strgettext("Singleplayer") << "\n";
4413 if (simple_singleplayer_mode || address.empty()) {
4414 static const std::string on = strgettext("On");
4415 static const std::string off = strgettext("Off");
4416 // Note: Status of enable_damage and creative_mode settings is intentionally
4417 // NOT shown here because the game might roll its own damage system and/or do
4418 // a per-player Creative Mode, in which case writing it here would mislead.
4419 bool damage = g_settings->getBool("enable_damage");
4420 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4421 if (!simple_singleplayer_mode) {
4423 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4424 //~ PvP = Player versus Player
4425 os << strgettext("- PvP: ") << pvp << "\n";
4427 os << strgettext("- Public: ") << announced << "\n";
4428 std::string server_name = g_settings->get("server_name");
4429 str_formspec_escape(server_name);
4430 if (announced == on && !server_name.empty())
4431 os << strgettext("- Server Name: ") << server_name;
4438 /* Note: FormspecFormSource and LocalFormspecHandler *
4439 * are deleted by guiFormSpecMenu */
4440 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4441 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4443 auto *&formspec = m_game_ui->getFormspecGUI();
4444 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4445 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4446 formspec->setFocus("btn_continue");
4447 // game will be paused in next step, if in singleplayer (see m_is_paused)
4448 formspec->doPause = true;
4451 /****************************************************************************/
4452 /****************************************************************************
4453 extern function for launching the game
4454 ****************************************************************************/
4455 /****************************************************************************/
4457 void the_game(bool *kill,
4458 InputHandler *input,
4459 RenderingEngine *rendering_engine,
4460 const GameStartData &start_data,
4461 std::string &error_message,
4462 ChatBackend &chat_backend,
4463 bool *reconnect_requested) // Used for local game
4467 /* Make a copy of the server address because if a local singleplayer server
4468 * is created then this is updated and we don't want to change the value
4469 * passed to us by the calling function
4474 if (game.startup(kill, input, rendering_engine, start_data,
4475 error_message, reconnect_requested, &chat_backend)) {
4479 } catch (SerializationError &e) {
4480 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
4481 error_message = strgettext("A serialization error occurred:") +"\n"
4482 + e.what() + "\n\n" + ver_err;
4483 errorstream << error_message << std::endl;
4484 } catch (ServerError &e) {
4485 error_message = e.what();
4486 errorstream << "ServerError: " << error_message << std::endl;
4487 } catch (ModError &e) {
4488 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
4489 error_message = std::string("ModError: ") + e.what() +
4490 strgettext("\nCheck debug.txt for details.");
4491 errorstream << error_message << std::endl;