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 CachedVertexShaderSetting<float> m_animation_timer_delta_vertex;
418 CachedPixelShaderSetting<float> m_animation_timer_delta_pixel;
419 CachedPixelShaderSetting<float, 3> m_day_light;
420 CachedPixelShaderSetting<float, 4> m_star_color;
421 CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
422 CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
423 CachedPixelShaderSetting<float, 3> m_minimap_yaw;
424 CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
425 CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
426 CachedPixelShaderSetting<SamplerLayer_t> m_texture0;
427 CachedPixelShaderSetting<SamplerLayer_t> m_texture1;
428 CachedPixelShaderSetting<SamplerLayer_t> m_texture2;
429 CachedPixelShaderSetting<SamplerLayer_t> m_texture3;
430 CachedPixelShaderSetting<float, 2> m_texel_size0;
431 std::array<float, 2> m_texel_size0_values;
432 CachedStructPixelShaderSetting<float, 7> m_exposure_params_pixel;
433 float m_user_exposure_compensation;
434 bool m_bloom_enabled;
435 CachedPixelShaderSetting<float> m_bloom_intensity_pixel;
436 float m_bloom_intensity;
437 CachedPixelShaderSetting<float> m_bloom_strength_pixel;
438 float m_bloom_strength;
439 CachedPixelShaderSetting<float> m_bloom_radius_pixel;
440 float m_bloom_radius;
441 CachedPixelShaderSetting<float> m_saturation_pixel;
444 void onSettingsChange(const std::string &name)
446 if (name == "enable_fog")
447 m_fog_enabled = g_settings->getBool("enable_fog");
448 if (name == "exposure_compensation")
449 m_user_exposure_compensation = g_settings->getFloat("exposure_compensation", -1.0f, 1.0f);
450 if (name == "bloom_intensity")
451 m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f);
452 if (name == "bloom_strength_factor")
453 m_bloom_strength = RenderingEngine::BASE_BLOOM_STRENGTH * g_settings->getFloat("bloom_strength_factor", 0.1f, 10.0f);
454 if (name == "bloom_radius")
455 m_bloom_radius = g_settings->getFloat("bloom_radius", 0.1f, 8.0f);
458 static void settingsCallback(const std::string &name, void *userdata)
460 reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
463 void setSky(Sky *sky) { m_sky = sky; }
465 GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
466 f32 *fog_range, Client *client) :
469 m_force_fog_off(force_fog_off),
470 m_fog_range(fog_range),
471 m_sky_bg_color("skyBgColor"),
472 m_fog_distance("fogDistance"),
473 m_animation_timer_vertex("animationTimer"),
474 m_animation_timer_pixel("animationTimer"),
475 m_animation_timer_delta_vertex("animationTimerDelta"),
476 m_animation_timer_delta_pixel("animationTimerDelta"),
477 m_day_light("dayLight"),
478 m_star_color("starColor"),
479 m_eye_position_pixel("eyePosition"),
480 m_eye_position_vertex("eyePosition"),
481 m_minimap_yaw("yawVec"),
482 m_camera_offset_pixel("cameraOffset"),
483 m_camera_offset_vertex("cameraOffset"),
484 m_texture0("texture0"),
485 m_texture1("texture1"),
486 m_texture2("texture2"),
487 m_texture3("texture3"),
488 m_texel_size0("texelSize0"),
489 m_exposure_params_pixel("exposureParams",
490 std::array<const char*, 7> {
491 "luminanceMin", "luminanceMax", "exposureCorrection",
492 "speedDarkBright", "speedBrightDark", "centerWeightPower", "compensationFactor"
494 m_bloom_intensity_pixel("bloomIntensity"),
495 m_bloom_strength_pixel("bloomStrength"),
496 m_bloom_radius_pixel("bloomRadius"),
497 m_saturation_pixel("saturation")
499 g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
500 g_settings->registerChangedCallback("exposure_compensation", settingsCallback, this);
501 g_settings->registerChangedCallback("bloom_intensity", settingsCallback, this);
502 g_settings->registerChangedCallback("bloom_strength_factor", settingsCallback, this);
503 g_settings->registerChangedCallback("bloom_radius", settingsCallback, this);
504 g_settings->registerChangedCallback("saturation", settingsCallback, this);
505 m_fog_enabled = g_settings->getBool("enable_fog");
506 m_user_exposure_compensation = g_settings->getFloat("exposure_compensation", -1.0f, 1.0f);
507 m_bloom_enabled = g_settings->getBool("enable_bloom");
508 m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f);
509 m_bloom_strength = RenderingEngine::BASE_BLOOM_STRENGTH * g_settings->getFloat("bloom_strength_factor", 0.1f, 10.0f);
510 m_bloom_radius = g_settings->getFloat("bloom_radius", 0.1f, 8.0f);
513 ~GameGlobalShaderConstantSetter()
515 g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
518 void onSetConstants(video::IMaterialRendererServices *services) override
521 video::SColor bgcolor = m_sky->getBgColor();
522 video::SColorf bgcolorf(bgcolor);
523 float bgcolorfa[4] = {
529 m_sky_bg_color.set(bgcolorfa, services);
532 float fog_distance = 10000 * BS;
534 if (m_fog_enabled && !*m_force_fog_off)
535 fog_distance = *m_fog_range;
537 m_fog_distance.set(&fog_distance, services);
539 u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
540 video::SColorf sunlight;
541 get_sunlight_color(&sunlight, daynight_ratio);
546 m_day_light.set(dnc, services);
548 video::SColorf star_color = m_sky->getCurrentStarColor();
549 float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a};
550 m_star_color.set(clr, services);
552 u32 animation_timer = m_client->getEnv().getFrameTime() % 1000000;
553 float animation_timer_f = (float)animation_timer / 100000.f;
554 m_animation_timer_vertex.set(&animation_timer_f, services);
555 m_animation_timer_pixel.set(&animation_timer_f, services);
557 float animation_timer_delta_f = (float)m_client->getEnv().getFrameTimeDelta() / 100000.f;
558 m_animation_timer_delta_vertex.set(&animation_timer_delta_f, services);
559 m_animation_timer_delta_pixel.set(&animation_timer_delta_f, services);
561 float eye_position_array[3];
562 v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
563 epos.getAs3Values(eye_position_array);
564 m_eye_position_pixel.set(eye_position_array, services);
565 m_eye_position_vertex.set(eye_position_array, services);
567 if (m_client->getMinimap()) {
568 float minimap_yaw_array[3];
569 v3f minimap_yaw = m_client->getMinimap()->getYawVec();
570 minimap_yaw.getAs3Values(minimap_yaw_array);
571 m_minimap_yaw.set(minimap_yaw_array, services);
574 float camera_offset_array[3];
575 v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
576 offset.getAs3Values(camera_offset_array);
577 m_camera_offset_pixel.set(camera_offset_array, services);
578 m_camera_offset_vertex.set(camera_offset_array, services);
580 SamplerLayer_t tex_id;
582 m_texture0.set(&tex_id, services);
584 m_texture1.set(&tex_id, services);
586 m_texture2.set(&tex_id, services);
588 m_texture3.set(&tex_id, services);
590 m_texel_size0.set(m_texel_size0_values.data(), services);
592 const AutoExposure &exposure_params = m_client->getEnv().getLocalPlayer()->getLighting().exposure;
593 std::array<float, 7> exposure_buffer = {
594 std::pow(2.0f, exposure_params.luminance_min),
595 std::pow(2.0f, exposure_params.luminance_max),
596 exposure_params.exposure_correction,
597 exposure_params.speed_dark_bright,
598 exposure_params.speed_bright_dark,
599 exposure_params.center_weight_power,
600 powf(2.f, m_user_exposure_compensation)
602 m_exposure_params_pixel.set(exposure_buffer.data(), services);
604 if (m_bloom_enabled) {
605 m_bloom_intensity_pixel.set(&m_bloom_intensity, services);
606 m_bloom_radius_pixel.set(&m_bloom_radius, services);
607 m_bloom_strength_pixel.set(&m_bloom_strength, services);
609 float saturation = m_client->getEnv().getLocalPlayer()->getLighting().saturation;
610 m_saturation_pixel.set(&saturation, services);
613 void onSetMaterial(const video::SMaterial &material)
615 video::ITexture *texture = material.getTexture(0);
617 core::dimension2du size = texture->getSize();
618 m_texel_size0_values[0] = 1.f / size.Width;
619 m_texel_size0_values[1] = 1.f / size.Height;
622 m_texel_size0_values[0] = 0.f;
623 m_texel_size0_values[1] = 0.f;
629 class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
632 bool *m_force_fog_off;
635 std::vector<GameGlobalShaderConstantSetter *> created_nosky;
637 GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
638 f32 *fog_range, Client *client) :
640 m_force_fog_off(force_fog_off),
641 m_fog_range(fog_range),
645 void setSky(Sky *sky) {
647 for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
648 ggscs->setSky(m_sky);
650 created_nosky.clear();
653 virtual IShaderConstantSetter* create()
655 auto *scs = new GameGlobalShaderConstantSetter(
656 m_sky, m_force_fog_off, m_fog_range, m_client);
658 created_nosky.push_back(scs);
663 #ifdef HAVE_TOUCHSCREENGUI
664 #define SIZE_TAG "size[11,5.5]"
666 #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
669 /****************************************************************************
670 ****************************************************************************/
672 const static float object_hit_delay = 0.2;
675 FpsControl() : last_time(0), busy_time(0), sleep_time(0) {}
679 void limit(IrrlichtDevice *device, f32 *dtime);
681 u32 getBusyMs() const { return busy_time / 1000; }
683 // all values in microseconds (us)
684 u64 last_time, busy_time, sleep_time;
688 /* The reason the following structs are not anonymous structs within the
689 * class is that they are not used by the majority of member functions and
690 * many functions that do require objects of thse types do not modify them
691 * (so they can be passed as a const qualified parameter)
697 PointedThing pointed_old;
700 bool btn_down_for_dig;
702 bool digging_blocked;
703 bool reset_jump_timer;
704 float nodig_delay_timer;
706 float dig_time_complete;
707 float repeat_place_timer;
708 float object_hit_delay_timer;
709 float time_from_last_punch;
710 ClientActiveObject *selected_object;
712 float jump_timer_up; // from key up until key down
713 float jump_timer_down; // since last key down
714 float jump_timer_down_before; // from key down until key down again
717 float update_draw_list_timer;
718 float touch_blocks_timer;
722 v3f update_draw_list_last_cam_dir;
724 float time_of_day_smooth;
729 struct ClientEventHandler
731 void (Game::*handler)(ClientEvent *, CameraOrientation *);
734 /****************************************************************************
736 ****************************************************************************/
738 using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
740 /* This is not intended to be a public class. If a public class becomes
741 * desirable then it may be better to create another 'wrapper' class that
742 * hides most of the stuff in this class (nothing in this class is required
743 * by any other file) but exposes the public methods/data only.
750 bool startup(bool *kill,
752 RenderingEngine *rendering_engine,
753 const GameStartData &game_params,
754 std::string &error_message,
756 ChatBackend *chat_backend);
763 // Basic initialisation
764 bool init(const std::string &map_dir, const std::string &address,
765 u16 port, const SubgameSpec &gamespec);
767 bool createSingleplayerServer(const std::string &map_dir,
768 const SubgameSpec &gamespec, u16 port);
771 bool createClient(const GameStartData &start_data);
775 bool connectToServer(const GameStartData &start_data,
776 bool *connect_ok, bool *aborted);
777 bool getServerContent(bool *aborted);
781 void updateInteractTimers(f32 dtime);
782 bool checkConnection();
783 bool handleCallbacks();
784 void processQueues();
785 void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
786 void updateDebugState();
787 void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
788 void updateProfilerGraphs(ProfilerGraph *graph);
791 void processUserInput(f32 dtime);
792 void processKeyInput();
793 void processItemSelection(u16 *new_playeritem);
795 void dropSelectedItem(bool single_item = false);
796 void openInventory();
797 void openConsole(float scale, const wchar_t *line=NULL);
798 void toggleFreeMove();
799 void toggleFreeMoveAlt();
800 void togglePitchMove();
803 void toggleCinematic();
804 void toggleBlockBounds();
805 void toggleAutoforward();
807 void toggleMinimap(bool shift_pressed);
810 void toggleUpdateCamera();
812 void increaseViewRange();
813 void decreaseViewRange();
814 void toggleFullViewRange();
815 void checkZoomEnabled();
817 void updateCameraDirection(CameraOrientation *cam, float dtime);
818 void updateCameraOrientation(CameraOrientation *cam, float dtime);
819 void updatePlayerControl(const CameraOrientation &cam);
820 void step(f32 dtime);
821 void processClientEvents(CameraOrientation *cam);
822 void updateCamera(f32 dtime);
823 void updateSound(f32 dtime);
824 void processPlayerInteraction(f32 dtime, bool show_hud);
826 * Returns the object or node the player is pointing at.
827 * Also updates the selected thing in the Hud.
829 * @param[in] shootline the shootline, starting from
830 * the camera position. This also gives the maximal distance
832 * @param[in] liquids_pointable if false, liquids are ignored
833 * @param[in] look_for_object if false, objects are ignored
834 * @param[in] camera_offset offset of the camera
835 * @param[out] selected_object the selected object or
838 PointedThing updatePointedThing(
839 const core::line3d<f32> &shootline, bool liquids_pointable,
840 bool look_for_object, const v3s16 &camera_offset);
841 void handlePointingAtNothing(const ItemStack &playerItem);
842 void handlePointingAtNode(const PointedThing &pointed,
843 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
844 void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
845 const v3f &player_position, bool show_debug);
846 void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
847 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
848 void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
849 const CameraOrientation &cam);
850 void updateShadows();
853 void showOverlayMessage(const char *msg, float dtime, int percent,
854 bool draw_clouds = true);
856 static void settingChangedCallback(const std::string &setting_name, void *data);
859 inline bool isKeyDown(GameKeyType k)
861 return input->isKeyDown(k);
863 inline bool wasKeyDown(GameKeyType k)
865 return input->wasKeyDown(k);
867 inline bool wasKeyPressed(GameKeyType k)
869 return input->wasKeyPressed(k);
871 inline bool wasKeyReleased(GameKeyType k)
873 return input->wasKeyReleased(k);
877 void handleAndroidChatInput();
882 bool force_fog_off = false;
883 bool disable_camera_update = false;
886 void showDeathFormspec();
887 void showPauseMenu();
889 void pauseAnimation();
890 void resumeAnimation();
892 // ClientEvent handlers
893 void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
894 void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
895 void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
896 void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
897 void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
898 void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
899 void handleClientEvent_HandleParticleEvent(ClientEvent *event,
900 CameraOrientation *cam);
901 void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
902 void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
903 void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
904 void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
905 void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
906 void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
907 void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
908 void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
909 CameraOrientation *cam);
910 void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
912 void updateChat(f32 dtime);
914 bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
915 const v3s16 &nodepos, const v3s16 &neighborpos, const PointedThing &pointed,
916 const NodeMetadata *meta);
917 static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
919 f32 getSensitivityScaleFactor() const;
921 InputHandler *input = nullptr;
923 Client *client = nullptr;
924 Server *server = nullptr;
926 IWritableTextureSource *texture_src = nullptr;
927 IWritableShaderSource *shader_src = nullptr;
929 // When created, these will be filled with data received from the server
930 IWritableItemDefManager *itemdef_manager = nullptr;
931 NodeDefManager *nodedef_manager = nullptr;
933 GameOnDemandSoundFetcher soundfetcher; // useful when testing
934 ISoundManager *sound = nullptr;
935 bool sound_is_dummy = false;
936 SoundMaker *soundmaker = nullptr;
938 ChatBackend *chat_backend = nullptr;
939 LogOutputBuffer m_chat_log_buf;
941 EventManager *eventmgr = nullptr;
942 QuicktuneShortcutter *quicktune = nullptr;
944 std::unique_ptr<GameUI> m_game_ui;
945 GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
946 MapDrawControl *draw_control = nullptr;
947 Camera *camera = nullptr;
948 Clouds *clouds = nullptr; // Free using ->Drop()
949 Sky *sky = nullptr; // Free using ->Drop()
951 Minimap *mapper = nullptr;
953 // Map server hud ids to client hud ids
954 std::unordered_map<u32, u32> m_hud_server_to_client;
960 This class does take ownership/responsibily for cleaning up etc of any of
961 these items (e.g. device)
963 IrrlichtDevice *device;
964 RenderingEngine *m_rendering_engine;
965 video::IVideoDriver *driver;
966 scene::ISceneManager *smgr;
968 std::string *error_message;
969 bool *reconnect_requested;
970 scene::ISceneNode *skybox;
971 PausedNodesList paused_animated_nodes;
973 bool simple_singleplayer_mode;
976 /* Pre-calculated values
978 int crack_animation_length;
980 IntervalLimiter profiler_interval;
983 * TODO: Local caching of settings is not optimal and should at some stage
984 * be updated to use a global settings object for getting thse values
985 * (as opposed to the this local caching). This can be addressed in
988 bool m_cache_doubletap_jump;
989 bool m_cache_enable_clouds;
990 bool m_cache_enable_joysticks;
991 bool m_cache_enable_particles;
992 bool m_cache_enable_fog;
993 bool m_cache_enable_noclip;
994 bool m_cache_enable_free_move;
995 f32 m_cache_mouse_sensitivity;
996 f32 m_cache_joystick_frustum_sensitivity;
997 f32 m_repeat_place_time;
998 f32 m_cache_cam_smoothing;
999 f32 m_cache_fog_start;
1001 bool m_invert_mouse = false;
1002 bool m_first_loop_after_window_activation = false;
1003 bool m_camera_offset_changed = false;
1005 bool m_does_lost_focus_pause_game = false;
1007 // if true, (almost) the whole game is paused
1008 // this happens in pause menu in singleplayer
1009 bool m_is_paused = false;
1011 #if IRRLICHT_VERSION_MT_REVISION < 5
1012 int m_reset_HW_buffer_counter = 0;
1015 #ifdef HAVE_TOUCHSCREENGUI
1016 bool m_cache_hold_aux1;
1017 bool m_touch_use_crosshair;
1018 inline bool isNoCrosshairAllowed() {
1019 return !m_touch_use_crosshair && camera->getCameraMode() == CAMERA_MODE_FIRST;
1023 bool m_android_chat_open;
1028 m_chat_log_buf(g_logger),
1029 m_game_ui(new GameUI())
1031 g_settings->registerChangedCallback("doubletap_jump",
1032 &settingChangedCallback, this);
1033 g_settings->registerChangedCallback("enable_clouds",
1034 &settingChangedCallback, this);
1035 g_settings->registerChangedCallback("doubletap_joysticks",
1036 &settingChangedCallback, this);
1037 g_settings->registerChangedCallback("enable_particles",
1038 &settingChangedCallback, this);
1039 g_settings->registerChangedCallback("enable_fog",
1040 &settingChangedCallback, this);
1041 g_settings->registerChangedCallback("mouse_sensitivity",
1042 &settingChangedCallback, this);
1043 g_settings->registerChangedCallback("joystick_frustum_sensitivity",
1044 &settingChangedCallback, this);
1045 g_settings->registerChangedCallback("repeat_place_time",
1046 &settingChangedCallback, this);
1047 g_settings->registerChangedCallback("noclip",
1048 &settingChangedCallback, this);
1049 g_settings->registerChangedCallback("free_move",
1050 &settingChangedCallback, this);
1051 g_settings->registerChangedCallback("cinematic",
1052 &settingChangedCallback, this);
1053 g_settings->registerChangedCallback("cinematic_camera_smoothing",
1054 &settingChangedCallback, this);
1055 g_settings->registerChangedCallback("camera_smoothing",
1056 &settingChangedCallback, this);
1060 #ifdef HAVE_TOUCHSCREENGUI
1061 m_cache_hold_aux1 = false; // This is initialised properly later
1068 /****************************************************************************
1070 ****************************************************************************/
1076 if (!sound_is_dummy)
1079 delete server; // deleted first to stop all server threads
1087 delete nodedef_manager;
1088 delete itemdef_manager;
1089 delete draw_control;
1091 clearTextureNameCache();
1093 g_settings->deregisterChangedCallback("doubletap_jump",
1094 &settingChangedCallback, this);
1095 g_settings->deregisterChangedCallback("enable_clouds",
1096 &settingChangedCallback, this);
1097 g_settings->deregisterChangedCallback("enable_particles",
1098 &settingChangedCallback, this);
1099 g_settings->deregisterChangedCallback("enable_fog",
1100 &settingChangedCallback, this);
1101 g_settings->deregisterChangedCallback("mouse_sensitivity",
1102 &settingChangedCallback, this);
1103 g_settings->deregisterChangedCallback("repeat_place_time",
1104 &settingChangedCallback, this);
1105 g_settings->deregisterChangedCallback("noclip",
1106 &settingChangedCallback, this);
1107 g_settings->deregisterChangedCallback("free_move",
1108 &settingChangedCallback, this);
1109 g_settings->deregisterChangedCallback("cinematic",
1110 &settingChangedCallback, this);
1111 g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
1112 &settingChangedCallback, this);
1113 g_settings->deregisterChangedCallback("camera_smoothing",
1114 &settingChangedCallback, this);
1117 bool Game::startup(bool *kill,
1118 InputHandler *input,
1119 RenderingEngine *rendering_engine,
1120 const GameStartData &start_data,
1121 std::string &error_message,
1123 ChatBackend *chat_backend)
1127 m_rendering_engine = rendering_engine;
1128 device = m_rendering_engine->get_raw_device();
1130 this->error_message = &error_message;
1131 reconnect_requested = reconnect;
1132 this->input = input;
1133 this->chat_backend = chat_backend;
1134 simple_singleplayer_mode = start_data.isSinglePlayer();
1136 input->keycache.populate();
1138 driver = device->getVideoDriver();
1139 smgr = m_rendering_engine->get_scene_manager();
1141 smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1144 runData = GameRunData();
1145 runData.time_from_last_punch = 10.0;
1147 m_game_ui->initFlags();
1149 m_invert_mouse = g_settings->getBool("invert_mouse");
1150 m_first_loop_after_window_activation = true;
1152 #ifdef HAVE_TOUCHSCREENGUI
1153 m_touch_use_crosshair = g_settings->getBool("touch_use_crosshair");
1156 g_client_translations->clear();
1158 // address can change if simple_singleplayer_mode
1159 if (!init(start_data.world_spec.path, start_data.address,
1160 start_data.socket_port, start_data.game_spec))
1163 if (!createClient(start_data))
1166 m_rendering_engine->initialize(client, hud);
1174 ProfilerGraph graph;
1175 RunStats stats = {};
1176 CameraOrientation cam_view_target = {};
1177 CameraOrientation cam_view = {};
1178 FpsControl draw_times;
1179 f32 dtime; // in seconds
1181 /* Clear the profiler */
1182 Profiler::GraphValues dummyvalues;
1183 g_profiler->graphGet(dummyvalues);
1187 set_light_table(g_settings->getFloat("display_gamma"));
1189 #ifdef HAVE_TOUCHSCREENGUI
1190 m_cache_hold_aux1 = g_settings->getBool("fast_move")
1191 && client->checkPrivilege("fast");
1194 irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1195 g_settings->getU16("screen_h"));
1197 while (m_rendering_engine->run()
1198 && !(*kill || g_gamecallback->shutdown_requested
1199 || (server && server->isShutdownRequested()))) {
1201 const irr::core::dimension2d<u32> ¤t_screen_size =
1202 m_rendering_engine->get_video_driver()->getScreenSize();
1203 // Verify if window size has changed and save it if it's the case
1204 // Ensure evaluating settings->getBool after verifying screensize
1205 // First condition is cheaper
1206 if (previous_screen_size != current_screen_size &&
1207 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1208 g_settings->getBool("autosave_screensize")) {
1209 g_settings->setU16("screen_w", current_screen_size.Width);
1210 g_settings->setU16("screen_h", current_screen_size.Height);
1211 previous_screen_size = current_screen_size;
1214 // Calculate dtime =
1215 // m_rendering_engine->run() from this iteration
1216 // + Sleep time until the wanted FPS are reached
1217 draw_times.limit(device, &dtime);
1219 // Prepare render data for next iteration
1221 updateStats(&stats, draw_times, dtime);
1222 updateInteractTimers(dtime);
1224 if (!checkConnection())
1226 if (!handleCallbacks())
1231 m_game_ui->clearInfoText();
1233 updateProfilers(stats, draw_times, dtime);
1234 processUserInput(dtime);
1235 // Update camera before player movement to avoid camera lag of one frame
1236 updateCameraDirection(&cam_view_target, dtime);
1237 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1238 cam_view.camera_yaw) * m_cache_cam_smoothing;
1239 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1240 cam_view.camera_pitch) * m_cache_cam_smoothing;
1241 updatePlayerControl(cam_view);
1244 bool was_paused = m_is_paused;
1245 m_is_paused = simple_singleplayer_mode && g_menumgr.pausesGame();
1249 if (!was_paused && m_is_paused)
1251 else if (was_paused && !m_is_paused)
1257 processClientEvents(&cam_view_target);
1259 updateCamera(dtime);
1261 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
1262 updateFrame(&graph, &stats, dtime, cam_view);
1263 updateProfilerGraphs(&graph);
1265 // Update if minimap has been disabled by the server
1266 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1268 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1275 void Game::shutdown()
1277 m_rendering_engine->finalize();
1279 auto formspec = m_game_ui->getFormspecGUI();
1281 formspec->quitMenu();
1283 #ifdef HAVE_TOUCHSCREENGUI
1284 g_touchscreengui->hide();
1287 showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1292 if (gui_chat_console)
1293 gui_chat_console->drop();
1299 while (g_menumgr.menuCount() > 0) {
1300 g_menumgr.m_stack.front()->setVisible(false);
1301 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1304 m_game_ui->deleteFormspec();
1306 chat_backend->addMessage(L"", L"# Disconnected.");
1307 chat_backend->addMessage(L"", L"");
1308 m_chat_log_buf.clear();
1312 while (!client->isShutdown()) {
1313 assert(texture_src != NULL);
1314 assert(shader_src != NULL);
1315 texture_src->processQueue();
1316 shader_src->processQueue();
1323 /****************************************************************************/
1324 /****************************************************************************
1326 ****************************************************************************/
1327 /****************************************************************************/
1330 const std::string &map_dir,
1331 const std::string &address,
1333 const SubgameSpec &gamespec)
1335 texture_src = createTextureSource();
1337 showOverlayMessage(N_("Loading..."), 0, 0);
1339 shader_src = createShaderSource();
1341 itemdef_manager = createItemDefManager();
1342 nodedef_manager = createNodeDefManager();
1344 eventmgr = new EventManager();
1345 quicktune = new QuicktuneShortcutter();
1347 if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1348 && eventmgr && quicktune))
1354 // Create a server if not connecting to an existing one
1355 if (address.empty()) {
1356 if (!createSingleplayerServer(map_dir, gamespec, port))
1363 bool Game::initSound()
1366 if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1367 infostream << "Attempting to use OpenAL audio" << std::endl;
1368 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1370 infostream << "Failed to initialize OpenAL audio" << std::endl;
1372 infostream << "Sound disabled." << std::endl;
1376 infostream << "Using dummy audio." << std::endl;
1377 sound = &dummySoundManager;
1378 sound_is_dummy = true;
1381 soundmaker = new SoundMaker(sound, nodedef_manager);
1385 soundmaker->registerReceiver(eventmgr);
1390 bool Game::createSingleplayerServer(const std::string &map_dir,
1391 const SubgameSpec &gamespec, u16 port)
1393 showOverlayMessage(N_("Creating server..."), 0, 5);
1395 std::string bind_str = g_settings->get("bind_address");
1396 Address bind_addr(0, 0, 0, 0, port);
1398 if (g_settings->getBool("ipv6_server")) {
1399 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1403 bind_addr.Resolve(bind_str.c_str());
1404 } catch (ResolveError &e) {
1405 infostream << "Resolving bind address \"" << bind_str
1406 << "\" failed: " << e.what()
1407 << " -- Listening on all addresses." << std::endl;
1410 if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1411 *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
1412 bind_addr.serializeString().c_str());
1413 errorstream << *error_message << std::endl;
1417 server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1418 false, nullptr, error_message);
1424 bool Game::createClient(const GameStartData &start_data)
1426 showOverlayMessage(N_("Creating client..."), 0, 10);
1428 draw_control = new MapDrawControl();
1432 bool could_connect, connect_aborted;
1433 #ifdef HAVE_TOUCHSCREENGUI
1434 if (g_touchscreengui) {
1435 g_touchscreengui->init(texture_src);
1436 g_touchscreengui->hide();
1439 if (!connectToServer(start_data, &could_connect, &connect_aborted))
1442 if (!could_connect) {
1443 if (error_message->empty() && !connect_aborted) {
1444 // Should not happen if error messages are set properly
1445 *error_message = gettext("Connection failed for unknown reason");
1446 errorstream << *error_message << std::endl;
1451 if (!getServerContent(&connect_aborted)) {
1452 if (error_message->empty() && !connect_aborted) {
1453 // Should not happen if error messages are set properly
1454 *error_message = gettext("Connection failed for unknown reason");
1455 errorstream << *error_message << std::endl;
1460 auto *scsf = new GameGlobalShaderConstantSetterFactory(
1461 &m_flags.force_fog_off, &runData.fog_range, client);
1462 shader_src->addShaderConstantSetterFactory(scsf);
1464 // Update cached textures, meshes and materials
1465 client->afterContentReceived();
1469 camera = new Camera(*draw_control, client, m_rendering_engine);
1470 if (client->modsLoaded())
1471 client->getScript()->on_camera_ready(camera);
1472 client->setCamera(camera);
1473 #ifdef HAVE_TOUCHSCREENGUI
1474 if (g_touchscreengui) {
1475 g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed());
1481 if (m_cache_enable_clouds)
1482 clouds = new Clouds(smgr, -1, time(0));
1486 sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
1488 skybox = NULL; // This is used/set later on in the main run loop
1490 /* Pre-calculated values
1492 video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1494 v2u32 size = t->getOriginalSize();
1495 crack_animation_length = size.Y / size.X;
1497 crack_animation_length = 5;
1503 /* Set window caption
1505 std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1507 str += utf8_to_wide(g_version_hash);
1509 const wchar_t *text = nullptr;
1510 if (simple_singleplayer_mode)
1511 text = wgettext("Singleplayer");
1513 text = wgettext("Multiplayer");
1520 str += driver->getName();
1523 device->setWindowCaption(str.c_str());
1525 LocalPlayer *player = client->getEnv().getLocalPlayer();
1526 player->hurt_tilt_timer = 0;
1527 player->hurt_tilt_strength = 0;
1529 hud = new Hud(client, player, &player->inventory);
1531 mapper = client->getMinimap();
1533 if (mapper && client->modsLoaded())
1534 client->getScript()->on_minimap_ready(mapper);
1539 bool Game::initGui()
1543 // Remove stale "recent" chat messages from previous connections
1544 chat_backend->clearRecentChat();
1546 // Make sure the size of the recent messages buffer is right
1547 chat_backend->applySettings();
1549 // Chat backend and console
1550 gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1551 -1, chat_backend, client, &g_menumgr);
1553 #ifdef HAVE_TOUCHSCREENGUI
1555 if (g_touchscreengui)
1556 g_touchscreengui->show();
1563 bool Game::connectToServer(const GameStartData &start_data,
1564 bool *connect_ok, bool *connection_aborted)
1566 *connect_ok = false; // Let's not be overly optimistic
1567 *connection_aborted = false;
1568 bool local_server_mode = false;
1570 showOverlayMessage(N_("Resolving address..."), 0, 15);
1572 Address connect_address(0, 0, 0, 0, start_data.socket_port);
1575 connect_address.Resolve(start_data.address.c_str());
1577 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1578 if (connect_address.isIPv6()) {
1579 IPv6AddressBytes addr_bytes;
1580 addr_bytes.bytes[15] = 1;
1581 connect_address.setAddress(&addr_bytes);
1583 connect_address.setAddress(127, 0, 0, 1);
1585 local_server_mode = true;
1587 } catch (ResolveError &e) {
1588 *error_message = fmtgettext("Couldn't resolve address: %s", e.what());
1590 errorstream << *error_message << std::endl;
1594 if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1595 *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
1596 errorstream << *error_message << std::endl;
1601 client = new Client(start_data.name.c_str(),
1602 start_data.password, start_data.address,
1603 *draw_control, texture_src, shader_src,
1604 itemdef_manager, nodedef_manager, sound, eventmgr,
1605 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get(),
1606 start_data.allow_login_or_register);
1607 client->migrateModStorage();
1608 } catch (const BaseException &e) {
1609 *error_message = fmtgettext("Error creating client: %s", e.what());
1610 errorstream << *error_message << std::endl;
1614 client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1616 infostream << "Connecting to server at ";
1617 connect_address.print(infostream);
1618 infostream << std::endl;
1620 client->connect(connect_address,
1621 simple_singleplayer_mode || local_server_mode);
1624 Wait for server to accept connection
1630 FpsControl fps_control;
1632 f32 wait_time = 0; // in seconds
1634 fps_control.reset();
1636 while (m_rendering_engine->run()) {
1638 fps_control.limit(device, &dtime);
1640 // Update client and server
1641 client->step(dtime);
1644 server->step(dtime);
1647 if (client->getState() == LC_Init) {
1653 if (*connection_aborted)
1656 if (client->accessDenied()) {
1657 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1658 *reconnect_requested = client->reconnectRequested();
1659 errorstream << *error_message << std::endl;
1663 if (input->cancelPressed()) {
1664 *connection_aborted = true;
1665 infostream << "Connect aborted [Escape]" << std::endl;
1670 // Only time out if we aren't waiting for the server we started
1671 if (!start_data.address.empty() && wait_time > 10) {
1672 *error_message = gettext("Connection timed out.");
1673 errorstream << *error_message << std::endl;
1678 showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1680 } catch (con::PeerNotFoundException &e) {
1681 // TODO: Should something be done here? At least an info/error
1689 bool Game::getServerContent(bool *aborted)
1693 FpsControl fps_control;
1694 f32 dtime; // in seconds
1696 fps_control.reset();
1698 while (m_rendering_engine->run()) {
1700 fps_control.limit(device, &dtime);
1702 // Update client and server
1703 client->step(dtime);
1706 server->step(dtime);
1709 if (client->mediaReceived() && client->itemdefReceived() &&
1710 client->nodedefReceived()) {
1715 if (!checkConnection())
1718 if (client->getState() < LC_Init) {
1719 *error_message = gettext("Client disconnected");
1720 errorstream << *error_message << std::endl;
1724 if (input->cancelPressed()) {
1726 infostream << "Connect aborted [Escape]" << std::endl;
1733 if (!client->itemdefReceived()) {
1734 const wchar_t *text = wgettext("Item definitions...");
1736 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1739 } else if (!client->nodedefReceived()) {
1740 const wchar_t *text = wgettext("Node definitions...");
1742 m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1746 std::ostringstream message;
1747 std::fixed(message);
1748 message.precision(0);
1749 float receive = client->mediaReceiveProgress() * 100;
1750 message << gettext("Media...");
1752 message << " " << receive << "%";
1753 message.precision(2);
1755 if ((USE_CURL == 0) ||
1756 (!g_settings->getBool("enable_remote_media_server"))) {
1757 float cur = client->getCurRate();
1758 std::string cur_unit = gettext("KiB/s");
1762 cur_unit = gettext("MiB/s");
1765 message << " (" << cur << ' ' << cur_unit << ")";
1768 progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1769 m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
1770 texture_src, dtime, progress);
1778 /****************************************************************************/
1779 /****************************************************************************
1781 ****************************************************************************/
1782 /****************************************************************************/
1784 inline void Game::updateInteractTimers(f32 dtime)
1786 if (runData.nodig_delay_timer >= 0)
1787 runData.nodig_delay_timer -= dtime;
1789 if (runData.object_hit_delay_timer >= 0)
1790 runData.object_hit_delay_timer -= dtime;
1792 runData.time_from_last_punch += dtime;
1796 /* returns false if game should exit, otherwise true
1798 inline bool Game::checkConnection()
1800 if (client->accessDenied()) {
1801 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1802 *reconnect_requested = client->reconnectRequested();
1803 errorstream << *error_message << std::endl;
1811 /* returns false if game should exit, otherwise true
1813 inline bool Game::handleCallbacks()
1815 if (g_gamecallback->disconnect_requested) {
1816 g_gamecallback->disconnect_requested = false;
1820 if (g_gamecallback->changepassword_requested) {
1821 (new GUIPasswordChange(guienv, guiroot, -1,
1822 &g_menumgr, client, texture_src))->drop();
1823 g_gamecallback->changepassword_requested = false;
1826 if (g_gamecallback->changevolume_requested) {
1827 (new GUIVolumeChange(guienv, guiroot, -1,
1828 &g_menumgr, texture_src))->drop();
1829 g_gamecallback->changevolume_requested = false;
1832 if (g_gamecallback->keyconfig_requested) {
1833 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1834 &g_menumgr, texture_src))->drop();
1835 g_gamecallback->keyconfig_requested = false;
1838 if (g_gamecallback->keyconfig_changed) {
1839 input->keycache.populate(); // update the cache with new settings
1840 g_gamecallback->keyconfig_changed = false;
1847 void Game::processQueues()
1849 texture_src->processQueue();
1850 itemdef_manager->processQueue(client);
1851 shader_src->processQueue();
1854 void Game::updateDebugState()
1856 LocalPlayer *player = client->getEnv().getLocalPlayer();
1858 // debug UI and wireframe
1859 bool has_debug = client->checkPrivilege("debug");
1860 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
1862 if (m_game_ui->m_flags.show_basic_debug) {
1863 if (!has_basic_debug)
1864 m_game_ui->m_flags.show_basic_debug = false;
1865 } else if (m_game_ui->m_flags.show_minimal_debug) {
1866 if (has_basic_debug)
1867 m_game_ui->m_flags.show_basic_debug = true;
1869 if (!has_basic_debug)
1870 hud->disableBlockBounds();
1872 draw_control->show_wireframe = false;
1875 draw_control->allow_noclip = m_cache_enable_noclip && client->checkPrivilege("noclip");
1878 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1881 float profiler_print_interval =
1882 g_settings->getFloat("profiler_print_interval");
1883 bool print_to_log = true;
1885 if (profiler_print_interval == 0) {
1886 print_to_log = false;
1887 profiler_print_interval = 3;
1890 if (profiler_interval.step(dtime, profiler_print_interval)) {
1892 infostream << "Profiler:" << std::endl;
1893 g_profiler->print(infostream);
1896 m_game_ui->updateProfiler();
1897 g_profiler->clear();
1900 // Update update graphs
1901 g_profiler->graphAdd("Time non-rendering [us]",
1902 draw_times.busy_time - stats.drawtime);
1904 g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
1905 g_profiler->graphAdd("FPS", 1.0f / dtime);
1908 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1915 /* Time average and jitter calculation
1917 jp = &stats->dtime_jitter;
1918 jp->avg = jp->avg * 0.96 + dtime * 0.04;
1920 jitter = dtime - jp->avg;
1922 if (jitter > jp->max)
1925 jp->counter += dtime;
1927 if (jp->counter > 0.0) {
1929 jp->max_sample = jp->max;
1930 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1934 /* Busytime average and jitter calculation
1936 jp = &stats->busy_time_jitter;
1937 jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
1939 jitter = draw_times.getBusyMs() - jp->avg;
1941 if (jitter > jp->max)
1943 if (jitter < jp->min)
1946 jp->counter += dtime;
1948 if (jp->counter > 0.0) {
1950 jp->max_sample = jp->max;
1951 jp->min_sample = jp->min;
1959 /****************************************************************************
1961 ****************************************************************************/
1963 void Game::processUserInput(f32 dtime)
1965 // Reset input if window not active or some menu is active
1966 if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1968 #ifdef HAVE_TOUCHSCREENGUI
1969 g_touchscreengui->hide();
1972 #ifdef HAVE_TOUCHSCREENGUI
1973 else if (g_touchscreengui) {
1974 /* on touchscreengui step may generate own input events which ain't
1975 * what we want in case we just did clear them */
1976 g_touchscreengui->show();
1977 g_touchscreengui->step(dtime);
1981 if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1982 gui_chat_console->closeConsoleAtOnce();
1985 // Input handler step() (used by the random input generator)
1989 auto formspec = m_game_ui->getFormspecGUI();
1991 formspec->getAndroidUIInput();
1993 handleAndroidChatInput();
1996 // Increase timer for double tap of "keymap_jump"
1997 if (m_cache_doubletap_jump && runData.jump_timer_up <= 0.2f)
1998 runData.jump_timer_up += dtime;
1999 if (m_cache_doubletap_jump && runData.jump_timer_down <= 0.4f)
2000 runData.jump_timer_down += dtime;
2003 processItemSelection(&runData.new_playeritem);
2007 void Game::processKeyInput()
2009 if (wasKeyDown(KeyType::DROP)) {
2010 dropSelectedItem(isKeyDown(KeyType::SNEAK));
2011 } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
2012 toggleAutoforward();
2013 } else if (wasKeyDown(KeyType::BACKWARD)) {
2014 if (g_settings->getBool("continuous_forward"))
2015 toggleAutoforward();
2016 } else if (wasKeyDown(KeyType::INVENTORY)) {
2018 } else if (input->cancelPressed()) {
2020 m_android_chat_open = false;
2022 if (!gui_chat_console->isOpenInhibited()) {
2025 } else if (wasKeyDown(KeyType::CHAT)) {
2026 openConsole(0.2, L"");
2027 } else if (wasKeyDown(KeyType::CMD)) {
2028 openConsole(0.2, L"/");
2029 } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
2030 if (client->modsLoaded())
2031 openConsole(0.2, L".");
2033 m_game_ui->showTranslatedStatusText("Client side scripting is disabled");
2034 } else if (wasKeyDown(KeyType::CONSOLE)) {
2035 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
2036 } else if (wasKeyDown(KeyType::FREEMOVE)) {
2038 } else if (wasKeyDown(KeyType::JUMP)) {
2039 toggleFreeMoveAlt();
2040 } else if (wasKeyDown(KeyType::PITCHMOVE)) {
2042 } else if (wasKeyDown(KeyType::FASTMOVE)) {
2044 } else if (wasKeyDown(KeyType::NOCLIP)) {
2047 } else if (wasKeyDown(KeyType::MUTE)) {
2048 if (g_settings->getBool("enable_sound")) {
2049 bool new_mute_sound = !g_settings->getBool("mute_sound");
2050 g_settings->setBool("mute_sound", new_mute_sound);
2052 m_game_ui->showTranslatedStatusText("Sound muted");
2054 m_game_ui->showTranslatedStatusText("Sound unmuted");
2056 m_game_ui->showTranslatedStatusText("Sound system is disabled");
2058 } else if (wasKeyDown(KeyType::INC_VOLUME)) {
2059 if (g_settings->getBool("enable_sound")) {
2060 float new_volume = g_settings->getFloat("sound_volume", 0.0f, 0.9f) + 0.1f;
2061 g_settings->setFloat("sound_volume", new_volume);
2062 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
2063 m_game_ui->showStatusText(msg);
2065 m_game_ui->showTranslatedStatusText("Sound system is disabled");
2067 } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
2068 if (g_settings->getBool("enable_sound")) {
2069 float new_volume = g_settings->getFloat("sound_volume", 0.1f, 1.0f) - 0.1f;
2070 g_settings->setFloat("sound_volume", new_volume);
2071 std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
2072 m_game_ui->showStatusText(msg);
2074 m_game_ui->showTranslatedStatusText("Sound system is disabled");
2077 } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
2078 || wasKeyDown(KeyType::DEC_VOLUME)) {
2079 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
2081 } else if (wasKeyDown(KeyType::CINEMATIC)) {
2083 } else if (wasKeyDown(KeyType::SCREENSHOT)) {
2084 client->makeScreenshot();
2085 } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
2086 toggleBlockBounds();
2087 } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
2088 m_game_ui->toggleHud();
2089 } else if (wasKeyDown(KeyType::MINIMAP)) {
2090 toggleMinimap(isKeyDown(KeyType::SNEAK));
2091 } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
2092 m_game_ui->toggleChat();
2093 } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
2095 } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
2096 toggleUpdateCamera();
2097 } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
2099 } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
2100 m_game_ui->toggleProfiler();
2101 } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
2102 increaseViewRange();
2103 } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
2104 decreaseViewRange();
2105 } else if (wasKeyDown(KeyType::RANGESELECT)) {
2106 toggleFullViewRange();
2107 } else if (wasKeyDown(KeyType::ZOOM)) {
2109 } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
2111 } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
2113 } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
2115 } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
2119 if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
2120 runData.reset_jump_timer = false;
2121 runData.jump_timer_up = 0.0f;
2124 if (quicktune->hasMessage()) {
2125 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2129 void Game::processItemSelection(u16 *new_playeritem)
2131 LocalPlayer *player = client->getEnv().getLocalPlayer();
2133 /* Item selection using mouse wheel
2135 *new_playeritem = player->getWieldIndex();
2136 s32 wheel = input->getMouseWheel();
2137 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2138 player->hud_hotbar_itemcount - 1);
2142 if (wasKeyDown(KeyType::HOTBAR_NEXT))
2145 if (wasKeyDown(KeyType::HOTBAR_PREV))
2149 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2151 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2154 /* Item selection using hotbar slot keys
2156 for (u16 i = 0; i <= max_item; i++) {
2157 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2158 *new_playeritem = i;
2163 // Clamp selection again in case it wasn't changed but max_item was
2164 *new_playeritem = MYMIN(*new_playeritem, max_item);
2168 void Game::dropSelectedItem(bool single_item)
2170 IDropAction *a = new IDropAction();
2171 a->count = single_item ? 1 : 0;
2172 a->from_inv.setCurrentPlayer();
2173 a->from_list = "main";
2174 a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2175 client->inventoryAction(a);
2179 void Game::openInventory()
2182 * Don't permit to open inventory is CAO or player doesn't exists.
2183 * This prevent showing an empty inventory at player load
2186 LocalPlayer *player = client->getEnv().getLocalPlayer();
2187 if (!player || !player->getCAO())
2190 infostream << "Game: Launching inventory" << std::endl;
2192 PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2194 InventoryLocation inventoryloc;
2195 inventoryloc.setCurrentPlayer();
2197 if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2202 if (fs_src->getForm().empty()) {
2207 TextDest *txt_dst = new TextDestPlayerInventory(client);
2208 auto *&formspec = m_game_ui->updateFormspec("");
2209 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2210 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2212 formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2216 void Game::openConsole(float scale, const wchar_t *line)
2218 assert(scale > 0.0f && scale <= 1.0f);
2221 porting::showInputDialog(gettext("ok"), "", "", 2);
2222 m_android_chat_open = true;
2224 if (gui_chat_console->isOpenInhibited())
2226 gui_chat_console->openConsole(scale);
2228 gui_chat_console->setCloseOnEnter(true);
2229 gui_chat_console->replaceAndAddToHistory(line);
2235 void Game::handleAndroidChatInput()
2237 if (m_android_chat_open && porting::getInputDialogState() == 0) {
2238 std::string text = porting::getInputDialogValue();
2239 client->typeChatMessage(utf8_to_wide(text));
2240 m_android_chat_open = false;
2246 void Game::toggleFreeMove()
2248 bool free_move = !g_settings->getBool("free_move");
2249 g_settings->set("free_move", bool_to_cstr(free_move));
2252 if (client->checkPrivilege("fly")) {
2253 m_game_ui->showTranslatedStatusText("Fly mode enabled");
2255 m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2258 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2262 void Game::toggleFreeMoveAlt()
2264 if (!runData.reset_jump_timer) {
2265 runData.jump_timer_down_before = runData.jump_timer_down;
2266 runData.jump_timer_down = 0.0f;
2269 // key down (0.2 s max.), then key up (0.2 s max.), then key down
2270 if (m_cache_doubletap_jump && runData.jump_timer_up < 0.2f &&
2271 runData.jump_timer_down_before < 0.4f) // 0.2 + 0.2
2274 runData.reset_jump_timer = true;
2278 void Game::togglePitchMove()
2280 bool pitch_move = !g_settings->getBool("pitch_move");
2281 g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2284 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2286 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2291 void Game::toggleFast()
2293 bool fast_move = !g_settings->getBool("fast_move");
2294 bool has_fast_privs = client->checkPrivilege("fast");
2295 g_settings->set("fast_move", bool_to_cstr(fast_move));
2298 if (has_fast_privs) {
2299 m_game_ui->showTranslatedStatusText("Fast mode enabled");
2301 m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2304 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2307 #ifdef HAVE_TOUCHSCREENGUI
2308 m_cache_hold_aux1 = fast_move && has_fast_privs;
2313 void Game::toggleNoClip()
2315 bool noclip = !g_settings->getBool("noclip");
2316 g_settings->set("noclip", bool_to_cstr(noclip));
2319 if (client->checkPrivilege("noclip")) {
2320 m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2322 m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2325 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2329 void Game::toggleCinematic()
2331 bool cinematic = !g_settings->getBool("cinematic");
2332 g_settings->set("cinematic", bool_to_cstr(cinematic));
2335 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2337 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2340 void Game::toggleBlockBounds()
2342 LocalPlayer *player = client->getEnv().getLocalPlayer();
2343 if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
2344 m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
2347 enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
2349 case Hud::BLOCK_BOUNDS_OFF:
2350 m_game_ui->showTranslatedStatusText("Block bounds hidden");
2352 case Hud::BLOCK_BOUNDS_CURRENT:
2353 m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
2355 case Hud::BLOCK_BOUNDS_NEAR:
2356 m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
2358 case Hud::BLOCK_BOUNDS_MAX:
2359 m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
2366 // Autoforward by toggling continuous forward.
2367 void Game::toggleAutoforward()
2369 bool autorun_enabled = !g_settings->getBool("continuous_forward");
2370 g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2372 if (autorun_enabled)
2373 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2375 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2378 void Game::toggleMinimap(bool shift_pressed)
2380 if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2384 mapper->toggleMinimapShape();
2388 // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2390 // Not so satisying code to keep compatibility with old fixed mode system
2392 u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2394 if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2395 m_game_ui->m_flags.show_minimap = false;
2398 // If radar is disabled, try to find a non radar mode or fall back to 0
2399 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2400 while (mapper->getModeIndex() &&
2401 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2404 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2408 // End of 'not so satifying code'
2409 if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2410 (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2411 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2413 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2416 void Game::toggleFog()
2418 bool fog_enabled = g_settings->getBool("enable_fog");
2419 g_settings->setBool("enable_fog", !fog_enabled);
2421 m_game_ui->showTranslatedStatusText("Fog disabled");
2423 m_game_ui->showTranslatedStatusText("Fog enabled");
2427 void Game::toggleDebug()
2429 LocalPlayer *player = client->getEnv().getLocalPlayer();
2430 bool has_debug = client->checkPrivilege("debug");
2431 bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
2432 // Initial: No debug info
2433 // 1x toggle: Debug text
2434 // 2x toggle: Debug text with profiler graph
2435 // 3x toggle: Debug text and wireframe (needs "debug" priv)
2436 // Next toggle: Back to initial
2438 // The debug text can be in 2 modes: minimal and basic.
2439 // * Minimal: Only technical client info that not gameplay-relevant
2440 // * Basic: Info that might give gameplay advantage, e.g. pos, angle
2441 // Basic mode is used when player has the debug HUD flag set,
2442 // otherwise the Minimal mode is used.
2443 if (!m_game_ui->m_flags.show_minimal_debug) {
2444 m_game_ui->m_flags.show_minimal_debug = true;
2445 if (has_basic_debug)
2446 m_game_ui->m_flags.show_basic_debug = true;
2447 m_game_ui->m_flags.show_profiler_graph = false;
2448 draw_control->show_wireframe = false;
2449 m_game_ui->showTranslatedStatusText("Debug info shown");
2450 } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2451 if (has_basic_debug)
2452 m_game_ui->m_flags.show_basic_debug = true;
2453 m_game_ui->m_flags.show_profiler_graph = true;
2454 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2455 } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2456 if (has_basic_debug)
2457 m_game_ui->m_flags.show_basic_debug = true;
2458 m_game_ui->m_flags.show_profiler_graph = false;
2459 draw_control->show_wireframe = true;
2460 m_game_ui->showTranslatedStatusText("Wireframe shown");
2462 m_game_ui->m_flags.show_minimal_debug = false;
2463 m_game_ui->m_flags.show_basic_debug = false;
2464 m_game_ui->m_flags.show_profiler_graph = false;
2465 draw_control->show_wireframe = false;
2467 m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2469 m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2475 void Game::toggleUpdateCamera()
2477 m_flags.disable_camera_update = !m_flags.disable_camera_update;
2478 if (m_flags.disable_camera_update)
2479 m_game_ui->showTranslatedStatusText("Camera update disabled");
2481 m_game_ui->showTranslatedStatusText("Camera update enabled");
2485 void Game::increaseViewRange()
2487 s16 range = g_settings->getS16("viewing_range");
2488 s16 range_new = range + 10;
2490 if (range_new > 4000) {
2492 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
2493 m_game_ui->showStatusText(msg);
2495 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2496 m_game_ui->showStatusText(msg);
2498 g_settings->set("viewing_range", itos(range_new));
2502 void Game::decreaseViewRange()
2504 s16 range = g_settings->getS16("viewing_range");
2505 s16 range_new = range - 10;
2507 if (range_new < 20) {
2509 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
2510 m_game_ui->showStatusText(msg);
2512 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2513 m_game_ui->showStatusText(msg);
2515 g_settings->set("viewing_range", itos(range_new));
2519 void Game::toggleFullViewRange()
2521 draw_control->range_all = !draw_control->range_all;
2522 if (draw_control->range_all)
2523 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2525 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2529 void Game::checkZoomEnabled()
2531 LocalPlayer *player = client->getEnv().getLocalPlayer();
2532 if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2533 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2536 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2538 #if !defined(__ANDROID__) && IRRLICHT_VERSION_MT_REVISION >= 9
2540 device->getCursorControl()->setRelativeMode(false);
2542 device->getCursorControl()->setRelativeMode(true);
2545 if ((device->isWindowActive() && device->isWindowFocused()
2546 && !isMenuActive()) || input->isRandom()) {
2549 if (!input->isRandom()) {
2550 // Mac OSX gets upset if this is set every frame
2551 if (device->getCursorControl()->isVisible())
2552 device->getCursorControl()->setVisible(false);
2556 if (m_first_loop_after_window_activation) {
2557 m_first_loop_after_window_activation = false;
2559 input->setMousePos(driver->getScreenSize().Width / 2,
2560 driver->getScreenSize().Height / 2);
2562 updateCameraOrientation(cam, dtime);
2568 // Mac OSX gets upset if this is set every frame
2569 if (!device->getCursorControl()->isVisible())
2570 device->getCursorControl()->setVisible(true);
2573 m_first_loop_after_window_activation = true;
2578 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2579 // responsiveness independently of FOV.
2580 f32 Game::getSensitivityScaleFactor() const
2582 f32 fov_y = client->getCamera()->getFovY();
2584 // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2585 // 16:9 aspect ratio to minimize disruption of existing sensitivity
2587 return tan(fov_y / 2.0f) * 1.3763818698f;
2590 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2592 #ifdef HAVE_TOUCHSCREENGUI
2593 if (g_touchscreengui) {
2594 cam->camera_yaw += g_touchscreengui->getYawChange();
2595 cam->camera_pitch = g_touchscreengui->getPitch();
2598 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2599 v2s32 dist = input->getMousePos() - center;
2601 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2605 f32 sens_scale = getSensitivityScaleFactor();
2606 cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2607 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2609 if (dist.X != 0 || dist.Y != 0)
2610 input->setMousePos(center.X, center.Y);
2611 #ifdef HAVE_TOUCHSCREENGUI
2615 if (m_cache_enable_joysticks) {
2616 f32 sens_scale = getSensitivityScaleFactor();
2617 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
2618 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2619 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2622 cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2626 void Game::updatePlayerControl(const CameraOrientation &cam)
2628 LocalPlayer *player = client->getEnv().getLocalPlayer();
2630 //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2632 PlayerControl control(
2633 isKeyDown(KeyType::FORWARD),
2634 isKeyDown(KeyType::BACKWARD),
2635 isKeyDown(KeyType::LEFT),
2636 isKeyDown(KeyType::RIGHT),
2637 isKeyDown(KeyType::JUMP) || player->getAutojump(),
2638 isKeyDown(KeyType::AUX1),
2639 isKeyDown(KeyType::SNEAK),
2640 isKeyDown(KeyType::ZOOM),
2641 isKeyDown(KeyType::DIG),
2642 isKeyDown(KeyType::PLACE),
2645 input->getMovementSpeed(),
2646 input->getMovementDirection()
2649 // autoforward if set: move at maximum speed
2650 if (player->getPlayerSettings().continuous_forward &&
2651 client->activeObjectsReceived() && !player->isDead()) {
2652 control.movement_speed = 1.0f;
2653 // sideways movement only
2654 float dx = sin(control.movement_direction);
2655 control.movement_direction = atan2(dx, 1.0f);
2658 #ifdef HAVE_TOUCHSCREENGUI
2659 /* For touch, simulate holding down AUX1 (fast move) if the user has
2660 * the fast_move setting toggled on. If there is an aux1 key defined for
2661 * touch then its meaning is inverted (i.e. holding aux1 means walk and
2664 if (m_cache_hold_aux1) {
2665 control.aux1 = control.aux1 ^ true;
2669 client->setPlayerControl(control);
2675 inline void Game::step(f32 dtime)
2678 server->step(dtime);
2680 client->step(dtime);
2683 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2686 for (auto &&child: node->getChildren())
2687 pauseNodeAnimation(paused, child);
2688 if (node->getType() != scene::ESNT_ANIMATED_MESH)
2690 auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2691 float speed = animated_node->getAnimationSpeed();
2694 paused.push_back({grab(animated_node), speed});
2695 animated_node->setAnimationSpeed(0.0f);
2698 void Game::pauseAnimation()
2700 pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2703 void Game::resumeAnimation()
2705 for (auto &&pair: paused_animated_nodes)
2706 pair.first->setAnimationSpeed(pair.second);
2707 paused_animated_nodes.clear();
2710 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2711 {&Game::handleClientEvent_None},
2712 {&Game::handleClientEvent_PlayerDamage},
2713 {&Game::handleClientEvent_PlayerForceMove},
2714 {&Game::handleClientEvent_Deathscreen},
2715 {&Game::handleClientEvent_ShowFormSpec},
2716 {&Game::handleClientEvent_ShowLocalFormSpec},
2717 {&Game::handleClientEvent_HandleParticleEvent},
2718 {&Game::handleClientEvent_HandleParticleEvent},
2719 {&Game::handleClientEvent_HandleParticleEvent},
2720 {&Game::handleClientEvent_HudAdd},
2721 {&Game::handleClientEvent_HudRemove},
2722 {&Game::handleClientEvent_HudChange},
2723 {&Game::handleClientEvent_SetSky},
2724 {&Game::handleClientEvent_SetSun},
2725 {&Game::handleClientEvent_SetMoon},
2726 {&Game::handleClientEvent_SetStars},
2727 {&Game::handleClientEvent_OverrideDayNigthRatio},
2728 {&Game::handleClientEvent_CloudParams},
2731 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2733 FATAL_ERROR("ClientEvent type None received");
2736 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2738 if (client->modsLoaded())
2739 client->getScript()->on_damage_taken(event->player_damage.amount);
2741 if (!event->player_damage.effect)
2744 // Damage flash and hurt tilt are not used at death
2745 if (client->getHP() > 0) {
2746 LocalPlayer *player = client->getEnv().getLocalPlayer();
2748 f32 hp_max = player->getCAO() ?
2749 player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2750 f32 damage_ratio = event->player_damage.amount / hp_max;
2752 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2753 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2755 player->hurt_tilt_timer = 1.5f;
2756 player->hurt_tilt_strength =
2757 rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2760 // Play damage sound
2761 client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2764 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2766 cam->camera_yaw = event->player_force_move.yaw;
2767 cam->camera_pitch = event->player_force_move.pitch;
2770 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2772 // If client scripting is enabled, deathscreen is handled by CSM code in
2773 // builtin/client/init.lua
2774 if (client->modsLoaded())
2775 client->getScript()->on_death();
2777 showDeathFormspec();
2779 /* Handle visualization */
2780 LocalPlayer *player = client->getEnv().getLocalPlayer();
2781 runData.damage_flash = 0;
2782 player->hurt_tilt_timer = 0;
2783 player->hurt_tilt_strength = 0;
2786 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2788 if (event->show_formspec.formspec->empty()) {
2789 auto formspec = m_game_ui->getFormspecGUI();
2790 if (formspec && (event->show_formspec.formname->empty()
2791 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2792 formspec->quitMenu();
2795 FormspecFormSource *fs_src =
2796 new FormspecFormSource(*(event->show_formspec.formspec));
2797 TextDestPlayerInventory *txt_dst =
2798 new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2800 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2801 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2802 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2805 delete event->show_formspec.formspec;
2806 delete event->show_formspec.formname;
2809 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2811 FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2812 LocalFormspecHandler *txt_dst =
2813 new LocalFormspecHandler(*event->show_formspec.formname, client);
2814 GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2815 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2817 delete event->show_formspec.formspec;
2818 delete event->show_formspec.formname;
2821 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2822 CameraOrientation *cam)
2824 LocalPlayer *player = client->getEnv().getLocalPlayer();
2825 client->getParticleManager()->handleParticleEvent(event, client, player);
2828 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2830 LocalPlayer *player = client->getEnv().getLocalPlayer();
2832 u32 server_id = event->hudadd->server_id;
2833 // ignore if we already have a HUD with that ID
2834 auto i = m_hud_server_to_client.find(server_id);
2835 if (i != m_hud_server_to_client.end()) {
2836 delete event->hudadd;
2840 HudElement *e = new HudElement;
2841 e->type = static_cast<HudElementType>(event->hudadd->type);
2842 e->pos = event->hudadd->pos;
2843 e->name = event->hudadd->name;
2844 e->scale = event->hudadd->scale;
2845 e->text = event->hudadd->text;
2846 e->number = event->hudadd->number;
2847 e->item = event->hudadd->item;
2848 e->dir = event->hudadd->dir;
2849 e->align = event->hudadd->align;
2850 e->offset = event->hudadd->offset;
2851 e->world_pos = event->hudadd->world_pos;
2852 e->size = event->hudadd->size;
2853 e->z_index = event->hudadd->z_index;
2854 e->text2 = event->hudadd->text2;
2855 e->style = event->hudadd->style;
2856 m_hud_server_to_client[server_id] = player->addHud(e);
2858 delete event->hudadd;
2861 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2863 LocalPlayer *player = client->getEnv().getLocalPlayer();
2865 auto i = m_hud_server_to_client.find(event->hudrm.id);
2866 if (i != m_hud_server_to_client.end()) {
2867 HudElement *e = player->removeHud(i->second);
2869 m_hud_server_to_client.erase(i);
2874 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2876 LocalPlayer *player = client->getEnv().getLocalPlayer();
2878 HudElement *e = nullptr;
2880 auto i = m_hud_server_to_client.find(event->hudchange->id);
2881 if (i != m_hud_server_to_client.end()) {
2882 e = player->getHud(i->second);
2886 delete event->hudchange;
2890 #define CASE_SET(statval, prop, dataprop) \
2892 e->prop = event->hudchange->dataprop; \
2895 switch (event->hudchange->stat) {
2896 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2898 CASE_SET(HUD_STAT_NAME, name, sdata);
2900 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2902 CASE_SET(HUD_STAT_TEXT, text, sdata);
2904 CASE_SET(HUD_STAT_NUMBER, number, data);
2906 CASE_SET(HUD_STAT_ITEM, item, data);
2908 CASE_SET(HUD_STAT_DIR, dir, data);
2910 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2912 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2914 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2916 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2918 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2920 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2922 CASE_SET(HUD_STAT_STYLE, style, data);
2927 delete event->hudchange;
2930 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2932 sky->setVisible(false);
2933 // Whether clouds are visible in front of a custom skybox.
2934 sky->setCloudsEnabled(event->set_sky->clouds);
2940 // Clear the old textures out in case we switch rendering type.
2941 sky->clearSkyboxTextures();
2942 // Handle according to type
2943 if (event->set_sky->type == "regular") {
2944 // Shows the mesh skybox
2945 sky->setVisible(true);
2946 // Update mesh based skybox colours if applicable.
2947 sky->setSkyColors(event->set_sky->sky_color);
2948 sky->setHorizonTint(
2949 event->set_sky->fog_sun_tint,
2950 event->set_sky->fog_moon_tint,
2951 event->set_sky->fog_tint_type
2953 } else if (event->set_sky->type == "skybox" &&
2954 event->set_sky->textures.size() == 6) {
2955 // Disable the dyanmic mesh skybox:
2956 sky->setVisible(false);
2958 sky->setFallbackBgColor(event->set_sky->bgcolor);
2959 // Set sunrise and sunset fog tinting:
2960 sky->setHorizonTint(
2961 event->set_sky->fog_sun_tint,
2962 event->set_sky->fog_moon_tint,
2963 event->set_sky->fog_tint_type
2965 // Add textures to skybox.
2966 for (int i = 0; i < 6; i++)
2967 sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2969 // Handle everything else as plain color.
2970 if (event->set_sky->type != "plain")
2971 infostream << "Unknown sky type: "
2972 << (event->set_sky->type) << std::endl;
2973 sky->setVisible(false);
2974 sky->setFallbackBgColor(event->set_sky->bgcolor);
2975 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2976 sky->setHorizonTint(
2977 event->set_sky->bgcolor,
2978 event->set_sky->bgcolor,
2983 delete event->set_sky;
2986 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2988 sky->setSunVisible(event->sun_params->visible);
2989 sky->setSunTexture(event->sun_params->texture,
2990 event->sun_params->tonemap, texture_src);
2991 sky->setSunScale(event->sun_params->scale);
2992 sky->setSunriseVisible(event->sun_params->sunrise_visible);
2993 sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2994 delete event->sun_params;
2997 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2999 sky->setMoonVisible(event->moon_params->visible);
3000 sky->setMoonTexture(event->moon_params->texture,
3001 event->moon_params->tonemap, texture_src);
3002 sky->setMoonScale(event->moon_params->scale);
3003 delete event->moon_params;
3006 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
3008 sky->setStarsVisible(event->star_params->visible);
3009 sky->setStarCount(event->star_params->count);
3010 sky->setStarColor(event->star_params->starcolor);
3011 sky->setStarScale(event->star_params->scale);
3012 sky->setStarDayOpacity(event->star_params->day_opacity);
3013 delete event->star_params;
3016 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
3017 CameraOrientation *cam)
3019 client->getEnv().setDayNightRatioOverride(
3020 event->override_day_night_ratio.do_override,
3021 event->override_day_night_ratio.ratio_f * 1000.0f);
3024 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
3029 clouds->setDensity(event->cloud_params.density);
3030 clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
3031 clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
3032 clouds->setHeight(event->cloud_params.height);
3033 clouds->setThickness(event->cloud_params.thickness);
3034 clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
3037 void Game::processClientEvents(CameraOrientation *cam)
3039 while (client->hasClientEvents()) {
3040 std::unique_ptr<ClientEvent> event(client->getClientEvent());
3041 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
3042 const ClientEventHandler& evHandler = clientEventHandler[event->type];
3043 (this->*evHandler.handler)(event.get(), cam);
3047 void Game::updateChat(f32 dtime)
3049 // Get new messages from error log buffer
3050 while (!m_chat_log_buf.empty())
3051 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
3053 // Get new messages from client
3054 std::wstring message;
3055 while (client->getChatMessage(message)) {
3056 chat_backend->addUnparsedMessage(message);
3059 // Remove old messages
3060 chat_backend->step(dtime);
3062 // Display all messages in a static text element
3063 auto &buf = chat_backend->getRecentBuffer();
3064 if (buf.getLinesModified()) {
3065 buf.resetLinesModified();
3066 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
3069 // Make sure that the size is still correct
3070 m_game_ui->updateChatSize();
3073 void Game::updateCamera(f32 dtime)
3075 LocalPlayer *player = client->getEnv().getLocalPlayer();
3078 For interaction purposes, get info about the held item
3080 - Is it a usable item?
3081 - Can it point to liquids?
3083 ItemStack playeritem;
3085 ItemStack selected, hand;
3086 playeritem = player->getWieldedItem(&selected, &hand);
3089 ToolCapabilities playeritem_toolcap =
3090 playeritem.getToolCapabilities(itemdef_manager);
3092 v3s16 old_camera_offset = camera->getOffset();
3094 if (wasKeyDown(KeyType::CAMERA_MODE)) {
3095 GenericCAO *playercao = player->getCAO();
3097 // If playercao not loaded, don't change camera
3101 camera->toggleCameraMode();
3103 #ifdef HAVE_TOUCHSCREENGUI
3104 if (g_touchscreengui)
3105 g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed());
3108 // Make the player visible depending on camera mode.
3109 playercao->updateMeshCulling();
3110 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
3113 float full_punch_interval = playeritem_toolcap.full_punch_interval;
3114 float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
3116 tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
3117 camera->update(player, dtime, tool_reload_ratio);
3118 camera->step(dtime);
3120 f32 camera_fov = camera->getFovMax();
3121 v3s16 camera_offset = camera->getOffset();
3123 m_camera_offset_changed = (camera_offset != old_camera_offset);
3125 if (!m_flags.disable_camera_update) {
3126 v3f camera_position = camera->getPosition();
3127 v3f camera_direction = camera->getDirection();
3129 client->getEnv().getClientMap().updateCamera(camera_position,
3130 camera_direction, camera_fov, camera_offset);
3132 if (m_camera_offset_changed) {
3133 client->updateCameraOffset(camera_offset);
3134 client->getEnv().updateCameraOffset(camera_offset);
3137 clouds->updateCameraOffset(camera_offset);
3143 void Game::updateSound(f32 dtime)
3145 // Update sound listener
3146 v3s16 camera_offset = camera->getOffset();
3147 sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3148 v3f(0, 0, 0), // velocity
3149 camera->getDirection(),
3150 camera->getCameraNode()->getUpVector());
3152 bool mute_sound = g_settings->getBool("mute_sound");
3154 sound->setListenerGain(0.0f);
3156 // Check if volume is in the proper range, else fix it.
3157 float old_volume = g_settings->getFloat("sound_volume");
3158 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3159 sound->setListenerGain(new_volume);
3161 if (old_volume != new_volume) {
3162 g_settings->setFloat("sound_volume", new_volume);
3166 LocalPlayer *player = client->getEnv().getLocalPlayer();
3168 // Tell the sound maker whether to make footstep sounds
3169 soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3171 // Update sound maker
3172 if (player->makes_footstep_sound)
3173 soundmaker->step(dtime);
3175 ClientMap &map = client->getEnv().getClientMap();
3176 MapNode n = map.getNode(player->getFootstepNodePos());
3177 soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3181 void Game::processPlayerInteraction(f32 dtime, bool show_hud)
3183 LocalPlayer *player = client->getEnv().getLocalPlayer();
3185 const v3f camera_direction = camera->getDirection();
3186 const v3s16 camera_offset = camera->getOffset();
3189 Calculate what block is the crosshair pointing to
3192 ItemStack selected_item, hand_item;
3193 const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3195 const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3196 f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3198 core::line3d<f32> shootline;
3200 switch (camera->getCameraMode()) {
3201 case CAMERA_MODE_FIRST:
3202 // Shoot from camera position, with bobbing
3203 shootline.start = camera->getPosition();
3205 case CAMERA_MODE_THIRD:
3206 // Shoot from player head, no bobbing
3207 shootline.start = camera->getHeadPosition();
3209 case CAMERA_MODE_THIRD_FRONT:
3210 shootline.start = camera->getHeadPosition();
3211 // prevent player pointing anything in front-view
3215 shootline.end = shootline.start + camera_direction * BS * d;
3217 #ifdef HAVE_TOUCHSCREENGUI
3218 if (g_touchscreengui && isNoCrosshairAllowed()) {
3219 shootline = g_touchscreengui->getShootline();
3220 // Scale shootline to the acual distance the player can reach
3221 shootline.end = shootline.start +
3222 shootline.getVector().normalize() * BS * d;
3223 shootline.start += intToFloat(camera_offset, BS);
3224 shootline.end += intToFloat(camera_offset, BS);
3228 PointedThing pointed = updatePointedThing(shootline,
3229 selected_def.liquids_pointable,
3230 !runData.btn_down_for_dig,
3233 if (pointed != runData.pointed_old)
3234 infostream << "Pointing at " << pointed.dump() << std::endl;
3236 // Note that updating the selection mesh every frame is not particularly efficient,
3237 // but the halo rendering code is already inefficient so there's no point in optimizing it here
3238 hud->updateSelectionMesh(camera_offset);
3240 // Allow digging again if button is not pressed
3241 if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3242 runData.digging_blocked = false;
3246 - releasing dig button
3247 - pointing away from node
3249 if (runData.digging) {
3250 if (wasKeyReleased(KeyType::DIG)) {
3251 infostream << "Dig button released (stopped digging)" << std::endl;
3252 runData.digging = false;
3253 } else if (pointed != runData.pointed_old) {
3254 if (pointed.type == POINTEDTHING_NODE
3255 && runData.pointed_old.type == POINTEDTHING_NODE
3256 && pointed.node_undersurface
3257 == runData.pointed_old.node_undersurface) {
3258 // Still pointing to the same node, but a different face.
3261 infostream << "Pointing away from node (stopped digging)" << std::endl;
3262 runData.digging = false;
3263 hud->updateSelectionMesh(camera_offset);
3267 if (!runData.digging) {
3268 client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3269 client->setCrack(-1, v3s16(0, 0, 0));
3270 runData.dig_time = 0.0;
3272 } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3273 // Remove e.g. torches faster when clicking instead of holding dig button
3274 runData.nodig_delay_timer = 0;
3275 runData.dig_instantly = false;
3278 if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3279 runData.btn_down_for_dig = false;
3281 runData.punching = false;
3283 soundmaker->m_player_leftpunch_sound = SimpleSoundSpec();
3284 soundmaker->m_player_leftpunch_sound2 = pointed.type != POINTEDTHING_NOTHING ?
3285 selected_def.sound_use : selected_def.sound_use_air;
3287 // Prepare for repeating, unless we're not supposed to
3288 if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3289 runData.repeat_place_timer += dtime;
3291 runData.repeat_place_timer = 0;
3293 if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3294 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3295 !client->getScript()->on_item_use(selected_item, pointed)))
3296 client->interact(INTERACT_USE, pointed);
3297 } else if (pointed.type == POINTEDTHING_NODE) {
3298 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3299 } else if (pointed.type == POINTEDTHING_OBJECT) {
3300 v3f player_position = player->getPosition();
3301 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
3302 handlePointingAtObject(pointed, tool_item, player_position,
3303 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
3304 } else if (isKeyDown(KeyType::DIG)) {
3305 // When button is held down in air, show continuous animation
3306 runData.punching = true;
3307 // Run callback even though item is not usable
3308 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3309 client->getScript()->on_item_use(selected_item, pointed);
3310 } else if (wasKeyPressed(KeyType::PLACE)) {
3311 handlePointingAtNothing(selected_item);
3314 runData.pointed_old = pointed;
3316 if (runData.punching || wasKeyPressed(KeyType::DIG))
3317 camera->setDigging(0); // dig animation
3319 input->clearWasKeyPressed();
3320 input->clearWasKeyReleased();
3321 // Ensure DIG & PLACE are marked as handled
3322 wasKeyDown(KeyType::DIG);
3323 wasKeyDown(KeyType::PLACE);
3325 input->joystick.clearWasKeyPressed(KeyType::DIG);
3326 input->joystick.clearWasKeyPressed(KeyType::PLACE);
3328 input->joystick.clearWasKeyReleased(KeyType::DIG);
3329 input->joystick.clearWasKeyReleased(KeyType::PLACE);
3333 PointedThing Game::updatePointedThing(
3334 const core::line3d<f32> &shootline,
3335 bool liquids_pointable,
3336 bool look_for_object,
3337 const v3s16 &camera_offset)
3339 std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3340 selectionboxes->clear();
3341 hud->setSelectedFaceNormal(v3f());
3342 static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3343 "show_entity_selectionbox");
3345 ClientEnvironment &env = client->getEnv();
3346 ClientMap &map = env.getClientMap();
3347 const NodeDefManager *nodedef = map.getNodeDefManager();
3349 runData.selected_object = NULL;
3350 hud->pointing_at_object = false;
3352 RaycastState s(shootline, look_for_object, liquids_pointable);
3353 PointedThing result;
3354 env.continueRaycast(&s, &result);
3355 if (result.type == POINTEDTHING_OBJECT) {
3356 hud->pointing_at_object = true;
3358 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3359 aabb3f selection_box;
3360 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3361 runData.selected_object->getSelectionBox(&selection_box)) {
3362 v3f pos = runData.selected_object->getPosition();
3363 selectionboxes->push_back(aabb3f(selection_box));
3364 hud->setSelectionPos(pos, camera_offset);
3365 GenericCAO* gcao = dynamic_cast<GenericCAO*>(runData.selected_object);
3366 if (gcao != nullptr && gcao->getProperties().rotate_selectionbox)
3367 hud->setSelectionRotation(gcao->getSceneNode()->getAbsoluteTransformation().getRotationDegrees());
3369 hud->setSelectionRotation(v3f());
3371 hud->setSelectedFaceNormal(result.raw_intersection_normal);
3372 } else if (result.type == POINTEDTHING_NODE) {
3373 // Update selection boxes
3374 MapNode n = map.getNode(result.node_undersurface);
3375 std::vector<aabb3f> boxes;
3376 n.getSelectionBoxes(nodedef, &boxes,
3377 n.getNeighbors(result.node_undersurface, &map));
3380 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3381 i != boxes.end(); ++i) {
3383 box.MinEdge -= v3f(d, d, d);
3384 box.MaxEdge += v3f(d, d, d);
3385 selectionboxes->push_back(box);
3387 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3389 hud->setSelectionRotation(v3f());
3390 hud->setSelectedFaceNormal(result.intersection_normal);
3393 // Update selection mesh light level and vertex colors
3394 if (!selectionboxes->empty()) {
3395 v3f pf = hud->getSelectionPos();
3396 v3s16 p = floatToInt(pf, BS);
3398 // Get selection mesh light level
3399 MapNode n = map.getNode(p);
3400 u16 node_light = getInteriorLight(n, -1, nodedef);
3401 u16 light_level = node_light;
3403 for (const v3s16 &dir : g_6dirs) {
3404 n = map.getNode(p + dir);
3405 node_light = getInteriorLight(n, -1, nodedef);
3406 if (node_light > light_level)
3407 light_level = node_light;
3410 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3412 final_color_blend(&c, light_level, daynight_ratio);
3414 // Modify final color a bit with time
3415 u32 timer = client->getEnv().getFrameTime() % 5000;
3416 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3417 float sin_r = 0.08f * std::sin(timerf);
3418 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3419 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3420 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3421 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3422 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3424 // Set mesh final color
3425 hud->setSelectionMeshColor(c);
3431 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3433 infostream << "Attempted to place item while pointing at nothing" << std::endl;
3434 PointedThing fauxPointed;
3435 fauxPointed.type = POINTEDTHING_NOTHING;
3436 client->interact(INTERACT_ACTIVATE, fauxPointed);
3440 void Game::handlePointingAtNode(const PointedThing &pointed,
3441 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3443 v3s16 nodepos = pointed.node_undersurface;
3444 v3s16 neighborpos = pointed.node_abovesurface;
3447 Check information text of node
3450 ClientMap &map = client->getEnv().getClientMap();
3452 if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3453 && !runData.digging_blocked
3454 && client->checkPrivilege("interact")) {
3455 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3458 // This should be done after digging handling
3459 NodeMetadata *meta = map.getNodeMetadata(nodepos);
3462 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3463 meta->getString("infotext"))));
3465 MapNode n = map.getNode(nodepos);
3467 if (nodedef_manager->get(n).name == "unknown") {
3468 m_game_ui->setInfoText(L"Unknown node");
3472 if ((wasKeyPressed(KeyType::PLACE) ||
3473 runData.repeat_place_timer >= m_repeat_place_time) &&
3474 client->checkPrivilege("interact")) {
3475 runData.repeat_place_timer = 0;
3476 infostream << "Place button pressed while looking at ground" << std::endl;
3478 // Placing animation (always shown for feedback)
3479 camera->setDigging(1);
3481 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3483 // If the wielded item has node placement prediction,
3485 // And also set the sound and send the interact
3486 // But first check for meta formspec and rightclickable
3487 auto &def = selected_item.getDefinition(itemdef_manager);
3488 bool placed = nodePlacement(def, selected_item, nodepos, neighborpos,
3491 if (placed && client->modsLoaded())
3492 client->getScript()->on_placenode(pointed, def);
3496 bool Game::nodePlacement(const ItemDefinition &selected_def,
3497 const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighborpos,
3498 const PointedThing &pointed, const NodeMetadata *meta)
3500 const auto &prediction = selected_def.node_placement_prediction;
3502 const NodeDefManager *nodedef = client->ndef();
3503 ClientMap &map = client->getEnv().getClientMap();
3505 bool is_valid_position;
3507 node = map.getNode(nodepos, &is_valid_position);
3508 if (!is_valid_position) {
3509 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3514 if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3515 && !isKeyDown(KeyType::SNEAK)) {
3516 // on_rightclick callbacks are called anyway
3517 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3518 client->interact(INTERACT_PLACE, pointed);
3520 infostream << "Launching custom inventory view" << std::endl;
3522 InventoryLocation inventoryloc;
3523 inventoryloc.setNodeMeta(nodepos);
3525 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3526 &client->getEnv().getClientMap(), nodepos);
3527 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3529 auto *&formspec = m_game_ui->updateFormspec("");
3530 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3531 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3533 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3537 // on_rightclick callback
3538 if (prediction.empty() || (nodedef->get(node).rightclickable &&
3539 !isKeyDown(KeyType::SNEAK))) {
3541 client->interact(INTERACT_PLACE, pointed);
3545 verbosestream << "Node placement prediction for "
3546 << selected_def.name << " is " << prediction << std::endl;
3547 v3s16 p = neighborpos;
3549 // Place inside node itself if buildable_to
3550 MapNode n_under = map.getNode(nodepos, &is_valid_position);
3551 if (is_valid_position) {
3552 if (nodedef->get(n_under).buildable_to) {
3555 node = map.getNode(p, &is_valid_position);
3556 if (is_valid_position && !nodedef->get(node).buildable_to) {
3557 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3559 client->interact(INTERACT_PLACE, pointed);
3565 // Find id of predicted node
3567 bool found = nodedef->getId(prediction, id);
3570 errorstream << "Node placement prediction failed for "
3571 << selected_def.name << " (places " << prediction
3572 << ") - Name not known" << std::endl;
3573 // Handle this as if prediction was empty
3575 client->interact(INTERACT_PLACE, pointed);
3579 const ContentFeatures &predicted_f = nodedef->get(id);
3581 // Compare core.item_place_node() for what the server does with param2
3582 MapNode predicted_node(id, 0, 0);
3584 const u8 place_param2 = selected_def.place_param2;
3587 predicted_node.setParam2(place_param2);
3588 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3589 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3590 v3s16 dir = nodepos - neighborpos;
3592 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3593 predicted_node.setParam2(dir.Y < 0 ? 1 : 0);
3594 } else if (abs(dir.X) > abs(dir.Z)) {
3595 predicted_node.setParam2(dir.X < 0 ? 3 : 2);
3597 predicted_node.setParam2(dir.Z < 0 ? 5 : 4);
3599 } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3600 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR ||
3601 predicted_f.param_type_2 == CPT2_4DIR ||
3602 predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3603 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3605 if (abs(dir.X) > abs(dir.Z)) {
3606 predicted_node.setParam2(dir.X < 0 ? 3 : 1);
3608 predicted_node.setParam2(dir.Z < 0 ? 2 : 0);
3612 // Check attachment if node is in group attached_node
3613 int an = itemgroup_get(predicted_f.groups, "attached_node");
3618 pp = p + v3s16(0, -1, 0);
3619 } else if (an == 4) {
3620 pp = p + v3s16(0, 1, 0);
3621 } else if (an == 2) {
3622 if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3623 predicted_f.param_type_2 == CPT2_COLORED_FACEDIR ||
3624 predicted_f.param_type_2 == CPT2_4DIR ||
3625 predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3626 pp = p + facedir_dirs[predicted_node.getFaceDir(nodedef)];
3630 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3631 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3632 pp = p + predicted_node.getWallMountedDir(nodedef);
3634 pp = p + v3s16(0, -1, 0);
3637 if (!nodedef->get(map.getNode(pp)).walkable) {
3638 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3640 client->interact(INTERACT_PLACE, pointed);
3646 if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3647 || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3648 || predicted_f.param_type_2 == CPT2_COLORED_4DIR
3649 || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3650 const auto &indexstr = selected_item.metadata.
3651 getString("palette_index", 0);
3652 if (!indexstr.empty()) {
3653 s32 index = mystoi(indexstr);
3654 if (predicted_f.param_type_2 == CPT2_COLOR) {
3655 predicted_node.setParam2(index);
3656 } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3657 // param2 = pure palette index + other
3658 predicted_node.setParam2((index & 0xf8) | (predicted_node.getParam2() & 0x07));
3659 } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3660 // param2 = pure palette index + other
3661 predicted_node.setParam2((index & 0xe0) | (predicted_node.getParam2() & 0x1f));
3662 } else if (predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3663 // param2 = pure palette index + other
3664 predicted_node.setParam2((index & 0xfc) | (predicted_node.getParam2() & 0x03));
3669 // Add node to client map
3671 LocalPlayer *player = client->getEnv().getLocalPlayer();
3673 // Don't place node when player would be inside new node
3674 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3675 if (!predicted_f.walkable ||
3676 g_settings->getBool("enable_build_where_you_stand") ||
3677 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3678 (predicted_f.walkable &&
3679 neighborpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3680 neighborpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3681 // This triggers the required mesh update too
3682 client->addNode(p, predicted_node);
3684 client->interact(INTERACT_PLACE, pointed);
3685 // A node is predicted, also play a sound
3686 soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3689 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3692 } catch (const InvalidPositionException &e) {
3693 errorstream << "Node placement prediction failed for "
3694 << selected_def.name << " (places "
3695 << prediction << ") - Position not loaded" << std::endl;
3696 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3701 void Game::handlePointingAtObject(const PointedThing &pointed,
3702 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3704 std::wstring infotext = unescape_translate(
3705 utf8_to_wide(runData.selected_object->infoText()));
3708 if (!infotext.empty()) {
3711 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3714 m_game_ui->setInfoText(infotext);
3716 if (isKeyDown(KeyType::DIG)) {
3717 bool do_punch = false;
3718 bool do_punch_damage = false;
3720 if (runData.object_hit_delay_timer <= 0.0) {
3722 do_punch_damage = true;
3723 runData.object_hit_delay_timer = object_hit_delay;
3726 if (wasKeyPressed(KeyType::DIG))
3730 infostream << "Punched object" << std::endl;
3731 runData.punching = true;
3734 if (do_punch_damage) {
3735 // Report direct punch
3736 v3f objpos = runData.selected_object->getPosition();
3737 v3f dir = (objpos - player_position).normalize();
3739 bool disable_send = runData.selected_object->directReportPunch(
3740 dir, &tool_item, runData.time_from_last_punch);
3741 runData.time_from_last_punch = 0;
3744 client->interact(INTERACT_START_DIGGING, pointed);
3746 } else if (wasKeyDown(KeyType::PLACE)) {
3747 infostream << "Pressed place button while pointing at object" << std::endl;
3748 client->interact(INTERACT_PLACE, pointed); // place
3753 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3754 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3756 // See also: serverpackethandle.cpp, action == 2
3757 LocalPlayer *player = client->getEnv().getLocalPlayer();
3758 ClientMap &map = client->getEnv().getClientMap();
3759 MapNode n = map.getNode(nodepos);
3760 const auto &features = nodedef_manager->get(n);
3762 // NOTE: Similar piece of code exists on the server side for
3764 // Get digging parameters
3765 DigParams params = getDigParams(features.groups,
3766 &selected_item.getToolCapabilities(itemdef_manager),
3767 selected_item.wear);
3769 // If can't dig, try hand
3770 if (!params.diggable) {
3771 params = getDigParams(features.groups,
3772 &hand_item.getToolCapabilities(itemdef_manager));
3775 if (!params.diggable) {
3776 // I guess nobody will wait for this long
3777 runData.dig_time_complete = 10000000.0;
3779 runData.dig_time_complete = params.time;
3781 if (m_cache_enable_particles) {
3782 client->getParticleManager()->addNodeParticle(client,
3783 player, nodepos, n, features);
3787 if (!runData.digging) {
3788 infostream << "Started digging" << std::endl;
3789 runData.dig_instantly = runData.dig_time_complete == 0;
3790 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3793 client->interact(INTERACT_START_DIGGING, pointed);
3794 runData.digging = true;
3795 runData.btn_down_for_dig = true;
3798 if (!runData.dig_instantly) {
3799 runData.dig_index = (float)crack_animation_length
3801 / runData.dig_time_complete;
3803 // This is for e.g. torches
3804 runData.dig_index = crack_animation_length;
3807 const auto &sound_dig = features.sound_dig;
3809 if (sound_dig.exists() && params.diggable) {
3810 if (sound_dig.name == "__group") {
3811 if (!params.main_group.empty()) {
3812 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3813 soundmaker->m_player_leftpunch_sound.name =
3814 std::string("default_dig_") +
3818 soundmaker->m_player_leftpunch_sound = sound_dig;
3822 // Don't show cracks if not diggable
3823 if (runData.dig_time_complete >= 100000.0) {
3824 } else if (runData.dig_index < crack_animation_length) {
3825 client->setCrack(runData.dig_index, nodepos);
3827 infostream << "Digging completed" << std::endl;
3828 client->setCrack(-1, v3s16(0, 0, 0));
3830 runData.dig_time = 0;
3831 runData.digging = false;
3832 // we successfully dug, now block it from repeating if we want to be safe
3833 if (g_settings->getBool("safe_dig_and_place"))
3834 runData.digging_blocked = true;
3836 runData.nodig_delay_timer =
3837 runData.dig_time_complete / (float)crack_animation_length;
3839 // We don't want a corresponding delay to very time consuming nodes
3840 // and nodes without digging time (e.g. torches) get a fixed delay.
3841 if (runData.nodig_delay_timer > 0.3)
3842 runData.nodig_delay_timer = 0.3;
3843 else if (runData.dig_instantly)
3844 runData.nodig_delay_timer = 0.15;
3846 if (client->modsLoaded() &&
3847 client->getScript()->on_dignode(nodepos, n)) {
3851 if (features.node_dig_prediction == "air") {
3852 client->removeNode(nodepos);
3853 } else if (!features.node_dig_prediction.empty()) {
3855 bool found = nodedef_manager->getId(features.node_dig_prediction, id);
3857 client->addNode(nodepos, id, true);
3859 // implicit else: no prediction
3861 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3863 if (m_cache_enable_particles) {
3864 client->getParticleManager()->addDiggingParticles(client,
3865 player, nodepos, n, features);
3869 // Send event to trigger sound
3870 client->getEventManager()->put(new NodeDugEvent(nodepos, n));
3873 if (runData.dig_time_complete < 100000.0) {
3874 runData.dig_time += dtime;
3876 runData.dig_time = 0;
3877 client->setCrack(-1, nodepos);
3880 camera->setDigging(0); // Dig animation
3883 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3884 const CameraOrientation &cam)
3886 TimeTaker tt_update("Game::updateFrame()");
3887 LocalPlayer *player = client->getEnv().getLocalPlayer();
3893 client->getEnv().updateFrameTime(m_is_paused);
3899 if (draw_control->range_all) {
3900 runData.fog_range = 100000 * BS;
3902 runData.fog_range = draw_control->wanted_range * BS;
3906 Calculate general brightness
3908 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3909 float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3910 float direct_brightness;
3913 // When in noclip mode force same sky brightness as above ground so you
3915 if (draw_control->allow_noclip && m_cache_enable_free_move &&
3916 client->checkPrivilege("fly")) {
3917 direct_brightness = time_brightness;
3918 sunlight_seen = true;
3920 float old_brightness = sky->getBrightness();
3921 direct_brightness = client->getEnv().getClientMap()
3922 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3923 daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3927 float time_of_day_smooth = runData.time_of_day_smooth;
3928 float time_of_day = client->getEnv().getTimeOfDayF();
3930 static const float maxsm = 0.05f;
3931 static const float todsm = 0.05f;
3933 if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3934 std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3935 std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3936 time_of_day_smooth = time_of_day;
3938 if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3939 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3940 + (time_of_day + 1.0) * todsm;
3942 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3943 + time_of_day * todsm;
3945 runData.time_of_day_smooth = time_of_day_smooth;
3947 sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3948 sunlight_seen, camera->getCameraMode(), player->getYaw(),
3949 player->getPitch());
3955 if (sky->getCloudsVisible()) {
3956 clouds->setVisible(true);
3957 clouds->step(dtime);
3958 // camera->getPosition is not enough for 3rd person views
3959 v3f camera_node_position = camera->getCameraNode()->getPosition();
3960 v3s16 camera_offset = camera->getOffset();
3961 camera_node_position.X = camera_node_position.X + camera_offset.X * BS;
3962 camera_node_position.Y = camera_node_position.Y + camera_offset.Y * BS;
3963 camera_node_position.Z = camera_node_position.Z + camera_offset.Z * BS;
3964 clouds->update(camera_node_position,
3965 sky->getCloudColor());
3966 if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3967 // if inside clouds, and fog enabled, use that as sky
3969 video::SColor clouds_dark = clouds->getColor()
3970 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3971 sky->overrideColors(clouds_dark, clouds->getColor());
3972 sky->setInClouds(true);
3973 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3974 // do not draw clouds after all
3975 clouds->setVisible(false);
3978 clouds->setVisible(false);
3985 client->getParticleManager()->step(dtime);
3991 if (m_cache_enable_fog) {
3994 video::EFT_FOG_LINEAR,
3995 runData.fog_range * m_cache_fog_start,
3996 runData.fog_range * 1.0,
4004 video::EFT_FOG_LINEAR,
4016 if (player->hurt_tilt_timer > 0.0f) {
4017 player->hurt_tilt_timer -= dtime * 6.0f;
4019 if (player->hurt_tilt_timer < 0.0f)
4020 player->hurt_tilt_strength = 0.0f;
4024 Update minimap pos and rotation
4026 if (mapper && m_game_ui->m_flags.show_hud) {
4027 mapper->setPos(floatToInt(player->getPosition(), BS));
4028 mapper->setAngle(player->getYaw());
4032 Get chat messages from client
4041 if (player->getWieldIndex() != runData.new_playeritem)
4042 client->setPlayerItem(runData.new_playeritem);
4044 if (client->updateWieldedItem()) {
4045 // Update wielded tool
4046 ItemStack selected_item, hand_item;
4047 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
4048 camera->wield(tool_item);
4052 Update block draw list every 200ms or when camera direction has
4055 runData.update_draw_list_timer += dtime;
4056 runData.touch_blocks_timer += dtime;
4058 bool draw_list_updated = false;
4060 float update_draw_list_delta = 0.2f;
4062 v3f camera_direction = camera->getDirection();
4063 if (runData.update_draw_list_timer >= update_draw_list_delta
4064 || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
4065 || m_camera_offset_changed
4066 || client->getEnv().getClientMap().needsUpdateDrawList()) {
4067 runData.update_draw_list_timer = 0;
4068 client->getEnv().getClientMap().updateDrawList();
4069 runData.update_draw_list_last_cam_dir = camera_direction;
4070 draw_list_updated = true;
4073 if (runData.touch_blocks_timer > update_draw_list_delta && !draw_list_updated) {
4074 client->getEnv().getClientMap().touchMapBlocks();
4075 runData.touch_blocks_timer = 0;
4078 if (RenderingEngine::get_shadow_renderer()) {
4082 m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
4085 make sure menu is on top
4086 1. Delete formspec menu reference if menu was removed
4087 2. Else, make sure formspec menu is on top
4089 auto formspec = m_game_ui->getFormspecGUI();
4090 do { // breakable. only runs for one iteration
4094 if (formspec->getReferenceCount() == 1) {
4095 m_game_ui->deleteFormspec();
4099 auto &loc = formspec->getFormspecLocation();
4100 if (loc.type == InventoryLocation::NODEMETA) {
4101 NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
4102 if (!meta || meta->getString("formspec").empty()) {
4103 formspec->quitMenu();
4109 guiroot->bringToFront(formspec);
4113 ==================== Drawing begins ====================
4115 const video::SColor skycolor = sky->getSkyColor();
4117 TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
4118 driver->beginScene(true, true, skycolor);
4120 bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
4121 (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
4122 (camera->getCameraMode() == CAMERA_MODE_FIRST));
4123 bool draw_crosshair = (
4124 (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
4125 (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
4126 #ifdef HAVE_TOUCHSCREENGUI
4127 if (isNoCrosshairAllowed())
4128 draw_crosshair = false;
4130 m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
4131 m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
4136 v2u32 screensize = driver->getScreenSize();
4138 if (m_game_ui->m_flags.show_profiler_graph)
4139 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
4144 if (runData.damage_flash > 0.0f) {
4145 video::SColor color(runData.damage_flash, 180, 0, 0);
4146 driver->draw2DRectangle(color,
4147 core::rect<s32>(0, 0, screensize.X, screensize.Y),
4150 runData.damage_flash -= 384.0f * dtime;
4154 ==================== End scene ====================
4156 #if IRRLICHT_VERSION_MT_REVISION < 5
4157 if (++m_reset_HW_buffer_counter > 500) {
4159 Periodically remove all mesh HW buffers.
4161 Work around for a quirk in Irrlicht where a HW buffer is only
4162 released after 20000 iterations (triggered from endScene()).
4164 Without this, all loaded but unused meshes will retain their HW
4165 buffers for at least 5 minutes, at which point looking up the HW buffers
4166 becomes a bottleneck and the framerate drops (as much as 30%).
4168 Tests showed that numbers between 50 and 1000 are good, so picked 500.
4169 There are no other public Irrlicht APIs that allow interacting with the
4170 HW buffers without tracking the status of every individual mesh.
4172 The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4174 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4175 driver->removeAllHardwareBuffers();
4176 m_reset_HW_buffer_counter = 0;
4182 stats->drawtime = tt_draw.stop(true);
4183 g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
4184 g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
4187 /* Log times and stuff for visualization */
4188 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4190 Profiler::GraphValues values;
4191 g_profiler->graphGet(values);
4195 /****************************************************************************
4197 *****************************************************************************/
4198 void Game::updateShadows()
4200 ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4204 float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4206 float timeoftheday = getWickedTimeOfDay(in_timeofday);
4207 bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
4208 bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
4209 shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
4211 timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
4212 const float offset_constant = 10000.0f;
4214 v3f light = is_day ? sky->getSunDirection() : sky->getMoonDirection();
4216 v3f sun_pos = light * offset_constant;
4218 if (shadow->getDirectionalLightCount() == 0)
4219 shadow->addDirectionalLight();
4220 shadow->getDirectionalLight().setDirection(sun_pos);
4221 shadow->setTimeOfDay(in_timeofday);
4223 shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4226 /****************************************************************************
4228 ****************************************************************************/
4230 void FpsControl::reset()
4232 last_time = porting::getTimeUs();
4236 * On some computers framerate doesn't seem to be automatically limited
4238 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
4240 const float fps_limit = (device->isWindowFocused() && !g_menumgr.pausesGame())
4241 ? g_settings->getFloat("fps_max")
4242 : g_settings->getFloat("fps_max_unfocused");
4243 const u64 frametime_min = 1000000.0f / std::max(fps_limit, 1.0f);
4245 u64 time = porting::getTimeUs();
4247 if (time > last_time) // Make sure time hasn't overflowed
4248 busy_time = time - last_time;
4252 if (busy_time < frametime_min) {
4253 sleep_time = frametime_min - busy_time;
4254 if (sleep_time > 1000)
4255 sleep_ms(sleep_time / 1000);
4260 // Read the timer again to accurately determine how long we actually slept,
4261 // rather than calculating it by adding sleep_time to time.
4262 time = porting::getTimeUs();
4264 if (time > last_time) // Make sure last_time hasn't overflowed
4265 *dtime = (time - last_time) / 1000000.0f;
4272 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4274 const wchar_t *wmsg = wgettext(msg);
4275 m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4280 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4282 ((Game *)data)->readSettings();
4285 void Game::readSettings()
4287 m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
4288 m_cache_enable_clouds = g_settings->getBool("enable_clouds");
4289 m_cache_enable_joysticks = g_settings->getBool("enable_joysticks");
4290 m_cache_enable_particles = g_settings->getBool("enable_particles");
4291 m_cache_enable_fog = g_settings->getBool("enable_fog");
4292 m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f);
4293 m_cache_joystick_frustum_sensitivity = std::max(g_settings->getFloat("joystick_frustum_sensitivity"), 0.001f);
4294 m_repeat_place_time = g_settings->getFloat("repeat_place_time", 0.16f, 2.0);
4296 m_cache_enable_noclip = g_settings->getBool("noclip");
4297 m_cache_enable_free_move = g_settings->getBool("free_move");
4299 m_cache_fog_start = g_settings->getFloat("fog_start");
4301 m_cache_cam_smoothing = 0;
4302 if (g_settings->getBool("cinematic"))
4303 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4305 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4307 m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4308 m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4309 m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4311 m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4314 /****************************************************************************/
4315 /****************************************************************************
4317 ****************************************************************************/
4318 /****************************************************************************/
4320 void Game::showDeathFormspec()
4322 static std::string formspec_str =
4323 std::string("formspec_version[1]") +
4325 "bgcolor[#320000b4;true]"
4326 "label[4.85,1.35;" + gettext("You died") + "]"
4327 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4331 /* Note: FormspecFormSource and LocalFormspecHandler *
4332 * are deleted by guiFormSpecMenu */
4333 FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4334 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4336 auto *&formspec = m_game_ui->getFormspecGUI();
4337 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4338 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4339 formspec->setFocus("btn_respawn");
4342 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4343 void Game::showPauseMenu()
4345 #ifdef HAVE_TOUCHSCREENGUI
4346 static const std::string control_text = strgettext("Default Controls:\n"
4347 "No menu visible:\n"
4348 "- single tap: button activate\n"
4349 "- double tap: place/use\n"
4350 "- slide finger: look around\n"
4351 "Menu/Inventory visible:\n"
4352 "- double tap (outside):\n"
4354 "- touch stack, touch slot:\n"
4356 "- touch&drag, tap 2nd finger\n"
4357 " --> place single item to slot\n"
4360 static const std::string control_text_template = strgettext("Controls:\n"
4361 "- %s: move forwards\n"
4362 "- %s: move backwards\n"
4364 "- %s: move right\n"
4365 "- %s: jump/climb up\n"
4368 "- %s: sneak/climb down\n"
4371 "- Mouse: turn/look\n"
4372 "- Mouse wheel: select item\n"
4376 char control_text_buf[600];
4378 porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4379 GET_KEY_NAME(keymap_forward),
4380 GET_KEY_NAME(keymap_backward),
4381 GET_KEY_NAME(keymap_left),
4382 GET_KEY_NAME(keymap_right),
4383 GET_KEY_NAME(keymap_jump),
4384 GET_KEY_NAME(keymap_dig),
4385 GET_KEY_NAME(keymap_place),
4386 GET_KEY_NAME(keymap_sneak),
4387 GET_KEY_NAME(keymap_drop),
4388 GET_KEY_NAME(keymap_inventory),
4389 GET_KEY_NAME(keymap_chat)
4392 std::string control_text = std::string(control_text_buf);
4393 str_formspec_escape(control_text);
4396 float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4397 std::ostringstream os;
4399 os << "formspec_version[1]" << SIZE_TAG
4400 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4401 << strgettext("Continue") << "]";
4403 if (!simple_singleplayer_mode) {
4404 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4405 << strgettext("Change Password") << "]";
4407 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4412 if (g_settings->getBool("enable_sound")) {
4413 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4414 << strgettext("Sound Volume") << "]";
4417 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4418 << strgettext("Change Keys") << "]";
4420 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4421 << strgettext("Exit to Menu") << "]";
4422 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4423 << strgettext("Exit to OS") << "]"
4424 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4425 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4427 << strgettext("Game info:") << "\n";
4428 const std::string &address = client->getAddressName();
4429 static const std::string mode = strgettext("- Mode: ");
4430 if (!simple_singleplayer_mode) {
4431 Address serverAddress = client->getServerAddress();
4432 if (!address.empty()) {
4433 os << mode << strgettext("Remote server") << "\n"
4434 << strgettext("- Address: ") << address;
4436 os << mode << strgettext("Hosting server");
4438 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4440 os << mode << strgettext("Singleplayer") << "\n";
4442 if (simple_singleplayer_mode || address.empty()) {
4443 static const std::string on = strgettext("On");
4444 static const std::string off = strgettext("Off");
4445 // Note: Status of enable_damage and creative_mode settings is intentionally
4446 // NOT shown here because the game might roll its own damage system and/or do
4447 // a per-player Creative Mode, in which case writing it here would mislead.
4448 bool damage = g_settings->getBool("enable_damage");
4449 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4450 if (!simple_singleplayer_mode) {
4452 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4453 //~ PvP = Player versus Player
4454 os << strgettext("- PvP: ") << pvp << "\n";
4456 os << strgettext("- Public: ") << announced << "\n";
4457 std::string server_name = g_settings->get("server_name");
4458 str_formspec_escape(server_name);
4459 if (announced == on && !server_name.empty())
4460 os << strgettext("- Server Name: ") << server_name;
4467 /* Note: FormspecFormSource and LocalFormspecHandler *
4468 * are deleted by guiFormSpecMenu */
4469 FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4470 LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4472 auto *&formspec = m_game_ui->getFormspecGUI();
4473 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4474 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4475 formspec->setFocus("btn_continue");
4476 // game will be paused in next step, if in singleplayer (see m_is_paused)
4477 formspec->doPause = true;
4480 /****************************************************************************/
4481 /****************************************************************************
4482 extern function for launching the game
4483 ****************************************************************************/
4484 /****************************************************************************/
4486 void the_game(bool *kill,
4487 InputHandler *input,
4488 RenderingEngine *rendering_engine,
4489 const GameStartData &start_data,
4490 std::string &error_message,
4491 ChatBackend &chat_backend,
4492 bool *reconnect_requested) // Used for local game
4496 /* Make a copy of the server address because if a local singleplayer server
4497 * is created then this is updated and we don't want to change the value
4498 * passed to us by the calling function
4503 if (game.startup(kill, input, rendering_engine, start_data,
4504 error_message, reconnect_requested, &chat_backend)) {
4508 } catch (SerializationError &e) {
4509 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
4510 error_message = strgettext("A serialization error occurred:") +"\n"
4511 + e.what() + "\n\n" + ver_err;
4512 errorstream << error_message << std::endl;
4513 } catch (ServerError &e) {
4514 error_message = e.what();
4515 errorstream << "ServerError: " << error_message << std::endl;
4516 } catch (ModError &e) {
4517 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
4518 error_message = std::string("ModError: ") + e.what() +
4519 strgettext("\nCheck debug.txt for details.");
4520 errorstream << error_message << std::endl;