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