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