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();
1261 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1262 if (g_settings->get("3d_mode") == "pageflip") {
1263 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1266 auto formspec = m_game_ui->getFormspecGUI();
1268 formspec->quitMenu();
1270 #ifdef HAVE_TOUCHSCREENGUI
1271 g_touchscreengui->hide();
1274 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1279 if (gui_chat_console)
1280 gui_chat_console->drop();
1286 while (g_menumgr.menuCount() > 0) {
1287 g_menumgr.m_stack.front()->setVisible(false);
1288 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1291 m_game_ui->deleteFormspec();
1293 chat_backend->addMessage(L"", L"# Disconnected.");
1294 chat_backend->addMessage(L"", L"");
1295 m_chat_log_buf.clear();
1299 while (!client->isShutdown()) {
1300 assert(texture_src != NULL);
1301 assert(shader_src != NULL);
1302 texture_src->processQueue();
1303 shader_src->processQueue();
1310 /****************************************************************************/
1311 /****************************************************************************
1313 ****************************************************************************/
1314 /****************************************************************************/
1317 const std::string &map_dir,
1318 const std::string &address,
1320 const SubgameSpec &gamespec)
1322 texture_src = createTextureSource();
1324 showOverlayMessage(N_("Loading..."), 0, 0);
1326 shader_src = createShaderSource();
1328 itemdef_manager = createItemDefManager();
1329 nodedef_manager = createNodeDefManager();
1331 eventmgr = new EventManager();
1332 quicktune = new QuicktuneShortcutter();
1334 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1335 && eventmgr && quicktune))
1341 // Create a server if not connecting to an existing one
1342 if (address.empty()) {
1343 if (!createSingleplayerServer(map_dir, gamespec, port))
1350 bool Game::initSound()
1353 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1354 infostream << "Attempting to use OpenAL audio" << std::endl;
1355 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1357 infostream << "Failed to initialize OpenAL audio" << std::endl;
1359 infostream << "Sound disabled." << std::endl;
1363 infostream << "Using dummy audio." << std::endl;
1364 sound = &dummySoundManager;
1365 sound_is_dummy = true;
1368 soundmaker = new SoundMaker(sound, nodedef_manager);
1372 soundmaker->registerReceiver(eventmgr);
1377 bool Game::createSingleplayerServer(const std::string &map_dir,
1378 const SubgameSpec &gamespec, u16 port)
1380 showOverlayMessage(N_("Creating server..."), 0, 5);
1382 std::string bind_str = g_settings->get("bind_address");
1383 Address bind_addr(0, 0, 0, 0, port);
1385 if (g_settings->getBool("ipv6_server")) {
1386 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1390 bind_addr.Resolve(bind_str.c_str());
1391 } catch (ResolveError &e) {
1392 infostream << "Resolving bind address \"" << bind_str
1393 << "\" failed: " << e.what()
1394 << " -- Listening on all addresses." << std::endl;
1397 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1398 *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
1399 bind_addr.serializeString().c_str());
1400 errorstream << *error_message << std::endl;
1404 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1405 false, nullptr, error_message);
1411 bool Game::createClient(const GameStartData &start_data)
1413 showOverlayMessage(N_("Creating client..."), 0, 10);
1415 draw_control = new MapDrawControl();
1419 bool could_connect, connect_aborted;
1420 #ifdef HAVE_TOUCHSCREENGUI
1421 if (g_touchscreengui) {
1422 g_touchscreengui->init(texture_src);
1423 g_touchscreengui->hide();
1426 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1429 if (!could_connect) {
1430 if (error_message->empty() && !connect_aborted) {
1431 // Should not happen if error messages are set properly
1432 *error_message = gettext("Connection failed for unknown reason");
1433 errorstream << *error_message << std::endl;
1438 if (!getServerContent(&connect_aborted)) {
1439 if (error_message->empty() && !connect_aborted) {
1440 // Should not happen if error messages are set properly
1441 *error_message = gettext("Connection failed for unknown reason");
1442 errorstream << *error_message << std::endl;
1447 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1448 &m_flags.force_fog_off, &runData.fog_range, client);
1449 shader_src->addShaderConstantSetterFactory(scsf);
1451 // Update cached textures, meshes and materials
1452 client->afterContentReceived();
1456 camera = new Camera(*draw_control, client, m_rendering_engine);
1457 if (client->modsLoaded())
1458 client->getScript()->on_camera_ready(camera);
1459 client->setCamera(camera);
1460 #ifdef HAVE_TOUCHSCREENGUI
1461 if (g_touchscreengui) {
1462 g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed());
1468 if (m_cache_enable_clouds)
1469 clouds = new Clouds(smgr, -1, time(0));
1473 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
1475 skybox = NULL; // This is used/set later on in the main run loop
1477 /* Pre-calculated values
1479 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1481 v2u32 size = t->getOriginalSize();
1482 crack_animation_length = size.Y / size.X;
1484 crack_animation_length = 5;
1490 /* Set window caption
1492 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1494 str += utf8_to_wide(g_version_hash);
1496 const wchar_t *text = nullptr;
1497 if (simple_singleplayer_mode)
1498 text = wgettext("Singleplayer");
1500 text = wgettext("Multiplayer");
1507 str += driver->getName();
1510 device->setWindowCaption(str.c_str());
1512 LocalPlayer *player = client->getEnv().getLocalPlayer();
1513 player->hurt_tilt_timer = 0;
1514 player->hurt_tilt_strength = 0;
1516 hud = new Hud(client, player, &player->inventory);
1518 mapper = client->getMinimap();
1520 if (mapper && client->modsLoaded())
1521 client->getScript()->on_minimap_ready(mapper);
1526 bool Game::initGui()
1530 // Remove stale "recent" chat messages from previous connections
1531 chat_backend->clearRecentChat();
1533 // Make sure the size of the recent messages buffer is right
1534 chat_backend->applySettings();
1536 // Chat backend and console
1537 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1538 -1, chat_backend, client, &g_menumgr);
1540 #ifdef HAVE_TOUCHSCREENGUI
1542 if (g_touchscreengui)
1543 g_touchscreengui->show();
1550 bool Game::connectToServer(const GameStartData &start_data,
1551 bool *connect_ok, bool *connection_aborted)
1553 *connect_ok = false; // Let's not be overly optimistic
1554 *connection_aborted = false;
1555 bool local_server_mode = false;
1557 showOverlayMessage(N_("Resolving address..."), 0, 15);
1559 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1562 connect_address.Resolve(start_data.address.c_str());
1564 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1565 if (connect_address.isIPv6()) {
1566 IPv6AddressBytes addr_bytes;
1567 addr_bytes.bytes[15] = 1;
1568 connect_address.setAddress(&addr_bytes);
1570 connect_address.setAddress(127, 0, 0, 1);
1572 local_server_mode = true;
1574 } catch (ResolveError &e) {
1575 *error_message = fmtgettext("Couldn't resolve address: %s", e.what());
1577 errorstream << *error_message << std::endl;
1581 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1582 *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
1583 errorstream << *error_message << std::endl;
1588 client = new Client(start_data.name.c_str(),
1589 start_data.password, start_data.address,
1590 *draw_control, texture_src, shader_src,
1591 itemdef_manager, nodedef_manager, sound, eventmgr,
1592 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get(),
1593 start_data.allow_login_or_register);
1594 client->migrateModStorage();
1595 } catch (const BaseException &e) {
1596 *error_message = fmtgettext("Error creating client: %s", e.what());
1597 errorstream << *error_message << std::endl;
1601 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1603 infostream << "Connecting to server at ";
1604 connect_address.print(infostream);
1605 infostream << std::endl;
1607 client->connect(connect_address,
1608 simple_singleplayer_mode || local_server_mode);
1611 Wait for server to accept connection
1617 FpsControl fps_control;
1619 f32 wait_time = 0; // in seconds
1621 fps_control.reset();
1623 while (m_rendering_engine->run()) {
1625 fps_control.limit(device, &dtime);
1627 // Update client and server
1628 client->step(dtime);
1631 server->step(dtime);
1634 if (client->getState() == LC_Init) {
1640 if (*connection_aborted)
1643 if (client->accessDenied()) {
1644 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1645 *reconnect_requested = client->reconnectRequested();
1646 errorstream << *error_message << std::endl;
1650 if (input->cancelPressed()) {
1651 *connection_aborted = true;
1652 infostream << "Connect aborted [Escape]" << std::endl;
1657 // Only time out if we aren't waiting for the server we started
1658 if (!start_data.address.empty() && wait_time > 10) {
1659 *error_message = gettext("Connection timed out.");
1660 errorstream << *error_message << std::endl;
1665 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1667 } catch (con::PeerNotFoundException &e) {
1668 // TODO: Should something be done here? At least an info/error
1676 bool Game::getServerContent(bool *aborted)
1680 FpsControl fps_control;
1681 f32 dtime; // in seconds
1683 fps_control.reset();
1685 while (m_rendering_engine->run()) {
1687 fps_control.limit(device, &dtime);
1689 // Update client and server
1690 client->step(dtime);
1693 server->step(dtime);
1696 if (client->mediaReceived() && client->itemdefReceived() &&
1697 client->nodedefReceived()) {
1702 if (!checkConnection())
1705 if (client->getState() < LC_Init) {
1706 *error_message = gettext("Client disconnected");
1707 errorstream << *error_message << std::endl;
1711 if (input->cancelPressed()) {
1713 infostream << "Connect aborted [Escape]" << std::endl;
1720 if (!client->itemdefReceived()) {
1721 const wchar_t *text = wgettext("Item definitions...");
1723 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1726 } else if (!client->nodedefReceived()) {
1727 const wchar_t *text = wgettext("Node definitions...");
1729 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1733 std::ostringstream message;
1734 std::fixed(message);
1735 message.precision(0);
1736 float receive = client->mediaReceiveProgress() * 100;
1737 message << gettext("Media...");
1739 message << " " << receive << "%";
1740 message.precision(2);
1742 if ((USE_CURL == 0) ||
1743 (!g_settings->getBool("enable_remote_media_server"))) {
1744 float cur = client->getCurRate();
1745 std::string cur_unit = gettext("KiB/s");
1749 cur_unit = gettext("MiB/s");
1752 message << " (" << cur << ' ' << cur_unit << ")";
1755 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1756 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
1757 texture_src, dtime, progress);
1765 /****************************************************************************/
1766 /****************************************************************************
1768 ****************************************************************************/
1769 /****************************************************************************/
1771 inline void Game::updateInteractTimers(f32 dtime)
1773 if (runData.nodig_delay_timer >= 0)
1774 runData.nodig_delay_timer -= dtime;
1776 if (runData.object_hit_delay_timer >= 0)
1777 runData.object_hit_delay_timer -= dtime;
1779 runData.time_from_last_punch += dtime;
1783 /* returns false if game should exit, otherwise true
1785 inline bool Game::checkConnection()
1787 if (client->accessDenied()) {
1788 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1789 *reconnect_requested = client->reconnectRequested();
1790 errorstream << *error_message << std::endl;
1798 /* returns false if game should exit, otherwise true
1800 inline bool Game::handleCallbacks()
1802 if (g_gamecallback->disconnect_requested) {
1803 g_gamecallback->disconnect_requested = false;
1807 if (g_gamecallback->changepassword_requested) {
1808 (new GUIPasswordChange(guienv, guiroot, -1,
1809 &g_menumgr, client, texture_src))->drop();
1810 g_gamecallback->changepassword_requested = false;
1813 if (g_gamecallback->changevolume_requested) {
1814 (new GUIVolumeChange(guienv, guiroot, -1,
1815 &g_menumgr, texture_src))->drop();
1816 g_gamecallback->changevolume_requested = false;
1819 if (g_gamecallback->keyconfig_requested) {
1820 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1821 &g_menumgr, texture_src))->drop();
1822 g_gamecallback->keyconfig_requested = false;
1825 if (g_gamecallback->keyconfig_changed) {
1826 input->keycache.populate(); // update the cache with new settings
1827 g_gamecallback->keyconfig_changed = false;
1834 void Game::processQueues()
1836 texture_src->processQueue();
1837 itemdef_manager->processQueue(client);
1838 shader_src->processQueue();
1841 void Game::updateDebugState()
1843 LocalPlayer *player = client->getEnv().getLocalPlayer();
1845 // debug UI and wireframe
1846 bool has_debug = client->checkPrivilege("debug");
1847 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
1849 if (m_game_ui->m_flags.show_basic_debug) {
1850 if (!has_basic_debug)
1851 m_game_ui->m_flags.show_basic_debug = false;
1852 } else if (m_game_ui->m_flags.show_minimal_debug) {
1853 if (has_basic_debug)
1854 m_game_ui->m_flags.show_basic_debug = true;
1856 if (!has_basic_debug)
1857 hud->disableBlockBounds();
1859 draw_control->show_wireframe = false;
1862 draw_control->allow_noclip = m_cache_enable_noclip && client->checkPrivilege("noclip");
1865 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1868 float profiler_print_interval =
1869 g_settings->getFloat("profiler_print_interval");
1870 bool print_to_log = true;
1872 if (profiler_print_interval == 0) {
1873 print_to_log = false;
1874 profiler_print_interval = 3;
1877 if (profiler_interval.step(dtime, profiler_print_interval)) {
1879 infostream << "Profiler:" << std::endl;
1880 g_profiler->print(infostream);
1883 m_game_ui->updateProfiler();
1884 g_profiler->clear();
1887 // Update update graphs
1888 g_profiler->graphAdd("Time non-rendering [us]",
1889 draw_times.busy_time - stats.drawtime);
1891 g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
1892 g_profiler->graphAdd("FPS", 1.0f / dtime);
1895 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1902 /* Time average and jitter calculation
1904 jp = &stats->dtime_jitter;
1905 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1907 jitter = dtime - jp->avg;
1909 if (jitter > jp->max)
1912 jp->counter += dtime;
1914 if (jp->counter > 0.0) {
1916 jp->max_sample = jp->max;
1917 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1921 /* Busytime average and jitter calculation
1923 jp = &stats->busy_time_jitter;
1924 jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
1926 jitter = draw_times.getBusyMs() - jp->avg;
1928 if (jitter > jp->max)
1930 if (jitter < jp->min)
1933 jp->counter += dtime;
1935 if (jp->counter > 0.0) {
1937 jp->max_sample = jp->max;
1938 jp->min_sample = jp->min;
1946 /****************************************************************************
1948 ****************************************************************************/
1950 void Game::processUserInput(f32 dtime)
1952 // Reset input if window not active or some menu is active
1953 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1955 #ifdef HAVE_TOUCHSCREENGUI
1956 g_touchscreengui->hide();
1959 #ifdef HAVE_TOUCHSCREENGUI
1960 else if (g_touchscreengui) {
1961 /* on touchscreengui step may generate own input events which ain't
1962 * what we want in case we just did clear them */
1963 g_touchscreengui->show();
1964 g_touchscreengui->step(dtime);
1968 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1969 gui_chat_console->closeConsoleAtOnce();
1972 // Input handler step() (used by the random input generator)
1976 auto formspec = m_game_ui->getFormspecGUI();
1978 formspec->getAndroidUIInput();
1980 handleAndroidChatInput();
1983 // Increase timer for double tap of "keymap_jump"
1984 if (m_cache_doubletap_jump && runData.jump_timer_up <= 0.2f)
1985 runData.jump_timer_up += dtime;
1986 if (m_cache_doubletap_jump && runData.jump_timer_down <= 0.4f)
1987 runData.jump_timer_down += dtime;
1990 processItemSelection(&runData.new_playeritem);
1994 void Game::processKeyInput()
1996 if (wasKeyDown(KeyType::DROP)) {
1997 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1998 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1999 toggleAutoforward();
2000 } else if (wasKeyDown(KeyType::BACKWARD)) {
2001 if (g_settings->getBool("continuous_forward"))
2002 toggleAutoforward();
2003 } else if (wasKeyDown(KeyType::INVENTORY)) {
2005 } else if (input->cancelPressed()) {
2007 m_android_chat_open = false;
2009 if (!gui_chat_console->isOpenInhibited()) {
2012 } else if (wasKeyDown(KeyType::CHAT)) {
2013 openConsole(0.2, L"");
2014 } else if (wasKeyDown(KeyType::CMD)) {
2015 openConsole(0.2, L"/");
2016 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
2017 if (client->modsLoaded())
2018 openConsole(0.2, L".");
2020 m_game_ui->showTranslatedStatusText("Client side scripting is disabled");
2021 } else if (wasKeyDown(KeyType::CONSOLE)) {
2022 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
2023 } else if (wasKeyDown(KeyType::FREEMOVE)) {
2025 } else if (wasKeyDown(KeyType::JUMP)) {
2026 toggleFreeMoveAlt();
2027 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
2029 } else if (wasKeyDown(KeyType::FASTMOVE)) {
2031 } else if (wasKeyDown(KeyType::NOCLIP)) {
2034 } else if (wasKeyDown(KeyType::MUTE)) {
2035 if (g_settings->getBool("enable_sound")) {
2036 bool new_mute_sound = !g_settings->getBool("mute_sound");
2037 g_settings->setBool("mute_sound", new_mute_sound);
2039 m_game_ui->showTranslatedStatusText("Sound muted");
2041 m_game_ui->showTranslatedStatusText("Sound unmuted");
2043 m_game_ui->showTranslatedStatusText("Sound system is disabled");
2045 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
2046 if (g_settings->getBool("enable_sound")) {
2047 float new_volume = g_settings->getFloat("sound_volume", 0.0f, 0.9f) + 0.1f;
2048 g_settings->setFloat("sound_volume", new_volume);
2049 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
2050 m_game_ui->showStatusText(msg);
2052 m_game_ui->showTranslatedStatusText("Sound system is disabled");
2054 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
2055 if (g_settings->getBool("enable_sound")) {
2056 float new_volume = g_settings->getFloat("sound_volume", 0.1f, 1.0f) - 0.1f;
2057 g_settings->setFloat("sound_volume", new_volume);
2058 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
2059 m_game_ui->showStatusText(msg);
2061 m_game_ui->showTranslatedStatusText("Sound system is disabled");
2064 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
2065 || wasKeyDown(KeyType::DEC_VOLUME)) {
2066 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
2068 } else if (wasKeyDown(KeyType::CINEMATIC)) {
2070 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
2071 client->makeScreenshot();
2072 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
2073 toggleBlockBounds();
2074 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
2075 m_game_ui->toggleHud();
2076 } else if (wasKeyDown(KeyType::MINIMAP)) {
2077 toggleMinimap(isKeyDown(KeyType::SNEAK));
2078 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
2079 m_game_ui->toggleChat();
2080 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
2082 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
2083 toggleUpdateCamera();
2084 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
2086 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
2087 m_game_ui->toggleProfiler();
2088 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
2089 increaseViewRange();
2090 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
2091 decreaseViewRange();
2092 } else if (wasKeyDown(KeyType::RANGESELECT)) {
2093 toggleFullViewRange();
2094 } else if (wasKeyDown(KeyType::ZOOM)) {
2096 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
2098 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
2100 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
2102 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
2106 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
2107 runData.reset_jump_timer = false;
2108 runData.jump_timer_up = 0.0f;
2111 if (quicktune->hasMessage()) {
2112 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2116 void Game::processItemSelection(u16 *new_playeritem)
2118 LocalPlayer *player = client->getEnv().getLocalPlayer();
2120 /* Item selection using mouse wheel
2122 *new_playeritem = player->getWieldIndex();
2124 s32 wheel = input->getMouseWheel();
2125 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2126 player->hud_hotbar_itemcount - 1);
2130 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2133 if (wasKeyDown(KeyType::HOTBAR_PREV))
2137 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2139 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2142 /* Item selection using hotbar slot keys
2144 for (u16 i = 0; i <= max_item; i++) {
2145 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2146 *new_playeritem = i;
2153 void Game::dropSelectedItem(bool single_item)
2155 IDropAction *a = new IDropAction();
2156 a->count = single_item ? 1 : 0;
2157 a->from_inv.setCurrentPlayer();
2158 a->from_list = "main";
2159 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2160 client->inventoryAction(a);
2164 void Game::openInventory()
2167 * Don't permit to open inventory is CAO or player doesn't exists.
2168 * This prevent showing an empty inventory at player load
2171 LocalPlayer *player = client->getEnv().getLocalPlayer();
2172 if (!player || !player->getCAO())
2175 infostream << "Game: Launching inventory" << std::endl;
2177 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2179 InventoryLocation inventoryloc;
2180 inventoryloc.setCurrentPlayer();
2182 if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2187 if (fs_src->getForm().empty()) {
2192 TextDest *txt_dst = new TextDestPlayerInventory(client);
2193 auto *&formspec = m_game_ui->updateFormspec("");
2194 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2195 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2197 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2201 void Game::openConsole(float scale, const wchar_t *line)
2203 assert(scale > 0.0f && scale <= 1.0f);
2206 porting::showInputDialog(gettext("ok"), "", "", 2);
2207 m_android_chat_open = true;
2209 if (gui_chat_console->isOpenInhibited())
2211 gui_chat_console->openConsole(scale);
2213 gui_chat_console->setCloseOnEnter(true);
2214 gui_chat_console->replaceAndAddToHistory(line);
2220 void Game::handleAndroidChatInput()
2222 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2223 std::string text = porting::getInputDialogValue();
2224 client->typeChatMessage(utf8_to_wide(text));
2225 m_android_chat_open = false;
2231 void Game::toggleFreeMove()
2233 bool free_move = !g_settings->getBool("free_move");
2234 g_settings->set("free_move", bool_to_cstr(free_move));
2237 if (client->checkPrivilege("fly")) {
2238 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2240 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2243 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2247 void Game::toggleFreeMoveAlt()
2249 if (!runData.reset_jump_timer) {
2250 runData.jump_timer_down_before = runData.jump_timer_down;
2251 runData.jump_timer_down = 0.0f;
2254 // key down (0.2 s max.), then key up (0.2 s max.), then key down
2255 if (m_cache_doubletap_jump && runData.jump_timer_up < 0.2f &&
2256 runData.jump_timer_down_before < 0.4f) // 0.2 + 0.2
2259 runData.reset_jump_timer = true;
2263 void Game::togglePitchMove()
2265 bool pitch_move = !g_settings->getBool("pitch_move");
2266 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2269 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2271 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2276 void Game::toggleFast()
2278 bool fast_move = !g_settings->getBool("fast_move");
2279 bool has_fast_privs = client->checkPrivilege("fast");
2280 g_settings->set("fast_move", bool_to_cstr(fast_move));
2283 if (has_fast_privs) {
2284 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2286 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2289 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2292 #ifdef HAVE_TOUCHSCREENGUI
2293 m_cache_hold_aux1 = fast_move && has_fast_privs;
2298 void Game::toggleNoClip()
2300 bool noclip = !g_settings->getBool("noclip");
2301 g_settings->set("noclip", bool_to_cstr(noclip));
2304 if (client->checkPrivilege("noclip")) {
2305 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2307 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2310 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2314 void Game::toggleCinematic()
2316 bool cinematic = !g_settings->getBool("cinematic");
2317 g_settings->set("cinematic", bool_to_cstr(cinematic));
2320 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2322 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2325 void Game::toggleBlockBounds()
2327 LocalPlayer *player = client->getEnv().getLocalPlayer();
2328 if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
2329 m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
2332 enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
2334 case Hud::BLOCK_BOUNDS_OFF:
2335 m_game_ui->showTranslatedStatusText("Block bounds hidden");
2337 case Hud::BLOCK_BOUNDS_CURRENT:
2338 m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
2340 case Hud::BLOCK_BOUNDS_NEAR:
2341 m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
2343 case Hud::BLOCK_BOUNDS_MAX:
2344 m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
2351 // Autoforward by toggling continuous forward.
2352 void Game::toggleAutoforward()
2354 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2355 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2357 if (autorun_enabled)
2358 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2360 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2363 void Game::toggleMinimap(bool shift_pressed)
2365 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2369 mapper->toggleMinimapShape();
2373 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2375 // Not so satisying code to keep compatibility with old fixed mode system
2377 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2379 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2380 m_game_ui->m_flags.show_minimap = false;
2383 // If radar is disabled, try to find a non radar mode or fall back to 0
2384 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2385 while (mapper->getModeIndex() &&
2386 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2389 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2393 // End of 'not so satifying code'
2394 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2395 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2396 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2398 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2401 void Game::toggleFog()
2403 bool fog_enabled = g_settings->getBool("enable_fog");
2404 g_settings->setBool("enable_fog", !fog_enabled);
2406 m_game_ui->showTranslatedStatusText("Fog disabled");
2408 m_game_ui->showTranslatedStatusText("Fog enabled");
2412 void Game::toggleDebug()
2414 LocalPlayer *player = client->getEnv().getLocalPlayer();
2415 bool has_debug = client->checkPrivilege("debug");
2416 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
2417 // Initial: No debug info
2418 // 1x toggle: Debug text
2419 // 2x toggle: Debug text with profiler graph
2420 // 3x toggle: Debug text and wireframe (needs "debug" priv)
2421 // Next toggle: Back to initial
2423 // The debug text can be in 2 modes: minimal and basic.
2424 // * Minimal: Only technical client info that not gameplay-relevant
2425 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
2426 // Basic mode is used when player has the debug HUD flag set,
2427 // otherwise the Minimal mode is used.
2428 if (!m_game_ui->m_flags.show_minimal_debug) {
2429 m_game_ui->m_flags.show_minimal_debug = true;
2430 if (has_basic_debug)
2431 m_game_ui->m_flags.show_basic_debug = true;
2432 m_game_ui->m_flags.show_profiler_graph = false;
2433 draw_control->show_wireframe = false;
2434 m_game_ui->showTranslatedStatusText("Debug info shown");
2435 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2436 if (has_basic_debug)
2437 m_game_ui->m_flags.show_basic_debug = true;
2438 m_game_ui->m_flags.show_profiler_graph = true;
2439 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2440 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2441 if (has_basic_debug)
2442 m_game_ui->m_flags.show_basic_debug = true;
2443 m_game_ui->m_flags.show_profiler_graph = false;
2444 draw_control->show_wireframe = true;
2445 m_game_ui->showTranslatedStatusText("Wireframe shown");
2447 m_game_ui->m_flags.show_minimal_debug = false;
2448 m_game_ui->m_flags.show_basic_debug = false;
2449 m_game_ui->m_flags.show_profiler_graph = false;
2450 draw_control->show_wireframe = false;
2452 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2454 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2460 void Game::toggleUpdateCamera()
2462 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2463 if (m_flags.disable_camera_update)
2464 m_game_ui->showTranslatedStatusText("Camera update disabled");
2466 m_game_ui->showTranslatedStatusText("Camera update enabled");
2470 void Game::increaseViewRange()
2472 s16 range = g_settings->getS16("viewing_range");
2473 s16 range_new = range + 10;
2475 if (range_new > 4000) {
2477 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
2478 m_game_ui->showStatusText(msg);
2480 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2481 m_game_ui->showStatusText(msg);
2483 g_settings->set("viewing_range", itos(range_new));
2487 void Game::decreaseViewRange()
2489 s16 range = g_settings->getS16("viewing_range");
2490 s16 range_new = range - 10;
2492 if (range_new < 20) {
2494 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
2495 m_game_ui->showStatusText(msg);
2497 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2498 m_game_ui->showStatusText(msg);
2500 g_settings->set("viewing_range", itos(range_new));
2504 void Game::toggleFullViewRange()
2506 draw_control->range_all = !draw_control->range_all;
2507 if (draw_control->range_all)
2508 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2510 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2514 void Game::checkZoomEnabled()
2516 LocalPlayer *player = client->getEnv().getLocalPlayer();
2517 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2518 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2521 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2523 #if IRRLICHT_VERSION_MT_REVISION >= 9
2525 device->getCursorControl()->setRelativeMode(false);
2527 device->getCursorControl()->setRelativeMode(true);
2529 if ((device->isWindowActive() && device->isWindowFocused()
2530 && !isMenuActive()) || input->isRandom()) {
2533 if (!input->isRandom()) {
2534 // Mac OSX gets upset if this is set every frame
2535 if (device->getCursorControl()->isVisible())
2536 device->getCursorControl()->setVisible(false);
2540 if (m_first_loop_after_window_activation) {
2541 m_first_loop_after_window_activation = false;
2543 input->setMousePos(driver->getScreenSize().Width / 2,
2544 driver->getScreenSize().Height / 2);
2546 updateCameraOrientation(cam, dtime);
2552 // Mac OSX gets upset if this is set every frame
2553 if (!device->getCursorControl()->isVisible())
2554 device->getCursorControl()->setVisible(true);
2557 m_first_loop_after_window_activation = true;
2562 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2563 // responsiveness independently of FOV.
2564 f32 Game::getSensitivityScaleFactor() const
2566 f32 fov_y = client->getCamera()->getFovY();
2568 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2569 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2571 return tan(fov_y / 2.0f) * 1.3763818698f;
2574 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2576 #ifdef HAVE_TOUCHSCREENGUI
2577 if (g_touchscreengui) {
2578 cam->camera_yaw += g_touchscreengui->getYawChange();
2579 cam->camera_pitch = g_touchscreengui->getPitch();
2582 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2583 v2s32 dist = input->getMousePos() - center;
2585 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2589 f32 sens_scale = getSensitivityScaleFactor();
2590 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2591 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2593 if (dist.X != 0 || dist.Y != 0)
2594 input->setMousePos(center.X, center.Y);
2595 #ifdef HAVE_TOUCHSCREENGUI
2599 if (m_cache_enable_joysticks) {
2600 f32 sens_scale = getSensitivityScaleFactor();
2601 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
2602 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2603 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2606 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2610 void Game::updatePlayerControl(const CameraOrientation &cam)
2612 LocalPlayer *player = client->getEnv().getLocalPlayer();
2614 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2616 PlayerControl control(
2617 isKeyDown(KeyType::FORWARD),
2618 isKeyDown(KeyType::BACKWARD),
2619 isKeyDown(KeyType::LEFT),
2620 isKeyDown(KeyType::RIGHT),
2621 isKeyDown(KeyType::JUMP) || player->getAutojump(),
2622 isKeyDown(KeyType::AUX1),
2623 isKeyDown(KeyType::SNEAK),
2624 isKeyDown(KeyType::ZOOM),
2625 isKeyDown(KeyType::DIG),
2626 isKeyDown(KeyType::PLACE),
2629 input->getMovementSpeed(),
2630 input->getMovementDirection()
2633 // autoforward if set: move at maximum speed
2634 if (player->getPlayerSettings().continuous_forward &&
2635 client->activeObjectsReceived() && !player->isDead()) {
2636 control.movement_speed = 1.0f;
2637 // sideways movement only
2638 float dx = sin(control.movement_direction);
2639 control.movement_direction = atan2(dx, 1.0f);
2642 #ifdef HAVE_TOUCHSCREENGUI
2643 /* For touch, simulate holding down AUX1 (fast move) if the user has
2644 * the fast_move setting toggled on. If there is an aux1 key defined for
2645 * touch then its meaning is inverted (i.e. holding aux1 means walk and
2648 if (m_cache_hold_aux1) {
2649 control.aux1 = control.aux1 ^ true;
2653 client->setPlayerControl(control);
2659 inline void Game::step(f32 dtime)
2662 server->step(dtime);
2664 client->step(dtime);
2667 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2670 for (auto &&child: node->getChildren())
2671 pauseNodeAnimation(paused, child);
2672 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2674 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2675 float speed = animated_node->getAnimationSpeed();
2678 paused.push_back({grab(animated_node), speed});
2679 animated_node->setAnimationSpeed(0.0f);
2682 void Game::pauseAnimation()
2684 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2687 void Game::resumeAnimation()
2689 for (auto &&pair: paused_animated_nodes)
2690 pair.first->setAnimationSpeed(pair.second);
2691 paused_animated_nodes.clear();
2694 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2695 {&Game::handleClientEvent_None},
2696 {&Game::handleClientEvent_PlayerDamage},
2697 {&Game::handleClientEvent_PlayerForceMove},
2698 {&Game::handleClientEvent_Deathscreen},
2699 {&Game::handleClientEvent_ShowFormSpec},
2700 {&Game::handleClientEvent_ShowLocalFormSpec},
2701 {&Game::handleClientEvent_HandleParticleEvent},
2702 {&Game::handleClientEvent_HandleParticleEvent},
2703 {&Game::handleClientEvent_HandleParticleEvent},
2704 {&Game::handleClientEvent_HudAdd},
2705 {&Game::handleClientEvent_HudRemove},
2706 {&Game::handleClientEvent_HudChange},
2707 {&Game::handleClientEvent_SetSky},
2708 {&Game::handleClientEvent_SetSun},
2709 {&Game::handleClientEvent_SetMoon},
2710 {&Game::handleClientEvent_SetStars},
2711 {&Game::handleClientEvent_OverrideDayNigthRatio},
2712 {&Game::handleClientEvent_CloudParams},
2715 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2717 FATAL_ERROR("ClientEvent type None received");
2720 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2722 if (client->modsLoaded())
2723 client->getScript()->on_damage_taken(event->player_damage.amount);
2725 if (!event->player_damage.effect)
2728 // Damage flash and hurt tilt are not used at death
2729 if (client->getHP() > 0) {
2730 LocalPlayer *player = client->getEnv().getLocalPlayer();
2732 f32 hp_max = player->getCAO() ?
2733 player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2734 f32 damage_ratio = event->player_damage.amount / hp_max;
2736 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2737 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2739 player->hurt_tilt_timer = 1.5f;
2740 player->hurt_tilt_strength =
2741 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2744 // Play damage sound
2745 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2748 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2750 cam->camera_yaw = event->player_force_move.yaw;
2751 cam->camera_pitch = event->player_force_move.pitch;
2754 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2756 // If client scripting is enabled, deathscreen is handled by CSM code in
2757 // builtin/client/init.lua
2758 if (client->modsLoaded())
2759 client->getScript()->on_death();
2761 showDeathFormspec();
2763 /* Handle visualization */
2764 LocalPlayer *player = client->getEnv().getLocalPlayer();
2765 runData.damage_flash = 0;
2766 player->hurt_tilt_timer = 0;
2767 player->hurt_tilt_strength = 0;
2770 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2772 if (event->show_formspec.formspec->empty()) {
2773 auto formspec = m_game_ui->getFormspecGUI();
2774 if (formspec && (event->show_formspec.formname->empty()
2775 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2776 formspec->quitMenu();
2779 FormspecFormSource *fs_src =
2780 new FormspecFormSource(*(event->show_formspec.formspec));
2781 TextDestPlayerInventory *txt_dst =
2782 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2784 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2785 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2786 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2789 delete event->show_formspec.formspec;
2790 delete event->show_formspec.formname;
2793 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2795 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2796 LocalFormspecHandler *txt_dst =
2797 new LocalFormspecHandler(*event->show_formspec.formname, client);
2798 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2799 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2801 delete event->show_formspec.formspec;
2802 delete event->show_formspec.formname;
2805 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2806 CameraOrientation *cam)
2808 LocalPlayer *player = client->getEnv().getLocalPlayer();
2809 client->getParticleManager()->handleParticleEvent(event, client, player);
2812 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2814 LocalPlayer *player = client->getEnv().getLocalPlayer();
2816 u32 server_id = event->hudadd->server_id;
2817 // ignore if we already have a HUD with that ID
2818 auto i = m_hud_server_to_client.find(server_id);
2819 if (i != m_hud_server_to_client.end()) {
2820 delete event->hudadd;
2824 HudElement *e = new HudElement;
2825 e->type = static_cast<HudElementType>(event->hudadd->type);
2826 e->pos = event->hudadd->pos;
2827 e->name = event->hudadd->name;
2828 e->scale = event->hudadd->scale;
2829 e->text = event->hudadd->text;
2830 e->number = event->hudadd->number;
2831 e->item = event->hudadd->item;
2832 e->dir = event->hudadd->dir;
2833 e->align = event->hudadd->align;
2834 e->offset = event->hudadd->offset;
2835 e->world_pos = event->hudadd->world_pos;
2836 e->size = event->hudadd->size;
2837 e->z_index = event->hudadd->z_index;
2838 e->text2 = event->hudadd->text2;
2839 e->style = event->hudadd->style;
2840 m_hud_server_to_client[server_id] = player->addHud(e);
2842 delete event->hudadd;
2845 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2847 LocalPlayer *player = client->getEnv().getLocalPlayer();
2849 auto i = m_hud_server_to_client.find(event->hudrm.id);
2850 if (i != m_hud_server_to_client.end()) {
2851 HudElement *e = player->removeHud(i->second);
2853 m_hud_server_to_client.erase(i);
2858 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2860 LocalPlayer *player = client->getEnv().getLocalPlayer();
2862 HudElement *e = nullptr;
2864 auto i = m_hud_server_to_client.find(event->hudchange->id);
2865 if (i != m_hud_server_to_client.end()) {
2866 e = player->getHud(i->second);
2870 delete event->hudchange;
2874 #define CASE_SET(statval, prop, dataprop) \
2876 e->prop = event->hudchange->dataprop; \
2879 switch (event->hudchange->stat) {
2880 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2882 CASE_SET(HUD_STAT_NAME, name, sdata);
2884 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2886 CASE_SET(HUD_STAT_TEXT, text, sdata);
2888 CASE_SET(HUD_STAT_NUMBER, number, data);
2890 CASE_SET(HUD_STAT_ITEM, item, data);
2892 CASE_SET(HUD_STAT_DIR, dir, data);
2894 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2896 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2898 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2900 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2902 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2904 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2906 CASE_SET(HUD_STAT_STYLE, style, data);
2911 delete event->hudchange;
2914 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2916 sky->setVisible(false);
2917 // Whether clouds are visible in front of a custom skybox.
2918 sky->setCloudsEnabled(event->set_sky->clouds);
2924 // Clear the old textures out in case we switch rendering type.
2925 sky->clearSkyboxTextures();
2926 // Handle according to type
2927 if (event->set_sky->type == "regular") {
2928 // Shows the mesh skybox
2929 sky->setVisible(true);
2930 // Update mesh based skybox colours if applicable.
2931 sky->setSkyColors(event->set_sky->sky_color);
2932 sky->setHorizonTint(
2933 event->set_sky->fog_sun_tint,
2934 event->set_sky->fog_moon_tint,
2935 event->set_sky->fog_tint_type
2937 } else if (event->set_sky->type == "skybox" &&
2938 event->set_sky->textures.size() == 6) {
2939 // Disable the dyanmic mesh skybox:
2940 sky->setVisible(false);
2942 sky->setFallbackBgColor(event->set_sky->bgcolor);
2943 // Set sunrise and sunset fog tinting:
2944 sky->setHorizonTint(
2945 event->set_sky->fog_sun_tint,
2946 event->set_sky->fog_moon_tint,
2947 event->set_sky->fog_tint_type
2949 // Add textures to skybox.
2950 for (int i = 0; i < 6; i++)
2951 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2953 // Handle everything else as plain color.
2954 if (event->set_sky->type != "plain")
2955 infostream << "Unknown sky type: "
2956 << (event->set_sky->type) << std::endl;
2957 sky->setVisible(false);
2958 sky->setFallbackBgColor(event->set_sky->bgcolor);
2959 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2960 sky->setHorizonTint(
2961 event->set_sky->bgcolor,
2962 event->set_sky->bgcolor,
2967 delete event->set_sky;
2970 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2972 sky->setSunVisible(event->sun_params->visible);
2973 sky->setSunTexture(event->sun_params->texture,
2974 event->sun_params->tonemap, texture_src);
2975 sky->setSunScale(event->sun_params->scale);
2976 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2977 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2978 delete event->sun_params;
2981 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2983 sky->setMoonVisible(event->moon_params->visible);
2984 sky->setMoonTexture(event->moon_params->texture,
2985 event->moon_params->tonemap, texture_src);
2986 sky->setMoonScale(event->moon_params->scale);
2987 delete event->moon_params;
2990 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2992 sky->setStarsVisible(event->star_params->visible);
2993 sky->setStarCount(event->star_params->count);
2994 sky->setStarColor(event->star_params->starcolor);
2995 sky->setStarScale(event->star_params->scale);
2996 sky->setStarDayOpacity(event->star_params->day_opacity);
2997 delete event->star_params;
3000 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
3001 CameraOrientation *cam)
3003 client->getEnv().setDayNightRatioOverride(
3004 event->override_day_night_ratio.do_override,
3005 event->override_day_night_ratio.ratio_f * 1000.0f);
3008 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
3013 clouds->setDensity(event->cloud_params.density);
3014 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
3015 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
3016 clouds->setHeight(event->cloud_params.height);
3017 clouds->setThickness(event->cloud_params.thickness);
3018 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
3021 void Game::processClientEvents(CameraOrientation *cam)
3023 while (client->hasClientEvents()) {
3024 std::unique_ptr<ClientEvent> event(client->getClientEvent());
3025 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
3026 const ClientEventHandler& evHandler = clientEventHandler[event->type];
3027 (this->*evHandler.handler)(event.get(), cam);
3031 void Game::updateChat(f32 dtime)
3033 // Get new messages from error log buffer
3034 while (!m_chat_log_buf.empty())
3035 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
3037 // Get new messages from client
3038 std::wstring message;
3039 while (client->getChatMessage(message)) {
3040 chat_backend->addUnparsedMessage(message);
3043 // Remove old messages
3044 chat_backend->step(dtime);
3046 // Display all messages in a static text element
3047 auto &buf = chat_backend->getRecentBuffer();
3048 if (buf.getLinesModified()) {
3049 buf.resetLinesModified();
3050 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
3053 // Make sure that the size is still correct
3054 m_game_ui->updateChatSize();
3057 void Game::updateCamera(f32 dtime)
3059 LocalPlayer *player = client->getEnv().getLocalPlayer();
3062 For interaction purposes, get info about the held item
3064 - Is it a usable item?
3065 - Can it point to liquids?
3067 ItemStack playeritem;
3069 ItemStack selected, hand;
3070 playeritem = player->getWieldedItem(&selected, &hand);
3073 ToolCapabilities playeritem_toolcap =
3074 playeritem.getToolCapabilities(itemdef_manager);
3076 v3s16 old_camera_offset = camera->getOffset();
3078 if (wasKeyDown(KeyType::CAMERA_MODE)) {
3079 GenericCAO *playercao = player->getCAO();
3081 // If playercao not loaded, don't change camera
3085 camera->toggleCameraMode();
3087 #ifdef HAVE_TOUCHSCREENGUI
3088 if (g_touchscreengui)
3089 g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed());
3092 // Make the player visible depending on camera mode.
3093 playercao->updateMeshCulling();
3094 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
3097 float full_punch_interval = playeritem_toolcap.full_punch_interval;
3098 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
3100 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
3101 camera->update(player, dtime, tool_reload_ratio);
3102 camera->step(dtime);
3104 f32 camera_fov = camera->getFovMax();
3105 v3s16 camera_offset = camera->getOffset();
3107 m_camera_offset_changed = (camera_offset != old_camera_offset);
3109 if (!m_flags.disable_camera_update) {
3110 v3f camera_position = camera->getPosition();
3111 v3f camera_direction = camera->getDirection();
3113 client->getEnv().getClientMap().updateCamera(camera_position,
3114 camera_direction, camera_fov, camera_offset);
3116 if (m_camera_offset_changed) {
3117 client->updateCameraOffset(camera_offset);
3118 client->getEnv().updateCameraOffset(camera_offset);
3121 clouds->updateCameraOffset(camera_offset);
3127 void Game::updateSound(f32 dtime)
3129 // Update sound listener
3130 v3s16 camera_offset = camera->getOffset();
3131 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3132 v3f(0, 0, 0), // velocity
3133 camera->getDirection(),
3134 camera->getCameraNode()->getUpVector());
3136 bool mute_sound = g_settings->getBool("mute_sound");
3138 sound->setListenerGain(0.0f);
3140 // Check if volume is in the proper range, else fix it.
3141 float old_volume = g_settings->getFloat("sound_volume");
3142 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3143 sound->setListenerGain(new_volume);
3145 if (old_volume != new_volume) {
3146 g_settings->setFloat("sound_volume", new_volume);
3150 LocalPlayer *player = client->getEnv().getLocalPlayer();
3152 // Tell the sound maker whether to make footstep sounds
3153 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3155 // Update sound maker
3156 if (player->makes_footstep_sound)
3157 soundmaker->step(dtime);
3159 ClientMap &map = client->getEnv().getClientMap();
3160 MapNode n = map.getNode(player->getFootstepNodePos());
3161 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3165 void Game::processPlayerInteraction(f32 dtime, bool show_hud)
3167 LocalPlayer *player = client->getEnv().getLocalPlayer();
3169 const v3f camera_direction = camera->getDirection();
3170 const v3s16 camera_offset = camera->getOffset();
3173 Calculate what block is the crosshair pointing to
3176 ItemStack selected_item, hand_item;
3177 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3179 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3180 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3182 core::line3d<f32> shootline;
3184 switch (camera->getCameraMode()) {
3185 case CAMERA_MODE_FIRST:
3186 // Shoot from camera position, with bobbing
3187 shootline.start = camera->getPosition();
3189 case CAMERA_MODE_THIRD:
3190 // Shoot from player head, no bobbing
3191 shootline.start = camera->getHeadPosition();
3193 case CAMERA_MODE_THIRD_FRONT:
3194 shootline.start = camera->getHeadPosition();
3195 // prevent player pointing anything in front-view
3199 shootline.end = shootline.start + camera_direction * BS * d;
3201 #ifdef HAVE_TOUCHSCREENGUI
3202 if (g_touchscreengui && isNoCrosshairAllowed()) {
3203 shootline = g_touchscreengui->getShootline();
3204 // Scale shootline to the acual distance the player can reach
3205 shootline.end = shootline.start +
3206 shootline.getVector().normalize() * BS * d;
3207 shootline.start += intToFloat(camera_offset, BS);
3208 shootline.end += intToFloat(camera_offset, BS);
3212 PointedThing pointed = updatePointedThing(shootline,
3213 selected_def.liquids_pointable,
3214 !runData.btn_down_for_dig,
3217 if (pointed != runData.pointed_old)
3218 infostream << "Pointing at " << pointed.dump() << std::endl;
3220 // Note that updating the selection mesh every frame is not particularly efficient,
3221 // but the halo rendering code is already inefficient so there's no point in optimizing it here
3222 hud->updateSelectionMesh(camera_offset);
3224 // Allow digging again if button is not pressed
3225 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3226 runData.digging_blocked = false;
3230 - releasing dig button
3231 - pointing away from node
3233 if (runData.digging) {
3234 if (wasKeyReleased(KeyType::DIG)) {
3235 infostream << "Dig button released (stopped digging)" << std::endl;
3236 runData.digging = false;
3237 } else if (pointed != runData.pointed_old) {
3238 if (pointed.type == POINTEDTHING_NODE
3239 && runData.pointed_old.type == POINTEDTHING_NODE
3240 && pointed.node_undersurface
3241 == runData.pointed_old.node_undersurface) {
3242 // Still pointing to the same node, but a different face.
3245 infostream << "Pointing away from node (stopped digging)" << std::endl;
3246 runData.digging = false;
3247 hud->updateSelectionMesh(camera_offset);
3251 if (!runData.digging) {
3252 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3253 client->setCrack(-1, v3s16(0, 0, 0));
3254 runData.dig_time = 0.0;
3256 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3257 // Remove e.g. torches faster when clicking instead of holding dig button
3258 runData.nodig_delay_timer = 0;
3259 runData.dig_instantly = false;
3262 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3263 runData.btn_down_for_dig = false;
3265 runData.punching = false;
3267 soundmaker->m_player_leftpunch_sound = SimpleSoundSpec();
3268 soundmaker->m_player_leftpunch_sound2 = pointed.type != POINTEDTHING_NOTHING ?
3269 selected_def.sound_use : selected_def.sound_use_air;
3271 // Prepare for repeating, unless we're not supposed to
3272 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3273 runData.repeat_place_timer += dtime;
3275 runData.repeat_place_timer = 0;
3277 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3278 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3279 !client->getScript()->on_item_use(selected_item, pointed)))
3280 client->interact(INTERACT_USE, pointed);
3281 } else if (pointed.type == POINTEDTHING_NODE) {
3282 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3283 } else if (pointed.type == POINTEDTHING_OBJECT) {
3284 v3f player_position = player->getPosition();
3285 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
3286 handlePointingAtObject(pointed, tool_item, player_position,
3287 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
3288 } else if (isKeyDown(KeyType::DIG)) {
3289 // When button is held down in air, show continuous animation
3290 runData.punching = true;
3291 // Run callback even though item is not usable
3292 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3293 client->getScript()->on_item_use(selected_item, pointed);
3294 } else if (wasKeyPressed(KeyType::PLACE)) {
3295 handlePointingAtNothing(selected_item);
3298 runData.pointed_old = pointed;
3300 if (runData.punching || wasKeyPressed(KeyType::DIG))
3301 camera->setDigging(0); // dig animation
3303 input->clearWasKeyPressed();
3304 input->clearWasKeyReleased();
3305 // Ensure DIG & PLACE are marked as handled
3306 wasKeyDown(KeyType::DIG);
3307 wasKeyDown(KeyType::PLACE);
3309 input->joystick.clearWasKeyPressed(KeyType::DIG);
3310 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3312 input->joystick.clearWasKeyReleased(KeyType::DIG);
3313 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3317 PointedThing Game::updatePointedThing(
3318 const core::line3d<f32> &shootline,
3319 bool liquids_pointable,
3320 bool look_for_object,
3321 const v3s16 &camera_offset)
3323 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3324 selectionboxes->clear();
3325 hud->setSelectedFaceNormal(v3f());
3326 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3327 "show_entity_selectionbox");
3329 ClientEnvironment &env = client->getEnv();
3330 ClientMap &map = env.getClientMap();
3331 const NodeDefManager *nodedef = map.getNodeDefManager();
3333 runData.selected_object = NULL;
3334 hud->pointing_at_object = false;
3336 RaycastState s(shootline, look_for_object, liquids_pointable);
3337 PointedThing result;
3338 env.continueRaycast(&s, &result);
3339 if (result.type == POINTEDTHING_OBJECT) {
3340 hud->pointing_at_object = true;
3342 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3343 aabb3f selection_box;
3344 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3345 runData.selected_object->getSelectionBox(&selection_box)) {
3346 v3f pos = runData.selected_object->getPosition();
3347 selectionboxes->push_back(aabb3f(selection_box));
3348 hud->setSelectionPos(pos, camera_offset);
3349 GenericCAO* gcao = dynamic_cast<GenericCAO*>(runData.selected_object);
3350 if (gcao != nullptr && gcao->getProperties().rotate_selectionbox)
3351 hud->setSelectionRotation(gcao->getSceneNode()->getAbsoluteTransformation().getRotationDegrees());
3353 hud->setSelectionRotation(v3f());
3355 hud->setSelectedFaceNormal(result.raw_intersection_normal);
3356 } else if (result.type == POINTEDTHING_NODE) {
3357 // Update selection boxes
3358 MapNode n = map.getNode(result.node_undersurface);
3359 std::vector<aabb3f> boxes;
3360 n.getSelectionBoxes(nodedef, &boxes,
3361 n.getNeighbors(result.node_undersurface, &map));
3364 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3365 i != boxes.end(); ++i) {
3367 box.MinEdge -= v3f(d, d, d);
3368 box.MaxEdge += v3f(d, d, d);
3369 selectionboxes->push_back(box);
3371 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3373 hud->setSelectionRotation(v3f());
3374 hud->setSelectedFaceNormal(result.intersection_normal);
3377 // Update selection mesh light level and vertex colors
3378 if (!selectionboxes->empty()) {
3379 v3f pf = hud->getSelectionPos();
3380 v3s16 p = floatToInt(pf, BS);
3382 // Get selection mesh light level
3383 MapNode n = map.getNode(p);
3384 u16 node_light = getInteriorLight(n, -1, nodedef);
3385 u16 light_level = node_light;
3387 for (const v3s16 &dir : g_6dirs) {
3388 n = map.getNode(p + dir);
3389 node_light = getInteriorLight(n, -1, nodedef);
3390 if (node_light > light_level)
3391 light_level = node_light;
3394 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3396 final_color_blend(&c, light_level, daynight_ratio);
3398 // Modify final color a bit with time
3399 u32 timer = client->getEnv().getFrameTime() % 5000;
3400 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3401 float sin_r = 0.08f * std::sin(timerf);
3402 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3403 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3404 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3405 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3406 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3408 // Set mesh final color
3409 hud->setSelectionMeshColor(c);
3415 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3417 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3418 PointedThing fauxPointed;
3419 fauxPointed.type = POINTEDTHING_NOTHING;
3420 client->interact(INTERACT_ACTIVATE, fauxPointed);
3424 void Game::handlePointingAtNode(const PointedThing &pointed,
3425 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3427 v3s16 nodepos = pointed.node_undersurface;
3428 v3s16 neighborpos = pointed.node_abovesurface;
3431 Check information text of node
3434 ClientMap &map = client->getEnv().getClientMap();
3436 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3437 && !runData.digging_blocked
3438 && client->checkPrivilege("interact")) {
3439 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3442 // This should be done after digging handling
3443 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3446 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3447 meta->getString("infotext"))));
3449 MapNode n = map.getNode(nodepos);
3451 if (nodedef_manager->get(n).name == "unknown") {
3452 m_game_ui->setInfoText(L"Unknown node");
3456 if ((wasKeyPressed(KeyType::PLACE) ||
3457 runData.repeat_place_timer >= m_repeat_place_time) &&
3458 client->checkPrivilege("interact")) {
3459 runData.repeat_place_timer = 0;
3460 infostream << "Place button pressed while looking at ground" << std::endl;
3462 // Placing animation (always shown for feedback)
3463 camera->setDigging(1);
3465 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3467 // If the wielded item has node placement prediction,
3469 // And also set the sound and send the interact
3470 // But first check for meta formspec and rightclickable
3471 auto &def = selected_item.getDefinition(itemdef_manager);
3472 bool placed = nodePlacement(def, selected_item, nodepos, neighborpos,
3475 if (placed && client->modsLoaded())
3476 client->getScript()->on_placenode(pointed, def);
3480 bool Game::nodePlacement(const ItemDefinition &selected_def,
3481 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighborpos,
3482 const PointedThing &pointed, const NodeMetadata *meta)
3484 const auto &prediction = selected_def.node_placement_prediction;
3486 const NodeDefManager *nodedef = client->ndef();
3487 ClientMap &map = client->getEnv().getClientMap();
3489 bool is_valid_position;
3491 node = map.getNode(nodepos, &is_valid_position);
3492 if (!is_valid_position) {
3493 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3498 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3499 && !isKeyDown(KeyType::SNEAK)) {
3500 // on_rightclick callbacks are called anyway
3501 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3502 client->interact(INTERACT_PLACE, pointed);
3504 infostream << "Launching custom inventory view" << std::endl;
3506 InventoryLocation inventoryloc;
3507 inventoryloc.setNodeMeta(nodepos);
3509 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3510 &client->getEnv().getClientMap(), nodepos);
3511 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3513 auto *&formspec = m_game_ui->updateFormspec("");
3514 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3515 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3517 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3521 // on_rightclick callback
3522 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3523 !isKeyDown(KeyType::SNEAK))) {
3525 client->interact(INTERACT_PLACE, pointed);
3529 verbosestream << "Node placement prediction for "
3530 << selected_def.name << " is " << prediction << std::endl;
3531 v3s16 p = neighborpos;
3533 // Place inside node itself if buildable_to
3534 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3535 if (is_valid_position) {
3536 if (nodedef->get(n_under).buildable_to) {
3539 node = map.getNode(p, &is_valid_position);
3540 if (is_valid_position && !nodedef->get(node).buildable_to) {
3541 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3543 client->interact(INTERACT_PLACE, pointed);
3549 // Find id of predicted node
3551 bool found = nodedef->getId(prediction, id);
3554 errorstream << "Node placement prediction failed for "
3555 << selected_def.name << " (places " << prediction
3556 << ") - Name not known" << std::endl;
3557 // Handle this as if prediction was empty
3559 client->interact(INTERACT_PLACE, pointed);
3563 const ContentFeatures &predicted_f = nodedef->get(id);
3565 // Compare core.item_place_node() for what the server does with param2
3566 MapNode predicted_node(id, 0, 0);
3568 const u8 place_param2 = selected_def.place_param2;
3571 predicted_node.setParam2(place_param2);
3572 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3573 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3574 v3s16 dir = nodepos - neighborpos;
3576 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3577 predicted_node.setParam2(dir.Y < 0 ? 1 : 0);
3578 } else if (abs(dir.X) > abs(dir.Z)) {
3579 predicted_node.setParam2(dir.X < 0 ? 3 : 2);
3581 predicted_node.setParam2(dir.Z < 0 ? 5 : 4);
3583 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3584 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR ||
3585 predicted_f.param_type_2 == CPT2_4DIR ||
3586 predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3587 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3589 if (abs(dir.X) > abs(dir.Z)) {
3590 predicted_node.setParam2(dir.X < 0 ? 3 : 1);
3592 predicted_node.setParam2(dir.Z < 0 ? 2 : 0);
3596 // Check attachment if node is in group attached_node
3597 int an = itemgroup_get(predicted_f.groups, "attached_node");
3602 pp = p + v3s16(0, -1, 0);
3603 } else if (an == 4) {
3604 pp = p + v3s16(0, 1, 0);
3605 } else if (an == 2) {
3606 if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3607 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR ||
3608 predicted_f.param_type_2 == CPT2_4DIR ||
3609 predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3610 pp = p + facedir_dirs[predicted_node.getFaceDir(nodedef)];
3614 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3615 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3616 pp = p + predicted_node.getWallMountedDir(nodedef);
3618 pp = p + v3s16(0, -1, 0);
3621 if (!nodedef->get(map.getNode(pp)).walkable) {
3622 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3624 client->interact(INTERACT_PLACE, pointed);
3630 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3631 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3632 || predicted_f.param_type_2 == CPT2_COLORED_4DIR
3633 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3634 const auto &indexstr = selected_item.metadata.
3635 getString("palette_index", 0);
3636 if (!indexstr.empty()) {
3637 s32 index = mystoi(indexstr);
3638 if (predicted_f.param_type_2 == CPT2_COLOR) {
3639 predicted_node.setParam2(index);
3640 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3641 // param2 = pure palette index + other
3642 predicted_node.setParam2((index & 0xf8) | (predicted_node.getParam2() & 0x07));
3643 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3644 // param2 = pure palette index + other
3645 predicted_node.setParam2((index & 0xe0) | (predicted_node.getParam2() & 0x1f));
3646 } else if (predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3647 // param2 = pure palette index + other
3648 predicted_node.setParam2((index & 0xfc) | (predicted_node.getParam2() & 0x03));
3653 // Add node to client map
3655 LocalPlayer *player = client->getEnv().getLocalPlayer();
3657 // Don't place node when player would be inside new node
3658 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3659 if (!predicted_f.walkable ||
3660 g_settings->getBool("enable_build_where_you_stand") ||
3661 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3662 (predicted_f.walkable &&
3663 neighborpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3664 neighborpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3665 // This triggers the required mesh update too
3666 client->addNode(p, predicted_node);
3668 client->interact(INTERACT_PLACE, pointed);
3669 // A node is predicted, also play a sound
3670 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3673 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3676 } catch (const InvalidPositionException &e) {
3677 errorstream << "Node placement prediction failed for "
3678 << selected_def.name << " (places "
3679 << prediction << ") - Position not loaded" << std::endl;
3680 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3685 void Game::handlePointingAtObject(const PointedThing &pointed,
3686 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3688 std::wstring infotext = unescape_translate(
3689 utf8_to_wide(runData.selected_object->infoText()));
3692 if (!infotext.empty()) {
3695 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3698 m_game_ui->setInfoText(infotext);
3700 if (isKeyDown(KeyType::DIG)) {
3701 bool do_punch = false;
3702 bool do_punch_damage = false;
3704 if (runData.object_hit_delay_timer <= 0.0) {
3706 do_punch_damage = true;
3707 runData.object_hit_delay_timer = object_hit_delay;
3710 if (wasKeyPressed(KeyType::DIG))
3714 infostream << "Punched object" << std::endl;
3715 runData.punching = true;
3718 if (do_punch_damage) {
3719 // Report direct punch
3720 v3f objpos = runData.selected_object->getPosition();
3721 v3f dir = (objpos - player_position).normalize();
3723 bool disable_send = runData.selected_object->directReportPunch(
3724 dir, &tool_item, runData.time_from_last_punch);
3725 runData.time_from_last_punch = 0;
3728 client->interact(INTERACT_START_DIGGING, pointed);
3730 } else if (wasKeyDown(KeyType::PLACE)) {
3731 infostream << "Pressed place button while pointing at object" << std::endl;
3732 client->interact(INTERACT_PLACE, pointed); // place
3737 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3738 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3740 // See also: serverpackethandle.cpp, action == 2
3741 LocalPlayer *player = client->getEnv().getLocalPlayer();
3742 ClientMap &map = client->getEnv().getClientMap();
3743 MapNode n = map.getNode(nodepos);
3744 const auto &features = nodedef_manager->get(n);
3746 // NOTE: Similar piece of code exists on the server side for
3748 // Get digging parameters
3749 DigParams params = getDigParams(features.groups,
3750 &selected_item.getToolCapabilities(itemdef_manager),
3751 selected_item.wear);
3753 // If can't dig, try hand
3754 if (!params.diggable) {
3755 params = getDigParams(features.groups,
3756 &hand_item.getToolCapabilities(itemdef_manager));
3759 if (!params.diggable) {
3760 // I guess nobody will wait for this long
3761 runData.dig_time_complete = 10000000.0;
3763 runData.dig_time_complete = params.time;
3765 if (m_cache_enable_particles) {
3766 client->getParticleManager()->addNodeParticle(client,
3767 player, nodepos, n, features);
3771 if (!runData.digging) {
3772 infostream << "Started digging" << std::endl;
3773 runData.dig_instantly = runData.dig_time_complete == 0;
3774 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3777 client->interact(INTERACT_START_DIGGING, pointed);
3778 runData.digging = true;
3779 runData.btn_down_for_dig = true;
3782 if (!runData.dig_instantly) {
3783 runData.dig_index = (float)crack_animation_length
3785 / runData.dig_time_complete;
3787 // This is for e.g. torches
3788 runData.dig_index = crack_animation_length;
3791 const auto &sound_dig = features.sound_dig;
3793 if (sound_dig.exists() && params.diggable) {
3794 if (sound_dig.name == "__group") {
3795 if (!params.main_group.empty()) {
3796 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3797 soundmaker->m_player_leftpunch_sound.name =
3798 std::string("default_dig_") +
3802 soundmaker->m_player_leftpunch_sound = sound_dig;
3806 // Don't show cracks if not diggable
3807 if (runData.dig_time_complete >= 100000.0) {
3808 } else if (runData.dig_index < crack_animation_length) {
3809 client->setCrack(runData.dig_index, nodepos);
3811 infostream << "Digging completed" << std::endl;
3812 client->setCrack(-1, v3s16(0, 0, 0));
3814 runData.dig_time = 0;
3815 runData.digging = false;
3816 // we successfully dug, now block it from repeating if we want to be safe
3817 if (g_settings->getBool("safe_dig_and_place"))
3818 runData.digging_blocked = true;
3820 runData.nodig_delay_timer =
3821 runData.dig_time_complete / (float)crack_animation_length;
3823 // We don't want a corresponding delay to very time consuming nodes
3824 // and nodes without digging time (e.g. torches) get a fixed delay.
3825 if (runData.nodig_delay_timer > 0.3)
3826 runData.nodig_delay_timer = 0.3;
3827 else if (runData.dig_instantly)
3828 runData.nodig_delay_timer = 0.15;
3830 if (client->modsLoaded() &&
3831 client->getScript()->on_dignode(nodepos, n)) {
3835 if (features.node_dig_prediction == "air") {
3836 client->removeNode(nodepos);
3837 } else if (!features.node_dig_prediction.empty()) {
3839 bool found = nodedef_manager->getId(features.node_dig_prediction, id);
3841 client->addNode(nodepos, id, true);
3843 // implicit else: no prediction
3845 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3847 if (m_cache_enable_particles) {
3848 client->getParticleManager()->addDiggingParticles(client,
3849 player, nodepos, n, features);
3853 // Send event to trigger sound
3854 client->getEventManager()->put(new NodeDugEvent(nodepos, n));
3857 if (runData.dig_time_complete < 100000.0) {
3858 runData.dig_time += dtime;
3860 runData.dig_time = 0;
3861 client->setCrack(-1, nodepos);
3864 camera->setDigging(0); // Dig animation
3867 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3868 const CameraOrientation &cam)
3870 TimeTaker tt_update("Game::updateFrame()");
3871 LocalPlayer *player = client->getEnv().getLocalPlayer();
3877 client->getEnv().updateFrameTime(m_is_paused);
3883 if (draw_control->range_all) {
3884 runData.fog_range = 100000 * BS;
3886 runData.fog_range = draw_control->wanted_range * BS;
3890 Calculate general brightness
3892 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3893 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3894 float direct_brightness;
3897 // When in noclip mode force same sky brightness as above ground so you
3899 if (draw_control->allow_noclip && m_cache_enable_free_move &&
3900 client->checkPrivilege("fly")) {
3901 direct_brightness = time_brightness;
3902 sunlight_seen = true;
3904 float old_brightness = sky->getBrightness();
3905 direct_brightness = client->getEnv().getClientMap()
3906 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3907 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3911 float time_of_day_smooth = runData.time_of_day_smooth;
3912 float time_of_day = client->getEnv().getTimeOfDayF();
3914 static const float maxsm = 0.05f;
3915 static const float todsm = 0.05f;
3917 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3918 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3919 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3920 time_of_day_smooth = time_of_day;
3922 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3923 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3924 + (time_of_day + 1.0) * todsm;
3926 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3927 + time_of_day * todsm;
3929 runData.time_of_day_smooth = time_of_day_smooth;
3931 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3932 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3933 player->getPitch());
3939 if (sky->getCloudsVisible()) {
3940 clouds->setVisible(true);
3941 clouds->step(dtime);
3942 // camera->getPosition is not enough for 3rd person views
3943 v3f camera_node_position = camera->getCameraNode()->getPosition();
3944 v3s16 camera_offset = camera->getOffset();
3945 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3946 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3947 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3948 clouds->update(camera_node_position,
3949 sky->getCloudColor());
3950 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3951 // if inside clouds, and fog enabled, use that as sky
3953 video::SColor clouds_dark = clouds->getColor()
3954 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3955 sky->overrideColors(clouds_dark, clouds->getColor());
3956 sky->setInClouds(true);
3957 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3958 // do not draw clouds after all
3959 clouds->setVisible(false);
3962 clouds->setVisible(false);
3969 client->getParticleManager()->step(dtime);
3975 if (m_cache_enable_fog) {
3978 video::EFT_FOG_LINEAR,
3979 runData.fog_range * m_cache_fog_start,
3980 runData.fog_range * 1.0,
3988 video::EFT_FOG_LINEAR,
4000 if (player->hurt_tilt_timer > 0.0f) {
4001 player->hurt_tilt_timer -= dtime * 6.0f;
4003 if (player->hurt_tilt_timer < 0.0f)
4004 player->hurt_tilt_strength = 0.0f;
4008 Update minimap pos and rotation
4010 if (mapper && m_game_ui->m_flags.show_hud) {
4011 mapper->setPos(floatToInt(player->getPosition(), BS));
4012 mapper->setAngle(player->getYaw());
4016 Get chat messages from client
4025 if (player->getWieldIndex() != runData.new_playeritem)
4026 client->setPlayerItem(runData.new_playeritem);
4028 if (client->updateWieldedItem()) {
4029 // Update wielded tool
4030 ItemStack selected_item, hand_item;
4031 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
4032 camera->wield(tool_item);
4036 Update block draw list every 200ms or when camera direction has
4039 runData.update_draw_list_timer += dtime;
4041 float update_draw_list_delta = 0.2f;
4043 v3f camera_direction = camera->getDirection();
4044 if (runData.update_draw_list_timer >= update_draw_list_delta
4045 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
4046 || m_camera_offset_changed
4047 || client->getEnv().getClientMap().needsUpdateDrawList()) {
4048 runData.update_draw_list_timer = 0;
4049 client->getEnv().getClientMap().updateDrawList();
4050 runData.update_draw_list_last_cam_dir = camera_direction;
4053 if (RenderingEngine::get_shadow_renderer()) {
4057 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
4060 make sure menu is on top
4061 1. Delete formspec menu reference if menu was removed
4062 2. Else, make sure formspec menu is on top
4064 auto formspec = m_game_ui->getFormspecGUI();
4065 do { // breakable. only runs for one iteration
4069 if (formspec->getReferenceCount() == 1) {
4070 m_game_ui->deleteFormspec();
4074 auto &loc = formspec->getFormspecLocation();
4075 if (loc.type == InventoryLocation::NODEMETA) {
4076 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
4077 if (!meta || meta->getString("formspec").empty()) {
4078 formspec->quitMenu();
4084 guiroot->bringToFront(formspec);
4088 ==================== Drawing begins ====================
4090 const video::SColor skycolor = sky->getSkyColor();
4092 TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
4093 driver->beginScene(true, true, skycolor);
4095 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
4096 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
4097 (camera->getCameraMode() == CAMERA_MODE_FIRST));
4098 bool draw_crosshair = (
4099 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
4100 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
4101 #ifdef HAVE_TOUCHSCREENGUI
4102 if (isNoCrosshairAllowed())
4103 draw_crosshair = false;
4105 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
4106 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
4111 v2u32 screensize = driver->getScreenSize();
4113 if (m_game_ui->m_flags.show_profiler_graph)
4114 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
4119 if (runData.damage_flash > 0.0f) {
4120 video::SColor color(runData.damage_flash, 180, 0, 0);
4121 driver->draw2DRectangle(color,
4122 core::rect<s32>(0, 0, screensize.X, screensize.Y),
4125 runData.damage_flash -= 384.0f * dtime;
4129 ==================== End scene ====================
4131 #if IRRLICHT_VERSION_MT_REVISION < 5
4132 if (++m_reset_HW_buffer_counter > 500) {
4134 Periodically remove all mesh HW buffers.
4136 Work around for a quirk in Irrlicht where a HW buffer is only
4137 released after 20000 iterations (triggered from endScene()).
4139 Without this, all loaded but unused meshes will retain their HW
4140 buffers for at least 5 minutes, at which point looking up the HW buffers
4141 becomes a bottleneck and the framerate drops (as much as 30%).
4143 Tests showed that numbers between 50 and 1000 are good, so picked 500.
4144 There are no other public Irrlicht APIs that allow interacting with the
4145 HW buffers without tracking the status of every individual mesh.
4147 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4149 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4150 driver->removeAllHardwareBuffers();
4151 m_reset_HW_buffer_counter = 0;
4157 stats->drawtime = tt_draw.stop(true);
4158 g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
4159 g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
4162 /* Log times and stuff for visualization */
4163 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4165 Profiler::GraphValues values;
4166 g_profiler->graphGet(values);
4170 /****************************************************************************
4172 *****************************************************************************/
4173 void Game::updateShadows()
4175 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4179 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4181 float timeoftheday = getWickedTimeOfDay(in_timeofday);
4182 bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
4183 bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
4184 shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
4186 timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
4187 const float offset_constant = 10000.0f;
4189 v3f light = is_day ? sky->getSunDirection() : sky->getMoonDirection();
4191 v3f sun_pos = light * offset_constant;
4193 if (shadow->getDirectionalLightCount() == 0)
4194 shadow->addDirectionalLight();
4195 shadow->getDirectionalLight().setDirection(sun_pos);
4196 shadow->setTimeOfDay(in_timeofday);
4198 shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4201 /****************************************************************************
4203 ****************************************************************************/
4205 void FpsControl::reset()
4207 last_time = porting::getTimeUs();
4211 * On some computers framerate doesn't seem to be automatically limited
4213 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
4215 const float fps_limit = (device->isWindowFocused() && !g_menumgr.pausesGame())
4216 ? g_settings->getFloat("fps_max")
4217 : g_settings->getFloat("fps_max_unfocused");
4218 const u64 frametime_min = 1000000.0f / std::max(fps_limit, 1.0f);
4220 u64 time = porting::getTimeUs();
4222 if (time > last_time) // Make sure time hasn't overflowed
4223 busy_time = time - last_time;
4227 if (busy_time < frametime_min) {
4228 sleep_time = frametime_min - busy_time;
4229 if (sleep_time > 1000)
4230 sleep_ms(sleep_time / 1000);
4235 // Read the timer again to accurately determine how long we actually slept,
4236 // rather than calculating it by adding sleep_time to time.
4237 time = porting::getTimeUs();
4239 if (time > last_time) // Make sure last_time hasn't overflowed
4240 *dtime = (time - last_time) / 1000000.0f;
4247 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4249 const wchar_t *wmsg = wgettext(msg);
4250 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4255 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4257 ((Game *)data)->readSettings();
4260 void Game::readSettings()
4262 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4263 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4264 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4265 m_cache_enable_particles = g_settings->getBool("enable_particles");
4266 m_cache_enable_fog = g_settings->getBool("enable_fog");
4267 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f);
4268 m_cache_joystick_frustum_sensitivity = std::max(g_settings->getFloat("joystick_frustum_sensitivity"), 0.001f);
4269 m_repeat_place_time = g_settings->getFloat("repeat_place_time", 0.25f, 2.0);
4271 m_cache_enable_noclip = g_settings->getBool("noclip");
4272 m_cache_enable_free_move = g_settings->getBool("free_move");
4274 m_cache_fog_start = g_settings->getFloat("fog_start");
4276 m_cache_cam_smoothing = 0;
4277 if (g_settings->getBool("cinematic"))
4278 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4280 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4282 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4283 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4284 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4286 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4289 /****************************************************************************/
4290 /****************************************************************************
4292 ****************************************************************************/
4293 /****************************************************************************/
4295 void Game::showDeathFormspec()
4297 static std::string formspec_str =
4298 std::string("formspec_version[1]") +
4300 "bgcolor[#320000b4;true]"
4301 "label[4.85,1.35;" + gettext("You died") + "]"
4302 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4306 /* Note: FormspecFormSource and LocalFormspecHandler *
4307 * are deleted by guiFormSpecMenu */
4308 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4309 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4311 auto *&formspec = m_game_ui->getFormspecGUI();
4312 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4313 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4314 formspec->setFocus("btn_respawn");
4317 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4318 void Game::showPauseMenu()
4320 #ifdef HAVE_TOUCHSCREENGUI
4321 static const std::string control_text = strgettext("Default Controls:\n"
4322 "No menu visible:\n"
4323 "- single tap: button activate\n"
4324 "- double tap: place/use\n"
4325 "- slide finger: look around\n"
4326 "Menu/Inventory visible:\n"
4327 "- double tap (outside):\n"
4329 "- touch stack, touch slot:\n"
4331 "- touch&drag, tap 2nd finger\n"
4332 " --> place single item to slot\n"
4335 static const std::string control_text_template = strgettext("Controls:\n"
4336 "- %s: move forwards\n"
4337 "- %s: move backwards\n"
4339 "- %s: move right\n"
4340 "- %s: jump/climb up\n"
4343 "- %s: sneak/climb down\n"
4346 "- Mouse: turn/look\n"
4347 "- Mouse wheel: select item\n"
4351 char control_text_buf[600];
4353 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4354 GET_KEY_NAME(keymap_forward),
4355 GET_KEY_NAME(keymap_backward),
4356 GET_KEY_NAME(keymap_left),
4357 GET_KEY_NAME(keymap_right),
4358 GET_KEY_NAME(keymap_jump),
4359 GET_KEY_NAME(keymap_dig),
4360 GET_KEY_NAME(keymap_place),
4361 GET_KEY_NAME(keymap_sneak),
4362 GET_KEY_NAME(keymap_drop),
4363 GET_KEY_NAME(keymap_inventory),
4364 GET_KEY_NAME(keymap_chat)
4367 std::string control_text = std::string(control_text_buf);
4368 str_formspec_escape(control_text);
4371 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4372 std::ostringstream os;
4374 os << "formspec_version[1]" << SIZE_TAG
4375 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4376 << strgettext("Continue") << "]";
4378 if (!simple_singleplayer_mode) {
4379 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4380 << strgettext("Change Password") << "]";
4382 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4387 if (g_settings->getBool("enable_sound")) {
4388 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4389 << strgettext("Sound Volume") << "]";
4392 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4393 << strgettext("Change Keys") << "]";
4395 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4396 << strgettext("Exit to Menu") << "]";
4397 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4398 << strgettext("Exit to OS") << "]"
4399 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4400 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4402 << strgettext("Game info:") << "\n";
4403 const std::string &address = client->getAddressName();
4404 static const std::string mode = strgettext("- Mode: ");
4405 if (!simple_singleplayer_mode) {
4406 Address serverAddress = client->getServerAddress();
4407 if (!address.empty()) {
4408 os << mode << strgettext("Remote server") << "\n"
4409 << strgettext("- Address: ") << address;
4411 os << mode << strgettext("Hosting server");
4413 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4415 os << mode << strgettext("Singleplayer") << "\n";
4417 if (simple_singleplayer_mode || address.empty()) {
4418 static const std::string on = strgettext("On");
4419 static const std::string off = strgettext("Off");
4420 // Note: Status of enable_damage and creative_mode settings is intentionally
4421 // NOT shown here because the game might roll its own damage system and/or do
4422 // a per-player Creative Mode, in which case writing it here would mislead.
4423 bool damage = g_settings->getBool("enable_damage");
4424 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4425 if (!simple_singleplayer_mode) {
4427 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4428 //~ PvP = Player versus Player
4429 os << strgettext("- PvP: ") << pvp << "\n";
4431 os << strgettext("- Public: ") << announced << "\n";
4432 std::string server_name = g_settings->get("server_name");
4433 str_formspec_escape(server_name);
4434 if (announced == on && !server_name.empty())
4435 os << strgettext("- Server Name: ") << server_name;
4442 /* Note: FormspecFormSource and LocalFormspecHandler *
4443 * are deleted by guiFormSpecMenu */
4444 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4445 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4447 auto *&formspec = m_game_ui->getFormspecGUI();
4448 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4449 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4450 formspec->setFocus("btn_continue");
4451 // game will be paused in next step, if in singleplayer (see m_is_paused)
4452 formspec->doPause = true;
4455 /****************************************************************************/
4456 /****************************************************************************
4457 extern function for launching the game
4458 ****************************************************************************/
4459 /****************************************************************************/
4461 void the_game(bool *kill,
4462 InputHandler *input,
4463 RenderingEngine *rendering_engine,
4464 const GameStartData &start_data,
4465 std::string &error_message,
4466 ChatBackend &chat_backend,
4467 bool *reconnect_requested) // Used for local game
4471 /* Make a copy of the server address because if a local singleplayer server
4472 * is created then this is updated and we don't want to change the value
4473 * passed to us by the calling function
4478 if (game.startup(kill, input, rendering_engine, start_data,
4479 error_message, reconnect_requested, &chat_backend)) {
4483 } catch (SerializationError &e) {
4484 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
4485 error_message = strgettext("A serialization error occurred:") +"\n"
4486 + e.what() + "\n\n" + ver_err;
4487 errorstream << error_message << std::endl;
4488 } catch (ServerError &e) {
4489 error_message = e.what();
4490 errorstream << "ServerError: " << error_message << std::endl;
4491 } catch (ModError &e) {
4492 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
4493 error_message = std::string("ModError: ") + e.what() +
4494 strgettext("\nCheck debug.txt for details.");
4495 errorstream << error_message << std::endl;