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