]> git.lizzy.rs Git - minetest.git/blob - src/client/game.cpp
Decrease minimum for repeat_place_time (#13165)
[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 !defined(__ANDROID__) && IRRLICHT_VERSION_MT_REVISION >= 9
2539         if (isMenuActive())
2540                 device->getCursorControl()->setRelativeMode(false);
2541         else
2542                 device->getCursorControl()->setRelativeMode(true);
2543 #endif
2544
2545         if ((device->isWindowActive() && device->isWindowFocused()
2546                         && !isMenuActive()) || input->isRandom()) {
2547
2548 #ifndef __ANDROID__
2549                 if (!input->isRandom()) {
2550                         // Mac OSX gets upset if this is set every frame
2551                         if (device->getCursorControl()->isVisible())
2552                                 device->getCursorControl()->setVisible(false);
2553                 }
2554 #endif
2555
2556                 if (m_first_loop_after_window_activation) {
2557                         m_first_loop_after_window_activation = false;
2558
2559                         input->setMousePos(driver->getScreenSize().Width / 2,
2560                                 driver->getScreenSize().Height / 2);
2561                 } else {
2562                         updateCameraOrientation(cam, dtime);
2563                 }
2564
2565         } else {
2566
2567 #ifndef ANDROID
2568                 // Mac OSX gets upset if this is set every frame
2569                 if (!device->getCursorControl()->isVisible())
2570                         device->getCursorControl()->setVisible(true);
2571 #endif
2572
2573                 m_first_loop_after_window_activation = true;
2574
2575         }
2576 }
2577
2578 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2579 // responsiveness independently of FOV.
2580 f32 Game::getSensitivityScaleFactor() const
2581 {
2582         f32 fov_y = client->getCamera()->getFovY();
2583
2584         // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2585         // 16:9 aspect ratio to minimize disruption of existing sensitivity
2586         // settings.
2587         return tan(fov_y / 2.0f) * 1.3763818698f;
2588 }
2589
2590 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2591 {
2592 #ifdef HAVE_TOUCHSCREENGUI
2593         if (g_touchscreengui) {
2594                 cam->camera_yaw   += g_touchscreengui->getYawChange();
2595                 cam->camera_pitch  = g_touchscreengui->getPitch();
2596         } else {
2597 #endif
2598                 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2599                 v2s32 dist = input->getMousePos() - center;
2600
2601                 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2602                         dist.Y = -dist.Y;
2603                 }
2604
2605                 f32 sens_scale = getSensitivityScaleFactor();
2606                 cam->camera_yaw   -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2607                 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2608
2609                 if (dist.X != 0 || dist.Y != 0)
2610                         input->setMousePos(center.X, center.Y);
2611 #ifdef HAVE_TOUCHSCREENGUI
2612         }
2613 #endif
2614
2615         if (m_cache_enable_joysticks) {
2616                 f32 sens_scale = getSensitivityScaleFactor();
2617                 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
2618                 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2619                 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2620         }
2621
2622         cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2623 }
2624
2625
2626 void Game::updatePlayerControl(const CameraOrientation &cam)
2627 {
2628         LocalPlayer *player = client->getEnv().getLocalPlayer();
2629
2630         //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2631
2632         PlayerControl control(
2633                 isKeyDown(KeyType::FORWARD),
2634                 isKeyDown(KeyType::BACKWARD),
2635                 isKeyDown(KeyType::LEFT),
2636                 isKeyDown(KeyType::RIGHT),
2637                 isKeyDown(KeyType::JUMP) || player->getAutojump(),
2638                 isKeyDown(KeyType::AUX1),
2639                 isKeyDown(KeyType::SNEAK),
2640                 isKeyDown(KeyType::ZOOM),
2641                 isKeyDown(KeyType::DIG),
2642                 isKeyDown(KeyType::PLACE),
2643                 cam.camera_pitch,
2644                 cam.camera_yaw,
2645                 input->getMovementSpeed(),
2646                 input->getMovementDirection()
2647         );
2648
2649         // autoforward if set: move at maximum speed
2650         if (player->getPlayerSettings().continuous_forward &&
2651                         client->activeObjectsReceived() && !player->isDead()) {
2652                 control.movement_speed = 1.0f;
2653                 // sideways movement only
2654                 float dx = sin(control.movement_direction);
2655                 control.movement_direction = atan2(dx, 1.0f);
2656         }
2657
2658 #ifdef HAVE_TOUCHSCREENGUI
2659         /* For touch, simulate holding down AUX1 (fast move) if the user has
2660          * the fast_move setting toggled on. If there is an aux1 key defined for
2661          * touch then its meaning is inverted (i.e. holding aux1 means walk and
2662          * not fast)
2663          */
2664         if (m_cache_hold_aux1) {
2665                 control.aux1 = control.aux1 ^ true;
2666         }
2667 #endif
2668
2669         client->setPlayerControl(control);
2670
2671         //tt.stop();
2672 }
2673
2674
2675 inline void Game::step(f32 dtime)
2676 {
2677         if (server)
2678                 server->step(dtime);
2679
2680         client->step(dtime);
2681 }
2682
2683 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2684         if (!node)
2685                 return;
2686         for (auto &&child: node->getChildren())
2687                 pauseNodeAnimation(paused, child);
2688         if (node->getType() != scene::ESNT_ANIMATED_MESH)
2689                 return;
2690         auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2691         float speed = animated_node->getAnimationSpeed();
2692         if (!speed)
2693                 return;
2694         paused.push_back({grab(animated_node), speed});
2695         animated_node->setAnimationSpeed(0.0f);
2696 }
2697
2698 void Game::pauseAnimation()
2699 {
2700         pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2701 }
2702
2703 void Game::resumeAnimation()
2704 {
2705         for (auto &&pair: paused_animated_nodes)
2706                 pair.first->setAnimationSpeed(pair.second);
2707         paused_animated_nodes.clear();
2708 }
2709
2710 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2711         {&Game::handleClientEvent_None},
2712         {&Game::handleClientEvent_PlayerDamage},
2713         {&Game::handleClientEvent_PlayerForceMove},
2714         {&Game::handleClientEvent_Deathscreen},
2715         {&Game::handleClientEvent_ShowFormSpec},
2716         {&Game::handleClientEvent_ShowLocalFormSpec},
2717         {&Game::handleClientEvent_HandleParticleEvent},
2718         {&Game::handleClientEvent_HandleParticleEvent},
2719         {&Game::handleClientEvent_HandleParticleEvent},
2720         {&Game::handleClientEvent_HudAdd},
2721         {&Game::handleClientEvent_HudRemove},
2722         {&Game::handleClientEvent_HudChange},
2723         {&Game::handleClientEvent_SetSky},
2724         {&Game::handleClientEvent_SetSun},
2725         {&Game::handleClientEvent_SetMoon},
2726         {&Game::handleClientEvent_SetStars},
2727         {&Game::handleClientEvent_OverrideDayNigthRatio},
2728         {&Game::handleClientEvent_CloudParams},
2729 };
2730
2731 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2732 {
2733         FATAL_ERROR("ClientEvent type None received");
2734 }
2735
2736 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2737 {
2738         if (client->modsLoaded())
2739                 client->getScript()->on_damage_taken(event->player_damage.amount);
2740
2741         if (!event->player_damage.effect)
2742                 return;
2743
2744         // Damage flash and hurt tilt are not used at death
2745         if (client->getHP() > 0) {
2746                 LocalPlayer *player = client->getEnv().getLocalPlayer();
2747
2748                 f32 hp_max = player->getCAO() ?
2749                         player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2750                 f32 damage_ratio = event->player_damage.amount / hp_max;
2751
2752                 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2753                 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2754
2755                 player->hurt_tilt_timer = 1.5f;
2756                 player->hurt_tilt_strength =
2757                         rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2758         }
2759
2760         // Play damage sound
2761         client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2762 }
2763
2764 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2765 {
2766         cam->camera_yaw = event->player_force_move.yaw;
2767         cam->camera_pitch = event->player_force_move.pitch;
2768 }
2769
2770 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2771 {
2772         // If client scripting is enabled, deathscreen is handled by CSM code in
2773         // builtin/client/init.lua
2774         if (client->modsLoaded())
2775                 client->getScript()->on_death();
2776         else
2777                 showDeathFormspec();
2778
2779         /* Handle visualization */
2780         LocalPlayer *player = client->getEnv().getLocalPlayer();
2781         runData.damage_flash = 0;
2782         player->hurt_tilt_timer = 0;
2783         player->hurt_tilt_strength = 0;
2784 }
2785
2786 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2787 {
2788         if (event->show_formspec.formspec->empty()) {
2789                 auto formspec = m_game_ui->getFormspecGUI();
2790                 if (formspec && (event->show_formspec.formname->empty()
2791                                 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2792                         formspec->quitMenu();
2793                 }
2794         } else {
2795                 FormspecFormSource *fs_src =
2796                         new FormspecFormSource(*(event->show_formspec.formspec));
2797                 TextDestPlayerInventory *txt_dst =
2798                         new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2799
2800                 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2801                 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2802                         &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2803         }
2804
2805         delete event->show_formspec.formspec;
2806         delete event->show_formspec.formname;
2807 }
2808
2809 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2810 {
2811         FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2812         LocalFormspecHandler *txt_dst =
2813                 new LocalFormspecHandler(*event->show_formspec.formname, client);
2814         GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2815                         &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2816
2817         delete event->show_formspec.formspec;
2818         delete event->show_formspec.formname;
2819 }
2820
2821 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2822                 CameraOrientation *cam)
2823 {
2824         LocalPlayer *player = client->getEnv().getLocalPlayer();
2825         client->getParticleManager()->handleParticleEvent(event, client, player);
2826 }
2827
2828 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2829 {
2830         LocalPlayer *player = client->getEnv().getLocalPlayer();
2831
2832         u32 server_id = event->hudadd->server_id;
2833         // ignore if we already have a HUD with that ID
2834         auto i = m_hud_server_to_client.find(server_id);
2835         if (i != m_hud_server_to_client.end()) {
2836                 delete event->hudadd;
2837                 return;
2838         }
2839
2840         HudElement *e = new HudElement;
2841         e->type   = static_cast<HudElementType>(event->hudadd->type);
2842         e->pos    = event->hudadd->pos;
2843         e->name   = event->hudadd->name;
2844         e->scale  = event->hudadd->scale;
2845         e->text   = event->hudadd->text;
2846         e->number = event->hudadd->number;
2847         e->item   = event->hudadd->item;
2848         e->dir    = event->hudadd->dir;
2849         e->align  = event->hudadd->align;
2850         e->offset = event->hudadd->offset;
2851         e->world_pos = event->hudadd->world_pos;
2852         e->size      = event->hudadd->size;
2853         e->z_index   = event->hudadd->z_index;
2854         e->text2     = event->hudadd->text2;
2855         e->style     = event->hudadd->style;
2856         m_hud_server_to_client[server_id] = player->addHud(e);
2857
2858         delete event->hudadd;
2859 }
2860
2861 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2862 {
2863         LocalPlayer *player = client->getEnv().getLocalPlayer();
2864
2865         auto i = m_hud_server_to_client.find(event->hudrm.id);
2866         if (i != m_hud_server_to_client.end()) {
2867                 HudElement *e = player->removeHud(i->second);
2868                 delete e;
2869                 m_hud_server_to_client.erase(i);
2870         }
2871
2872 }
2873
2874 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2875 {
2876         LocalPlayer *player = client->getEnv().getLocalPlayer();
2877
2878         HudElement *e = nullptr;
2879
2880         auto i = m_hud_server_to_client.find(event->hudchange->id);
2881         if (i != m_hud_server_to_client.end()) {
2882                 e = player->getHud(i->second);
2883         }
2884
2885         if (e == nullptr) {
2886                 delete event->hudchange;
2887                 return;
2888         }
2889
2890 #define CASE_SET(statval, prop, dataprop) \
2891         case statval: \
2892                 e->prop = event->hudchange->dataprop; \
2893                 break
2894
2895         switch (event->hudchange->stat) {
2896                 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2897
2898                 CASE_SET(HUD_STAT_NAME, name, sdata);
2899
2900                 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2901
2902                 CASE_SET(HUD_STAT_TEXT, text, sdata);
2903
2904                 CASE_SET(HUD_STAT_NUMBER, number, data);
2905
2906                 CASE_SET(HUD_STAT_ITEM, item, data);
2907
2908                 CASE_SET(HUD_STAT_DIR, dir, data);
2909
2910                 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2911
2912                 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2913
2914                 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2915
2916                 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2917
2918                 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2919
2920                 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2921
2922                 CASE_SET(HUD_STAT_STYLE, style, data);
2923         }
2924
2925 #undef CASE_SET
2926
2927         delete event->hudchange;
2928 }
2929
2930 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2931 {
2932         sky->setVisible(false);
2933         // Whether clouds are visible in front of a custom skybox.
2934         sky->setCloudsEnabled(event->set_sky->clouds);
2935
2936         if (skybox) {
2937                 skybox->remove();
2938                 skybox = NULL;
2939         }
2940         // Clear the old textures out in case we switch rendering type.
2941         sky->clearSkyboxTextures();
2942         // Handle according to type
2943         if (event->set_sky->type == "regular") {
2944                 // Shows the mesh skybox
2945                 sky->setVisible(true);
2946                 // Update mesh based skybox colours if applicable.
2947                 sky->setSkyColors(event->set_sky->sky_color);
2948                 sky->setHorizonTint(
2949                         event->set_sky->fog_sun_tint,
2950                         event->set_sky->fog_moon_tint,
2951                         event->set_sky->fog_tint_type
2952                 );
2953         } else if (event->set_sky->type == "skybox" &&
2954                         event->set_sky->textures.size() == 6) {
2955                 // Disable the dyanmic mesh skybox:
2956                 sky->setVisible(false);
2957                 // Set fog colors:
2958                 sky->setFallbackBgColor(event->set_sky->bgcolor);
2959                 // Set sunrise and sunset fog tinting:
2960                 sky->setHorizonTint(
2961                         event->set_sky->fog_sun_tint,
2962                         event->set_sky->fog_moon_tint,
2963                         event->set_sky->fog_tint_type
2964                 );
2965                 // Add textures to skybox.
2966                 for (int i = 0; i < 6; i++)
2967                         sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2968         } else {
2969                 // Handle everything else as plain color.
2970                 if (event->set_sky->type != "plain")
2971                         infostream << "Unknown sky type: "
2972                                 << (event->set_sky->type) << std::endl;
2973                 sky->setVisible(false);
2974                 sky->setFallbackBgColor(event->set_sky->bgcolor);
2975                 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2976                 sky->setHorizonTint(
2977                         event->set_sky->bgcolor,
2978                         event->set_sky->bgcolor,
2979                         "custom"
2980                 );
2981         }
2982
2983         delete event->set_sky;
2984 }
2985
2986 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2987 {
2988         sky->setSunVisible(event->sun_params->visible);
2989         sky->setSunTexture(event->sun_params->texture,
2990                 event->sun_params->tonemap, texture_src);
2991         sky->setSunScale(event->sun_params->scale);
2992         sky->setSunriseVisible(event->sun_params->sunrise_visible);
2993         sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2994         delete event->sun_params;
2995 }
2996
2997 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2998 {
2999         sky->setMoonVisible(event->moon_params->visible);
3000         sky->setMoonTexture(event->moon_params->texture,
3001                 event->moon_params->tonemap, texture_src);
3002         sky->setMoonScale(event->moon_params->scale);
3003         delete event->moon_params;
3004 }
3005
3006 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
3007 {
3008         sky->setStarsVisible(event->star_params->visible);
3009         sky->setStarCount(event->star_params->count);
3010         sky->setStarColor(event->star_params->starcolor);
3011         sky->setStarScale(event->star_params->scale);
3012         sky->setStarDayOpacity(event->star_params->day_opacity);
3013         delete event->star_params;
3014 }
3015
3016 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
3017                 CameraOrientation *cam)
3018 {
3019         client->getEnv().setDayNightRatioOverride(
3020                 event->override_day_night_ratio.do_override,
3021                 event->override_day_night_ratio.ratio_f * 1000.0f);
3022 }
3023
3024 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
3025 {
3026         if (!clouds)
3027                 return;
3028
3029         clouds->setDensity(event->cloud_params.density);
3030         clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
3031         clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
3032         clouds->setHeight(event->cloud_params.height);
3033         clouds->setThickness(event->cloud_params.thickness);
3034         clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
3035 }
3036
3037 void Game::processClientEvents(CameraOrientation *cam)
3038 {
3039         while (client->hasClientEvents()) {
3040                 std::unique_ptr<ClientEvent> event(client->getClientEvent());
3041                 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
3042                 const ClientEventHandler& evHandler = clientEventHandler[event->type];
3043                 (this->*evHandler.handler)(event.get(), cam);
3044         }
3045 }
3046
3047 void Game::updateChat(f32 dtime)
3048 {
3049         // Get new messages from error log buffer
3050         while (!m_chat_log_buf.empty())
3051                 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
3052
3053         // Get new messages from client
3054         std::wstring message;
3055         while (client->getChatMessage(message)) {
3056                 chat_backend->addUnparsedMessage(message);
3057         }
3058
3059         // Remove old messages
3060         chat_backend->step(dtime);
3061
3062         // Display all messages in a static text element
3063         auto &buf = chat_backend->getRecentBuffer();
3064         if (buf.getLinesModified()) {
3065                 buf.resetLinesModified();
3066                 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
3067         }
3068
3069         // Make sure that the size is still correct
3070         m_game_ui->updateChatSize();
3071 }
3072
3073 void Game::updateCamera(f32 dtime)
3074 {
3075         LocalPlayer *player = client->getEnv().getLocalPlayer();
3076
3077         /*
3078                 For interaction purposes, get info about the held item
3079                 - What item is it?
3080                 - Is it a usable item?
3081                 - Can it point to liquids?
3082         */
3083         ItemStack playeritem;
3084         {
3085                 ItemStack selected, hand;
3086                 playeritem = player->getWieldedItem(&selected, &hand);
3087         }
3088
3089         ToolCapabilities playeritem_toolcap =
3090                 playeritem.getToolCapabilities(itemdef_manager);
3091
3092         v3s16 old_camera_offset = camera->getOffset();
3093
3094         if (wasKeyDown(KeyType::CAMERA_MODE)) {
3095                 GenericCAO *playercao = player->getCAO();
3096
3097                 // If playercao not loaded, don't change camera
3098                 if (!playercao)
3099                         return;
3100
3101                 camera->toggleCameraMode();
3102
3103 #ifdef HAVE_TOUCHSCREENGUI
3104                 if (g_touchscreengui)
3105                         g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed());
3106 #endif
3107
3108                 // Make the player visible depending on camera mode.
3109                 playercao->updateMeshCulling();
3110                 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
3111         }
3112
3113         float full_punch_interval = playeritem_toolcap.full_punch_interval;
3114         float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
3115
3116         tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
3117         camera->update(player, dtime, tool_reload_ratio);
3118         camera->step(dtime);
3119
3120         f32 camera_fov = camera->getFovMax();
3121         v3s16 camera_offset = camera->getOffset();
3122
3123         m_camera_offset_changed = (camera_offset != old_camera_offset);
3124
3125         if (!m_flags.disable_camera_update) {
3126                 v3f camera_position = camera->getPosition();
3127                 v3f camera_direction = camera->getDirection();
3128
3129                 client->getEnv().getClientMap().updateCamera(camera_position,
3130                                 camera_direction, camera_fov, camera_offset);
3131
3132                 if (m_camera_offset_changed) {
3133                         client->updateCameraOffset(camera_offset);
3134                         client->getEnv().updateCameraOffset(camera_offset);
3135
3136                         if (clouds)
3137                                 clouds->updateCameraOffset(camera_offset);
3138                 }
3139         }
3140 }
3141
3142
3143 void Game::updateSound(f32 dtime)
3144 {
3145         // Update sound listener
3146         v3s16 camera_offset = camera->getOffset();
3147         sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3148                               v3f(0, 0, 0), // velocity
3149                               camera->getDirection(),
3150                               camera->getCameraNode()->getUpVector());
3151
3152         bool mute_sound = g_settings->getBool("mute_sound");
3153         if (mute_sound) {
3154                 sound->setListenerGain(0.0f);
3155         } else {
3156                 // Check if volume is in the proper range, else fix it.
3157                 float old_volume = g_settings->getFloat("sound_volume");
3158                 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3159                 sound->setListenerGain(new_volume);
3160
3161                 if (old_volume != new_volume) {
3162                         g_settings->setFloat("sound_volume", new_volume);
3163                 }
3164         }
3165
3166         LocalPlayer *player = client->getEnv().getLocalPlayer();
3167
3168         // Tell the sound maker whether to make footstep sounds
3169         soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3170
3171         //      Update sound maker
3172         if (player->makes_footstep_sound)
3173                 soundmaker->step(dtime);
3174
3175         ClientMap &map = client->getEnv().getClientMap();
3176         MapNode n = map.getNode(player->getFootstepNodePos());
3177         soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3178 }
3179
3180
3181 void Game::processPlayerInteraction(f32 dtime, bool show_hud)
3182 {
3183         LocalPlayer *player = client->getEnv().getLocalPlayer();
3184
3185         const v3f camera_direction = camera->getDirection();
3186         const v3s16 camera_offset  = camera->getOffset();
3187
3188         /*
3189                 Calculate what block is the crosshair pointing to
3190         */
3191
3192         ItemStack selected_item, hand_item;
3193         const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3194
3195         const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3196         f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3197
3198         core::line3d<f32> shootline;
3199
3200         switch (camera->getCameraMode()) {
3201         case CAMERA_MODE_FIRST:
3202                 // Shoot from camera position, with bobbing
3203                 shootline.start = camera->getPosition();
3204                 break;
3205         case CAMERA_MODE_THIRD:
3206                 // Shoot from player head, no bobbing
3207                 shootline.start = camera->getHeadPosition();
3208                 break;
3209         case CAMERA_MODE_THIRD_FRONT:
3210                 shootline.start = camera->getHeadPosition();
3211                 // prevent player pointing anything in front-view
3212                 d = 0;
3213                 break;
3214         }
3215         shootline.end = shootline.start + camera_direction * BS * d;
3216
3217 #ifdef HAVE_TOUCHSCREENGUI
3218         if (g_touchscreengui && isNoCrosshairAllowed()) {
3219                 shootline = g_touchscreengui->getShootline();
3220                 // Scale shootline to the acual distance the player can reach
3221                 shootline.end = shootline.start +
3222                                 shootline.getVector().normalize() * BS * d;
3223                 shootline.start += intToFloat(camera_offset, BS);
3224                 shootline.end += intToFloat(camera_offset, BS);
3225         }
3226 #endif
3227
3228         PointedThing pointed = updatePointedThing(shootline,
3229                         selected_def.liquids_pointable,
3230                         !runData.btn_down_for_dig,
3231                         camera_offset);
3232
3233         if (pointed != runData.pointed_old)
3234                 infostream << "Pointing at " << pointed.dump() << std::endl;
3235
3236         // Note that updating the selection mesh every frame is not particularly efficient,
3237         // but the halo rendering code is already inefficient so there's no point in optimizing it here
3238         hud->updateSelectionMesh(camera_offset);
3239
3240         // Allow digging again if button is not pressed
3241         if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3242                 runData.digging_blocked = false;
3243
3244         /*
3245                 Stop digging when
3246                 - releasing dig button
3247                 - pointing away from node
3248         */
3249         if (runData.digging) {
3250                 if (wasKeyReleased(KeyType::DIG)) {
3251                         infostream << "Dig button released (stopped digging)" << std::endl;
3252                         runData.digging = false;
3253                 } else if (pointed != runData.pointed_old) {
3254                         if (pointed.type == POINTEDTHING_NODE
3255                                         && runData.pointed_old.type == POINTEDTHING_NODE
3256                                         && pointed.node_undersurface
3257                                                         == runData.pointed_old.node_undersurface) {
3258                                 // Still pointing to the same node, but a different face.
3259                                 // Don't reset.
3260                         } else {
3261                                 infostream << "Pointing away from node (stopped digging)" << std::endl;
3262                                 runData.digging = false;
3263                                 hud->updateSelectionMesh(camera_offset);
3264                         }
3265                 }
3266
3267                 if (!runData.digging) {
3268                         client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3269                         client->setCrack(-1, v3s16(0, 0, 0));
3270                         runData.dig_time = 0.0;
3271                 }
3272         } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3273                 // Remove e.g. torches faster when clicking instead of holding dig button
3274                 runData.nodig_delay_timer = 0;
3275                 runData.dig_instantly = false;
3276         }
3277
3278         if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3279                 runData.btn_down_for_dig = false;
3280
3281         runData.punching = false;
3282
3283         soundmaker->m_player_leftpunch_sound = SimpleSoundSpec();
3284         soundmaker->m_player_leftpunch_sound2 = pointed.type != POINTEDTHING_NOTHING ?
3285                 selected_def.sound_use : selected_def.sound_use_air;
3286
3287         // Prepare for repeating, unless we're not supposed to
3288         if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3289                 runData.repeat_place_timer += dtime;
3290         else
3291                 runData.repeat_place_timer = 0;
3292
3293         if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3294                 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3295                                 !client->getScript()->on_item_use(selected_item, pointed)))
3296                         client->interact(INTERACT_USE, pointed);
3297         } else if (pointed.type == POINTEDTHING_NODE) {
3298                 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3299         } else if (pointed.type == POINTEDTHING_OBJECT) {
3300                 v3f player_position  = player->getPosition();
3301                 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
3302                 handlePointingAtObject(pointed, tool_item, player_position,
3303                                 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
3304         } else if (isKeyDown(KeyType::DIG)) {
3305                 // When button is held down in air, show continuous animation
3306                 runData.punching = true;
3307                 // Run callback even though item is not usable
3308                 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3309                         client->getScript()->on_item_use(selected_item, pointed);
3310         } else if (wasKeyPressed(KeyType::PLACE)) {
3311                 handlePointingAtNothing(selected_item);
3312         }
3313
3314         runData.pointed_old = pointed;
3315
3316         if (runData.punching || wasKeyPressed(KeyType::DIG))
3317                 camera->setDigging(0); // dig animation
3318
3319         input->clearWasKeyPressed();
3320         input->clearWasKeyReleased();
3321         // Ensure DIG & PLACE are marked as handled
3322         wasKeyDown(KeyType::DIG);
3323         wasKeyDown(KeyType::PLACE);
3324
3325         input->joystick.clearWasKeyPressed(KeyType::DIG);
3326         input->joystick.clearWasKeyPressed(KeyType::PLACE);
3327
3328         input->joystick.clearWasKeyReleased(KeyType::DIG);
3329         input->joystick.clearWasKeyReleased(KeyType::PLACE);
3330 }
3331
3332
3333 PointedThing Game::updatePointedThing(
3334         const core::line3d<f32> &shootline,
3335         bool liquids_pointable,
3336         bool look_for_object,
3337         const v3s16 &camera_offset)
3338 {
3339         std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3340         selectionboxes->clear();
3341         hud->setSelectedFaceNormal(v3f());
3342         static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3343                 "show_entity_selectionbox");
3344
3345         ClientEnvironment &env = client->getEnv();
3346         ClientMap &map = env.getClientMap();
3347         const NodeDefManager *nodedef = map.getNodeDefManager();
3348
3349         runData.selected_object = NULL;
3350         hud->pointing_at_object = false;
3351
3352         RaycastState s(shootline, look_for_object, liquids_pointable);
3353         PointedThing result;
3354         env.continueRaycast(&s, &result);
3355         if (result.type == POINTEDTHING_OBJECT) {
3356                 hud->pointing_at_object = true;
3357
3358                 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3359                 aabb3f selection_box;
3360                 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3361                                 runData.selected_object->getSelectionBox(&selection_box)) {
3362                         v3f pos = runData.selected_object->getPosition();
3363                         selectionboxes->push_back(aabb3f(selection_box));
3364                         hud->setSelectionPos(pos, camera_offset);
3365                         GenericCAO* gcao = dynamic_cast<GenericCAO*>(runData.selected_object);
3366                         if (gcao != nullptr && gcao->getProperties().rotate_selectionbox)
3367                                 hud->setSelectionRotation(gcao->getSceneNode()->getAbsoluteTransformation().getRotationDegrees());
3368                         else
3369                                 hud->setSelectionRotation(v3f());
3370                 }
3371                 hud->setSelectedFaceNormal(result.raw_intersection_normal);
3372         } else if (result.type == POINTEDTHING_NODE) {
3373                 // Update selection boxes
3374                 MapNode n = map.getNode(result.node_undersurface);
3375                 std::vector<aabb3f> boxes;
3376                 n.getSelectionBoxes(nodedef, &boxes,
3377                         n.getNeighbors(result.node_undersurface, &map));
3378
3379                 f32 d = 0.002 * BS;
3380                 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3381                         i != boxes.end(); ++i) {
3382                         aabb3f box = *i;
3383                         box.MinEdge -= v3f(d, d, d);
3384                         box.MaxEdge += v3f(d, d, d);
3385                         selectionboxes->push_back(box);
3386                 }
3387                 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3388                         camera_offset);
3389                 hud->setSelectionRotation(v3f());
3390                 hud->setSelectedFaceNormal(result.intersection_normal);
3391         }
3392
3393         // Update selection mesh light level and vertex colors
3394         if (!selectionboxes->empty()) {
3395                 v3f pf = hud->getSelectionPos();
3396                 v3s16 p = floatToInt(pf, BS);
3397
3398                 // Get selection mesh light level
3399                 MapNode n = map.getNode(p);
3400                 u16 node_light = getInteriorLight(n, -1, nodedef);
3401                 u16 light_level = node_light;
3402
3403                 for (const v3s16 &dir : g_6dirs) {
3404                         n = map.getNode(p + dir);
3405                         node_light = getInteriorLight(n, -1, nodedef);
3406                         if (node_light > light_level)
3407                                 light_level = node_light;
3408                 }
3409
3410                 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3411                 video::SColor c;
3412                 final_color_blend(&c, light_level, daynight_ratio);
3413
3414                 // Modify final color a bit with time
3415                 u32 timer = client->getEnv().getFrameTime() % 5000;
3416                 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3417                 float sin_r = 0.08f * std::sin(timerf);
3418                 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3419                 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3420                 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3421                 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3422                 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3423
3424                 // Set mesh final color
3425                 hud->setSelectionMeshColor(c);
3426         }
3427         return result;
3428 }
3429
3430
3431 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3432 {
3433         infostream << "Attempted to place item while pointing at nothing" << std::endl;
3434         PointedThing fauxPointed;
3435         fauxPointed.type = POINTEDTHING_NOTHING;
3436         client->interact(INTERACT_ACTIVATE, fauxPointed);
3437 }
3438
3439
3440 void Game::handlePointingAtNode(const PointedThing &pointed,
3441         const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3442 {
3443         v3s16 nodepos = pointed.node_undersurface;
3444         v3s16 neighborpos = pointed.node_abovesurface;
3445
3446         /*
3447                 Check information text of node
3448         */
3449
3450         ClientMap &map = client->getEnv().getClientMap();
3451
3452         if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3453                         && !runData.digging_blocked
3454                         && client->checkPrivilege("interact")) {
3455                 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3456         }
3457
3458         // This should be done after digging handling
3459         NodeMetadata *meta = map.getNodeMetadata(nodepos);
3460
3461         if (meta) {
3462                 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3463                         meta->getString("infotext"))));
3464         } else {
3465                 MapNode n = map.getNode(nodepos);
3466
3467                 if (nodedef_manager->get(n).name == "unknown") {
3468                         m_game_ui->setInfoText(L"Unknown node");
3469                 }
3470         }
3471
3472         if ((wasKeyPressed(KeyType::PLACE) ||
3473                         runData.repeat_place_timer >= m_repeat_place_time) &&
3474                         client->checkPrivilege("interact")) {
3475                 runData.repeat_place_timer = 0;
3476                 infostream << "Place button pressed while looking at ground" << std::endl;
3477
3478                 // Placing animation (always shown for feedback)
3479                 camera->setDigging(1);
3480
3481                 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3482
3483                 // If the wielded item has node placement prediction,
3484                 // make that happen
3485                 // And also set the sound and send the interact
3486                 // But first check for meta formspec and rightclickable
3487                 auto &def = selected_item.getDefinition(itemdef_manager);
3488                 bool placed = nodePlacement(def, selected_item, nodepos, neighborpos,
3489                         pointed, meta);
3490
3491                 if (placed && client->modsLoaded())
3492                         client->getScript()->on_placenode(pointed, def);
3493         }
3494 }
3495
3496 bool Game::nodePlacement(const ItemDefinition &selected_def,
3497         const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighborpos,
3498         const PointedThing &pointed, const NodeMetadata *meta)
3499 {
3500         const auto &prediction = selected_def.node_placement_prediction;
3501
3502         const NodeDefManager *nodedef = client->ndef();
3503         ClientMap &map = client->getEnv().getClientMap();
3504         MapNode node;
3505         bool is_valid_position;
3506
3507         node = map.getNode(nodepos, &is_valid_position);
3508         if (!is_valid_position) {
3509                 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3510                 return false;
3511         }
3512
3513         // formspec in meta
3514         if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3515                         && !isKeyDown(KeyType::SNEAK)) {
3516                 // on_rightclick callbacks are called anyway
3517                 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3518                         client->interact(INTERACT_PLACE, pointed);
3519
3520                 infostream << "Launching custom inventory view" << std::endl;
3521
3522                 InventoryLocation inventoryloc;
3523                 inventoryloc.setNodeMeta(nodepos);
3524
3525                 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3526                         &client->getEnv().getClientMap(), nodepos);
3527                 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3528
3529                 auto *&formspec = m_game_ui->updateFormspec("");
3530                 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3531                         &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3532
3533                 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3534                 return false;
3535         }
3536
3537         // on_rightclick callback
3538         if (prediction.empty() || (nodedef->get(node).rightclickable &&
3539                         !isKeyDown(KeyType::SNEAK))) {
3540                 // Report to server
3541                 client->interact(INTERACT_PLACE, pointed);
3542                 return false;
3543         }
3544
3545         verbosestream << "Node placement prediction for "
3546                 << selected_def.name << " is " << prediction << std::endl;
3547         v3s16 p = neighborpos;
3548
3549         // Place inside node itself if buildable_to
3550         MapNode n_under = map.getNode(nodepos, &is_valid_position);
3551         if (is_valid_position) {
3552                 if (nodedef->get(n_under).buildable_to) {
3553                         p = nodepos;
3554                 } else {
3555                         node = map.getNode(p, &is_valid_position);
3556                         if (is_valid_position && !nodedef->get(node).buildable_to) {
3557                                 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3558                                 // Report to server
3559                                 client->interact(INTERACT_PLACE, pointed);
3560                                 return false;
3561                         }
3562                 }
3563         }
3564
3565         // Find id of predicted node
3566         content_t id;
3567         bool found = nodedef->getId(prediction, id);
3568
3569         if (!found) {
3570                 errorstream << "Node placement prediction failed for "
3571                         << selected_def.name << " (places " << prediction
3572                         << ") - Name not known" << std::endl;
3573                 // Handle this as if prediction was empty
3574                 // Report to server
3575                 client->interact(INTERACT_PLACE, pointed);
3576                 return false;
3577         }
3578
3579         const ContentFeatures &predicted_f = nodedef->get(id);
3580
3581         // Compare core.item_place_node() for what the server does with param2
3582         MapNode predicted_node(id, 0, 0);
3583
3584         const u8 place_param2 = selected_def.place_param2;
3585
3586         if (place_param2) {
3587                 predicted_node.setParam2(place_param2);
3588         } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3589                         predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3590                 v3s16 dir = nodepos - neighborpos;
3591
3592                 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3593                         predicted_node.setParam2(dir.Y < 0 ? 1 : 0);
3594                 } else if (abs(dir.X) > abs(dir.Z)) {
3595                         predicted_node.setParam2(dir.X < 0 ? 3 : 2);
3596                 } else {
3597                         predicted_node.setParam2(dir.Z < 0 ? 5 : 4);
3598                 }
3599         } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3600                         predicted_f.param_type_2 == CPT2_COLORED_FACEDIR ||
3601                         predicted_f.param_type_2 == CPT2_4DIR ||
3602                         predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3603                 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3604
3605                 if (abs(dir.X) > abs(dir.Z)) {
3606                         predicted_node.setParam2(dir.X < 0 ? 3 : 1);
3607                 } else {
3608                         predicted_node.setParam2(dir.Z < 0 ? 2 : 0);
3609                 }
3610         }
3611
3612         // Check attachment if node is in group attached_node
3613         int an = itemgroup_get(predicted_f.groups, "attached_node");
3614         if (an != 0) {
3615                 v3s16 pp;
3616
3617                 if (an == 3) {
3618                         pp = p + v3s16(0, -1, 0);
3619                 } else if (an == 4) {
3620                         pp = p + v3s16(0, 1, 0);
3621                 } else if (an == 2) {
3622                         if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3623                                         predicted_f.param_type_2 == CPT2_COLORED_FACEDIR ||
3624                                         predicted_f.param_type_2 == CPT2_4DIR ||
3625                                         predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3626                                 pp = p + facedir_dirs[predicted_node.getFaceDir(nodedef)];
3627                         } else {
3628                                 pp = p;
3629                         }
3630                 } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3631                                 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3632                         pp = p + predicted_node.getWallMountedDir(nodedef);
3633                 } else {
3634                         pp = p + v3s16(0, -1, 0);
3635                 }
3636
3637                 if (!nodedef->get(map.getNode(pp)).walkable) {
3638                         soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3639                         // Report to server
3640                         client->interact(INTERACT_PLACE, pointed);
3641                         return false;
3642                 }
3643         }
3644
3645         // Apply color
3646         if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3647                         || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3648                         || predicted_f.param_type_2 == CPT2_COLORED_4DIR
3649                         || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3650                 const auto &indexstr = selected_item.metadata.
3651                         getString("palette_index", 0);
3652                 if (!indexstr.empty()) {
3653                         s32 index = mystoi(indexstr);
3654                         if (predicted_f.param_type_2 == CPT2_COLOR) {
3655                                 predicted_node.setParam2(index);
3656                         } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3657                                 // param2 = pure palette index + other
3658                                 predicted_node.setParam2((index & 0xf8) | (predicted_node.getParam2() & 0x07));
3659                         } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3660                                 // param2 = pure palette index + other
3661                                 predicted_node.setParam2((index & 0xe0) | (predicted_node.getParam2() & 0x1f));
3662                         } else if (predicted_f.param_type_2 == CPT2_COLORED_4DIR) {
3663                                 // param2 = pure palette index + other
3664                                 predicted_node.setParam2((index & 0xfc) | (predicted_node.getParam2() & 0x03));
3665                         }
3666                 }
3667         }
3668
3669         // Add node to client map
3670         try {
3671                 LocalPlayer *player = client->getEnv().getLocalPlayer();
3672
3673                 // Don't place node when player would be inside new node
3674                 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3675                 if (!predicted_f.walkable ||
3676                                 g_settings->getBool("enable_build_where_you_stand") ||
3677                                 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3678                                 (predicted_f.walkable &&
3679                                         neighborpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3680                                         neighborpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3681                         // This triggers the required mesh update too
3682                         client->addNode(p, predicted_node);
3683                         // Report to server
3684                         client->interact(INTERACT_PLACE, pointed);
3685                         // A node is predicted, also play a sound
3686                         soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3687                         return true;
3688                 } else {
3689                         soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3690                         return false;
3691                 }
3692         } catch (const InvalidPositionException &e) {
3693                 errorstream << "Node placement prediction failed for "
3694                         << selected_def.name << " (places "
3695                         << prediction << ") - Position not loaded" << std::endl;
3696                 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3697                 return false;
3698         }
3699 }
3700
3701 void Game::handlePointingAtObject(const PointedThing &pointed,
3702                 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3703 {
3704         std::wstring infotext = unescape_translate(
3705                 utf8_to_wide(runData.selected_object->infoText()));
3706
3707         if (show_debug) {
3708                 if (!infotext.empty()) {
3709                         infotext += L"\n";
3710                 }
3711                 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3712         }
3713
3714         m_game_ui->setInfoText(infotext);
3715
3716         if (isKeyDown(KeyType::DIG)) {
3717                 bool do_punch = false;
3718                 bool do_punch_damage = false;
3719
3720                 if (runData.object_hit_delay_timer <= 0.0) {
3721                         do_punch = true;
3722                         do_punch_damage = true;
3723                         runData.object_hit_delay_timer = object_hit_delay;
3724                 }
3725
3726                 if (wasKeyPressed(KeyType::DIG))
3727                         do_punch = true;
3728
3729                 if (do_punch) {
3730                         infostream << "Punched object" << std::endl;
3731                         runData.punching = true;
3732                 }
3733
3734                 if (do_punch_damage) {
3735                         // Report direct punch
3736                         v3f objpos = runData.selected_object->getPosition();
3737                         v3f dir = (objpos - player_position).normalize();
3738
3739                         bool disable_send = runData.selected_object->directReportPunch(
3740                                         dir, &tool_item, runData.time_from_last_punch);
3741                         runData.time_from_last_punch = 0;
3742
3743                         if (!disable_send)
3744                                 client->interact(INTERACT_START_DIGGING, pointed);
3745                 }
3746         } else if (wasKeyDown(KeyType::PLACE)) {
3747                 infostream << "Pressed place button while pointing at object" << std::endl;
3748                 client->interact(INTERACT_PLACE, pointed);  // place
3749         }
3750 }
3751
3752
3753 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3754                 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3755 {
3756         // See also: serverpackethandle.cpp, action == 2
3757         LocalPlayer *player = client->getEnv().getLocalPlayer();
3758         ClientMap &map = client->getEnv().getClientMap();
3759         MapNode n = map.getNode(nodepos);
3760         const auto &features = nodedef_manager->get(n);
3761
3762         // NOTE: Similar piece of code exists on the server side for
3763         // cheat detection.
3764         // Get digging parameters
3765         DigParams params = getDigParams(features.groups,
3766                         &selected_item.getToolCapabilities(itemdef_manager),
3767                         selected_item.wear);
3768
3769         // If can't dig, try hand
3770         if (!params.diggable) {
3771                 params = getDigParams(features.groups,
3772                                 &hand_item.getToolCapabilities(itemdef_manager));
3773         }
3774
3775         if (!params.diggable) {
3776                 // I guess nobody will wait for this long
3777                 runData.dig_time_complete = 10000000.0;
3778         } else {
3779                 runData.dig_time_complete = params.time;
3780
3781                 if (m_cache_enable_particles) {
3782                         client->getParticleManager()->addNodeParticle(client,
3783                                         player, nodepos, n, features);
3784                 }
3785         }
3786
3787         if (!runData.digging) {
3788                 infostream << "Started digging" << std::endl;
3789                 runData.dig_instantly = runData.dig_time_complete == 0;
3790                 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3791                         return;
3792
3793                 client->interact(INTERACT_START_DIGGING, pointed);
3794                 runData.digging = true;
3795                 runData.btn_down_for_dig = true;
3796         }
3797
3798         if (!runData.dig_instantly) {
3799                 runData.dig_index = (float)crack_animation_length
3800                                 * runData.dig_time
3801                                 / runData.dig_time_complete;
3802         } else {
3803                 // This is for e.g. torches
3804                 runData.dig_index = crack_animation_length;
3805         }
3806
3807         const auto &sound_dig = features.sound_dig;
3808
3809         if (sound_dig.exists() && params.diggable) {
3810                 if (sound_dig.name == "__group") {
3811                         if (!params.main_group.empty()) {
3812                                 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3813                                 soundmaker->m_player_leftpunch_sound.name =
3814                                                 std::string("default_dig_") +
3815                                                 params.main_group;
3816                         }
3817                 } else {
3818                         soundmaker->m_player_leftpunch_sound = sound_dig;
3819                 }
3820         }
3821
3822         // Don't show cracks if not diggable
3823         if (runData.dig_time_complete >= 100000.0) {
3824         } else if (runData.dig_index < crack_animation_length) {
3825                 client->setCrack(runData.dig_index, nodepos);
3826         } else {
3827                 infostream << "Digging completed" << std::endl;
3828                 client->setCrack(-1, v3s16(0, 0, 0));
3829
3830                 runData.dig_time = 0;
3831                 runData.digging = false;
3832                 // we successfully dug, now block it from repeating if we want to be safe
3833                 if (g_settings->getBool("safe_dig_and_place"))
3834                         runData.digging_blocked = true;
3835
3836                 runData.nodig_delay_timer =
3837                                 runData.dig_time_complete / (float)crack_animation_length;
3838
3839                 // We don't want a corresponding delay to very time consuming nodes
3840                 // and nodes without digging time (e.g. torches) get a fixed delay.
3841                 if (runData.nodig_delay_timer > 0.3)
3842                         runData.nodig_delay_timer = 0.3;
3843                 else if (runData.dig_instantly)
3844                         runData.nodig_delay_timer = 0.15;
3845
3846                 if (client->modsLoaded() &&
3847                                 client->getScript()->on_dignode(nodepos, n)) {
3848                         return;
3849                 }
3850
3851                 if (features.node_dig_prediction == "air") {
3852                         client->removeNode(nodepos);
3853                 } else if (!features.node_dig_prediction.empty()) {
3854                         content_t id;
3855                         bool found = nodedef_manager->getId(features.node_dig_prediction, id);
3856                         if (found)
3857                                 client->addNode(nodepos, id, true);
3858                 }
3859                 // implicit else: no prediction
3860
3861                 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3862
3863                 if (m_cache_enable_particles) {
3864                         client->getParticleManager()->addDiggingParticles(client,
3865                                 player, nodepos, n, features);
3866                 }
3867
3868
3869                 // Send event to trigger sound
3870                 client->getEventManager()->put(new NodeDugEvent(nodepos, n));
3871         }
3872
3873         if (runData.dig_time_complete < 100000.0) {
3874                 runData.dig_time += dtime;
3875         } else {
3876                 runData.dig_time = 0;
3877                 client->setCrack(-1, nodepos);
3878         }
3879
3880         camera->setDigging(0);  // Dig animation
3881 }
3882
3883 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3884                 const CameraOrientation &cam)
3885 {
3886         TimeTaker tt_update("Game::updateFrame()");
3887         LocalPlayer *player = client->getEnv().getLocalPlayer();
3888
3889         /*
3890                 Frame time
3891         */
3892
3893         client->getEnv().updateFrameTime(m_is_paused);
3894
3895         /*
3896                 Fog range
3897         */
3898
3899         if (draw_control->range_all) {
3900                 runData.fog_range = 100000 * BS;
3901         } else {
3902                 runData.fog_range = draw_control->wanted_range * BS;
3903         }
3904
3905         /*
3906                 Calculate general brightness
3907         */
3908         u32 daynight_ratio = client->getEnv().getDayNightRatio();
3909         float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3910         float direct_brightness;
3911         bool sunlight_seen;
3912
3913         // When in noclip mode force same sky brightness as above ground so you
3914         // can see properly
3915         if (draw_control->allow_noclip && m_cache_enable_free_move &&
3916                 client->checkPrivilege("fly")) {
3917                 direct_brightness = time_brightness;
3918                 sunlight_seen = true;
3919         } else {
3920                 float old_brightness = sky->getBrightness();
3921                 direct_brightness = client->getEnv().getClientMap()
3922                                 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3923                                         daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3924                                     / 255.0;
3925         }
3926
3927         float time_of_day_smooth = runData.time_of_day_smooth;
3928         float time_of_day = client->getEnv().getTimeOfDayF();
3929
3930         static const float maxsm = 0.05f;
3931         static const float todsm = 0.05f;
3932
3933         if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3934                         std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3935                         std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3936                 time_of_day_smooth = time_of_day;
3937
3938         if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3939                 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3940                                 + (time_of_day + 1.0) * todsm;
3941         else
3942                 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3943                                 + time_of_day * todsm;
3944
3945         runData.time_of_day_smooth = time_of_day_smooth;
3946
3947         sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3948                         sunlight_seen, camera->getCameraMode(), player->getYaw(),
3949                         player->getPitch());
3950
3951         /*
3952                 Update clouds
3953         */
3954         if (clouds) {
3955                 if (sky->getCloudsVisible()) {
3956                         clouds->setVisible(true);
3957                         clouds->step(dtime);
3958                         // camera->getPosition is not enough for 3rd person views
3959                         v3f camera_node_position = camera->getCameraNode()->getPosition();
3960                         v3s16 camera_offset      = camera->getOffset();
3961                         camera_node_position.X   = camera_node_position.X + camera_offset.X * BS;
3962                         camera_node_position.Y   = camera_node_position.Y + camera_offset.Y * BS;
3963                         camera_node_position.Z   = camera_node_position.Z + camera_offset.Z * BS;
3964                         clouds->update(camera_node_position,
3965                                         sky->getCloudColor());
3966                         if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3967                                 // if inside clouds, and fog enabled, use that as sky
3968                                 // color(s)
3969                                 video::SColor clouds_dark = clouds->getColor()
3970                                                 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3971                                 sky->overrideColors(clouds_dark, clouds->getColor());
3972                                 sky->setInClouds(true);
3973                                 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3974                                 // do not draw clouds after all
3975                                 clouds->setVisible(false);
3976                         }
3977                 } else {
3978                         clouds->setVisible(false);
3979                 }
3980         }
3981
3982         /*
3983                 Update particles
3984         */
3985         client->getParticleManager()->step(dtime);
3986
3987         /*
3988                 Fog
3989         */
3990
3991         if (m_cache_enable_fog) {
3992                 driver->setFog(
3993                                 sky->getBgColor(),
3994                                 video::EFT_FOG_LINEAR,
3995                                 runData.fog_range * m_cache_fog_start,
3996                                 runData.fog_range * 1.0,
3997                                 0.01,
3998                                 false, // pixel fog
3999                                 true // range fog
4000                 );
4001         } else {
4002                 driver->setFog(
4003                                 sky->getBgColor(),
4004                                 video::EFT_FOG_LINEAR,
4005                                 100000 * BS,
4006                                 110000 * BS,
4007                                 0.01f,
4008                                 false, // pixel fog
4009                                 false // range fog
4010                 );
4011         }
4012
4013         /*
4014                 Damage camera tilt
4015         */
4016         if (player->hurt_tilt_timer > 0.0f) {
4017                 player->hurt_tilt_timer -= dtime * 6.0f;
4018
4019                 if (player->hurt_tilt_timer < 0.0f)
4020                         player->hurt_tilt_strength = 0.0f;
4021         }
4022
4023         /*
4024                 Update minimap pos and rotation
4025         */
4026         if (mapper && m_game_ui->m_flags.show_hud) {
4027                 mapper->setPos(floatToInt(player->getPosition(), BS));
4028                 mapper->setAngle(player->getYaw());
4029         }
4030
4031         /*
4032                 Get chat messages from client
4033         */
4034
4035         updateChat(dtime);
4036
4037         /*
4038                 Inventory
4039         */
4040
4041         if (player->getWieldIndex() != runData.new_playeritem)
4042                 client->setPlayerItem(runData.new_playeritem);
4043
4044         if (client->updateWieldedItem()) {
4045                 // Update wielded tool
4046                 ItemStack selected_item, hand_item;
4047                 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
4048                 camera->wield(tool_item);
4049         }
4050
4051         /*
4052                 Update block draw list every 200ms or when camera direction has
4053                 changed much
4054         */
4055         runData.update_draw_list_timer += dtime;
4056         runData.touch_blocks_timer += dtime;
4057
4058         bool draw_list_updated = false;
4059
4060         float update_draw_list_delta = 0.2f;
4061
4062         v3f camera_direction = camera->getDirection();
4063         if (runData.update_draw_list_timer >= update_draw_list_delta
4064                         || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
4065                         || m_camera_offset_changed
4066                         || client->getEnv().getClientMap().needsUpdateDrawList()) {
4067                 runData.update_draw_list_timer = 0;
4068                 client->getEnv().getClientMap().updateDrawList();
4069                 runData.update_draw_list_last_cam_dir = camera_direction;
4070                 draw_list_updated = true;
4071         }
4072
4073         if (runData.touch_blocks_timer > update_draw_list_delta && !draw_list_updated) {
4074                 client->getEnv().getClientMap().touchMapBlocks();
4075                 runData.touch_blocks_timer = 0;
4076         }
4077
4078         if (RenderingEngine::get_shadow_renderer()) {
4079                 updateShadows();
4080         }
4081
4082         m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
4083
4084         /*
4085            make sure menu is on top
4086            1. Delete formspec menu reference if menu was removed
4087            2. Else, make sure formspec menu is on top
4088         */
4089         auto formspec = m_game_ui->getFormspecGUI();
4090         do { // breakable. only runs for one iteration
4091                 if (!formspec)
4092                         break;
4093
4094                 if (formspec->getReferenceCount() == 1) {
4095                         m_game_ui->deleteFormspec();
4096                         break;
4097                 }
4098
4099                 auto &loc = formspec->getFormspecLocation();
4100                 if (loc.type == InventoryLocation::NODEMETA) {
4101                         NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
4102                         if (!meta || meta->getString("formspec").empty()) {
4103                                 formspec->quitMenu();
4104                                 break;
4105                         }
4106                 }
4107
4108                 if (isMenuActive())
4109                         guiroot->bringToFront(formspec);
4110         } while (false);
4111
4112         /*
4113                 ==================== Drawing begins ====================
4114         */
4115         const video::SColor skycolor = sky->getSkyColor();
4116
4117         TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
4118         driver->beginScene(true, true, skycolor);
4119
4120         bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
4121                         (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
4122                         (camera->getCameraMode() == CAMERA_MODE_FIRST));
4123         bool draw_crosshair = (
4124                         (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
4125                         (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
4126 #ifdef HAVE_TOUCHSCREENGUI
4127         if (isNoCrosshairAllowed())
4128                 draw_crosshair = false;
4129 #endif
4130         m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
4131                         m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
4132
4133         /*
4134                 Profiler graph
4135         */
4136         v2u32 screensize = driver->getScreenSize();
4137
4138         if (m_game_ui->m_flags.show_profiler_graph)
4139                 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
4140
4141         /*
4142                 Damage flash
4143         */
4144         if (runData.damage_flash > 0.0f) {
4145                 video::SColor color(runData.damage_flash, 180, 0, 0);
4146                 driver->draw2DRectangle(color,
4147                                         core::rect<s32>(0, 0, screensize.X, screensize.Y),
4148                                         NULL);
4149
4150                 runData.damage_flash -= 384.0f * dtime;
4151         }
4152
4153         /*
4154                 ==================== End scene ====================
4155         */
4156 #if IRRLICHT_VERSION_MT_REVISION < 5
4157         if (++m_reset_HW_buffer_counter > 500) {
4158                 /*
4159                   Periodically remove all mesh HW buffers.
4160
4161                   Work around for a quirk in Irrlicht where a HW buffer is only
4162                   released after 20000 iterations (triggered from endScene()).
4163
4164                   Without this, all loaded but unused meshes will retain their HW
4165                   buffers for at least 5 minutes, at which point looking up the HW buffers
4166                   becomes a bottleneck and the framerate drops (as much as 30%).
4167
4168                   Tests showed that numbers between 50 and 1000 are good, so picked 500.
4169                   There are no other public Irrlicht APIs that allow interacting with the
4170                   HW buffers without tracking the status of every individual mesh.
4171
4172                   The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4173                 */
4174                 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4175                 driver->removeAllHardwareBuffers();
4176                 m_reset_HW_buffer_counter = 0;
4177         }
4178 #endif
4179
4180         driver->endScene();
4181
4182         stats->drawtime = tt_draw.stop(true);
4183         g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
4184         g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
4185 }
4186
4187 /* Log times and stuff for visualization */
4188 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4189 {
4190         Profiler::GraphValues values;
4191         g_profiler->graphGet(values);
4192         graph->put(values);
4193 }
4194
4195 /****************************************************************************
4196  * Shadows
4197  *****************************************************************************/
4198 void Game::updateShadows()
4199 {
4200         ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4201         if (!shadow)
4202                 return;
4203
4204         float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4205
4206         float timeoftheday = getWickedTimeOfDay(in_timeofday);
4207         bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
4208         bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
4209         shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
4210
4211         timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
4212         const float offset_constant = 10000.0f;
4213
4214         v3f light = is_day ? sky->getSunDirection() : sky->getMoonDirection();
4215
4216         v3f sun_pos = light * offset_constant;
4217
4218         if (shadow->getDirectionalLightCount() == 0)
4219                 shadow->addDirectionalLight();
4220         shadow->getDirectionalLight().setDirection(sun_pos);
4221         shadow->setTimeOfDay(in_timeofday);
4222
4223         shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4224 }
4225
4226 /****************************************************************************
4227  Misc
4228  ****************************************************************************/
4229
4230 void FpsControl::reset()
4231 {
4232         last_time = porting::getTimeUs();
4233 }
4234
4235 /*
4236  * On some computers framerate doesn't seem to be automatically limited
4237  */
4238 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
4239 {
4240         const float fps_limit = (device->isWindowFocused() && !g_menumgr.pausesGame())
4241                         ? g_settings->getFloat("fps_max")
4242                         : g_settings->getFloat("fps_max_unfocused");
4243         const u64 frametime_min = 1000000.0f / std::max(fps_limit, 1.0f);
4244
4245         u64 time = porting::getTimeUs();
4246
4247         if (time > last_time) // Make sure time hasn't overflowed
4248                 busy_time = time - last_time;
4249         else
4250                 busy_time = 0;
4251
4252         if (busy_time < frametime_min) {
4253                 sleep_time = frametime_min - busy_time;
4254                 if (sleep_time > 1000)
4255                         sleep_ms(sleep_time / 1000);
4256         } else {
4257                 sleep_time = 0;
4258         }
4259
4260         // Read the timer again to accurately determine how long we actually slept,
4261         // rather than calculating it by adding sleep_time to time.
4262         time = porting::getTimeUs();
4263
4264         if (time > last_time) // Make sure last_time hasn't overflowed
4265                 *dtime = (time - last_time) / 1000000.0f;
4266         else
4267                 *dtime = 0;
4268
4269         last_time = time;
4270 }
4271
4272 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4273 {
4274         const wchar_t *wmsg = wgettext(msg);
4275         m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4276                 draw_clouds);
4277         delete[] wmsg;
4278 }
4279
4280 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4281 {
4282         ((Game *)data)->readSettings();
4283 }
4284
4285 void Game::readSettings()
4286 {
4287         m_cache_doubletap_jump               = g_settings->getBool("doubletap_jump");
4288         m_cache_enable_clouds                = g_settings->getBool("enable_clouds");
4289         m_cache_enable_joysticks             = g_settings->getBool("enable_joysticks");
4290         m_cache_enable_particles             = g_settings->getBool("enable_particles");
4291         m_cache_enable_fog                   = g_settings->getBool("enable_fog");
4292         m_cache_mouse_sensitivity            = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f);
4293         m_cache_joystick_frustum_sensitivity = std::max(g_settings->getFloat("joystick_frustum_sensitivity"), 0.001f);
4294         m_repeat_place_time                  = g_settings->getFloat("repeat_place_time", 0.16f, 2.0);
4295
4296         m_cache_enable_noclip                = g_settings->getBool("noclip");
4297         m_cache_enable_free_move             = g_settings->getBool("free_move");
4298
4299         m_cache_fog_start                    = g_settings->getFloat("fog_start");
4300
4301         m_cache_cam_smoothing = 0;
4302         if (g_settings->getBool("cinematic"))
4303                 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4304         else
4305                 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4306
4307         m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4308         m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4309         m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4310
4311         m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4312 }
4313
4314 /****************************************************************************/
4315 /****************************************************************************
4316  Shutdown / cleanup
4317  ****************************************************************************/
4318 /****************************************************************************/
4319
4320 void Game::showDeathFormspec()
4321 {
4322         static std::string formspec_str =
4323                 std::string("formspec_version[1]") +
4324                 SIZE_TAG
4325                 "bgcolor[#320000b4;true]"
4326                 "label[4.85,1.35;" + gettext("You died") + "]"
4327                 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4328                 ;
4329
4330         /* Create menu */
4331         /* Note: FormspecFormSource and LocalFormspecHandler  *
4332          * are deleted by guiFormSpecMenu                     */
4333         FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4334         LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4335
4336         auto *&formspec = m_game_ui->getFormspecGUI();
4337         GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4338                 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4339         formspec->setFocus("btn_respawn");
4340 }
4341
4342 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4343 void Game::showPauseMenu()
4344 {
4345 #ifdef HAVE_TOUCHSCREENGUI
4346         static const std::string control_text = strgettext("Default Controls:\n"
4347                 "No menu visible:\n"
4348                 "- single tap: button activate\n"
4349                 "- double tap: place/use\n"
4350                 "- slide finger: look around\n"
4351                 "Menu/Inventory visible:\n"
4352                 "- double tap (outside):\n"
4353                 " -->close\n"
4354                 "- touch stack, touch slot:\n"
4355                 " --> move stack\n"
4356                 "- touch&drag, tap 2nd finger\n"
4357                 " --> place single item to slot\n"
4358                 );
4359 #else
4360         static const std::string control_text_template = strgettext("Controls:\n"
4361                 "- %s: move forwards\n"
4362                 "- %s: move backwards\n"
4363                 "- %s: move left\n"
4364                 "- %s: move right\n"
4365                 "- %s: jump/climb up\n"
4366                 "- %s: dig/punch\n"
4367                 "- %s: place/use\n"
4368                 "- %s: sneak/climb down\n"
4369                 "- %s: drop item\n"
4370                 "- %s: inventory\n"
4371                 "- Mouse: turn/look\n"
4372                 "- Mouse wheel: select item\n"
4373                 "- %s: chat\n"
4374         );
4375
4376         char control_text_buf[600];
4377
4378         porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4379                 GET_KEY_NAME(keymap_forward),
4380                 GET_KEY_NAME(keymap_backward),
4381                 GET_KEY_NAME(keymap_left),
4382                 GET_KEY_NAME(keymap_right),
4383                 GET_KEY_NAME(keymap_jump),
4384                 GET_KEY_NAME(keymap_dig),
4385                 GET_KEY_NAME(keymap_place),
4386                 GET_KEY_NAME(keymap_sneak),
4387                 GET_KEY_NAME(keymap_drop),
4388                 GET_KEY_NAME(keymap_inventory),
4389                 GET_KEY_NAME(keymap_chat)
4390         );
4391
4392         std::string control_text = std::string(control_text_buf);
4393         str_formspec_escape(control_text);
4394 #endif
4395
4396         float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4397         std::ostringstream os;
4398
4399         os << "formspec_version[1]" << SIZE_TAG
4400                 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4401                 << strgettext("Continue") << "]";
4402
4403         if (!simple_singleplayer_mode) {
4404                 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4405                         << strgettext("Change Password") << "]";
4406         } else {
4407                 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4408         }
4409
4410 #ifndef __ANDROID__
4411 #if USE_SOUND
4412         if (g_settings->getBool("enable_sound")) {
4413                 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4414                         << strgettext("Sound Volume") << "]";
4415         }
4416 #endif
4417         os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4418                 << strgettext("Change Keys")  << "]";
4419 #endif
4420         os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4421                 << strgettext("Exit to Menu") << "]";
4422         os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4423                 << strgettext("Exit to OS")   << "]"
4424                 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4425                 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4426                 << "\n"
4427                 <<  strgettext("Game info:") << "\n";
4428         const std::string &address = client->getAddressName();
4429         static const std::string mode = strgettext("- Mode: ");
4430         if (!simple_singleplayer_mode) {
4431                 Address serverAddress = client->getServerAddress();
4432                 if (!address.empty()) {
4433                         os << mode << strgettext("Remote server") << "\n"
4434                                         << strgettext("- Address: ") << address;
4435                 } else {
4436                         os << mode << strgettext("Hosting server");
4437                 }
4438                 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4439         } else {
4440                 os << mode << strgettext("Singleplayer") << "\n";
4441         }
4442         if (simple_singleplayer_mode || address.empty()) {
4443                 static const std::string on = strgettext("On");
4444                 static const std::string off = strgettext("Off");
4445                 // Note: Status of enable_damage and creative_mode settings is intentionally
4446                 // NOT shown here because the game might roll its own damage system and/or do
4447                 // a per-player Creative Mode, in which case writing it here would mislead.
4448                 bool damage = g_settings->getBool("enable_damage");
4449                 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4450                 if (!simple_singleplayer_mode) {
4451                         if (damage) {
4452                                 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4453                                 //~ PvP = Player versus Player
4454                                 os << strgettext("- PvP: ") << pvp << "\n";
4455                         }
4456                         os << strgettext("- Public: ") << announced << "\n";
4457                         std::string server_name = g_settings->get("server_name");
4458                         str_formspec_escape(server_name);
4459                         if (announced == on && !server_name.empty())
4460                                 os << strgettext("- Server Name: ") << server_name;
4461
4462                 }
4463         }
4464         os << ";]";
4465
4466         /* Create menu */
4467         /* Note: FormspecFormSource and LocalFormspecHandler  *
4468          * are deleted by guiFormSpecMenu                     */
4469         FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4470         LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4471
4472         auto *&formspec = m_game_ui->getFormspecGUI();
4473         GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4474                         &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4475         formspec->setFocus("btn_continue");
4476         // game will be paused in next step, if in singleplayer (see m_is_paused)
4477         formspec->doPause = true;
4478 }
4479
4480 /****************************************************************************/
4481 /****************************************************************************
4482  extern function for launching the game
4483  ****************************************************************************/
4484 /****************************************************************************/
4485
4486 void the_game(bool *kill,
4487                 InputHandler *input,
4488                 RenderingEngine *rendering_engine,
4489                 const GameStartData &start_data,
4490                 std::string &error_message,
4491                 ChatBackend &chat_backend,
4492                 bool *reconnect_requested) // Used for local game
4493 {
4494         Game game;
4495
4496         /* Make a copy of the server address because if a local singleplayer server
4497          * is created then this is updated and we don't want to change the value
4498          * passed to us by the calling function
4499          */
4500
4501         try {
4502
4503                 if (game.startup(kill, input, rendering_engine, start_data,
4504                                 error_message, reconnect_requested, &chat_backend)) {
4505                         game.run();
4506                 }
4507
4508         } catch (SerializationError &e) {
4509                 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
4510                 error_message = strgettext("A serialization error occurred:") +"\n"
4511                                 + e.what() + "\n\n" + ver_err;
4512                 errorstream << error_message << std::endl;
4513         } catch (ServerError &e) {
4514                 error_message = e.what();
4515                 errorstream << "ServerError: " << error_message << std::endl;
4516         } catch (ModError &e) {
4517                 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
4518                 error_message = std::string("ModError: ") + e.what() +
4519                                 strgettext("\nCheck debug.txt for details.");
4520                 errorstream << error_message << std::endl;
4521         }
4522         game.shutdown();
4523 }