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