]> git.lizzy.rs Git - minetest.git/blob - src/client/game.cpp
Clean up Game::handleDigging() and some related parts
[minetest.git] / src / client / game.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
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.
9
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.
14
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.
18 */
19
20 #include "game.h"
21
22 #include <iomanip>
23 #include <cmath>
24 #include "client/renderingengine.h"
25 #include "camera.h"
26 #include "client.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"
34 #include "clouds.h"
35 #include "config.h"
36 #include "content_cao.h"
37 #include "content/subgames.h"
38 #include "client/event_manager.h"
39 #include "fontengine.h"
40 #include "itemdef.h"
41 #include "log.h"
42 #include "filesys.h"
43 #include "gameparams.h"
44 #include "gettext.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"
52 #include "mapblock.h"
53 #include "minimap.h"
54 #include "nodedef.h"         // Needed for determining pointing to nodes
55 #include "nodemetadata.h"
56 #include "particles.h"
57 #include "porting.h"
58 #include "profiler.h"
59 #include "raycast.h"
60 #include "server.h"
61 #include "settings.h"
62 #include "shader.h"
63 #include "sky.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"
70 #include "irr_ptr.h"
71 #include "version.h"
72 #include "script/scripting_client.h"
73 #include "hud.h"
74
75 #if USE_SOUND
76         #include "client/sound_openal.h"
77 #else
78         #include "client/sound.h"
79 #endif
80 /*
81         Text input system
82 */
83
84 struct TextDestNodeMetadata : public TextDest
85 {
86         TextDestNodeMetadata(v3s16 p, Client *client)
87         {
88                 m_p = p;
89                 m_client = client;
90         }
91         // This is deprecated I guess? -celeron55
92         void gotText(const std::wstring &text)
93         {
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;
97                 StringMap fields;
98                 fields["text"] = ntext;
99                 m_client->sendNodemetaFields(m_p, "", fields);
100         }
101         void gotText(const StringMap &fields)
102         {
103                 m_client->sendNodemetaFields(m_p, "", fields);
104         }
105
106         v3s16 m_p;
107         Client *m_client;
108 };
109
110 struct TextDestPlayerInventory : public TextDest
111 {
112         TextDestPlayerInventory(Client *client)
113         {
114                 m_client = client;
115                 m_formname.clear();
116         }
117         TextDestPlayerInventory(Client *client, const std::string &formname)
118         {
119                 m_client = client;
120                 m_formname = formname;
121         }
122         void gotText(const StringMap &fields)
123         {
124                 m_client->sendInventoryFields(m_formname, fields);
125         }
126
127         Client *m_client;
128 };
129
130 struct LocalFormspecHandler : public TextDest
131 {
132         LocalFormspecHandler(const std::string &formname)
133         {
134                 m_formname = formname;
135         }
136
137         LocalFormspecHandler(const std::string &formname, Client *client):
138                 m_client(client)
139         {
140                 m_formname = formname;
141         }
142
143         void gotText(const StringMap &fields)
144         {
145                 if (m_formname == "MT_PAUSE_MENU") {
146                         if (fields.find("btn_sound") != fields.end()) {
147                                 g_gamecallback->changeVolume();
148                                 return;
149                         }
150
151                         if (fields.find("btn_key_config") != fields.end()) {
152                                 g_gamecallback->keyConfig();
153                                 return;
154                         }
155
156                         if (fields.find("btn_exit_menu") != fields.end()) {
157                                 g_gamecallback->disconnect();
158                                 return;
159                         }
160
161                         if (fields.find("btn_exit_os") != fields.end()) {
162                                 g_gamecallback->exitToOS();
163 #ifndef __ANDROID__
164                                 RenderingEngine::get_raw_device()->closeDevice();
165 #endif
166                                 return;
167                         }
168
169                         if (fields.find("btn_change_password") != fields.end()) {
170                                 g_gamecallback->changePassword();
171                                 return;
172                         }
173
174                         return;
175                 }
176
177                 if (m_formname == "MT_DEATH_SCREEN") {
178                         assert(m_client != 0);
179                         m_client->sendRespawn();
180                         return;
181                 }
182
183                 if (m_client->modsLoaded())
184                         m_client->getScript()->on_formspec_input(m_formname, fields);
185         }
186
187         Client *m_client = nullptr;
188 };
189
190 /* Form update callback */
191
192 class NodeMetadataFormSource: public IFormSource
193 {
194 public:
195         NodeMetadataFormSource(ClientMap *map, v3s16 p):
196                 m_map(map),
197                 m_p(p)
198         {
199         }
200         const std::string &getForm() const
201         {
202                 static const std::string empty_string = "";
203                 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
204
205                 if (!meta)
206                         return empty_string;
207
208                 return meta->getString("formspec");
209         }
210
211         virtual std::string resolveText(const std::string &str)
212         {
213                 NodeMetadata *meta = m_map->getNodeMetadata(m_p);
214
215                 if (!meta)
216                         return str;
217
218                 return meta->resolveString(str);
219         }
220
221         ClientMap *m_map;
222         v3s16 m_p;
223 };
224
225 class PlayerInventoryFormSource: public IFormSource
226 {
227 public:
228         PlayerInventoryFormSource(Client *client):
229                 m_client(client)
230         {
231         }
232
233         const std::string &getForm() const
234         {
235                 LocalPlayer *player = m_client->getEnv().getLocalPlayer();
236                 return player->inventory_formspec;
237         }
238
239         Client *m_client;
240 };
241
242 class NodeDugEvent : public MtEvent
243 {
244 public:
245         v3s16 p;
246         MapNode n;
247
248         NodeDugEvent(v3s16 p, MapNode n):
249                 p(p),
250                 n(n)
251         {}
252         Type getType() const { return NODE_DUG; }
253 };
254
255 class SoundMaker
256 {
257         ISoundManager *m_sound;
258         const NodeDefManager *m_ndef;
259
260 public:
261         bool makes_footstep_sound;
262         float m_player_step_timer;
263         float m_player_jump_timer;
264
265         SimpleSoundSpec m_player_step_sound;
266         SimpleSoundSpec m_player_leftpunch_sound;
267         SimpleSoundSpec m_player_rightpunch_sound;
268
269         SoundMaker(ISoundManager *sound, const NodeDefManager *ndef):
270                 m_sound(sound),
271                 m_ndef(ndef),
272                 makes_footstep_sound(true),
273                 m_player_step_timer(0.0f),
274                 m_player_jump_timer(0.0f)
275         {
276         }
277
278         void playPlayerStep()
279         {
280                 if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
281                         m_player_step_timer = 0.03;
282                         if (makes_footstep_sound)
283                                 m_sound->playSound(m_player_step_sound);
284                 }
285         }
286
287         void playPlayerJump()
288         {
289                 if (m_player_jump_timer <= 0.0f) {
290                         m_player_jump_timer = 0.2f;
291                         m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f));
292                 }
293         }
294
295         static void viewBobbingStep(MtEvent *e, void *data)
296         {
297                 SoundMaker *sm = (SoundMaker *)data;
298                 sm->playPlayerStep();
299         }
300
301         static void playerRegainGround(MtEvent *e, void *data)
302         {
303                 SoundMaker *sm = (SoundMaker *)data;
304                 sm->playPlayerStep();
305         }
306
307         static void playerJump(MtEvent *e, void *data)
308         {
309                 SoundMaker *sm = (SoundMaker *)data;
310                 sm->playPlayerJump();
311         }
312
313         static void cameraPunchLeft(MtEvent *e, void *data)
314         {
315                 SoundMaker *sm = (SoundMaker *)data;
316                 sm->m_sound->playSound(sm->m_player_leftpunch_sound);
317         }
318
319         static void cameraPunchRight(MtEvent *e, void *data)
320         {
321                 SoundMaker *sm = (SoundMaker *)data;
322                 sm->m_sound->playSound(sm->m_player_rightpunch_sound);
323         }
324
325         static void nodeDug(MtEvent *e, void *data)
326         {
327                 SoundMaker *sm = (SoundMaker *)data;
328                 NodeDugEvent *nde = (NodeDugEvent *)e;
329                 sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug);
330         }
331
332         static void playerDamage(MtEvent *e, void *data)
333         {
334                 SoundMaker *sm = (SoundMaker *)data;
335                 sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5));
336         }
337
338         static void playerFallingDamage(MtEvent *e, void *data)
339         {
340                 SoundMaker *sm = (SoundMaker *)data;
341                 sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5));
342         }
343
344         void registerReceiver(MtEventManager *mgr)
345         {
346                 mgr->reg(MtEvent::VIEW_BOBBING_STEP, SoundMaker::viewBobbingStep, this);
347                 mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, this);
348                 mgr->reg(MtEvent::PLAYER_JUMP, SoundMaker::playerJump, this);
349                 mgr->reg(MtEvent::CAMERA_PUNCH_LEFT, SoundMaker::cameraPunchLeft, this);
350                 mgr->reg(MtEvent::CAMERA_PUNCH_RIGHT, SoundMaker::cameraPunchRight, this);
351                 mgr->reg(MtEvent::NODE_DUG, SoundMaker::nodeDug, this);
352                 mgr->reg(MtEvent::PLAYER_DAMAGE, SoundMaker::playerDamage, this);
353                 mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, this);
354         }
355
356         void step(float dtime)
357         {
358                 m_player_step_timer -= dtime;
359                 m_player_jump_timer -= dtime;
360         }
361 };
362
363 // Locally stored sounds don't need to be preloaded because of this
364 class GameOnDemandSoundFetcher: public OnDemandSoundFetcher
365 {
366         std::set<std::string> m_fetched;
367 private:
368         void paths_insert(std::set<std::string> &dst_paths,
369                 const std::string &base,
370                 const std::string &name)
371         {
372                 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg");
373                 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg");
374                 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg");
375                 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg");
376                 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg");
377                 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg");
378                 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg");
379                 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg");
380                 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg");
381                 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg");
382                 dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg");
383         }
384 public:
385         void fetchSounds(const std::string &name,
386                 std::set<std::string> &dst_paths,
387                 std::set<std::string> &dst_datas)
388         {
389                 if (m_fetched.count(name))
390                         return;
391
392                 m_fetched.insert(name);
393
394                 paths_insert(dst_paths, porting::path_share, name);
395                 paths_insert(dst_paths, porting::path_user,  name);
396         }
397 };
398
399
400 typedef s32 SamplerLayer_t;
401
402
403 class GameGlobalShaderConstantSetter : public IShaderConstantSetter
404 {
405         Sky *m_sky;
406         Client *m_client;
407         bool *m_force_fog_off;
408         f32 *m_fog_range;
409         bool m_fog_enabled;
410         CachedPixelShaderSetting<float, 4> m_sky_bg_color;
411         CachedPixelShaderSetting<float> m_fog_distance;
412         CachedVertexShaderSetting<float> m_animation_timer_vertex;
413         CachedPixelShaderSetting<float> m_animation_timer_pixel;
414         CachedPixelShaderSetting<float, 3> m_day_light;
415         CachedPixelShaderSetting<float, 4> m_star_color;
416         CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
417         CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
418         CachedPixelShaderSetting<float, 3> m_minimap_yaw;
419         CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
420         CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
421         CachedPixelShaderSetting<SamplerLayer_t> m_texture0;
422         CachedPixelShaderSetting<SamplerLayer_t> m_texture1;
423         CachedPixelShaderSetting<SamplerLayer_t> m_texture2;
424         CachedPixelShaderSetting<SamplerLayer_t> m_texture3;
425         CachedPixelShaderSetting<float, 2> m_texel_size0;
426         std::array<float, 2> m_texel_size0_values;
427         CachedPixelShaderSetting<float> m_exposure_factor_pixel;
428         float m_user_exposure_factor;
429         bool m_bloom_enabled;
430         CachedPixelShaderSetting<float> m_bloom_intensity_pixel;
431         float m_bloom_intensity;
432         CachedPixelShaderSetting<float> m_bloom_radius_pixel;
433         float m_bloom_radius;
434
435 public:
436         void onSettingsChange(const std::string &name)
437         {
438                 if (name == "enable_fog")
439                         m_fog_enabled = g_settings->getBool("enable_fog");
440                 if (name == "exposure_factor")
441                         m_user_exposure_factor = g_settings->getFloat("exposure_factor", 0.1f, 10.0f);
442                 if (name == "bloom_intensity")
443                         m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f);
444                 if (name == "bloom_radius")
445                         m_bloom_radius = g_settings->getFloat("bloom_radius", 1.0f, 64.0f);
446         }
447
448         static void settingsCallback(const std::string &name, void *userdata)
449         {
450                 reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
451         }
452
453         void setSky(Sky *sky) { m_sky = sky; }
454
455         GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
456                         f32 *fog_range, Client *client) :
457                 m_sky(sky),
458                 m_client(client),
459                 m_force_fog_off(force_fog_off),
460                 m_fog_range(fog_range),
461                 m_sky_bg_color("skyBgColor"),
462                 m_fog_distance("fogDistance"),
463                 m_animation_timer_vertex("animationTimer"),
464                 m_animation_timer_pixel("animationTimer"),
465                 m_day_light("dayLight"),
466                 m_star_color("starColor"),
467                 m_eye_position_pixel("eyePosition"),
468                 m_eye_position_vertex("eyePosition"),
469                 m_minimap_yaw("yawVec"),
470                 m_camera_offset_pixel("cameraOffset"),
471                 m_camera_offset_vertex("cameraOffset"),
472                 m_texture0("texture0"),
473                 m_texture1("texture1"),
474                 m_texture2("texture2"),
475                 m_texture3("texture3"),
476                 m_texel_size0("texelSize0"),
477                 m_exposure_factor_pixel("exposureFactor"),
478                 m_bloom_intensity_pixel("bloomIntensity"),
479                 m_bloom_radius_pixel("bloomRadius")
480         {
481                 g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
482                 g_settings->registerChangedCallback("exposure_factor", settingsCallback, this);
483                 g_settings->registerChangedCallback("bloom_intensity", settingsCallback, this);
484                 g_settings->registerChangedCallback("bloom_radius", settingsCallback, this);
485                 m_fog_enabled = g_settings->getBool("enable_fog");
486                 m_user_exposure_factor = g_settings->getFloat("exposure_factor", 0.1f, 10.0f);
487                 m_bloom_enabled = g_settings->getBool("enable_bloom");
488                 m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f);
489                 m_bloom_radius = g_settings->getFloat("bloom_radius", 1.0f, 64.0f);
490         }
491
492         ~GameGlobalShaderConstantSetter()
493         {
494                 g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this);
495         }
496
497         void onSetConstants(video::IMaterialRendererServices *services) override
498         {
499                 // Background color
500                 video::SColor bgcolor = m_sky->getBgColor();
501                 video::SColorf bgcolorf(bgcolor);
502                 float bgcolorfa[4] = {
503                         bgcolorf.r,
504                         bgcolorf.g,
505                         bgcolorf.b,
506                         bgcolorf.a,
507                 };
508                 m_sky_bg_color.set(bgcolorfa, services);
509
510                 // Fog distance
511                 float fog_distance = 10000 * BS;
512
513                 if (m_fog_enabled && !*m_force_fog_off)
514                         fog_distance = *m_fog_range;
515
516                 m_fog_distance.set(&fog_distance, services);
517
518                 u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio();
519                 video::SColorf sunlight;
520                 get_sunlight_color(&sunlight, daynight_ratio);
521                 float dnc[3] = {
522                         sunlight.r,
523                         sunlight.g,
524                         sunlight.b };
525                 m_day_light.set(dnc, services);
526
527                 video::SColorf star_color = m_sky->getCurrentStarColor();
528                 float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a};
529                 m_star_color.set(clr, services);
530
531                 u32 animation_timer = m_client->getEnv().getFrameTime() % 1000000;
532                 float animation_timer_f = (float)animation_timer / 100000.f;
533                 m_animation_timer_vertex.set(&animation_timer_f, services);
534                 m_animation_timer_pixel.set(&animation_timer_f, services);
535
536                 float eye_position_array[3];
537                 v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
538                 epos.getAs3Values(eye_position_array);
539                 m_eye_position_pixel.set(eye_position_array, services);
540                 m_eye_position_vertex.set(eye_position_array, services);
541
542                 if (m_client->getMinimap()) {
543                         float minimap_yaw_array[3];
544                         v3f minimap_yaw = m_client->getMinimap()->getYawVec();
545                         minimap_yaw.getAs3Values(minimap_yaw_array);
546                         m_minimap_yaw.set(minimap_yaw_array, services);
547                 }
548
549                 float camera_offset_array[3];
550                 v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
551                 offset.getAs3Values(camera_offset_array);
552                 m_camera_offset_pixel.set(camera_offset_array, services);
553                 m_camera_offset_vertex.set(camera_offset_array, services);
554
555                 SamplerLayer_t tex_id;
556                 tex_id = 0;
557                 m_texture0.set(&tex_id, services);
558                 tex_id = 1;
559                 m_texture1.set(&tex_id, services);
560                 tex_id = 2;
561                 m_texture2.set(&tex_id, services);
562                 tex_id = 3;
563                 m_texture3.set(&tex_id, services);
564
565                 m_texel_size0.set(m_texel_size0_values.data(), services);
566
567                 float exposure_factor = RenderingEngine::DEFAULT_EXPOSURE_FACTOR * m_user_exposure_factor;
568                 if (std::isnan(exposure_factor))
569                         exposure_factor = RenderingEngine::DEFAULT_EXPOSURE_FACTOR;
570                 m_exposure_factor_pixel.set(&exposure_factor, services);
571
572                 if (m_bloom_enabled) {
573                         m_bloom_intensity_pixel.set(&m_bloom_intensity, services);
574                         m_bloom_radius_pixel.set(&m_bloom_radius, services);
575                 }
576         }
577
578         void onSetMaterial(const video::SMaterial &material)
579         {
580                 video::ITexture *texture = material.getTexture(0);
581                 if (texture) {
582                         core::dimension2du size = texture->getSize();
583                         m_texel_size0_values[0] = 1.f / size.Width;
584                         m_texel_size0_values[1] = 1.f / size.Height;
585                 }
586                 else {
587                         m_texel_size0_values[0] = 0.f;
588                         m_texel_size0_values[1] = 0.f;
589                 }
590         }
591 };
592
593
594 class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory
595 {
596         Sky *m_sky;
597         bool *m_force_fog_off;
598         f32 *m_fog_range;
599         Client *m_client;
600         std::vector<GameGlobalShaderConstantSetter *> created_nosky;
601 public:
602         GameGlobalShaderConstantSetterFactory(bool *force_fog_off,
603                         f32 *fog_range, Client *client) :
604                 m_sky(NULL),
605                 m_force_fog_off(force_fog_off),
606                 m_fog_range(fog_range),
607                 m_client(client)
608         {}
609
610         void setSky(Sky *sky) {
611                 m_sky = sky;
612                 for (GameGlobalShaderConstantSetter *ggscs : created_nosky) {
613                         ggscs->setSky(m_sky);
614                 }
615                 created_nosky.clear();
616         }
617
618         virtual IShaderConstantSetter* create()
619         {
620                 auto *scs = new GameGlobalShaderConstantSetter(
621                                 m_sky, m_force_fog_off, m_fog_range, m_client);
622                 if (!m_sky)
623                         created_nosky.push_back(scs);
624                 return scs;
625         }
626 };
627
628 #ifdef HAVE_TOUCHSCREENGUI
629 #define SIZE_TAG "size[11,5.5]"
630 #else
631 #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
632 #endif
633
634 /****************************************************************************
635  ****************************************************************************/
636
637 const static float object_hit_delay = 0.2;
638
639 struct FpsControl {
640         FpsControl() : last_time(0), busy_time(0), sleep_time(0) {}
641
642         void reset();
643
644         void limit(IrrlichtDevice *device, f32 *dtime);
645
646         u32 getBusyMs() const { return busy_time / 1000; }
647
648         // all values in microseconds (us)
649         u64 last_time, busy_time, sleep_time;
650 };
651
652
653 /* The reason the following structs are not anonymous structs within the
654  * class is that they are not used by the majority of member functions and
655  * many functions that do require objects of thse types do not modify them
656  * (so they can be passed as a const qualified parameter)
657  */
658
659 struct GameRunData {
660         u16 dig_index;
661         u16 new_playeritem;
662         PointedThing pointed_old;
663         bool digging;
664         bool punching;
665         bool btn_down_for_dig;
666         bool dig_instantly;
667         bool digging_blocked;
668         bool reset_jump_timer;
669         float nodig_delay_timer;
670         float dig_time;
671         float dig_time_complete;
672         float repeat_place_timer;
673         float object_hit_delay_timer;
674         float time_from_last_punch;
675         ClientActiveObject *selected_object;
676
677         float jump_timer_up;          // from key up until key down
678         float jump_timer_down;        // since last key down
679         float jump_timer_down_before; // from key down until key down again
680
681         float damage_flash;
682         float update_draw_list_timer;
683
684         f32 fog_range;
685
686         v3f update_draw_list_last_cam_dir;
687
688         float time_of_day_smooth;
689 };
690
691 class Game;
692
693 struct ClientEventHandler
694 {
695         void (Game::*handler)(ClientEvent *, CameraOrientation *);
696 };
697
698 /****************************************************************************
699  THE GAME
700  ****************************************************************************/
701
702 using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>;
703
704 /* This is not intended to be a public class. If a public class becomes
705  * desirable then it may be better to create another 'wrapper' class that
706  * hides most of the stuff in this class (nothing in this class is required
707  * by any other file) but exposes the public methods/data only.
708  */
709 class Game {
710 public:
711         Game();
712         ~Game();
713
714         bool startup(bool *kill,
715                         InputHandler *input,
716                         RenderingEngine *rendering_engine,
717                         const GameStartData &game_params,
718                         std::string &error_message,
719                         bool *reconnect,
720                         ChatBackend *chat_backend);
721
722         void run();
723         void shutdown();
724
725 protected:
726
727         // Basic initialisation
728         bool init(const std::string &map_dir, const std::string &address,
729                         u16 port, const SubgameSpec &gamespec);
730         bool initSound();
731         bool createSingleplayerServer(const std::string &map_dir,
732                         const SubgameSpec &gamespec, u16 port);
733
734         // Client creation
735         bool createClient(const GameStartData &start_data);
736         bool initGui();
737
738         // Client connection
739         bool connectToServer(const GameStartData &start_data,
740                         bool *connect_ok, bool *aborted);
741         bool getServerContent(bool *aborted);
742
743         // Main loop
744
745         void updateInteractTimers(f32 dtime);
746         bool checkConnection();
747         bool handleCallbacks();
748         void processQueues();
749         void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
750         void updateDebugState();
751         void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
752         void updateProfilerGraphs(ProfilerGraph *graph);
753
754         // Input related
755         void processUserInput(f32 dtime);
756         void processKeyInput();
757         void processItemSelection(u16 *new_playeritem);
758
759         void dropSelectedItem(bool single_item = false);
760         void openInventory();
761         void openConsole(float scale, const wchar_t *line=NULL);
762         void toggleFreeMove();
763         void toggleFreeMoveAlt();
764         void togglePitchMove();
765         void toggleFast();
766         void toggleNoClip();
767         void toggleCinematic();
768         void toggleBlockBounds();
769         void toggleAutoforward();
770
771         void toggleMinimap(bool shift_pressed);
772         void toggleFog();
773         void toggleDebug();
774         void toggleUpdateCamera();
775
776         void increaseViewRange();
777         void decreaseViewRange();
778         void toggleFullViewRange();
779         void checkZoomEnabled();
780
781         void updateCameraDirection(CameraOrientation *cam, float dtime);
782         void updateCameraOrientation(CameraOrientation *cam, float dtime);
783         void updatePlayerControl(const CameraOrientation &cam);
784         void step(f32 dtime);
785         void processClientEvents(CameraOrientation *cam);
786         void updateCamera(f32 dtime);
787         void updateSound(f32 dtime);
788         void processPlayerInteraction(f32 dtime, bool show_hud);
789         /*!
790          * Returns the object or node the player is pointing at.
791          * Also updates the selected thing in the Hud.
792          *
793          * @param[in]  shootline         the shootline, starting from
794          * the camera position. This also gives the maximal distance
795          * of the search.
796          * @param[in]  liquids_pointable if false, liquids are ignored
797          * @param[in]  look_for_object   if false, objects are ignored
798          * @param[in]  camera_offset     offset of the camera
799          * @param[out] selected_object   the selected object or
800          * NULL if not found
801          */
802         PointedThing updatePointedThing(
803                         const core::line3d<f32> &shootline, bool liquids_pointable,
804                         bool look_for_object, const v3s16 &camera_offset);
805         void handlePointingAtNothing(const ItemStack &playerItem);
806         void handlePointingAtNode(const PointedThing &pointed,
807                         const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
808         void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
809                         const v3f &player_position, bool show_debug);
810         void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
811                         const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
812         void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
813                         const CameraOrientation &cam);
814         void updateShadows();
815
816         // Misc
817         void showOverlayMessage(const char *msg, float dtime, int percent,
818                         bool draw_clouds = true);
819
820         static void settingChangedCallback(const std::string &setting_name, void *data);
821         void readSettings();
822
823         inline bool isKeyDown(GameKeyType k)
824         {
825                 return input->isKeyDown(k);
826         }
827         inline bool wasKeyDown(GameKeyType k)
828         {
829                 return input->wasKeyDown(k);
830         }
831         inline bool wasKeyPressed(GameKeyType k)
832         {
833                 return input->wasKeyPressed(k);
834         }
835         inline bool wasKeyReleased(GameKeyType k)
836         {
837                 return input->wasKeyReleased(k);
838         }
839
840 #ifdef __ANDROID__
841         void handleAndroidChatInput();
842 #endif
843
844 private:
845         struct Flags {
846                 bool force_fog_off = false;
847                 bool disable_camera_update = false;
848         };
849
850         void showDeathFormspec();
851         void showPauseMenu();
852
853         void pauseAnimation();
854         void resumeAnimation();
855
856         // ClientEvent handlers
857         void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
858         void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
859         void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
860         void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
861         void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
862         void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
863         void handleClientEvent_HandleParticleEvent(ClientEvent *event,
864                 CameraOrientation *cam);
865         void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam);
866         void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam);
867         void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam);
868         void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam);
869         void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam);
870         void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam);
871         void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam);
872         void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
873                 CameraOrientation *cam);
874         void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam);
875
876         void updateChat(f32 dtime);
877
878         bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
879                 const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
880                 const NodeMetadata *meta);
881         static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
882
883         f32 getSensitivityScaleFactor() const;
884
885         InputHandler *input = nullptr;
886
887         Client *client = nullptr;
888         Server *server = nullptr;
889
890         IWritableTextureSource *texture_src = nullptr;
891         IWritableShaderSource *shader_src = nullptr;
892
893         // When created, these will be filled with data received from the server
894         IWritableItemDefManager *itemdef_manager = nullptr;
895         NodeDefManager *nodedef_manager = nullptr;
896
897         GameOnDemandSoundFetcher soundfetcher; // useful when testing
898         ISoundManager *sound = nullptr;
899         bool sound_is_dummy = false;
900         SoundMaker *soundmaker = nullptr;
901
902         ChatBackend *chat_backend = nullptr;
903         LogOutputBuffer m_chat_log_buf;
904
905         EventManager *eventmgr = nullptr;
906         QuicktuneShortcutter *quicktune = nullptr;
907
908         std::unique_ptr<GameUI> m_game_ui;
909         GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop()
910         MapDrawControl *draw_control = nullptr;
911         Camera *camera = nullptr;
912         Clouds *clouds = nullptr;                         // Free using ->Drop()
913         Sky *sky = nullptr;                         // Free using ->Drop()
914         Hud *hud = nullptr;
915         Minimap *mapper = nullptr;
916
917         // Map server hud ids to client hud ids
918         std::unordered_map<u32, u32> m_hud_server_to_client;
919
920         GameRunData runData;
921         Flags m_flags;
922
923         /* 'cache'
924            This class does take ownership/responsibily for cleaning up etc of any of
925            these items (e.g. device)
926         */
927         IrrlichtDevice *device;
928         RenderingEngine *m_rendering_engine;
929         video::IVideoDriver *driver;
930         scene::ISceneManager *smgr;
931         bool *kill;
932         std::string *error_message;
933         bool *reconnect_requested;
934         scene::ISceneNode *skybox;
935         PausedNodesList paused_animated_nodes;
936
937         bool simple_singleplayer_mode;
938         /* End 'cache' */
939
940         /* Pre-calculated values
941          */
942         int crack_animation_length;
943
944         IntervalLimiter profiler_interval;
945
946         /*
947          * TODO: Local caching of settings is not optimal and should at some stage
948          *       be updated to use a global settings object for getting thse values
949          *       (as opposed to the this local caching). This can be addressed in
950          *       a later release.
951          */
952         bool m_cache_doubletap_jump;
953         bool m_cache_enable_clouds;
954         bool m_cache_enable_joysticks;
955         bool m_cache_enable_particles;
956         bool m_cache_enable_fog;
957         bool m_cache_enable_noclip;
958         bool m_cache_enable_free_move;
959         f32  m_cache_mouse_sensitivity;
960         f32  m_cache_joystick_frustum_sensitivity;
961         f32  m_repeat_place_time;
962         f32  m_cache_cam_smoothing;
963         f32  m_cache_fog_start;
964
965         bool m_invert_mouse = false;
966         bool m_first_loop_after_window_activation = false;
967         bool m_camera_offset_changed = false;
968
969         bool m_does_lost_focus_pause_game = false;
970
971         // if true, (almost) the whole game is paused
972         // this happens in pause menu in singleplayer
973         bool m_is_paused = false;
974
975 #if IRRLICHT_VERSION_MT_REVISION < 5
976         int m_reset_HW_buffer_counter = 0;
977 #endif
978
979 #ifdef HAVE_TOUCHSCREENGUI
980         bool m_cache_hold_aux1;
981         bool m_touch_use_crosshair;
982         inline bool isNoCrosshairAllowed() {
983                 return !m_touch_use_crosshair && camera->getCameraMode() == CAMERA_MODE_FIRST;
984         }
985 #endif
986 #ifdef __ANDROID__
987         bool m_android_chat_open;
988 #endif
989 };
990
991 Game::Game() :
992         m_chat_log_buf(g_logger),
993         m_game_ui(new GameUI())
994 {
995         g_settings->registerChangedCallback("doubletap_jump",
996                 &settingChangedCallback, this);
997         g_settings->registerChangedCallback("enable_clouds",
998                 &settingChangedCallback, this);
999         g_settings->registerChangedCallback("doubletap_joysticks",
1000                 &settingChangedCallback, this);
1001         g_settings->registerChangedCallback("enable_particles",
1002                 &settingChangedCallback, this);
1003         g_settings->registerChangedCallback("enable_fog",
1004                 &settingChangedCallback, this);
1005         g_settings->registerChangedCallback("mouse_sensitivity",
1006                 &settingChangedCallback, this);
1007         g_settings->registerChangedCallback("joystick_frustum_sensitivity",
1008                 &settingChangedCallback, this);
1009         g_settings->registerChangedCallback("repeat_place_time",
1010                 &settingChangedCallback, this);
1011         g_settings->registerChangedCallback("noclip",
1012                 &settingChangedCallback, this);
1013         g_settings->registerChangedCallback("free_move",
1014                 &settingChangedCallback, this);
1015         g_settings->registerChangedCallback("cinematic",
1016                 &settingChangedCallback, this);
1017         g_settings->registerChangedCallback("cinematic_camera_smoothing",
1018                 &settingChangedCallback, this);
1019         g_settings->registerChangedCallback("camera_smoothing",
1020                 &settingChangedCallback, this);
1021
1022         readSettings();
1023
1024 #ifdef HAVE_TOUCHSCREENGUI
1025         m_cache_hold_aux1 = false;      // This is initialised properly later
1026 #endif
1027
1028 }
1029
1030
1031
1032 /****************************************************************************
1033  MinetestApp Public
1034  ****************************************************************************/
1035
1036 Game::~Game()
1037 {
1038         delete client;
1039         delete soundmaker;
1040         if (!sound_is_dummy)
1041                 delete sound;
1042
1043         delete server; // deleted first to stop all server threads
1044
1045         delete hud;
1046         delete camera;
1047         delete quicktune;
1048         delete eventmgr;
1049         delete texture_src;
1050         delete shader_src;
1051         delete nodedef_manager;
1052         delete itemdef_manager;
1053         delete draw_control;
1054
1055         clearTextureNameCache();
1056
1057         g_settings->deregisterChangedCallback("doubletap_jump",
1058                 &settingChangedCallback, this);
1059         g_settings->deregisterChangedCallback("enable_clouds",
1060                 &settingChangedCallback, this);
1061         g_settings->deregisterChangedCallback("enable_particles",
1062                 &settingChangedCallback, this);
1063         g_settings->deregisterChangedCallback("enable_fog",
1064                 &settingChangedCallback, this);
1065         g_settings->deregisterChangedCallback("mouse_sensitivity",
1066                 &settingChangedCallback, this);
1067         g_settings->deregisterChangedCallback("repeat_place_time",
1068                 &settingChangedCallback, this);
1069         g_settings->deregisterChangedCallback("noclip",
1070                 &settingChangedCallback, this);
1071         g_settings->deregisterChangedCallback("free_move",
1072                 &settingChangedCallback, this);
1073         g_settings->deregisterChangedCallback("cinematic",
1074                 &settingChangedCallback, this);
1075         g_settings->deregisterChangedCallback("cinematic_camera_smoothing",
1076                 &settingChangedCallback, this);
1077         g_settings->deregisterChangedCallback("camera_smoothing",
1078                 &settingChangedCallback, this);
1079 }
1080
1081 bool Game::startup(bool *kill,
1082                 InputHandler *input,
1083                 RenderingEngine *rendering_engine,
1084                 const GameStartData &start_data,
1085                 std::string &error_message,
1086                 bool *reconnect,
1087                 ChatBackend *chat_backend)
1088 {
1089
1090         // "cache"
1091         m_rendering_engine        = rendering_engine;
1092         device                    = m_rendering_engine->get_raw_device();
1093         this->kill                = kill;
1094         this->error_message       = &error_message;
1095         reconnect_requested       = reconnect;
1096         this->input               = input;
1097         this->chat_backend        = chat_backend;
1098         simple_singleplayer_mode  = start_data.isSinglePlayer();
1099
1100         input->keycache.populate();
1101
1102         driver = device->getVideoDriver();
1103         smgr = m_rendering_engine->get_scene_manager();
1104
1105         smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1106
1107         // Reinit runData
1108         runData = GameRunData();
1109         runData.time_from_last_punch = 10.0;
1110
1111         m_game_ui->initFlags();
1112
1113         m_invert_mouse = g_settings->getBool("invert_mouse");
1114         m_first_loop_after_window_activation = true;
1115
1116 #ifdef HAVE_TOUCHSCREENGUI
1117         m_touch_use_crosshair = g_settings->getBool("touch_use_crosshair");
1118 #endif
1119
1120         g_client_translations->clear();
1121
1122         // address can change if simple_singleplayer_mode
1123         if (!init(start_data.world_spec.path, start_data.address,
1124                         start_data.socket_port, start_data.game_spec))
1125                 return false;
1126
1127         if (!createClient(start_data))
1128                 return false;
1129
1130         m_rendering_engine->initialize(client, hud);
1131
1132         return true;
1133 }
1134
1135
1136 void Game::run()
1137 {
1138         ProfilerGraph graph;
1139         RunStats stats = {};
1140         CameraOrientation cam_view_target = {};
1141         CameraOrientation cam_view = {};
1142         FpsControl draw_times;
1143         f32 dtime; // in seconds
1144
1145         /* Clear the profiler */
1146         Profiler::GraphValues dummyvalues;
1147         g_profiler->graphGet(dummyvalues);
1148
1149         draw_times.reset();
1150
1151         set_light_table(g_settings->getFloat("display_gamma"));
1152
1153 #ifdef HAVE_TOUCHSCREENGUI
1154         m_cache_hold_aux1 = g_settings->getBool("fast_move")
1155                         && client->checkPrivilege("fast");
1156 #endif
1157
1158         irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
1159                 g_settings->getU16("screen_h"));
1160
1161         while (m_rendering_engine->run()
1162                         && !(*kill || g_gamecallback->shutdown_requested
1163                         || (server && server->isShutdownRequested()))) {
1164
1165                 const irr::core::dimension2d<u32> &current_screen_size =
1166                         m_rendering_engine->get_video_driver()->getScreenSize();
1167                 // Verify if window size has changed and save it if it's the case
1168                 // Ensure evaluating settings->getBool after verifying screensize
1169                 // First condition is cheaper
1170                 if (previous_screen_size != current_screen_size &&
1171                                 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
1172                                 g_settings->getBool("autosave_screensize")) {
1173                         g_settings->setU16("screen_w", current_screen_size.Width);
1174                         g_settings->setU16("screen_h", current_screen_size.Height);
1175                         previous_screen_size = current_screen_size;
1176                 }
1177
1178                 // Calculate dtime =
1179                 //    m_rendering_engine->run() from this iteration
1180                 //  + Sleep time until the wanted FPS are reached
1181                 draw_times.limit(device, &dtime);
1182
1183                 // Prepare render data for next iteration
1184
1185                 updateStats(&stats, draw_times, dtime);
1186                 updateInteractTimers(dtime);
1187
1188                 if (!checkConnection())
1189                         break;
1190                 if (!handleCallbacks())
1191                         break;
1192
1193                 processQueues();
1194
1195                 m_game_ui->clearInfoText();
1196                 hud->resizeHotbar();
1197
1198
1199                 updateProfilers(stats, draw_times, dtime);
1200                 processUserInput(dtime);
1201                 // Update camera before player movement to avoid camera lag of one frame
1202                 updateCameraDirection(&cam_view_target, dtime);
1203                 cam_view.camera_yaw += (cam_view_target.camera_yaw -
1204                                 cam_view.camera_yaw) * m_cache_cam_smoothing;
1205                 cam_view.camera_pitch += (cam_view_target.camera_pitch -
1206                                 cam_view.camera_pitch) * m_cache_cam_smoothing;
1207                 updatePlayerControl(cam_view);
1208
1209                 {
1210                         bool was_paused = m_is_paused;
1211                         m_is_paused = simple_singleplayer_mode && g_menumgr.pausesGame();
1212                         if (m_is_paused)
1213                                 dtime = 0.0f;
1214
1215                         if (!was_paused && m_is_paused)
1216                                 pauseAnimation();
1217                         else if (was_paused && !m_is_paused)
1218                                 resumeAnimation();
1219                 }
1220
1221                 if (!m_is_paused)
1222                         step(dtime);
1223                 processClientEvents(&cam_view_target);
1224                 updateDebugState();
1225                 updateCamera(dtime);
1226                 updateSound(dtime);
1227                 processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud);
1228                 updateFrame(&graph, &stats, dtime, cam_view);
1229                 updateProfilerGraphs(&graph);
1230
1231                 // Update if minimap has been disabled by the server
1232                 m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
1233
1234                 if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
1235                         showPauseMenu();
1236                 }
1237         }
1238 }
1239
1240
1241 void Game::shutdown()
1242 {
1243         m_rendering_engine->finalize();
1244 #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8
1245         if (g_settings->get("3d_mode") == "pageflip") {
1246                 driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
1247         }
1248 #endif
1249         auto formspec = m_game_ui->getFormspecGUI();
1250         if (formspec)
1251                 formspec->quitMenu();
1252
1253 #ifdef HAVE_TOUCHSCREENGUI
1254         g_touchscreengui->hide();
1255 #endif
1256
1257         showOverlayMessage(N_("Shutting down..."), 0, 0, false);
1258
1259         if (clouds)
1260                 clouds->drop();
1261
1262         if (gui_chat_console)
1263                 gui_chat_console->drop();
1264
1265         if (sky)
1266                 sky->drop();
1267
1268         /* cleanup menus */
1269         while (g_menumgr.menuCount() > 0) {
1270                 g_menumgr.m_stack.front()->setVisible(false);
1271                 g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1272         }
1273
1274         m_game_ui->deleteFormspec();
1275
1276         chat_backend->addMessage(L"", L"# Disconnected.");
1277         chat_backend->addMessage(L"", L"");
1278         m_chat_log_buf.clear();
1279
1280         if (client) {
1281                 client->Stop();
1282                 while (!client->isShutdown()) {
1283                         assert(texture_src != NULL);
1284                         assert(shader_src != NULL);
1285                         texture_src->processQueue();
1286                         shader_src->processQueue();
1287                         sleep_ms(100);
1288                 }
1289         }
1290 }
1291
1292
1293 /****************************************************************************/
1294 /****************************************************************************
1295  Startup
1296  ****************************************************************************/
1297 /****************************************************************************/
1298
1299 bool Game::init(
1300                 const std::string &map_dir,
1301                 const std::string &address,
1302                 u16 port,
1303                 const SubgameSpec &gamespec)
1304 {
1305         texture_src = createTextureSource();
1306
1307         showOverlayMessage(N_("Loading..."), 0, 0);
1308
1309         shader_src = createShaderSource();
1310
1311         itemdef_manager = createItemDefManager();
1312         nodedef_manager = createNodeDefManager();
1313
1314         eventmgr = new EventManager();
1315         quicktune = new QuicktuneShortcutter();
1316
1317         if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1318                         && eventmgr && quicktune))
1319                 return false;
1320
1321         if (!initSound())
1322                 return false;
1323
1324         // Create a server if not connecting to an existing one
1325         if (address.empty()) {
1326                 if (!createSingleplayerServer(map_dir, gamespec, port))
1327                         return false;
1328         }
1329
1330         return true;
1331 }
1332
1333 bool Game::initSound()
1334 {
1335 #if USE_SOUND
1336         if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
1337                 infostream << "Attempting to use OpenAL audio" << std::endl;
1338                 sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
1339                 if (!sound)
1340                         infostream << "Failed to initialize OpenAL audio" << std::endl;
1341         } else
1342                 infostream << "Sound disabled." << std::endl;
1343 #endif
1344
1345         if (!sound) {
1346                 infostream << "Using dummy audio." << std::endl;
1347                 sound = &dummySoundManager;
1348                 sound_is_dummy = true;
1349         }
1350
1351         soundmaker = new SoundMaker(sound, nodedef_manager);
1352         if (!soundmaker)
1353                 return false;
1354
1355         soundmaker->registerReceiver(eventmgr);
1356
1357         return true;
1358 }
1359
1360 bool Game::createSingleplayerServer(const std::string &map_dir,
1361                 const SubgameSpec &gamespec, u16 port)
1362 {
1363         showOverlayMessage(N_("Creating server..."), 0, 5);
1364
1365         std::string bind_str = g_settings->get("bind_address");
1366         Address bind_addr(0, 0, 0, 0, port);
1367
1368         if (g_settings->getBool("ipv6_server")) {
1369                 bind_addr.setAddress((IPv6AddressBytes *) NULL);
1370         }
1371
1372         try {
1373                 bind_addr.Resolve(bind_str.c_str());
1374         } catch (ResolveError &e) {
1375                 infostream << "Resolving bind address \"" << bind_str
1376                            << "\" failed: " << e.what()
1377                            << " -- Listening on all addresses." << std::endl;
1378         }
1379
1380         if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1381                 *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled",
1382                         bind_addr.serializeString().c_str());
1383                 errorstream << *error_message << std::endl;
1384                 return false;
1385         }
1386
1387         server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr,
1388                         false, nullptr, error_message);
1389         server->start();
1390
1391         return true;
1392 }
1393
1394 bool Game::createClient(const GameStartData &start_data)
1395 {
1396         showOverlayMessage(N_("Creating client..."), 0, 10);
1397
1398         draw_control = new MapDrawControl();
1399         if (!draw_control)
1400                 return false;
1401
1402         bool could_connect, connect_aborted;
1403 #ifdef HAVE_TOUCHSCREENGUI
1404         if (g_touchscreengui) {
1405                 g_touchscreengui->init(texture_src);
1406                 g_touchscreengui->hide();
1407         }
1408 #endif
1409         if (!connectToServer(start_data, &could_connect, &connect_aborted))
1410                 return false;
1411
1412         if (!could_connect) {
1413                 if (error_message->empty() && !connect_aborted) {
1414                         // Should not happen if error messages are set properly
1415                         *error_message = gettext("Connection failed for unknown reason");
1416                         errorstream << *error_message << std::endl;
1417                 }
1418                 return false;
1419         }
1420
1421         if (!getServerContent(&connect_aborted)) {
1422                 if (error_message->empty() && !connect_aborted) {
1423                         // Should not happen if error messages are set properly
1424                         *error_message = gettext("Connection failed for unknown reason");
1425                         errorstream << *error_message << std::endl;
1426                 }
1427                 return false;
1428         }
1429
1430         auto *scsf = new GameGlobalShaderConstantSetterFactory(
1431                         &m_flags.force_fog_off, &runData.fog_range, client);
1432         shader_src->addShaderConstantSetterFactory(scsf);
1433
1434         // Update cached textures, meshes and materials
1435         client->afterContentReceived();
1436
1437         /* Camera
1438          */
1439         camera = new Camera(*draw_control, client, m_rendering_engine);
1440         if (client->modsLoaded())
1441                 client->getScript()->on_camera_ready(camera);
1442         client->setCamera(camera);
1443
1444         /* Clouds
1445          */
1446         if (m_cache_enable_clouds)
1447                 clouds = new Clouds(smgr, -1, time(0));
1448
1449         /* Skybox
1450          */
1451         sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
1452         scsf->setSky(sky);
1453         skybox = NULL;  // This is used/set later on in the main run loop
1454
1455         /* Pre-calculated values
1456          */
1457         video::ITexture *t = texture_src->getTexture("crack_anylength.png");
1458         if (t) {
1459                 v2u32 size = t->getOriginalSize();
1460                 crack_animation_length = size.Y / size.X;
1461         } else {
1462                 crack_animation_length = 5;
1463         }
1464
1465         if (!initGui())
1466                 return false;
1467
1468         /* Set window caption
1469          */
1470         std::wstring str = utf8_to_wide(PROJECT_NAME_C);
1471         str += L" ";
1472         str += utf8_to_wide(g_version_hash);
1473         {
1474                 const wchar_t *text = nullptr;
1475                 if (simple_singleplayer_mode)
1476                         text = wgettext("Singleplayer");
1477                 else
1478                         text = wgettext("Multiplayer");
1479                 str += L" [";
1480                 str += text;
1481                 str += L"]";
1482                 delete[] text;
1483         }
1484         str += L" [";
1485         str += driver->getName();
1486         str += L"]";
1487
1488         device->setWindowCaption(str.c_str());
1489
1490         LocalPlayer *player = client->getEnv().getLocalPlayer();
1491         player->hurt_tilt_timer = 0;
1492         player->hurt_tilt_strength = 0;
1493
1494         hud = new Hud(client, player, &player->inventory);
1495
1496         mapper = client->getMinimap();
1497
1498         if (mapper && client->modsLoaded())
1499                 client->getScript()->on_minimap_ready(mapper);
1500
1501         return true;
1502 }
1503
1504 bool Game::initGui()
1505 {
1506         m_game_ui->init();
1507
1508         // Remove stale "recent" chat messages from previous connections
1509         chat_backend->clearRecentChat();
1510
1511         // Make sure the size of the recent messages buffer is right
1512         chat_backend->applySettings();
1513
1514         // Chat backend and console
1515         gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
1516                         -1, chat_backend, client, &g_menumgr);
1517
1518 #ifdef HAVE_TOUCHSCREENGUI
1519
1520         if (g_touchscreengui)
1521                 g_touchscreengui->show();
1522
1523 #endif
1524
1525         return true;
1526 }
1527
1528 bool Game::connectToServer(const GameStartData &start_data,
1529                 bool *connect_ok, bool *connection_aborted)
1530 {
1531         *connect_ok = false;    // Let's not be overly optimistic
1532         *connection_aborted = false;
1533         bool local_server_mode = false;
1534
1535         showOverlayMessage(N_("Resolving address..."), 0, 15);
1536
1537         Address connect_address(0, 0, 0, 0, start_data.socket_port);
1538
1539         try {
1540                 connect_address.Resolve(start_data.address.c_str());
1541
1542                 if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
1543                         if (connect_address.isIPv6()) {
1544                                 IPv6AddressBytes addr_bytes;
1545                                 addr_bytes.bytes[15] = 1;
1546                                 connect_address.setAddress(&addr_bytes);
1547                         } else {
1548                                 connect_address.setAddress(127, 0, 0, 1);
1549                         }
1550                         local_server_mode = true;
1551                 }
1552         } catch (ResolveError &e) {
1553                 *error_message = fmtgettext("Couldn't resolve address: %s", e.what());
1554
1555                 errorstream << *error_message << std::endl;
1556                 return false;
1557         }
1558
1559         if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1560                 *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
1561                 errorstream << *error_message << std::endl;
1562                 return false;
1563         }
1564
1565         try {
1566                 client = new Client(start_data.name.c_str(),
1567                                 start_data.password, start_data.address,
1568                                 *draw_control, texture_src, shader_src,
1569                                 itemdef_manager, nodedef_manager, sound, eventmgr,
1570                                 m_rendering_engine, connect_address.isIPv6(), m_game_ui.get(),
1571                                 start_data.allow_login_or_register);
1572                 client->migrateModStorage();
1573         } catch (const BaseException &e) {
1574                 *error_message = fmtgettext("Error creating client: %s", e.what());
1575                 errorstream << *error_message << std::endl;
1576                 return false;
1577         }
1578
1579         client->m_simple_singleplayer_mode = simple_singleplayer_mode;
1580
1581         infostream << "Connecting to server at ";
1582         connect_address.print(infostream);
1583         infostream << std::endl;
1584
1585         client->connect(connect_address,
1586                 simple_singleplayer_mode || local_server_mode);
1587
1588         /*
1589                 Wait for server to accept connection
1590         */
1591
1592         try {
1593                 input->clear();
1594
1595                 FpsControl fps_control;
1596                 f32 dtime;
1597                 f32 wait_time = 0; // in seconds
1598
1599                 fps_control.reset();
1600
1601                 while (m_rendering_engine->run()) {
1602
1603                         fps_control.limit(device, &dtime);
1604
1605                         // Update client and server
1606                         client->step(dtime);
1607
1608                         if (server != NULL)
1609                                 server->step(dtime);
1610
1611                         // End condition
1612                         if (client->getState() == LC_Init) {
1613                                 *connect_ok = true;
1614                                 break;
1615                         }
1616
1617                         // Break conditions
1618                         if (*connection_aborted)
1619                                 break;
1620
1621                         if (client->accessDenied()) {
1622                                 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1623                                 *reconnect_requested = client->reconnectRequested();
1624                                 errorstream << *error_message << std::endl;
1625                                 break;
1626                         }
1627
1628                         if (input->cancelPressed()) {
1629                                 *connection_aborted = true;
1630                                 infostream << "Connect aborted [Escape]" << std::endl;
1631                                 break;
1632                         }
1633
1634                         wait_time += dtime;
1635                         // Only time out if we aren't waiting for the server we started
1636                         if (!start_data.address.empty() && wait_time > 10) {
1637                                 *error_message = gettext("Connection timed out.");
1638                                 errorstream << *error_message << std::endl;
1639                                 break;
1640                         }
1641
1642                         // Update status
1643                         showOverlayMessage(N_("Connecting to server..."), dtime, 20);
1644                 }
1645         } catch (con::PeerNotFoundException &e) {
1646                 // TODO: Should something be done here? At least an info/error
1647                 // message?
1648                 return false;
1649         }
1650
1651         return true;
1652 }
1653
1654 bool Game::getServerContent(bool *aborted)
1655 {
1656         input->clear();
1657
1658         FpsControl fps_control;
1659         f32 dtime; // in seconds
1660
1661         fps_control.reset();
1662
1663         while (m_rendering_engine->run()) {
1664
1665                 fps_control.limit(device, &dtime);
1666
1667                 // Update client and server
1668                 client->step(dtime);
1669
1670                 if (server != NULL)
1671                         server->step(dtime);
1672
1673                 // End condition
1674                 if (client->mediaReceived() && client->itemdefReceived() &&
1675                                 client->nodedefReceived()) {
1676                         break;
1677                 }
1678
1679                 // Error conditions
1680                 if (!checkConnection())
1681                         return false;
1682
1683                 if (client->getState() < LC_Init) {
1684                         *error_message = gettext("Client disconnected");
1685                         errorstream << *error_message << std::endl;
1686                         return false;
1687                 }
1688
1689                 if (input->cancelPressed()) {
1690                         *aborted = true;
1691                         infostream << "Connect aborted [Escape]" << std::endl;
1692                         return false;
1693                 }
1694
1695                 // Display status
1696                 int progress = 25;
1697
1698                 if (!client->itemdefReceived()) {
1699                         const wchar_t *text = wgettext("Item definitions...");
1700                         progress = 25;
1701                         m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1702                                 dtime, progress);
1703                         delete[] text;
1704                 } else if (!client->nodedefReceived()) {
1705                         const wchar_t *text = wgettext("Node definitions...");
1706                         progress = 30;
1707                         m_rendering_engine->draw_load_screen(text, guienv, texture_src,
1708                                 dtime, progress);
1709                         delete[] text;
1710                 } else {
1711                         std::ostringstream message;
1712                         std::fixed(message);
1713                         message.precision(0);
1714                         float receive = client->mediaReceiveProgress() * 100;
1715                         message << gettext("Media...");
1716                         if (receive > 0)
1717                                 message << " " << receive << "%";
1718                         message.precision(2);
1719
1720                         if ((USE_CURL == 0) ||
1721                                         (!g_settings->getBool("enable_remote_media_server"))) {
1722                                 float cur = client->getCurRate();
1723                                 std::string cur_unit = gettext("KiB/s");
1724
1725                                 if (cur > 900) {
1726                                         cur /= 1024.0;
1727                                         cur_unit = gettext("MiB/s");
1728                                 }
1729
1730                                 message << " (" << cur << ' ' << cur_unit << ")";
1731                         }
1732
1733                         progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
1734                         m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv,
1735                                 texture_src, dtime, progress);
1736                 }
1737         }
1738
1739         return true;
1740 }
1741
1742
1743 /****************************************************************************/
1744 /****************************************************************************
1745  Run
1746  ****************************************************************************/
1747 /****************************************************************************/
1748
1749 inline void Game::updateInteractTimers(f32 dtime)
1750 {
1751         if (runData.nodig_delay_timer >= 0)
1752                 runData.nodig_delay_timer -= dtime;
1753
1754         if (runData.object_hit_delay_timer >= 0)
1755                 runData.object_hit_delay_timer -= dtime;
1756
1757         runData.time_from_last_punch += dtime;
1758 }
1759
1760
1761 /* returns false if game should exit, otherwise true
1762  */
1763 inline bool Game::checkConnection()
1764 {
1765         if (client->accessDenied()) {
1766                 *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str());
1767                 *reconnect_requested = client->reconnectRequested();
1768                 errorstream << *error_message << std::endl;
1769                 return false;
1770         }
1771
1772         return true;
1773 }
1774
1775
1776 /* returns false if game should exit, otherwise true
1777  */
1778 inline bool Game::handleCallbacks()
1779 {
1780         if (g_gamecallback->disconnect_requested) {
1781                 g_gamecallback->disconnect_requested = false;
1782                 return false;
1783         }
1784
1785         if (g_gamecallback->changepassword_requested) {
1786                 (new GUIPasswordChange(guienv, guiroot, -1,
1787                                        &g_menumgr, client, texture_src))->drop();
1788                 g_gamecallback->changepassword_requested = false;
1789         }
1790
1791         if (g_gamecallback->changevolume_requested) {
1792                 (new GUIVolumeChange(guienv, guiroot, -1,
1793                                      &g_menumgr, texture_src))->drop();
1794                 g_gamecallback->changevolume_requested = false;
1795         }
1796
1797         if (g_gamecallback->keyconfig_requested) {
1798                 (new GUIKeyChangeMenu(guienv, guiroot, -1,
1799                                       &g_menumgr, texture_src))->drop();
1800                 g_gamecallback->keyconfig_requested = false;
1801         }
1802
1803         if (g_gamecallback->keyconfig_changed) {
1804                 input->keycache.populate(); // update the cache with new settings
1805                 g_gamecallback->keyconfig_changed = false;
1806         }
1807
1808         return true;
1809 }
1810
1811
1812 void Game::processQueues()
1813 {
1814         texture_src->processQueue();
1815         itemdef_manager->processQueue(client);
1816         shader_src->processQueue();
1817 }
1818
1819 void Game::updateDebugState()
1820 {
1821         LocalPlayer *player = client->getEnv().getLocalPlayer();
1822
1823         // debug UI and wireframe
1824         bool has_debug = client->checkPrivilege("debug");
1825         bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
1826
1827         if (m_game_ui->m_flags.show_basic_debug) {
1828                 if (!has_basic_debug)
1829                         m_game_ui->m_flags.show_basic_debug = false;
1830         } else if (m_game_ui->m_flags.show_minimal_debug) {
1831                 if (has_basic_debug)
1832                         m_game_ui->m_flags.show_basic_debug = true;
1833         }
1834         if (!has_basic_debug)
1835                 hud->disableBlockBounds();
1836         if (!has_debug)
1837                 draw_control->show_wireframe = false;
1838
1839         // noclip
1840         draw_control->allow_noclip = m_cache_enable_noclip && client->checkPrivilege("noclip");
1841 }
1842
1843 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1844                 f32 dtime)
1845 {
1846         float profiler_print_interval =
1847                         g_settings->getFloat("profiler_print_interval");
1848         bool print_to_log = true;
1849
1850         if (profiler_print_interval == 0) {
1851                 print_to_log = false;
1852                 profiler_print_interval = 3;
1853         }
1854
1855         if (profiler_interval.step(dtime, profiler_print_interval)) {
1856                 if (print_to_log) {
1857                         infostream << "Profiler:" << std::endl;
1858                         g_profiler->print(infostream);
1859                 }
1860
1861                 m_game_ui->updateProfiler();
1862                 g_profiler->clear();
1863         }
1864
1865         // Update update graphs
1866         g_profiler->graphAdd("Time non-rendering [us]",
1867                 draw_times.busy_time - stats.drawtime);
1868
1869         g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
1870         g_profiler->graphAdd("FPS", 1.0f / dtime);
1871 }
1872
1873 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1874                 f32 dtime)
1875 {
1876
1877         f32 jitter;
1878         Jitter *jp;
1879
1880         /* Time average and jitter calculation
1881          */
1882         jp = &stats->dtime_jitter;
1883         jp->avg = jp->avg * 0.96 + dtime * 0.04;
1884
1885         jitter = dtime - jp->avg;
1886
1887         if (jitter > jp->max)
1888                 jp->max = jitter;
1889
1890         jp->counter += dtime;
1891
1892         if (jp->counter > 0.0) {
1893                 jp->counter -= 3.0;
1894                 jp->max_sample = jp->max;
1895                 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1896                 jp->max = 0.0;
1897         }
1898
1899         /* Busytime average and jitter calculation
1900          */
1901         jp = &stats->busy_time_jitter;
1902         jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
1903
1904         jitter = draw_times.getBusyMs() - jp->avg;
1905
1906         if (jitter > jp->max)
1907                 jp->max = jitter;
1908         if (jitter < jp->min)
1909                 jp->min = jitter;
1910
1911         jp->counter += dtime;
1912
1913         if (jp->counter > 0.0) {
1914                 jp->counter -= 3.0;
1915                 jp->max_sample = jp->max;
1916                 jp->min_sample = jp->min;
1917                 jp->max = 0.0;
1918                 jp->min = 0.0;
1919         }
1920 }
1921
1922
1923
1924 /****************************************************************************
1925  Input handling
1926  ****************************************************************************/
1927
1928 void Game::processUserInput(f32 dtime)
1929 {
1930         // Reset input if window not active or some menu is active
1931         if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1932                 input->clear();
1933 #ifdef HAVE_TOUCHSCREENGUI
1934                 g_touchscreengui->hide();
1935 #endif
1936         }
1937 #ifdef HAVE_TOUCHSCREENGUI
1938         else if (g_touchscreengui) {
1939                 /* on touchscreengui step may generate own input events which ain't
1940                  * what we want in case we just did clear them */
1941                 g_touchscreengui->show();
1942                 g_touchscreengui->step(dtime);
1943         }
1944 #endif
1945
1946         if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1947                 gui_chat_console->closeConsoleAtOnce();
1948         }
1949
1950         // Input handler step() (used by the random input generator)
1951         input->step(dtime);
1952
1953 #ifdef __ANDROID__
1954         auto formspec = m_game_ui->getFormspecGUI();
1955         if (formspec)
1956                 formspec->getAndroidUIInput();
1957         else
1958                 handleAndroidChatInput();
1959 #endif
1960
1961         // Increase timer for double tap of "keymap_jump"
1962         if (m_cache_doubletap_jump && runData.jump_timer_up <= 0.2f)
1963                 runData.jump_timer_up += dtime;
1964         if (m_cache_doubletap_jump && runData.jump_timer_down <= 0.4f)
1965                 runData.jump_timer_down += dtime;
1966
1967         processKeyInput();
1968         processItemSelection(&runData.new_playeritem);
1969 }
1970
1971
1972 void Game::processKeyInput()
1973 {
1974         if (wasKeyDown(KeyType::DROP)) {
1975                 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1976         } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1977                 toggleAutoforward();
1978         } else if (wasKeyDown(KeyType::BACKWARD)) {
1979                 if (g_settings->getBool("continuous_forward"))
1980                         toggleAutoforward();
1981         } else if (wasKeyDown(KeyType::INVENTORY)) {
1982                 openInventory();
1983         } else if (input->cancelPressed()) {
1984 #ifdef __ANDROID__
1985                 m_android_chat_open = false;
1986 #endif
1987                 if (!gui_chat_console->isOpenInhibited()) {
1988                         showPauseMenu();
1989                 }
1990         } else if (wasKeyDown(KeyType::CHAT)) {
1991                 openConsole(0.2, L"");
1992         } else if (wasKeyDown(KeyType::CMD)) {
1993                 openConsole(0.2, L"/");
1994         } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1995                 if (client->modsLoaded())
1996                         openConsole(0.2, L".");
1997                 else
1998                         m_game_ui->showTranslatedStatusText("Client side scripting is disabled");
1999         } else if (wasKeyDown(KeyType::CONSOLE)) {
2000                 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
2001         } else if (wasKeyDown(KeyType::FREEMOVE)) {
2002                 toggleFreeMove();
2003         } else if (wasKeyDown(KeyType::JUMP)) {
2004                 toggleFreeMoveAlt();
2005         } else if (wasKeyDown(KeyType::PITCHMOVE)) {
2006                 togglePitchMove();
2007         } else if (wasKeyDown(KeyType::FASTMOVE)) {
2008                 toggleFast();
2009         } else if (wasKeyDown(KeyType::NOCLIP)) {
2010                 toggleNoClip();
2011 #if USE_SOUND
2012         } else if (wasKeyDown(KeyType::MUTE)) {
2013                 if (g_settings->getBool("enable_sound")) {
2014                         bool new_mute_sound = !g_settings->getBool("mute_sound");
2015                         g_settings->setBool("mute_sound", new_mute_sound);
2016                         if (new_mute_sound)
2017                                 m_game_ui->showTranslatedStatusText("Sound muted");
2018                         else
2019                                 m_game_ui->showTranslatedStatusText("Sound unmuted");
2020                 } else {
2021                         m_game_ui->showTranslatedStatusText("Sound system is disabled");
2022                 }
2023         } else if (wasKeyDown(KeyType::INC_VOLUME)) {
2024                 if (g_settings->getBool("enable_sound")) {
2025                         float new_volume = g_settings->getFloat("sound_volume", 0.0f, 0.9f) + 0.1f;
2026                         g_settings->setFloat("sound_volume", new_volume);
2027                         std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
2028                         m_game_ui->showStatusText(msg);
2029                 } else {
2030                         m_game_ui->showTranslatedStatusText("Sound system is disabled");
2031                 }
2032         } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
2033                 if (g_settings->getBool("enable_sound")) {
2034                         float new_volume = g_settings->getFloat("sound_volume", 0.1f, 1.0f) - 0.1f;
2035                         g_settings->setFloat("sound_volume", new_volume);
2036                         std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
2037                         m_game_ui->showStatusText(msg);
2038                 } else {
2039                         m_game_ui->showTranslatedStatusText("Sound system is disabled");
2040                 }
2041 #else
2042         } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
2043                         || wasKeyDown(KeyType::DEC_VOLUME)) {
2044                 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
2045 #endif
2046         } else if (wasKeyDown(KeyType::CINEMATIC)) {
2047                 toggleCinematic();
2048         } else if (wasKeyDown(KeyType::SCREENSHOT)) {
2049                 client->makeScreenshot();
2050         } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
2051                 toggleBlockBounds();
2052         } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
2053                 m_game_ui->toggleHud();
2054         } else if (wasKeyDown(KeyType::MINIMAP)) {
2055                 toggleMinimap(isKeyDown(KeyType::SNEAK));
2056         } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
2057                 m_game_ui->toggleChat();
2058         } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
2059                 toggleFog();
2060         } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
2061                 toggleUpdateCamera();
2062         } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
2063                 toggleDebug();
2064         } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
2065                 m_game_ui->toggleProfiler();
2066         } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
2067                 increaseViewRange();
2068         } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
2069                 decreaseViewRange();
2070         } else if (wasKeyDown(KeyType::RANGESELECT)) {
2071                 toggleFullViewRange();
2072         } else if (wasKeyDown(KeyType::ZOOM)) {
2073                 checkZoomEnabled();
2074         } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
2075                 quicktune->next();
2076         } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
2077                 quicktune->prev();
2078         } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
2079                 quicktune->inc();
2080         } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
2081                 quicktune->dec();
2082         }
2083
2084         if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
2085                 runData.reset_jump_timer = false;
2086                 runData.jump_timer_up = 0.0f;
2087         }
2088
2089         if (quicktune->hasMessage()) {
2090                 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2091         }
2092 }
2093
2094 void Game::processItemSelection(u16 *new_playeritem)
2095 {
2096         LocalPlayer *player = client->getEnv().getLocalPlayer();
2097
2098         /* Item selection using mouse wheel
2099          */
2100         *new_playeritem = player->getWieldIndex();
2101
2102         s32 wheel = input->getMouseWheel();
2103         u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2104                     player->hud_hotbar_itemcount - 1);
2105
2106         s32 dir = wheel;
2107
2108         if (wasKeyDown(KeyType::HOTBAR_NEXT))
2109                 dir = -1;
2110
2111         if (wasKeyDown(KeyType::HOTBAR_PREV))
2112                 dir = 1;
2113
2114         if (dir < 0)
2115                 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2116         else if (dir > 0)
2117                 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2118         // else dir == 0
2119
2120         /* Item selection using hotbar slot keys
2121          */
2122         for (u16 i = 0; i <= max_item; i++) {
2123                 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2124                         *new_playeritem = i;
2125                         break;
2126                 }
2127         }
2128 }
2129
2130
2131 void Game::dropSelectedItem(bool single_item)
2132 {
2133         IDropAction *a = new IDropAction();
2134         a->count = single_item ? 1 : 0;
2135         a->from_inv.setCurrentPlayer();
2136         a->from_list = "main";
2137         a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2138         client->inventoryAction(a);
2139 }
2140
2141
2142 void Game::openInventory()
2143 {
2144         /*
2145          * Don't permit to open inventory is CAO or player doesn't exists.
2146          * This prevent showing an empty inventory at player load
2147          */
2148
2149         LocalPlayer *player = client->getEnv().getLocalPlayer();
2150         if (!player || !player->getCAO())
2151                 return;
2152
2153         infostream << "Game: Launching inventory" << std::endl;
2154
2155         PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2156
2157         InventoryLocation inventoryloc;
2158         inventoryloc.setCurrentPlayer();
2159
2160         if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2161                 delete fs_src;
2162                 return;
2163         }
2164
2165         if (fs_src->getForm().empty()) {
2166                 delete fs_src;
2167                 return;
2168         }
2169
2170         TextDest *txt_dst = new TextDestPlayerInventory(client);
2171         auto *&formspec = m_game_ui->updateFormspec("");
2172         GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2173                 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2174
2175         formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2176 }
2177
2178
2179 void Game::openConsole(float scale, const wchar_t *line)
2180 {
2181         assert(scale > 0.0f && scale <= 1.0f);
2182
2183 #ifdef __ANDROID__
2184         porting::showInputDialog(gettext("ok"), "", "", 2);
2185         m_android_chat_open = true;
2186 #else
2187         if (gui_chat_console->isOpenInhibited())
2188                 return;
2189         gui_chat_console->openConsole(scale);
2190         if (line) {
2191                 gui_chat_console->setCloseOnEnter(true);
2192                 gui_chat_console->replaceAndAddToHistory(line);
2193         }
2194 #endif
2195 }
2196
2197 #ifdef __ANDROID__
2198 void Game::handleAndroidChatInput()
2199 {
2200         if (m_android_chat_open && porting::getInputDialogState() == 0) {
2201                 std::string text = porting::getInputDialogValue();
2202                 client->typeChatMessage(utf8_to_wide(text));
2203                 m_android_chat_open = false;
2204         }
2205 }
2206 #endif
2207
2208
2209 void Game::toggleFreeMove()
2210 {
2211         bool free_move = !g_settings->getBool("free_move");
2212         g_settings->set("free_move", bool_to_cstr(free_move));
2213
2214         if (free_move) {
2215                 if (client->checkPrivilege("fly")) {
2216                         m_game_ui->showTranslatedStatusText("Fly mode enabled");
2217                 } else {
2218                         m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2219                 }
2220         } else {
2221                 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2222         }
2223 }
2224
2225 void Game::toggleFreeMoveAlt()
2226 {
2227         if (!runData.reset_jump_timer) {
2228                 runData.jump_timer_down_before = runData.jump_timer_down;
2229                 runData.jump_timer_down = 0.0f;
2230         }
2231
2232         // key down (0.2 s max.), then key up (0.2 s max.), then key down
2233         if (m_cache_doubletap_jump && runData.jump_timer_up < 0.2f &&
2234                         runData.jump_timer_down_before < 0.4f) // 0.2 + 0.2
2235                 toggleFreeMove();
2236
2237         runData.reset_jump_timer = true;
2238 }
2239
2240
2241 void Game::togglePitchMove()
2242 {
2243         bool pitch_move = !g_settings->getBool("pitch_move");
2244         g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2245
2246         if (pitch_move) {
2247                 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2248         } else {
2249                 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2250         }
2251 }
2252
2253
2254 void Game::toggleFast()
2255 {
2256         bool fast_move = !g_settings->getBool("fast_move");
2257         bool has_fast_privs = client->checkPrivilege("fast");
2258         g_settings->set("fast_move", bool_to_cstr(fast_move));
2259
2260         if (fast_move) {
2261                 if (has_fast_privs) {
2262                         m_game_ui->showTranslatedStatusText("Fast mode enabled");
2263                 } else {
2264                         m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2265                 }
2266         } else {
2267                 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2268         }
2269
2270 #ifdef HAVE_TOUCHSCREENGUI
2271         m_cache_hold_aux1 = fast_move && has_fast_privs;
2272 #endif
2273 }
2274
2275
2276 void Game::toggleNoClip()
2277 {
2278         bool noclip = !g_settings->getBool("noclip");
2279         g_settings->set("noclip", bool_to_cstr(noclip));
2280
2281         if (noclip) {
2282                 if (client->checkPrivilege("noclip")) {
2283                         m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2284                 } else {
2285                         m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2286                 }
2287         } else {
2288                 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2289         }
2290 }
2291
2292 void Game::toggleCinematic()
2293 {
2294         bool cinematic = !g_settings->getBool("cinematic");
2295         g_settings->set("cinematic", bool_to_cstr(cinematic));
2296
2297         if (cinematic)
2298                 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2299         else
2300                 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2301 }
2302
2303 void Game::toggleBlockBounds()
2304 {
2305         LocalPlayer *player = client->getEnv().getLocalPlayer();
2306         if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
2307                 m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
2308                 return;
2309         }
2310         enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
2311         switch (newmode) {
2312                 case Hud::BLOCK_BOUNDS_OFF:
2313                         m_game_ui->showTranslatedStatusText("Block bounds hidden");
2314                         break;
2315                 case Hud::BLOCK_BOUNDS_CURRENT:
2316                         m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
2317                         break;
2318                 case Hud::BLOCK_BOUNDS_NEAR:
2319                         m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
2320                         break;
2321                 case Hud::BLOCK_BOUNDS_MAX:
2322                         m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
2323                         break;
2324                 default:
2325                         break;
2326         }
2327 }
2328
2329 // Autoforward by toggling continuous forward.
2330 void Game::toggleAutoforward()
2331 {
2332         bool autorun_enabled = !g_settings->getBool("continuous_forward");
2333         g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2334
2335         if (autorun_enabled)
2336                 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2337         else
2338                 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2339 }
2340
2341 void Game::toggleMinimap(bool shift_pressed)
2342 {
2343         if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2344                 return;
2345
2346         if (shift_pressed)
2347                 mapper->toggleMinimapShape();
2348         else
2349                 mapper->nextMode();
2350
2351         // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2352
2353         // Not so satisying code to keep compatibility with old fixed mode system
2354         // -->
2355         u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2356
2357         if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2358                 m_game_ui->m_flags.show_minimap = false;
2359         } else {
2360
2361         // If radar is disabled, try to find a non radar mode or fall back to 0
2362                 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2363                         while (mapper->getModeIndex() &&
2364                                         mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2365                                 mapper->nextMode();
2366
2367                 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2368                                 MINIMAP_TYPE_OFF;
2369         }
2370         // <--
2371         // End of 'not so satifying code'
2372         if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2373                         (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2374                 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2375         else
2376                 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2377 }
2378
2379 void Game::toggleFog()
2380 {
2381         bool fog_enabled = g_settings->getBool("enable_fog");
2382         g_settings->setBool("enable_fog", !fog_enabled);
2383         if (fog_enabled)
2384                 m_game_ui->showTranslatedStatusText("Fog disabled");
2385         else
2386                 m_game_ui->showTranslatedStatusText("Fog enabled");
2387 }
2388
2389
2390 void Game::toggleDebug()
2391 {
2392         LocalPlayer *player = client->getEnv().getLocalPlayer();
2393         bool has_debug = client->checkPrivilege("debug");
2394         bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
2395         // Initial: No debug info
2396         // 1x toggle: Debug text
2397         // 2x toggle: Debug text with profiler graph
2398         // 3x toggle: Debug text and wireframe (needs "debug" priv)
2399         // Next toggle: Back to initial
2400         //
2401         // The debug text can be in 2 modes: minimal and basic.
2402         // * Minimal: Only technical client info that not gameplay-relevant
2403         // * Basic: Info that might give gameplay advantage, e.g. pos, angle
2404         // Basic mode is used when player has the debug HUD flag set,
2405         // otherwise the Minimal mode is used.
2406         if (!m_game_ui->m_flags.show_minimal_debug) {
2407                 m_game_ui->m_flags.show_minimal_debug = true;
2408                 if (has_basic_debug)
2409                         m_game_ui->m_flags.show_basic_debug = true;
2410                 m_game_ui->m_flags.show_profiler_graph = false;
2411                 draw_control->show_wireframe = false;
2412                 m_game_ui->showTranslatedStatusText("Debug info shown");
2413         } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2414                 if (has_basic_debug)
2415                         m_game_ui->m_flags.show_basic_debug = true;
2416                 m_game_ui->m_flags.show_profiler_graph = true;
2417                 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2418         } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2419                 if (has_basic_debug)
2420                         m_game_ui->m_flags.show_basic_debug = true;
2421                 m_game_ui->m_flags.show_profiler_graph = false;
2422                 draw_control->show_wireframe = true;
2423                 m_game_ui->showTranslatedStatusText("Wireframe shown");
2424         } else {
2425                 m_game_ui->m_flags.show_minimal_debug = false;
2426                 m_game_ui->m_flags.show_basic_debug = false;
2427                 m_game_ui->m_flags.show_profiler_graph = false;
2428                 draw_control->show_wireframe = false;
2429                 if (has_debug) {
2430                         m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2431                 } else {
2432                         m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2433                 }
2434         }
2435 }
2436
2437
2438 void Game::toggleUpdateCamera()
2439 {
2440         m_flags.disable_camera_update = !m_flags.disable_camera_update;
2441         if (m_flags.disable_camera_update)
2442                 m_game_ui->showTranslatedStatusText("Camera update disabled");
2443         else
2444                 m_game_ui->showTranslatedStatusText("Camera update enabled");
2445 }
2446
2447
2448 void Game::increaseViewRange()
2449 {
2450         s16 range = g_settings->getS16("viewing_range");
2451         s16 range_new = range + 10;
2452
2453         if (range_new > 4000) {
2454                 range_new = 4000;
2455                 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
2456                 m_game_ui->showStatusText(msg);
2457         } else {
2458                 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2459                 m_game_ui->showStatusText(msg);
2460         }
2461         g_settings->set("viewing_range", itos(range_new));
2462 }
2463
2464
2465 void Game::decreaseViewRange()
2466 {
2467         s16 range = g_settings->getS16("viewing_range");
2468         s16 range_new = range - 10;
2469
2470         if (range_new < 20) {
2471                 range_new = 20;
2472                 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
2473                 m_game_ui->showStatusText(msg);
2474         } else {
2475                 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2476                 m_game_ui->showStatusText(msg);
2477         }
2478         g_settings->set("viewing_range", itos(range_new));
2479 }
2480
2481
2482 void Game::toggleFullViewRange()
2483 {
2484         draw_control->range_all = !draw_control->range_all;
2485         if (draw_control->range_all)
2486                 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2487         else
2488                 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2489 }
2490
2491
2492 void Game::checkZoomEnabled()
2493 {
2494         LocalPlayer *player = client->getEnv().getLocalPlayer();
2495         if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2496                 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2497 }
2498
2499 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2500 {
2501         if ((device->isWindowActive() && device->isWindowFocused()
2502                         && !isMenuActive()) || input->isRandom()) {
2503
2504 #ifndef __ANDROID__
2505                 if (!input->isRandom()) {
2506                         // Mac OSX gets upset if this is set every frame
2507                         if (device->getCursorControl()->isVisible())
2508                                 device->getCursorControl()->setVisible(false);
2509                 }
2510 #endif
2511
2512                 if (m_first_loop_after_window_activation) {
2513                         m_first_loop_after_window_activation = false;
2514
2515                         input->setMousePos(driver->getScreenSize().Width / 2,
2516                                 driver->getScreenSize().Height / 2);
2517                 } else {
2518                         updateCameraOrientation(cam, dtime);
2519                 }
2520
2521         } else {
2522
2523 #ifndef ANDROID
2524                 // Mac OSX gets upset if this is set every frame
2525                 if (!device->getCursorControl()->isVisible())
2526                         device->getCursorControl()->setVisible(true);
2527 #endif
2528
2529                 m_first_loop_after_window_activation = true;
2530
2531         }
2532 }
2533
2534 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2535 // responsiveness independently of FOV.
2536 f32 Game::getSensitivityScaleFactor() const
2537 {
2538         f32 fov_y = client->getCamera()->getFovY();
2539
2540         // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2541         // 16:9 aspect ratio to minimize disruption of existing sensitivity
2542         // settings.
2543         return tan(fov_y / 2.0f) * 1.3763818698f;
2544 }
2545
2546 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2547 {
2548 #ifdef HAVE_TOUCHSCREENGUI
2549         if (g_touchscreengui) {
2550                 cam->camera_yaw   += g_touchscreengui->getYawChange();
2551                 cam->camera_pitch  = g_touchscreengui->getPitch();
2552         } else {
2553 #endif
2554                 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2555                 v2s32 dist = input->getMousePos() - center;
2556
2557                 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2558                         dist.Y = -dist.Y;
2559                 }
2560
2561                 f32 sens_scale = getSensitivityScaleFactor();
2562                 cam->camera_yaw   -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2563                 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2564
2565                 if (dist.X != 0 || dist.Y != 0)
2566                         input->setMousePos(center.X, center.Y);
2567 #ifdef HAVE_TOUCHSCREENGUI
2568         }
2569 #endif
2570
2571         if (m_cache_enable_joysticks) {
2572                 f32 sens_scale = getSensitivityScaleFactor();
2573                 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
2574                 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2575                 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2576         }
2577
2578         cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2579 }
2580
2581
2582 void Game::updatePlayerControl(const CameraOrientation &cam)
2583 {
2584         LocalPlayer *player = client->getEnv().getLocalPlayer();
2585
2586         //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2587
2588         PlayerControl control(
2589                 isKeyDown(KeyType::FORWARD),
2590                 isKeyDown(KeyType::BACKWARD),
2591                 isKeyDown(KeyType::LEFT),
2592                 isKeyDown(KeyType::RIGHT),
2593                 isKeyDown(KeyType::JUMP) || player->getAutojump(),
2594                 isKeyDown(KeyType::AUX1),
2595                 isKeyDown(KeyType::SNEAK),
2596                 isKeyDown(KeyType::ZOOM),
2597                 isKeyDown(KeyType::DIG),
2598                 isKeyDown(KeyType::PLACE),
2599                 cam.camera_pitch,
2600                 cam.camera_yaw,
2601                 input->getMovementSpeed(),
2602                 input->getMovementDirection()
2603         );
2604
2605         // autoforward if set: move at maximum speed
2606         if (player->getPlayerSettings().continuous_forward &&
2607                         client->activeObjectsReceived() && !player->isDead()) {
2608                 control.movement_speed = 1.0f;
2609                 // sideways movement only
2610                 float dx = sin(control.movement_direction);
2611                 control.movement_direction = atan2(dx, 1.0f);
2612         }
2613
2614 #ifdef HAVE_TOUCHSCREENGUI
2615         /* For touch, simulate holding down AUX1 (fast move) if the user has
2616          * the fast_move setting toggled on. If there is an aux1 key defined for
2617          * touch then its meaning is inverted (i.e. holding aux1 means walk and
2618          * not fast)
2619          */
2620         if (m_cache_hold_aux1) {
2621                 control.aux1 = control.aux1 ^ true;
2622         }
2623 #endif
2624
2625         client->setPlayerControl(control);
2626
2627         //tt.stop();
2628 }
2629
2630
2631 inline void Game::step(f32 dtime)
2632 {
2633         if (server)
2634                 server->step(dtime);
2635
2636         client->step(dtime);
2637 }
2638
2639 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2640         if (!node)
2641                 return;
2642         for (auto &&child: node->getChildren())
2643                 pauseNodeAnimation(paused, child);
2644         if (node->getType() != scene::ESNT_ANIMATED_MESH)
2645                 return;
2646         auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2647         float speed = animated_node->getAnimationSpeed();
2648         if (!speed)
2649                 return;
2650         paused.push_back({grab(animated_node), speed});
2651         animated_node->setAnimationSpeed(0.0f);
2652 }
2653
2654 void Game::pauseAnimation()
2655 {
2656         pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2657 }
2658
2659 void Game::resumeAnimation()
2660 {
2661         for (auto &&pair: paused_animated_nodes)
2662                 pair.first->setAnimationSpeed(pair.second);
2663         paused_animated_nodes.clear();
2664 }
2665
2666 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2667         {&Game::handleClientEvent_None},
2668         {&Game::handleClientEvent_PlayerDamage},
2669         {&Game::handleClientEvent_PlayerForceMove},
2670         {&Game::handleClientEvent_Deathscreen},
2671         {&Game::handleClientEvent_ShowFormSpec},
2672         {&Game::handleClientEvent_ShowLocalFormSpec},
2673         {&Game::handleClientEvent_HandleParticleEvent},
2674         {&Game::handleClientEvent_HandleParticleEvent},
2675         {&Game::handleClientEvent_HandleParticleEvent},
2676         {&Game::handleClientEvent_HudAdd},
2677         {&Game::handleClientEvent_HudRemove},
2678         {&Game::handleClientEvent_HudChange},
2679         {&Game::handleClientEvent_SetSky},
2680         {&Game::handleClientEvent_SetSun},
2681         {&Game::handleClientEvent_SetMoon},
2682         {&Game::handleClientEvent_SetStars},
2683         {&Game::handleClientEvent_OverrideDayNigthRatio},
2684         {&Game::handleClientEvent_CloudParams},
2685 };
2686
2687 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2688 {
2689         FATAL_ERROR("ClientEvent type None received");
2690 }
2691
2692 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2693 {
2694         if (client->modsLoaded())
2695                 client->getScript()->on_damage_taken(event->player_damage.amount);
2696
2697         if (!event->player_damage.effect)
2698                 return;
2699
2700         // Damage flash and hurt tilt are not used at death
2701         if (client->getHP() > 0) {
2702                 LocalPlayer *player = client->getEnv().getLocalPlayer();
2703
2704                 f32 hp_max = player->getCAO() ?
2705                         player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2706                 f32 damage_ratio = event->player_damage.amount / hp_max;
2707
2708                 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2709                 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2710
2711                 player->hurt_tilt_timer = 1.5f;
2712                 player->hurt_tilt_strength =
2713                         rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2714         }
2715
2716         // Play damage sound
2717         client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2718 }
2719
2720 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2721 {
2722         cam->camera_yaw = event->player_force_move.yaw;
2723         cam->camera_pitch = event->player_force_move.pitch;
2724 }
2725
2726 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2727 {
2728         // If client scripting is enabled, deathscreen is handled by CSM code in
2729         // builtin/client/init.lua
2730         if (client->modsLoaded())
2731                 client->getScript()->on_death();
2732         else
2733                 showDeathFormspec();
2734
2735         /* Handle visualization */
2736         LocalPlayer *player = client->getEnv().getLocalPlayer();
2737         runData.damage_flash = 0;
2738         player->hurt_tilt_timer = 0;
2739         player->hurt_tilt_strength = 0;
2740 }
2741
2742 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2743 {
2744         if (event->show_formspec.formspec->empty()) {
2745                 auto formspec = m_game_ui->getFormspecGUI();
2746                 if (formspec && (event->show_formspec.formname->empty()
2747                                 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2748                         formspec->quitMenu();
2749                 }
2750         } else {
2751                 FormspecFormSource *fs_src =
2752                         new FormspecFormSource(*(event->show_formspec.formspec));
2753                 TextDestPlayerInventory *txt_dst =
2754                         new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2755
2756                 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2757                 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2758                         &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2759         }
2760
2761         delete event->show_formspec.formspec;
2762         delete event->show_formspec.formname;
2763 }
2764
2765 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2766 {
2767         FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2768         LocalFormspecHandler *txt_dst =
2769                 new LocalFormspecHandler(*event->show_formspec.formname, client);
2770         GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2771                         &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2772
2773         delete event->show_formspec.formspec;
2774         delete event->show_formspec.formname;
2775 }
2776
2777 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2778                 CameraOrientation *cam)
2779 {
2780         LocalPlayer *player = client->getEnv().getLocalPlayer();
2781         client->getParticleManager()->handleParticleEvent(event, client, player);
2782 }
2783
2784 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2785 {
2786         LocalPlayer *player = client->getEnv().getLocalPlayer();
2787
2788         u32 server_id = event->hudadd->server_id;
2789         // ignore if we already have a HUD with that ID
2790         auto i = m_hud_server_to_client.find(server_id);
2791         if (i != m_hud_server_to_client.end()) {
2792                 delete event->hudadd;
2793                 return;
2794         }
2795
2796         HudElement *e = new HudElement;
2797         e->type   = static_cast<HudElementType>(event->hudadd->type);
2798         e->pos    = event->hudadd->pos;
2799         e->name   = event->hudadd->name;
2800         e->scale  = event->hudadd->scale;
2801         e->text   = event->hudadd->text;
2802         e->number = event->hudadd->number;
2803         e->item   = event->hudadd->item;
2804         e->dir    = event->hudadd->dir;
2805         e->align  = event->hudadd->align;
2806         e->offset = event->hudadd->offset;
2807         e->world_pos = event->hudadd->world_pos;
2808         e->size      = event->hudadd->size;
2809         e->z_index   = event->hudadd->z_index;
2810         e->text2     = event->hudadd->text2;
2811         e->style     = event->hudadd->style;
2812         m_hud_server_to_client[server_id] = player->addHud(e);
2813
2814         delete event->hudadd;
2815 }
2816
2817 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2818 {
2819         LocalPlayer *player = client->getEnv().getLocalPlayer();
2820
2821         auto i = m_hud_server_to_client.find(event->hudrm.id);
2822         if (i != m_hud_server_to_client.end()) {
2823                 HudElement *e = player->removeHud(i->second);
2824                 delete e;
2825                 m_hud_server_to_client.erase(i);
2826         }
2827
2828 }
2829
2830 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2831 {
2832         LocalPlayer *player = client->getEnv().getLocalPlayer();
2833
2834         HudElement *e = nullptr;
2835
2836         auto i = m_hud_server_to_client.find(event->hudchange->id);
2837         if (i != m_hud_server_to_client.end()) {
2838                 e = player->getHud(i->second);
2839         }
2840
2841         if (e == nullptr) {
2842                 delete event->hudchange;
2843                 return;
2844         }
2845
2846 #define CASE_SET(statval, prop, dataprop) \
2847         case statval: \
2848                 e->prop = event->hudchange->dataprop; \
2849                 break
2850
2851         switch (event->hudchange->stat) {
2852                 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2853
2854                 CASE_SET(HUD_STAT_NAME, name, sdata);
2855
2856                 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2857
2858                 CASE_SET(HUD_STAT_TEXT, text, sdata);
2859
2860                 CASE_SET(HUD_STAT_NUMBER, number, data);
2861
2862                 CASE_SET(HUD_STAT_ITEM, item, data);
2863
2864                 CASE_SET(HUD_STAT_DIR, dir, data);
2865
2866                 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2867
2868                 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2869
2870                 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2871
2872                 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2873
2874                 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2875
2876                 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2877
2878                 CASE_SET(HUD_STAT_STYLE, style, data);
2879         }
2880
2881 #undef CASE_SET
2882
2883         delete event->hudchange;
2884 }
2885
2886 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2887 {
2888         sky->setVisible(false);
2889         // Whether clouds are visible in front of a custom skybox.
2890         sky->setCloudsEnabled(event->set_sky->clouds);
2891
2892         if (skybox) {
2893                 skybox->remove();
2894                 skybox = NULL;
2895         }
2896         // Clear the old textures out in case we switch rendering type.
2897         sky->clearSkyboxTextures();
2898         // Handle according to type
2899         if (event->set_sky->type == "regular") {
2900                 // Shows the mesh skybox
2901                 sky->setVisible(true);
2902                 // Update mesh based skybox colours if applicable.
2903                 sky->setSkyColors(event->set_sky->sky_color);
2904                 sky->setHorizonTint(
2905                         event->set_sky->fog_sun_tint,
2906                         event->set_sky->fog_moon_tint,
2907                         event->set_sky->fog_tint_type
2908                 );
2909         } else if (event->set_sky->type == "skybox" &&
2910                         event->set_sky->textures.size() == 6) {
2911                 // Disable the dyanmic mesh skybox:
2912                 sky->setVisible(false);
2913                 // Set fog colors:
2914                 sky->setFallbackBgColor(event->set_sky->bgcolor);
2915                 // Set sunrise and sunset fog tinting:
2916                 sky->setHorizonTint(
2917                         event->set_sky->fog_sun_tint,
2918                         event->set_sky->fog_moon_tint,
2919                         event->set_sky->fog_tint_type
2920                 );
2921                 // Add textures to skybox.
2922                 for (int i = 0; i < 6; i++)
2923                         sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2924         } else {
2925                 // Handle everything else as plain color.
2926                 if (event->set_sky->type != "plain")
2927                         infostream << "Unknown sky type: "
2928                                 << (event->set_sky->type) << std::endl;
2929                 sky->setVisible(false);
2930                 sky->setFallbackBgColor(event->set_sky->bgcolor);
2931                 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2932                 sky->setHorizonTint(
2933                         event->set_sky->bgcolor,
2934                         event->set_sky->bgcolor,
2935                         "custom"
2936                 );
2937         }
2938
2939         delete event->set_sky;
2940 }
2941
2942 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2943 {
2944         sky->setSunVisible(event->sun_params->visible);
2945         sky->setSunTexture(event->sun_params->texture,
2946                 event->sun_params->tonemap, texture_src);
2947         sky->setSunScale(event->sun_params->scale);
2948         sky->setSunriseVisible(event->sun_params->sunrise_visible);
2949         sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2950         delete event->sun_params;
2951 }
2952
2953 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2954 {
2955         sky->setMoonVisible(event->moon_params->visible);
2956         sky->setMoonTexture(event->moon_params->texture,
2957                 event->moon_params->tonemap, texture_src);
2958         sky->setMoonScale(event->moon_params->scale);
2959         delete event->moon_params;
2960 }
2961
2962 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2963 {
2964         sky->setStarsVisible(event->star_params->visible);
2965         sky->setStarCount(event->star_params->count);
2966         sky->setStarColor(event->star_params->starcolor);
2967         sky->setStarScale(event->star_params->scale);
2968         sky->setStarDayOpacity(event->star_params->day_opacity);
2969         delete event->star_params;
2970 }
2971
2972 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2973                 CameraOrientation *cam)
2974 {
2975         client->getEnv().setDayNightRatioOverride(
2976                 event->override_day_night_ratio.do_override,
2977                 event->override_day_night_ratio.ratio_f * 1000.0f);
2978 }
2979
2980 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2981 {
2982         if (!clouds)
2983                 return;
2984
2985         clouds->setDensity(event->cloud_params.density);
2986         clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2987         clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2988         clouds->setHeight(event->cloud_params.height);
2989         clouds->setThickness(event->cloud_params.thickness);
2990         clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2991 }
2992
2993 void Game::processClientEvents(CameraOrientation *cam)
2994 {
2995         while (client->hasClientEvents()) {
2996                 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2997                 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2998                 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2999                 (this->*evHandler.handler)(event.get(), cam);
3000         }
3001 }
3002
3003 void Game::updateChat(f32 dtime)
3004 {
3005         // Get new messages from error log buffer
3006         while (!m_chat_log_buf.empty())
3007                 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
3008
3009         // Get new messages from client
3010         std::wstring message;
3011         while (client->getChatMessage(message)) {
3012                 chat_backend->addUnparsedMessage(message);
3013         }
3014
3015         // Remove old messages
3016         chat_backend->step(dtime);
3017
3018         // Display all messages in a static text element
3019         auto &buf = chat_backend->getRecentBuffer();
3020         if (buf.getLinesModified()) {
3021                 buf.resetLinesModified();
3022                 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
3023         }
3024
3025         // Make sure that the size is still correct
3026         m_game_ui->updateChatSize();
3027 }
3028
3029 void Game::updateCamera(f32 dtime)
3030 {
3031         LocalPlayer *player = client->getEnv().getLocalPlayer();
3032
3033         /*
3034                 For interaction purposes, get info about the held item
3035                 - What item is it?
3036                 - Is it a usable item?
3037                 - Can it point to liquids?
3038         */
3039         ItemStack playeritem;
3040         {
3041                 ItemStack selected, hand;
3042                 playeritem = player->getWieldedItem(&selected, &hand);
3043         }
3044
3045         ToolCapabilities playeritem_toolcap =
3046                 playeritem.getToolCapabilities(itemdef_manager);
3047
3048         v3s16 old_camera_offset = camera->getOffset();
3049
3050         if (wasKeyDown(KeyType::CAMERA_MODE)) {
3051                 GenericCAO *playercao = player->getCAO();
3052
3053                 // If playercao not loaded, don't change camera
3054                 if (!playercao)
3055                         return;
3056
3057                 camera->toggleCameraMode();
3058
3059 #ifdef HAVE_TOUCHSCREENGUI
3060                 if (g_touchscreengui)
3061                         g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed());
3062 #endif
3063
3064                 // Make the player visible depending on camera mode.
3065                 playercao->updateMeshCulling();
3066                 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
3067         }
3068
3069         float full_punch_interval = playeritem_toolcap.full_punch_interval;
3070         float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
3071
3072         tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
3073         camera->update(player, dtime, tool_reload_ratio);
3074         camera->step(dtime);
3075
3076         f32 camera_fov = camera->getFovMax();
3077         v3s16 camera_offset = camera->getOffset();
3078
3079         m_camera_offset_changed = (camera_offset != old_camera_offset);
3080
3081         if (!m_flags.disable_camera_update) {
3082                 v3f camera_position = camera->getPosition();
3083                 v3f camera_direction = camera->getDirection();
3084
3085                 client->getEnv().getClientMap().updateCamera(camera_position,
3086                                 camera_direction, camera_fov, camera_offset);
3087
3088                 if (m_camera_offset_changed) {
3089                         client->updateCameraOffset(camera_offset);
3090                         client->getEnv().updateCameraOffset(camera_offset);
3091
3092                         if (clouds)
3093                                 clouds->updateCameraOffset(camera_offset);
3094                 }
3095         }
3096 }
3097
3098
3099 void Game::updateSound(f32 dtime)
3100 {
3101         // Update sound listener
3102         v3s16 camera_offset = camera->getOffset();
3103         sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3104                               v3f(0, 0, 0), // velocity
3105                               camera->getDirection(),
3106                               camera->getCameraNode()->getUpVector());
3107
3108         bool mute_sound = g_settings->getBool("mute_sound");
3109         if (mute_sound) {
3110                 sound->setListenerGain(0.0f);
3111         } else {
3112                 // Check if volume is in the proper range, else fix it.
3113                 float old_volume = g_settings->getFloat("sound_volume");
3114                 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3115                 sound->setListenerGain(new_volume);
3116
3117                 if (old_volume != new_volume) {
3118                         g_settings->setFloat("sound_volume", new_volume);
3119                 }
3120         }
3121
3122         LocalPlayer *player = client->getEnv().getLocalPlayer();
3123
3124         // Tell the sound maker whether to make footstep sounds
3125         soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3126
3127         //      Update sound maker
3128         if (player->makes_footstep_sound)
3129                 soundmaker->step(dtime);
3130
3131         ClientMap &map = client->getEnv().getClientMap();
3132         MapNode n = map.getNode(player->getFootstepNodePos());
3133         soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3134 }
3135
3136
3137 void Game::processPlayerInteraction(f32 dtime, bool show_hud)
3138 {
3139         LocalPlayer *player = client->getEnv().getLocalPlayer();
3140
3141         const v3f camera_direction = camera->getDirection();
3142         const v3s16 camera_offset  = camera->getOffset();
3143
3144         /*
3145                 Calculate what block is the crosshair pointing to
3146         */
3147
3148         ItemStack selected_item, hand_item;
3149         const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3150
3151         const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3152         f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3153
3154         core::line3d<f32> shootline;
3155
3156         switch (camera->getCameraMode()) {
3157         case CAMERA_MODE_FIRST:
3158                 // Shoot from camera position, with bobbing
3159                 shootline.start = camera->getPosition();
3160                 break;
3161         case CAMERA_MODE_THIRD:
3162                 // Shoot from player head, no bobbing
3163                 shootline.start = camera->getHeadPosition();
3164                 break;
3165         case CAMERA_MODE_THIRD_FRONT:
3166                 shootline.start = camera->getHeadPosition();
3167                 // prevent player pointing anything in front-view
3168                 d = 0;
3169                 break;
3170         }
3171         shootline.end = shootline.start + camera_direction * BS * d;
3172
3173 #ifdef HAVE_TOUCHSCREENGUI
3174         if (g_touchscreengui && isNoCrosshairAllowed()) {
3175                 shootline = g_touchscreengui->getShootline();
3176                 // Scale shootline to the acual distance the player can reach
3177                 shootline.end = shootline.start +
3178                                 shootline.getVector().normalize() * BS * d;
3179                 shootline.start += intToFloat(camera_offset, BS);
3180                 shootline.end += intToFloat(camera_offset, BS);
3181         }
3182 #endif
3183
3184         PointedThing pointed = updatePointedThing(shootline,
3185                         selected_def.liquids_pointable,
3186                         !runData.btn_down_for_dig,
3187                         camera_offset);
3188
3189         if (pointed != runData.pointed_old)
3190                 infostream << "Pointing at " << pointed.dump() << std::endl;
3191
3192         // Note that updating the selection mesh every frame is not particularly efficient,
3193         // but the halo rendering code is already inefficient so there's no point in optimizing it here
3194         hud->updateSelectionMesh(camera_offset);
3195
3196         // Allow digging again if button is not pressed
3197         if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3198                 runData.digging_blocked = false;
3199
3200         /*
3201                 Stop digging when
3202                 - releasing dig button
3203                 - pointing away from node
3204         */
3205         if (runData.digging) {
3206                 if (wasKeyReleased(KeyType::DIG)) {
3207                         infostream << "Dig button released (stopped digging)" << std::endl;
3208                         runData.digging = false;
3209                 } else if (pointed != runData.pointed_old) {
3210                         if (pointed.type == POINTEDTHING_NODE
3211                                         && runData.pointed_old.type == POINTEDTHING_NODE
3212                                         && pointed.node_undersurface
3213                                                         == runData.pointed_old.node_undersurface) {
3214                                 // Still pointing to the same node, but a different face.
3215                                 // Don't reset.
3216                         } else {
3217                                 infostream << "Pointing away from node (stopped digging)" << std::endl;
3218                                 runData.digging = false;
3219                                 hud->updateSelectionMesh(camera_offset);
3220                         }
3221                 }
3222
3223                 if (!runData.digging) {
3224                         client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3225                         client->setCrack(-1, v3s16(0, 0, 0));
3226                         runData.dig_time = 0.0;
3227                 }
3228         } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3229                 // Remove e.g. torches faster when clicking instead of holding dig button
3230                 runData.nodig_delay_timer = 0;
3231                 runData.dig_instantly = false;
3232         }
3233
3234         if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3235                 runData.btn_down_for_dig = false;
3236
3237         runData.punching = false;
3238
3239         soundmaker->m_player_leftpunch_sound.name.clear();
3240
3241         // Prepare for repeating, unless we're not supposed to
3242         if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3243                 runData.repeat_place_timer += dtime;
3244         else
3245                 runData.repeat_place_timer = 0;
3246
3247         if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3248                 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3249                                 !client->getScript()->on_item_use(selected_item, pointed)))
3250                         client->interact(INTERACT_USE, pointed);
3251         } else if (pointed.type == POINTEDTHING_NODE) {
3252                 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3253         } else if (pointed.type == POINTEDTHING_OBJECT) {
3254                 v3f player_position  = player->getPosition();
3255                 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
3256                 handlePointingAtObject(pointed, tool_item, player_position,
3257                                 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
3258         } else if (isKeyDown(KeyType::DIG)) {
3259                 // When button is held down in air, show continuous animation
3260                 runData.punching = true;
3261                 // Run callback even though item is not usable
3262                 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3263                         client->getScript()->on_item_use(selected_item, pointed);
3264         } else if (wasKeyPressed(KeyType::PLACE)) {
3265                 handlePointingAtNothing(selected_item);
3266         }
3267
3268         runData.pointed_old = pointed;
3269
3270         if (runData.punching || wasKeyPressed(KeyType::DIG))
3271                 camera->setDigging(0); // dig animation
3272
3273         input->clearWasKeyPressed();
3274         input->clearWasKeyReleased();
3275         // Ensure DIG & PLACE are marked as handled
3276         wasKeyDown(KeyType::DIG);
3277         wasKeyDown(KeyType::PLACE);
3278
3279         input->joystick.clearWasKeyPressed(KeyType::DIG);
3280         input->joystick.clearWasKeyPressed(KeyType::PLACE);
3281
3282         input->joystick.clearWasKeyReleased(KeyType::DIG);
3283         input->joystick.clearWasKeyReleased(KeyType::PLACE);
3284 }
3285
3286
3287 PointedThing Game::updatePointedThing(
3288         const core::line3d<f32> &shootline,
3289         bool liquids_pointable,
3290         bool look_for_object,
3291         const v3s16 &camera_offset)
3292 {
3293         std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3294         selectionboxes->clear();
3295         hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3296         static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3297                 "show_entity_selectionbox");
3298
3299         ClientEnvironment &env = client->getEnv();
3300         ClientMap &map = env.getClientMap();
3301         const NodeDefManager *nodedef = map.getNodeDefManager();
3302
3303         runData.selected_object = NULL;
3304         hud->pointing_at_object = false;
3305
3306         RaycastState s(shootline, look_for_object, liquids_pointable);
3307         PointedThing result;
3308         env.continueRaycast(&s, &result);
3309         if (result.type == POINTEDTHING_OBJECT) {
3310                 hud->pointing_at_object = true;
3311
3312                 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3313                 aabb3f selection_box;
3314                 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3315                                 runData.selected_object->getSelectionBox(&selection_box)) {
3316                         v3f pos = runData.selected_object->getPosition();
3317                         selectionboxes->push_back(aabb3f(selection_box));
3318                         hud->setSelectionPos(pos, camera_offset);
3319                 }
3320         } else if (result.type == POINTEDTHING_NODE) {
3321                 // Update selection boxes
3322                 MapNode n = map.getNode(result.node_undersurface);
3323                 std::vector<aabb3f> boxes;
3324                 n.getSelectionBoxes(nodedef, &boxes,
3325                         n.getNeighbors(result.node_undersurface, &map));
3326
3327                 f32 d = 0.002 * BS;
3328                 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3329                         i != boxes.end(); ++i) {
3330                         aabb3f box = *i;
3331                         box.MinEdge -= v3f(d, d, d);
3332                         box.MaxEdge += v3f(d, d, d);
3333                         selectionboxes->push_back(box);
3334                 }
3335                 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3336                         camera_offset);
3337                 hud->setSelectedFaceNormal(v3f(
3338                         result.intersection_normal.X,
3339                         result.intersection_normal.Y,
3340                         result.intersection_normal.Z));
3341         }
3342
3343         // Update selection mesh light level and vertex colors
3344         if (!selectionboxes->empty()) {
3345                 v3f pf = hud->getSelectionPos();
3346                 v3s16 p = floatToInt(pf, BS);
3347
3348                 // Get selection mesh light level
3349                 MapNode n = map.getNode(p);
3350                 u16 node_light = getInteriorLight(n, -1, nodedef);
3351                 u16 light_level = node_light;
3352
3353                 for (const v3s16 &dir : g_6dirs) {
3354                         n = map.getNode(p + dir);
3355                         node_light = getInteriorLight(n, -1, nodedef);
3356                         if (node_light > light_level)
3357                                 light_level = node_light;
3358                 }
3359
3360                 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3361                 video::SColor c;
3362                 final_color_blend(&c, light_level, daynight_ratio);
3363
3364                 // Modify final color a bit with time
3365                 u32 timer = client->getEnv().getFrameTime() % 5000;
3366                 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3367                 float sin_r = 0.08f * std::sin(timerf);
3368                 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3369                 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3370                 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3371                 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3372                 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3373
3374                 // Set mesh final color
3375                 hud->setSelectionMeshColor(c);
3376         }
3377         return result;
3378 }
3379
3380
3381 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3382 {
3383         infostream << "Attempted to place item while pointing at nothing" << std::endl;
3384         PointedThing fauxPointed;
3385         fauxPointed.type = POINTEDTHING_NOTHING;
3386         client->interact(INTERACT_ACTIVATE, fauxPointed);
3387 }
3388
3389
3390 void Game::handlePointingAtNode(const PointedThing &pointed,
3391         const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3392 {
3393         v3s16 nodepos = pointed.node_undersurface;
3394         v3s16 neighbourpos = pointed.node_abovesurface;
3395
3396         /*
3397                 Check information text of node
3398         */
3399
3400         ClientMap &map = client->getEnv().getClientMap();
3401
3402         if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3403                         && !runData.digging_blocked
3404                         && client->checkPrivilege("interact")) {
3405                 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3406         }
3407
3408         // This should be done after digging handling
3409         NodeMetadata *meta = map.getNodeMetadata(nodepos);
3410
3411         if (meta) {
3412                 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3413                         meta->getString("infotext"))));
3414         } else {
3415                 MapNode n = map.getNode(nodepos);
3416
3417                 if (nodedef_manager->get(n).name == "unknown") {
3418                         m_game_ui->setInfoText(L"Unknown node");
3419                 }
3420         }
3421
3422         if ((wasKeyPressed(KeyType::PLACE) ||
3423                         runData.repeat_place_timer >= m_repeat_place_time) &&
3424                         client->checkPrivilege("interact")) {
3425                 runData.repeat_place_timer = 0;
3426                 infostream << "Place button pressed while looking at ground" << std::endl;
3427
3428                 // Placing animation (always shown for feedback)
3429                 camera->setDigging(1);
3430
3431                 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3432
3433                 // If the wielded item has node placement prediction,
3434                 // make that happen
3435                 // And also set the sound and send the interact
3436                 // But first check for meta formspec and rightclickable
3437                 auto &def = selected_item.getDefinition(itemdef_manager);
3438                 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3439                         pointed, meta);
3440
3441                 if (placed && client->modsLoaded())
3442                         client->getScript()->on_placenode(pointed, def);
3443         }
3444 }
3445
3446 bool Game::nodePlacement(const ItemDefinition &selected_def,
3447         const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3448         const PointedThing &pointed, const NodeMetadata *meta)
3449 {
3450         const auto &prediction = selected_def.node_placement_prediction;
3451
3452         const NodeDefManager *nodedef = client->ndef();
3453         ClientMap &map = client->getEnv().getClientMap();
3454         MapNode node;
3455         bool is_valid_position;
3456
3457         node = map.getNode(nodepos, &is_valid_position);
3458         if (!is_valid_position) {
3459                 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3460                 return false;
3461         }
3462
3463         // formspec in meta
3464         if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3465                         && !isKeyDown(KeyType::SNEAK)) {
3466                 // on_rightclick callbacks are called anyway
3467                 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3468                         client->interact(INTERACT_PLACE, pointed);
3469
3470                 infostream << "Launching custom inventory view" << std::endl;
3471
3472                 InventoryLocation inventoryloc;
3473                 inventoryloc.setNodeMeta(nodepos);
3474
3475                 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3476                         &client->getEnv().getClientMap(), nodepos);
3477                 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3478
3479                 auto *&formspec = m_game_ui->updateFormspec("");
3480                 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3481                         &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3482
3483                 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3484                 return false;
3485         }
3486
3487         // on_rightclick callback
3488         if (prediction.empty() || (nodedef->get(node).rightclickable &&
3489                         !isKeyDown(KeyType::SNEAK))) {
3490                 // Report to server
3491                 client->interact(INTERACT_PLACE, pointed);
3492                 return false;
3493         }
3494
3495         verbosestream << "Node placement prediction for "
3496                 << selected_def.name << " is " << prediction << std::endl;
3497         v3s16 p = neighbourpos;
3498
3499         // Place inside node itself if buildable_to
3500         MapNode n_under = map.getNode(nodepos, &is_valid_position);
3501         if (is_valid_position) {
3502                 if (nodedef->get(n_under).buildable_to) {
3503                         p = nodepos;
3504                 } else {
3505                         node = map.getNode(p, &is_valid_position);
3506                         if (is_valid_position && !nodedef->get(node).buildable_to) {
3507                                 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3508                                 // Report to server
3509                                 client->interact(INTERACT_PLACE, pointed);
3510                                 return false;
3511                         }
3512                 }
3513         }
3514
3515         // Find id of predicted node
3516         content_t id;
3517         bool found = nodedef->getId(prediction, id);
3518
3519         if (!found) {
3520                 errorstream << "Node placement prediction failed for "
3521                         << selected_def.name << " (places " << prediction
3522                         << ") - Name not known" << std::endl;
3523                 // Handle this as if prediction was empty
3524                 // Report to server
3525                 client->interact(INTERACT_PLACE, pointed);
3526                 return false;
3527         }
3528
3529         const ContentFeatures &predicted_f = nodedef->get(id);
3530
3531         // Predict param2 for facedir and wallmounted nodes
3532         // Compare core.item_place_node() for what the server does
3533         u8 param2 = 0;
3534
3535         const u8 place_param2 = selected_def.place_param2;
3536
3537         if (place_param2) {
3538                 param2 = place_param2;
3539         } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3540                         predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3541                 v3s16 dir = nodepos - neighbourpos;
3542
3543                 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3544                         param2 = dir.Y < 0 ? 1 : 0;
3545                 } else if (abs(dir.X) > abs(dir.Z)) {
3546                         param2 = dir.X < 0 ? 3 : 2;
3547                 } else {
3548                         param2 = dir.Z < 0 ? 5 : 4;
3549                 }
3550         } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3551                         predicted_f.param_type_2 == CPT2_COLORED_FACEDIR ||
3552                         predicted_f.param_type_2 == CPT2_4DIR ||
3553                         predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3554                 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3555
3556                 if (abs(dir.X) > abs(dir.Z)) {
3557                         param2 = dir.X < 0 ? 3 : 1;
3558                 } else {
3559                         param2 = dir.Z < 0 ? 2 : 0;
3560                 }
3561         }
3562
3563         // Check attachment if node is in group attached_node
3564         if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3565                 const static v3s16 wallmounted_dirs[8] = {
3566                         v3s16(0, 1, 0),
3567                         v3s16(0, -1, 0),
3568                         v3s16(1, 0, 0),
3569                         v3s16(-1, 0, 0),
3570                         v3s16(0, 0, 1),
3571                         v3s16(0, 0, -1),
3572                 };
3573                 v3s16 pp;
3574
3575                 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3576                                 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3577                         pp = p + wallmounted_dirs[param2];
3578                 else
3579                         pp = p + v3s16(0, -1, 0);
3580
3581                 if (!nodedef->get(map.getNode(pp)).walkable) {
3582                         soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3583                         // Report to server
3584                         client->interact(INTERACT_PLACE, pointed);
3585                         return false;
3586                 }
3587         }
3588
3589         // Apply color
3590         if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3591                         || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3592                         || predicted_f.param_type_2 == CPT2_COLORED_4DIR
3593                         || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3594                 const auto &indexstr = selected_item.metadata.
3595                         getString("palette_index", 0);
3596                 if (!indexstr.empty()) {
3597                         s32 index = mystoi(indexstr);
3598                         if (predicted_f.param_type_2 == CPT2_COLOR) {
3599                                 param2 = index;
3600                         } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3601                                 // param2 = pure palette index + other
3602                                 param2 = (index & 0xf8) | (param2 & 0x07);
3603                         } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3604                                 // param2 = pure palette index + other
3605                                 param2 = (index & 0xe0) | (param2 & 0x1f);
3606                         } else if (predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3607                                 // param2 = pure palette index + other
3608                                 param2 = (index & 0xfc) | (param2 & 0x03);
3609                         }
3610                 }
3611         }
3612
3613         // Add node to client map
3614         MapNode n(id, 0, param2);
3615
3616         try {
3617                 LocalPlayer *player = client->getEnv().getLocalPlayer();
3618
3619                 // Dont place node when player would be inside new node
3620                 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3621                 if (!nodedef->get(n).walkable ||
3622                                 g_settings->getBool("enable_build_where_you_stand") ||
3623                                 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3624                                 (nodedef->get(n).walkable &&
3625                                         neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3626                                         neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3627                         // This triggers the required mesh update too
3628                         client->addNode(p, n);
3629                         // Report to server
3630                         client->interact(INTERACT_PLACE, pointed);
3631                         // A node is predicted, also play a sound
3632                         soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3633                         return true;
3634                 } else {
3635                         soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3636                         return false;
3637                 }
3638         } catch (const InvalidPositionException &e) {
3639                 errorstream << "Node placement prediction failed for "
3640                         << selected_def.name << " (places "
3641                         << prediction << ") - Position not loaded" << std::endl;
3642                 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3643                 return false;
3644         }
3645 }
3646
3647 void Game::handlePointingAtObject(const PointedThing &pointed,
3648                 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3649 {
3650         std::wstring infotext = unescape_translate(
3651                 utf8_to_wide(runData.selected_object->infoText()));
3652
3653         if (show_debug) {
3654                 if (!infotext.empty()) {
3655                         infotext += L"\n";
3656                 }
3657                 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3658         }
3659
3660         m_game_ui->setInfoText(infotext);
3661
3662         if (isKeyDown(KeyType::DIG)) {
3663                 bool do_punch = false;
3664                 bool do_punch_damage = false;
3665
3666                 if (runData.object_hit_delay_timer <= 0.0) {
3667                         do_punch = true;
3668                         do_punch_damage = true;
3669                         runData.object_hit_delay_timer = object_hit_delay;
3670                 }
3671
3672                 if (wasKeyPressed(KeyType::DIG))
3673                         do_punch = true;
3674
3675                 if (do_punch) {
3676                         infostream << "Punched object" << std::endl;
3677                         runData.punching = true;
3678                 }
3679
3680                 if (do_punch_damage) {
3681                         // Report direct punch
3682                         v3f objpos = runData.selected_object->getPosition();
3683                         v3f dir = (objpos - player_position).normalize();
3684
3685                         bool disable_send = runData.selected_object->directReportPunch(
3686                                         dir, &tool_item, runData.time_from_last_punch);
3687                         runData.time_from_last_punch = 0;
3688
3689                         if (!disable_send)
3690                                 client->interact(INTERACT_START_DIGGING, pointed);
3691                 }
3692         } else if (wasKeyDown(KeyType::PLACE)) {
3693                 infostream << "Pressed place button while pointing at object" << std::endl;
3694                 client->interact(INTERACT_PLACE, pointed);  // place
3695         }
3696 }
3697
3698
3699 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3700                 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3701 {
3702         // See also: serverpackethandle.cpp, action == 2
3703         LocalPlayer *player = client->getEnv().getLocalPlayer();
3704         ClientMap &map = client->getEnv().getClientMap();
3705         MapNode n = map.getNode(nodepos);
3706         const auto &features = nodedef_manager->get(n);
3707
3708         // NOTE: Similar piece of code exists on the server side for
3709         // cheat detection.
3710         // Get digging parameters
3711         DigParams params = getDigParams(features.groups,
3712                         &selected_item.getToolCapabilities(itemdef_manager),
3713                         selected_item.wear);
3714
3715         // If can't dig, try hand
3716         if (!params.diggable) {
3717                 params = getDigParams(features.groups,
3718                                 &hand_item.getToolCapabilities(itemdef_manager));
3719         }
3720
3721         if (!params.diggable) {
3722                 // I guess nobody will wait for this long
3723                 runData.dig_time_complete = 10000000.0;
3724         } else {
3725                 runData.dig_time_complete = params.time;
3726
3727                 if (m_cache_enable_particles) {
3728                         client->getParticleManager()->addNodeParticle(client,
3729                                         player, nodepos, n, features);
3730                 }
3731         }
3732
3733         if (!runData.digging) {
3734                 infostream << "Started digging" << std::endl;
3735                 runData.dig_instantly = runData.dig_time_complete == 0;
3736                 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3737                         return;
3738
3739                 client->interact(INTERACT_START_DIGGING, pointed);
3740                 runData.digging = true;
3741                 runData.btn_down_for_dig = true;
3742         }
3743
3744         if (!runData.dig_instantly) {
3745                 runData.dig_index = (float)crack_animation_length
3746                                 * runData.dig_time
3747                                 / runData.dig_time_complete;
3748         } else {
3749                 // This is for e.g. torches
3750                 runData.dig_index = crack_animation_length;
3751         }
3752
3753         const auto &sound_dig = features.sound_dig;
3754
3755         if (sound_dig.exists() && params.diggable) {
3756                 if (sound_dig.name == "__group") {
3757                         if (!params.main_group.empty()) {
3758                                 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3759                                 soundmaker->m_player_leftpunch_sound.name =
3760                                                 std::string("default_dig_") +
3761                                                 params.main_group;
3762                         }
3763                 } else {
3764                         soundmaker->m_player_leftpunch_sound = sound_dig;
3765                 }
3766         }
3767
3768         // Don't show cracks if not diggable
3769         if (runData.dig_time_complete >= 100000.0) {
3770         } else if (runData.dig_index < crack_animation_length) {
3771                 client->setCrack(runData.dig_index, nodepos);
3772         } else {
3773                 infostream << "Digging completed" << std::endl;
3774                 client->setCrack(-1, v3s16(0, 0, 0));
3775
3776                 runData.dig_time = 0;
3777                 runData.digging = false;
3778                 // we successfully dug, now block it from repeating if we want to be safe
3779                 if (g_settings->getBool("safe_dig_and_place"))
3780                         runData.digging_blocked = true;
3781
3782                 runData.nodig_delay_timer =
3783                                 runData.dig_time_complete / (float)crack_animation_length;
3784
3785                 // We don't want a corresponding delay to very time consuming nodes
3786                 // and nodes without digging time (e.g. torches) get a fixed delay.
3787                 if (runData.nodig_delay_timer > 0.3)
3788                         runData.nodig_delay_timer = 0.3;
3789                 else if (runData.dig_instantly)
3790                         runData.nodig_delay_timer = 0.15;
3791
3792                 if (client->modsLoaded() &&
3793                                 client->getScript()->on_dignode(nodepos, n)) {
3794                         return;
3795                 }
3796
3797                 if (features.node_dig_prediction == "air") {
3798                         client->removeNode(nodepos);
3799                 } else if (!features.node_dig_prediction.empty()) {
3800                         content_t id;
3801                         bool found = nodedef_manager->getId(features.node_dig_prediction, id);
3802                         if (found)
3803                                 client->addNode(nodepos, id, true);
3804                 }
3805                 // implicit else: no prediction
3806
3807                 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3808
3809                 if (m_cache_enable_particles) {
3810                         client->getParticleManager()->addDiggingParticles(client,
3811                                 player, nodepos, n, features);
3812                 }
3813
3814
3815                 // Send event to trigger sound
3816                 client->getEventManager()->put(new NodeDugEvent(nodepos, n));
3817         }
3818
3819         if (runData.dig_time_complete < 100000.0) {
3820                 runData.dig_time += dtime;
3821         } else {
3822                 runData.dig_time = 0;
3823                 client->setCrack(-1, nodepos);
3824         }
3825
3826         camera->setDigging(0);  // Dig animation
3827 }
3828
3829 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3830                 const CameraOrientation &cam)
3831 {
3832         TimeTaker tt_update("Game::updateFrame()");
3833         LocalPlayer *player = client->getEnv().getLocalPlayer();
3834
3835         /*
3836                 Frame time
3837         */
3838
3839         client->getEnv().updateFrameTime(m_is_paused);
3840
3841         /*
3842                 Fog range
3843         */
3844
3845         if (draw_control->range_all) {
3846                 runData.fog_range = 100000 * BS;
3847         } else {
3848                 runData.fog_range = draw_control->wanted_range * BS;
3849         }
3850
3851         /*
3852                 Calculate general brightness
3853         */
3854         u32 daynight_ratio = client->getEnv().getDayNightRatio();
3855         float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3856         float direct_brightness;
3857         bool sunlight_seen;
3858
3859         // When in noclip mode force same sky brightness as above ground so you
3860         // can see properly
3861         if (draw_control->allow_noclip && m_cache_enable_free_move &&
3862                 client->checkPrivilege("fly")) {
3863                 direct_brightness = time_brightness;
3864                 sunlight_seen = true;
3865         } else {
3866                 float old_brightness = sky->getBrightness();
3867                 direct_brightness = client->getEnv().getClientMap()
3868                                 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3869                                         daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3870                                     / 255.0;
3871         }
3872
3873         float time_of_day_smooth = runData.time_of_day_smooth;
3874         float time_of_day = client->getEnv().getTimeOfDayF();
3875
3876         static const float maxsm = 0.05f;
3877         static const float todsm = 0.05f;
3878
3879         if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3880                         std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3881                         std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3882                 time_of_day_smooth = time_of_day;
3883
3884         if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3885                 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3886                                 + (time_of_day + 1.0) * todsm;
3887         else
3888                 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3889                                 + time_of_day * todsm;
3890
3891         runData.time_of_day_smooth = time_of_day_smooth;
3892
3893         sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3894                         sunlight_seen, camera->getCameraMode(), player->getYaw(),
3895                         player->getPitch());
3896
3897         /*
3898                 Update clouds
3899         */
3900         if (clouds) {
3901                 if (sky->getCloudsVisible()) {
3902                         clouds->setVisible(true);
3903                         clouds->step(dtime);
3904                         // camera->getPosition is not enough for 3rd person views
3905                         v3f camera_node_position = camera->getCameraNode()->getPosition();
3906                         v3s16 camera_offset      = camera->getOffset();
3907                         camera_node_position.X   = camera_node_position.X + camera_offset.X * BS;
3908                         camera_node_position.Y   = camera_node_position.Y + camera_offset.Y * BS;
3909                         camera_node_position.Z   = camera_node_position.Z + camera_offset.Z * BS;
3910                         clouds->update(camera_node_position,
3911                                         sky->getCloudColor());
3912                         if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3913                                 // if inside clouds, and fog enabled, use that as sky
3914                                 // color(s)
3915                                 video::SColor clouds_dark = clouds->getColor()
3916                                                 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3917                                 sky->overrideColors(clouds_dark, clouds->getColor());
3918                                 sky->setInClouds(true);
3919                                 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3920                                 // do not draw clouds after all
3921                                 clouds->setVisible(false);
3922                         }
3923                 } else {
3924                         clouds->setVisible(false);
3925                 }
3926         }
3927
3928         /*
3929                 Update particles
3930         */
3931         client->getParticleManager()->step(dtime);
3932
3933         /*
3934                 Fog
3935         */
3936
3937         if (m_cache_enable_fog) {
3938                 driver->setFog(
3939                                 sky->getBgColor(),
3940                                 video::EFT_FOG_LINEAR,
3941                                 runData.fog_range * m_cache_fog_start,
3942                                 runData.fog_range * 1.0,
3943                                 0.01,
3944                                 false, // pixel fog
3945                                 true // range fog
3946                 );
3947         } else {
3948                 driver->setFog(
3949                                 sky->getBgColor(),
3950                                 video::EFT_FOG_LINEAR,
3951                                 100000 * BS,
3952                                 110000 * BS,
3953                                 0.01f,
3954                                 false, // pixel fog
3955                                 false // range fog
3956                 );
3957         }
3958
3959         /*
3960                 Damage camera tilt
3961         */
3962         if (player->hurt_tilt_timer > 0.0f) {
3963                 player->hurt_tilt_timer -= dtime * 6.0f;
3964
3965                 if (player->hurt_tilt_timer < 0.0f)
3966                         player->hurt_tilt_strength = 0.0f;
3967         }
3968
3969         /*
3970                 Update minimap pos and rotation
3971         */
3972         if (mapper && m_game_ui->m_flags.show_hud) {
3973                 mapper->setPos(floatToInt(player->getPosition(), BS));
3974                 mapper->setAngle(player->getYaw());
3975         }
3976
3977         /*
3978                 Get chat messages from client
3979         */
3980
3981         updateChat(dtime);
3982
3983         /*
3984                 Inventory
3985         */
3986
3987         if (player->getWieldIndex() != runData.new_playeritem)
3988                 client->setPlayerItem(runData.new_playeritem);
3989
3990         if (client->updateWieldedItem()) {
3991                 // Update wielded tool
3992                 ItemStack selected_item, hand_item;
3993                 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3994                 camera->wield(tool_item);
3995         }
3996
3997         /*
3998                 Update block draw list every 200ms or when camera direction has
3999                 changed much
4000         */
4001         runData.update_draw_list_timer += dtime;
4002
4003         float update_draw_list_delta = 0.2f;
4004
4005         v3f camera_direction = camera->getDirection();
4006         if (runData.update_draw_list_timer >= update_draw_list_delta
4007                         || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
4008                         || m_camera_offset_changed
4009                         || client->getEnv().getClientMap().needsUpdateDrawList()) {
4010                 runData.update_draw_list_timer = 0;
4011                 client->getEnv().getClientMap().updateDrawList();
4012                 runData.update_draw_list_last_cam_dir = camera_direction;
4013         }
4014
4015         if (RenderingEngine::get_shadow_renderer()) {
4016                 updateShadows();
4017         }
4018
4019         m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
4020
4021         /*
4022            make sure menu is on top
4023            1. Delete formspec menu reference if menu was removed
4024            2. Else, make sure formspec menu is on top
4025         */
4026         auto formspec = m_game_ui->getFormspecGUI();
4027         do { // breakable. only runs for one iteration
4028                 if (!formspec)
4029                         break;
4030
4031                 if (formspec->getReferenceCount() == 1) {
4032                         m_game_ui->deleteFormspec();
4033                         break;
4034                 }
4035
4036                 auto &loc = formspec->getFormspecLocation();
4037                 if (loc.type == InventoryLocation::NODEMETA) {
4038                         NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
4039                         if (!meta || meta->getString("formspec").empty()) {
4040                                 formspec->quitMenu();
4041                                 break;
4042                         }
4043                 }
4044
4045                 if (isMenuActive())
4046                         guiroot->bringToFront(formspec);
4047         } while (false);
4048
4049         /*
4050                 ==================== Drawing begins ====================
4051         */
4052         const video::SColor skycolor = sky->getSkyColor();
4053
4054         TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
4055         driver->beginScene(true, true, skycolor);
4056
4057         bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
4058                         (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
4059                         (camera->getCameraMode() == CAMERA_MODE_FIRST));
4060         bool draw_crosshair = (
4061                         (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
4062                         (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
4063 #ifdef HAVE_TOUCHSCREENGUI
4064         if (isNoCrosshairAllowed())
4065                 draw_crosshair = false;
4066 #endif
4067         m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
4068                         m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
4069
4070         /*
4071                 Profiler graph
4072         */
4073         v2u32 screensize = driver->getScreenSize();
4074
4075         if (m_game_ui->m_flags.show_profiler_graph)
4076                 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
4077
4078         /*
4079                 Damage flash
4080         */
4081         if (runData.damage_flash > 0.0f) {
4082                 video::SColor color(runData.damage_flash, 180, 0, 0);
4083                 driver->draw2DRectangle(color,
4084                                         core::rect<s32>(0, 0, screensize.X, screensize.Y),
4085                                         NULL);
4086
4087                 runData.damage_flash -= 384.0f * dtime;
4088         }
4089
4090         /*
4091                 ==================== End scene ====================
4092         */
4093 #if IRRLICHT_VERSION_MT_REVISION < 5
4094         if (++m_reset_HW_buffer_counter > 500) {
4095                 /*
4096                   Periodically remove all mesh HW buffers.
4097
4098                   Work around for a quirk in Irrlicht where a HW buffer is only
4099                   released after 20000 iterations (triggered from endScene()).
4100
4101                   Without this, all loaded but unused meshes will retain their HW
4102                   buffers for at least 5 minutes, at which point looking up the HW buffers
4103                   becomes a bottleneck and the framerate drops (as much as 30%).
4104
4105                   Tests showed that numbers between 50 and 1000 are good, so picked 500.
4106                   There are no other public Irrlicht APIs that allow interacting with the
4107                   HW buffers without tracking the status of every individual mesh.
4108
4109                   The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4110                 */
4111                 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4112                 driver->removeAllHardwareBuffers();
4113                 m_reset_HW_buffer_counter = 0;
4114         }
4115 #endif
4116
4117         driver->endScene();
4118
4119         stats->drawtime = tt_draw.stop(true);
4120         g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
4121         g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
4122 }
4123
4124 /* Log times and stuff for visualization */
4125 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4126 {
4127         Profiler::GraphValues values;
4128         g_profiler->graphGet(values);
4129         graph->put(values);
4130 }
4131
4132 /****************************************************************************
4133  * Shadows
4134  *****************************************************************************/
4135 void Game::updateShadows()
4136 {
4137         ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4138         if (!shadow)
4139                 return;
4140
4141         float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4142
4143         float timeoftheday = getWickedTimeOfDay(in_timeofday);
4144         bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
4145         bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
4146         shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
4147
4148         timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
4149         const float offset_constant = 10000.0f;
4150
4151         v3f light = is_day ? sky->getSunDirection() : sky->getMoonDirection();
4152
4153         v3f sun_pos = light * offset_constant;
4154
4155         if (shadow->getDirectionalLightCount() == 0)
4156                 shadow->addDirectionalLight();
4157         shadow->getDirectionalLight().setDirection(sun_pos);
4158         shadow->setTimeOfDay(in_timeofday);
4159
4160         shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4161 }
4162
4163 /****************************************************************************
4164  Misc
4165  ****************************************************************************/
4166
4167 void FpsControl::reset()
4168 {
4169         last_time = porting::getTimeUs();
4170 }
4171
4172 /*
4173  * On some computers framerate doesn't seem to be automatically limited
4174  */
4175 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
4176 {
4177         const float fps_limit = (device->isWindowFocused() && !g_menumgr.pausesGame())
4178                         ? g_settings->getFloat("fps_max")
4179                         : g_settings->getFloat("fps_max_unfocused");
4180         const u64 frametime_min = 1000000.0f / std::max(fps_limit, 1.0f);
4181
4182         u64 time = porting::getTimeUs();
4183
4184         if (time > last_time) // Make sure time hasn't overflowed
4185                 busy_time = time - last_time;
4186         else
4187                 busy_time = 0;
4188
4189         if (busy_time < frametime_min) {
4190                 sleep_time = frametime_min - busy_time;
4191                 if (sleep_time > 1000)
4192                         sleep_ms(sleep_time / 1000);
4193         } else {
4194                 sleep_time = 0;
4195         }
4196
4197         // Read the timer again to accurately determine how long we actually slept,
4198         // rather than calculating it by adding sleep_time to time.
4199         time = porting::getTimeUs();
4200
4201         if (time > last_time) // Make sure last_time hasn't overflowed
4202                 *dtime = (time - last_time) / 1000000.0f;
4203         else
4204                 *dtime = 0;
4205
4206         last_time = time;
4207 }
4208
4209 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4210 {
4211         const wchar_t *wmsg = wgettext(msg);
4212         m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4213                 draw_clouds);
4214         delete[] wmsg;
4215 }
4216
4217 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4218 {
4219         ((Game *)data)->readSettings();
4220 }
4221
4222 void Game::readSettings()
4223 {
4224         m_cache_doubletap_jump               = g_settings->getBool("doubletap_jump");
4225         m_cache_enable_clouds                = g_settings->getBool("enable_clouds");
4226         m_cache_enable_joysticks             = g_settings->getBool("enable_joysticks");
4227         m_cache_enable_particles             = g_settings->getBool("enable_particles");
4228         m_cache_enable_fog                   = g_settings->getBool("enable_fog");
4229         m_cache_mouse_sensitivity            = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f);
4230         m_cache_joystick_frustum_sensitivity = std::max(g_settings->getFloat("joystick_frustum_sensitivity"), 0.001f);
4231         m_repeat_place_time                  = g_settings->getFloat("repeat_place_time", 0.25f, 2.0);
4232
4233         m_cache_enable_noclip                = g_settings->getBool("noclip");
4234         m_cache_enable_free_move             = g_settings->getBool("free_move");
4235
4236         m_cache_fog_start                    = g_settings->getFloat("fog_start");
4237
4238         m_cache_cam_smoothing = 0;
4239         if (g_settings->getBool("cinematic"))
4240                 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4241         else
4242                 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4243
4244         m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4245         m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4246         m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4247
4248         m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4249 }
4250
4251 /****************************************************************************/
4252 /****************************************************************************
4253  Shutdown / cleanup
4254  ****************************************************************************/
4255 /****************************************************************************/
4256
4257 void Game::showDeathFormspec()
4258 {
4259         static std::string formspec_str =
4260                 std::string("formspec_version[1]") +
4261                 SIZE_TAG
4262                 "bgcolor[#320000b4;true]"
4263                 "label[4.85,1.35;" + gettext("You died") + "]"
4264                 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4265                 ;
4266
4267         /* Create menu */
4268         /* Note: FormspecFormSource and LocalFormspecHandler  *
4269          * are deleted by guiFormSpecMenu                     */
4270         FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4271         LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4272
4273         auto *&formspec = m_game_ui->getFormspecGUI();
4274         GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4275                 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4276         formspec->setFocus("btn_respawn");
4277 }
4278
4279 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4280 void Game::showPauseMenu()
4281 {
4282 #ifdef HAVE_TOUCHSCREENGUI
4283         static const std::string control_text = strgettext("Default Controls:\n"
4284                 "No menu visible:\n"
4285                 "- single tap: button activate\n"
4286                 "- double tap: place/use\n"
4287                 "- slide finger: look around\n"
4288                 "Menu/Inventory visible:\n"
4289                 "- double tap (outside):\n"
4290                 " -->close\n"
4291                 "- touch stack, touch slot:\n"
4292                 " --> move stack\n"
4293                 "- touch&drag, tap 2nd finger\n"
4294                 " --> place single item to slot\n"
4295                 );
4296 #else
4297         static const std::string control_text_template = strgettext("Controls:\n"
4298                 "- %s: move forwards\n"
4299                 "- %s: move backwards\n"
4300                 "- %s: move left\n"
4301                 "- %s: move right\n"
4302                 "- %s: jump/climb up\n"
4303                 "- %s: dig/punch\n"
4304                 "- %s: place/use\n"
4305                 "- %s: sneak/climb down\n"
4306                 "- %s: drop item\n"
4307                 "- %s: inventory\n"
4308                 "- Mouse: turn/look\n"
4309                 "- Mouse wheel: select item\n"
4310                 "- %s: chat\n"
4311         );
4312
4313         char control_text_buf[600];
4314
4315         porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4316                 GET_KEY_NAME(keymap_forward),
4317                 GET_KEY_NAME(keymap_backward),
4318                 GET_KEY_NAME(keymap_left),
4319                 GET_KEY_NAME(keymap_right),
4320                 GET_KEY_NAME(keymap_jump),
4321                 GET_KEY_NAME(keymap_dig),
4322                 GET_KEY_NAME(keymap_place),
4323                 GET_KEY_NAME(keymap_sneak),
4324                 GET_KEY_NAME(keymap_drop),
4325                 GET_KEY_NAME(keymap_inventory),
4326                 GET_KEY_NAME(keymap_chat)
4327         );
4328
4329         std::string control_text = std::string(control_text_buf);
4330         str_formspec_escape(control_text);
4331 #endif
4332
4333         float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4334         std::ostringstream os;
4335
4336         os << "formspec_version[1]" << SIZE_TAG
4337                 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4338                 << strgettext("Continue") << "]";
4339
4340         if (!simple_singleplayer_mode) {
4341                 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4342                         << strgettext("Change Password") << "]";
4343         } else {
4344                 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4345         }
4346
4347 #ifndef __ANDROID__
4348 #if USE_SOUND
4349         if (g_settings->getBool("enable_sound")) {
4350                 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4351                         << strgettext("Sound Volume") << "]";
4352         }
4353 #endif
4354         os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4355                 << strgettext("Change Keys")  << "]";
4356 #endif
4357         os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4358                 << strgettext("Exit to Menu") << "]";
4359         os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4360                 << strgettext("Exit to OS")   << "]"
4361                 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4362                 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4363                 << "\n"
4364                 <<  strgettext("Game info:") << "\n";
4365         const std::string &address = client->getAddressName();
4366         static const std::string mode = strgettext("- Mode: ");
4367         if (!simple_singleplayer_mode) {
4368                 Address serverAddress = client->getServerAddress();
4369                 if (!address.empty()) {
4370                         os << mode << strgettext("Remote server") << "\n"
4371                                         << strgettext("- Address: ") << address;
4372                 } else {
4373                         os << mode << strgettext("Hosting server");
4374                 }
4375                 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4376         } else {
4377                 os << mode << strgettext("Singleplayer") << "\n";
4378         }
4379         if (simple_singleplayer_mode || address.empty()) {
4380                 static const std::string on = strgettext("On");
4381                 static const std::string off = strgettext("Off");
4382                 // Note: Status of enable_damage and creative_mode settings is intentionally
4383                 // NOT shown here because the game might roll its own damage system and/or do
4384                 // a per-player Creative Mode, in which case writing it here would mislead.
4385                 bool damage = g_settings->getBool("enable_damage");
4386                 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4387                 if (!simple_singleplayer_mode) {
4388                         if (damage) {
4389                                 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4390                                 //~ PvP = Player versus Player
4391                                 os << strgettext("- PvP: ") << pvp << "\n";
4392                         }
4393                         os << strgettext("- Public: ") << announced << "\n";
4394                         std::string server_name = g_settings->get("server_name");
4395                         str_formspec_escape(server_name);
4396                         if (announced == on && !server_name.empty())
4397                                 os << strgettext("- Server Name: ") << server_name;
4398
4399                 }
4400         }
4401         os << ";]";
4402
4403         /* Create menu */
4404         /* Note: FormspecFormSource and LocalFormspecHandler  *
4405          * are deleted by guiFormSpecMenu                     */
4406         FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4407         LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4408
4409         auto *&formspec = m_game_ui->getFormspecGUI();
4410         GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4411                         &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4412         formspec->setFocus("btn_continue");
4413         // game will be paused in next step, if in singleplayer (see m_is_paused)
4414         formspec->doPause = true;
4415 }
4416
4417 /****************************************************************************/
4418 /****************************************************************************
4419  extern function for launching the game
4420  ****************************************************************************/
4421 /****************************************************************************/
4422
4423 void the_game(bool *kill,
4424                 InputHandler *input,
4425                 RenderingEngine *rendering_engine,
4426                 const GameStartData &start_data,
4427                 std::string &error_message,
4428                 ChatBackend &chat_backend,
4429                 bool *reconnect_requested) // Used for local game
4430 {
4431         Game game;
4432
4433         /* Make a copy of the server address because if a local singleplayer server
4434          * is created then this is updated and we don't want to change the value
4435          * passed to us by the calling function
4436          */
4437
4438         try {
4439
4440                 if (game.startup(kill, input, rendering_engine, start_data,
4441                                 error_message, reconnect_requested, &chat_backend)) {
4442                         game.run();
4443                 }
4444
4445         } catch (SerializationError &e) {
4446                 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
4447                 error_message = strgettext("A serialization error occurred:") +"\n"
4448                                 + e.what() + "\n\n" + ver_err;
4449                 errorstream << error_message << std::endl;
4450         } catch (ServerError &e) {
4451                 error_message = e.what();
4452                 errorstream << "ServerError: " << error_message << std::endl;
4453         } catch (ModError &e) {
4454                 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
4455                 error_message = std::string("ModError: ") + e.what() +
4456                                 strgettext("\nCheck debug.txt for details.");
4457                 errorstream << error_message << std::endl;
4458         }
4459         game.shutdown();
4460 }