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