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