]> git.lizzy.rs Git - dragonfireclient.git/blob - src/client/game.cpp
1e34ca286f115e77864814603c9f3b45e8d23c53
[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                 hud->updateSelectionMesh(camera_offset);
3102         }
3103
3104         // Allow digging again if button is not pressed
3105         if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3106                 runData.digging_blocked = false;
3107
3108         /*
3109                 Stop digging when
3110                 - releasing dig button
3111                 - pointing away from node
3112         */
3113         if (runData.digging) {
3114                 if (wasKeyReleased(KeyType::DIG)) {
3115                         infostream << "Dig button released (stopped digging)" << std::endl;
3116                         runData.digging = false;
3117                 } else if (pointed != runData.pointed_old) {
3118                         if (pointed.type == POINTEDTHING_NODE
3119                                         && runData.pointed_old.type == POINTEDTHING_NODE
3120                                         && pointed.node_undersurface
3121                                                         == runData.pointed_old.node_undersurface) {
3122                                 // Still pointing to the same node, but a different face.
3123                                 // Don't reset.
3124                         } else {
3125                                 infostream << "Pointing away from node (stopped digging)" << std::endl;
3126                                 runData.digging = false;
3127                                 hud->updateSelectionMesh(camera_offset);
3128                         }
3129                 }
3130
3131                 if (!runData.digging) {
3132                         client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3133                         client->setCrack(-1, v3s16(0, 0, 0));
3134                         runData.dig_time = 0.0;
3135                 }
3136         } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3137                 // Remove e.g. torches faster when clicking instead of holding dig button
3138                 runData.nodig_delay_timer = 0;
3139                 runData.dig_instantly = false;
3140         }
3141
3142         if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3143                 runData.btn_down_for_dig = false;
3144
3145         runData.punching = false;
3146
3147         soundmaker->m_player_leftpunch_sound.name = "";
3148
3149         // Prepare for repeating, unless we're not supposed to
3150         if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3151                 runData.repeat_place_timer += dtime;
3152         else
3153                 runData.repeat_place_timer = 0;
3154
3155         if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3156                 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3157                                 !client->getScript()->on_item_use(selected_item, pointed)))
3158                         client->interact(INTERACT_USE, pointed);
3159         } else if (pointed.type == POINTEDTHING_NODE) {
3160                 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3161         } else if (pointed.type == POINTEDTHING_OBJECT) {
3162                 v3f player_position  = player->getPosition();
3163                 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
3164                 handlePointingAtObject(pointed, tool_item, player_position,
3165                                 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
3166         } else if (isKeyDown(KeyType::DIG)) {
3167                 // When button is held down in air, show continuous animation
3168                 runData.punching = true;
3169                 // Run callback even though item is not usable
3170                 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3171                         client->getScript()->on_item_use(selected_item, pointed);
3172         } else if (wasKeyPressed(KeyType::PLACE)) {
3173                 handlePointingAtNothing(selected_item);
3174         }
3175
3176         runData.pointed_old = pointed;
3177
3178         if (runData.punching || wasKeyPressed(KeyType::DIG))
3179                 camera->setDigging(0); // dig animation
3180
3181         input->clearWasKeyPressed();
3182         input->clearWasKeyReleased();
3183         // Ensure DIG & PLACE are marked as handled
3184         wasKeyDown(KeyType::DIG);
3185         wasKeyDown(KeyType::PLACE);
3186
3187         input->joystick.clearWasKeyPressed(KeyType::DIG);
3188         input->joystick.clearWasKeyPressed(KeyType::PLACE);
3189
3190         input->joystick.clearWasKeyReleased(KeyType::DIG);
3191         input->joystick.clearWasKeyReleased(KeyType::PLACE);
3192 }
3193
3194
3195 PointedThing Game::updatePointedThing(
3196         const core::line3d<f32> &shootline,
3197         bool liquids_pointable,
3198         bool look_for_object,
3199         const v3s16 &camera_offset)
3200 {
3201         std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3202         selectionboxes->clear();
3203         hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3204         static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3205                 "show_entity_selectionbox");
3206
3207         ClientEnvironment &env = client->getEnv();
3208         ClientMap &map = env.getClientMap();
3209         const NodeDefManager *nodedef = map.getNodeDefManager();
3210
3211         runData.selected_object = NULL;
3212         hud->pointing_at_object = false;
3213
3214         RaycastState s(shootline, look_for_object, liquids_pointable);
3215         PointedThing result;
3216         env.continueRaycast(&s, &result);
3217         if (result.type == POINTEDTHING_OBJECT) {
3218                 hud->pointing_at_object = true;
3219
3220                 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3221                 aabb3f selection_box;
3222                 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3223                                 runData.selected_object->getSelectionBox(&selection_box)) {
3224                         v3f pos = runData.selected_object->getPosition();
3225                         selectionboxes->push_back(aabb3f(selection_box));
3226                         hud->setSelectionPos(pos, camera_offset);
3227                 }
3228         } else if (result.type == POINTEDTHING_NODE) {
3229                 // Update selection boxes
3230                 MapNode n = map.getNode(result.node_undersurface);
3231                 std::vector<aabb3f> boxes;
3232                 n.getSelectionBoxes(nodedef, &boxes,
3233                         n.getNeighbors(result.node_undersurface, &map));
3234
3235                 f32 d = 0.002 * BS;
3236                 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3237                         i != boxes.end(); ++i) {
3238                         aabb3f box = *i;
3239                         box.MinEdge -= v3f(d, d, d);
3240                         box.MaxEdge += v3f(d, d, d);
3241                         selectionboxes->push_back(box);
3242                 }
3243                 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3244                         camera_offset);
3245                 hud->setSelectedFaceNormal(v3f(
3246                         result.intersection_normal.X,
3247                         result.intersection_normal.Y,
3248                         result.intersection_normal.Z));
3249         }
3250
3251         // Update selection mesh light level and vertex colors
3252         if (!selectionboxes->empty()) {
3253                 v3f pf = hud->getSelectionPos();
3254                 v3s16 p = floatToInt(pf, BS);
3255
3256                 // Get selection mesh light level
3257                 MapNode n = map.getNode(p);
3258                 u16 node_light = getInteriorLight(n, -1, nodedef);
3259                 u16 light_level = node_light;
3260
3261                 for (const v3s16 &dir : g_6dirs) {
3262                         n = map.getNode(p + dir);
3263                         node_light = getInteriorLight(n, -1, nodedef);
3264                         if (node_light > light_level)
3265                                 light_level = node_light;
3266                 }
3267
3268                 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3269                 video::SColor c;
3270                 final_color_blend(&c, light_level, daynight_ratio);
3271
3272                 // Modify final color a bit with time
3273                 u32 timer = porting::getTimeMs() % 5000;
3274                 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3275                 float sin_r = 0.08f * std::sin(timerf);
3276                 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3277                 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3278                 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3279                 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3280                 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3281
3282                 // Set mesh final color
3283                 hud->setSelectionMeshColor(c);
3284         }
3285         return result;
3286 }
3287
3288
3289 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3290 {
3291         infostream << "Attempted to place item while pointing at nothing" << std::endl;
3292         PointedThing fauxPointed;
3293         fauxPointed.type = POINTEDTHING_NOTHING;
3294         client->interact(INTERACT_ACTIVATE, fauxPointed);
3295 }
3296
3297
3298 void Game::handlePointingAtNode(const PointedThing &pointed,
3299         const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3300 {
3301         v3s16 nodepos = pointed.node_undersurface;
3302         v3s16 neighbourpos = pointed.node_abovesurface;
3303
3304         /*
3305                 Check information text of node
3306         */
3307
3308         ClientMap &map = client->getEnv().getClientMap();
3309
3310         if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3311                         && !runData.digging_blocked
3312                         && client->checkPrivilege("interact")) {
3313                 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3314         }
3315
3316         // This should be done after digging handling
3317         NodeMetadata *meta = map.getNodeMetadata(nodepos);
3318
3319         if (meta) {
3320                 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3321                         meta->getString("infotext"))));
3322         } else {
3323                 MapNode n = map.getNode(nodepos);
3324
3325                 if (nodedef_manager->get(n).name == "unknown") {
3326                         m_game_ui->setInfoText(L"Unknown node");
3327                 }
3328         }
3329
3330         if ((wasKeyPressed(KeyType::PLACE) ||
3331                         runData.repeat_place_timer >= m_repeat_place_time) &&
3332                         client->checkPrivilege("interact")) {
3333                 runData.repeat_place_timer = 0;
3334                 infostream << "Place button pressed while looking at ground" << std::endl;
3335
3336                 // Placing animation (always shown for feedback)
3337                 camera->setDigging(1);
3338
3339                 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3340
3341                 // If the wielded item has node placement prediction,
3342                 // make that happen
3343                 // And also set the sound and send the interact
3344                 // But first check for meta formspec and rightclickable
3345                 auto &def = selected_item.getDefinition(itemdef_manager);
3346                 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3347                         pointed, meta);
3348
3349                 if (placed && client->modsLoaded())
3350                         client->getScript()->on_placenode(pointed, def);
3351         }
3352 }
3353
3354 bool Game::nodePlacement(const ItemDefinition &selected_def,
3355         const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3356         const PointedThing &pointed, const NodeMetadata *meta)
3357 {
3358         const auto &prediction = selected_def.node_placement_prediction;
3359
3360         const NodeDefManager *nodedef = client->ndef();
3361         ClientMap &map = client->getEnv().getClientMap();
3362         MapNode node;
3363         bool is_valid_position;
3364
3365         node = map.getNode(nodepos, &is_valid_position);
3366         if (!is_valid_position) {
3367                 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3368                 return false;
3369         }
3370
3371         // formspec in meta
3372         if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3373                         && !isKeyDown(KeyType::SNEAK)) {
3374                 // on_rightclick callbacks are called anyway
3375                 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3376                         client->interact(INTERACT_PLACE, pointed);
3377
3378                 infostream << "Launching custom inventory view" << std::endl;
3379
3380                 InventoryLocation inventoryloc;
3381                 inventoryloc.setNodeMeta(nodepos);
3382
3383                 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3384                         &client->getEnv().getClientMap(), nodepos);
3385                 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3386
3387                 auto *&formspec = m_game_ui->updateFormspec("");
3388                 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3389                         &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3390
3391                 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3392                 return false;
3393         }
3394
3395         // on_rightclick callback
3396         if (prediction.empty() || (nodedef->get(node).rightclickable &&
3397                         !isKeyDown(KeyType::SNEAK))) {
3398                 // Report to server
3399                 client->interact(INTERACT_PLACE, pointed);
3400                 return false;
3401         }
3402
3403         verbosestream << "Node placement prediction for "
3404                 << selected_def.name << " is " << prediction << std::endl;
3405         v3s16 p = neighbourpos;
3406
3407         // Place inside node itself if buildable_to
3408         MapNode n_under = map.getNode(nodepos, &is_valid_position);
3409         if (is_valid_position) {
3410                 if (nodedef->get(n_under).buildable_to) {
3411                         p = nodepos;
3412                 } else {
3413                         node = map.getNode(p, &is_valid_position);
3414                         if (is_valid_position && !nodedef->get(node).buildable_to) {
3415                                 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3416                                 // Report to server
3417                                 client->interact(INTERACT_PLACE, pointed);
3418                                 return false;
3419                         }
3420                 }
3421         }
3422
3423         // Find id of predicted node
3424         content_t id;
3425         bool found = nodedef->getId(prediction, id);
3426
3427         if (!found) {
3428                 errorstream << "Node placement prediction failed for "
3429                         << selected_def.name << " (places " << prediction
3430                         << ") - Name not known" << std::endl;
3431                 // Handle this as if prediction was empty
3432                 // Report to server
3433                 client->interact(INTERACT_PLACE, pointed);
3434                 return false;
3435         }
3436
3437         const ContentFeatures &predicted_f = nodedef->get(id);
3438
3439         // Predict param2 for facedir and wallmounted nodes
3440         // Compare core.item_place_node() for what the server does
3441         u8 param2 = 0;
3442
3443         const u8 place_param2 = selected_def.place_param2;
3444
3445         if (place_param2) {
3446                 param2 = place_param2;
3447         } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3448                         predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3449                 v3s16 dir = nodepos - neighbourpos;
3450
3451                 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3452                         param2 = dir.Y < 0 ? 1 : 0;
3453                 } else if (abs(dir.X) > abs(dir.Z)) {
3454                         param2 = dir.X < 0 ? 3 : 2;
3455                 } else {
3456                         param2 = dir.Z < 0 ? 5 : 4;
3457                 }
3458         } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3459                         predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3460                 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3461
3462                 if (abs(dir.X) > abs(dir.Z)) {
3463                         param2 = dir.X < 0 ? 3 : 1;
3464                 } else {
3465                         param2 = dir.Z < 0 ? 2 : 0;
3466                 }
3467         }
3468
3469         // Check attachment if node is in group attached_node
3470         if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3471                 const static v3s16 wallmounted_dirs[8] = {
3472                         v3s16(0, 1, 0),
3473                         v3s16(0, -1, 0),
3474                         v3s16(1, 0, 0),
3475                         v3s16(-1, 0, 0),
3476                         v3s16(0, 0, 1),
3477                         v3s16(0, 0, -1),
3478                 };
3479                 v3s16 pp;
3480
3481                 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3482                                 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3483                         pp = p + wallmounted_dirs[param2];
3484                 else
3485                         pp = p + v3s16(0, -1, 0);
3486
3487                 if (!nodedef->get(map.getNode(pp)).walkable) {
3488                         soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3489                         // Report to server
3490                         client->interact(INTERACT_PLACE, pointed);
3491                         return false;
3492                 }
3493         }
3494
3495         // Apply color
3496         if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3497                         || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3498                         || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3499                 const auto &indexstr = selected_item.metadata.
3500                         getString("palette_index", 0);
3501                 if (!indexstr.empty()) {
3502                         s32 index = mystoi(indexstr);
3503                         if (predicted_f.param_type_2 == CPT2_COLOR) {
3504                                 param2 = index;
3505                         } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3506                                 // param2 = pure palette index + other
3507                                 param2 = (index & 0xf8) | (param2 & 0x07);
3508                         } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3509                                 // param2 = pure palette index + other
3510                                 param2 = (index & 0xe0) | (param2 & 0x1f);
3511                         }
3512                 }
3513         }
3514
3515         // Add node to client map
3516         MapNode n(id, 0, param2);
3517
3518         try {
3519                 LocalPlayer *player = client->getEnv().getLocalPlayer();
3520
3521                 // Dont place node when player would be inside new node
3522                 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3523                 if (!nodedef->get(n).walkable ||
3524                                 g_settings->getBool("enable_build_where_you_stand") ||
3525                                 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3526                                 (nodedef->get(n).walkable &&
3527                                         neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3528                                         neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3529                         // This triggers the required mesh update too
3530                         client->addNode(p, n);
3531                         // Report to server
3532                         client->interact(INTERACT_PLACE, pointed);
3533                         // A node is predicted, also play a sound
3534                         soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3535                         return true;
3536                 } else {
3537                         soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3538                         return false;
3539                 }
3540         } catch (const InvalidPositionException &e) {
3541                 errorstream << "Node placement prediction failed for "
3542                         << selected_def.name << " (places "
3543                         << prediction << ") - Position not loaded" << std::endl;
3544                 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3545                 return false;
3546         }
3547 }
3548
3549 void Game::handlePointingAtObject(const PointedThing &pointed,
3550                 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3551 {
3552         std::wstring infotext = unescape_translate(
3553                 utf8_to_wide(runData.selected_object->infoText()));
3554
3555         if (show_debug) {
3556                 if (!infotext.empty()) {
3557                         infotext += L"\n";
3558                 }
3559                 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3560         }
3561
3562         m_game_ui->setInfoText(infotext);
3563
3564         if (isKeyDown(KeyType::DIG)) {
3565                 bool do_punch = false;
3566                 bool do_punch_damage = false;
3567
3568                 if (runData.object_hit_delay_timer <= 0.0) {
3569                         do_punch = true;
3570                         do_punch_damage = true;
3571                         runData.object_hit_delay_timer = object_hit_delay;
3572                 }
3573
3574                 if (wasKeyPressed(KeyType::DIG))
3575                         do_punch = true;
3576
3577                 if (do_punch) {
3578                         infostream << "Punched object" << std::endl;
3579                         runData.punching = true;
3580                 }
3581
3582                 if (do_punch_damage) {
3583                         // Report direct punch
3584                         v3f objpos = runData.selected_object->getPosition();
3585                         v3f dir = (objpos - player_position).normalize();
3586
3587                         bool disable_send = runData.selected_object->directReportPunch(
3588                                         dir, &tool_item, runData.time_from_last_punch);
3589                         runData.time_from_last_punch = 0;
3590
3591                         if (!disable_send)
3592                                 client->interact(INTERACT_START_DIGGING, pointed);
3593                 }
3594         } else if (wasKeyDown(KeyType::PLACE)) {
3595                 infostream << "Pressed place button while pointing at object" << std::endl;
3596                 client->interact(INTERACT_PLACE, pointed);  // place
3597         }
3598 }
3599
3600
3601 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3602                 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3603 {
3604         // See also: serverpackethandle.cpp, action == 2
3605         LocalPlayer *player = client->getEnv().getLocalPlayer();
3606         ClientMap &map = client->getEnv().getClientMap();
3607         MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3608
3609         // NOTE: Similar piece of code exists on the server side for
3610         // cheat detection.
3611         // Get digging parameters
3612         DigParams params = getDigParams(nodedef_manager->get(n).groups,
3613                         &selected_item.getToolCapabilities(itemdef_manager),
3614                         selected_item.wear);
3615
3616         // If can't dig, try hand
3617         if (!params.diggable) {
3618                 params = getDigParams(nodedef_manager->get(n).groups,
3619                                 &hand_item.getToolCapabilities(itemdef_manager));
3620         }
3621
3622         if (!params.diggable) {
3623                 // I guess nobody will wait for this long
3624                 runData.dig_time_complete = 10000000.0;
3625         } else {
3626                 runData.dig_time_complete = params.time;
3627
3628                 if (m_cache_enable_particles) {
3629                         const ContentFeatures &features = client->getNodeDefManager()->get(n);
3630                         client->getParticleManager()->addNodeParticle(client,
3631                                         player, nodepos, n, features);
3632                 }
3633         }
3634
3635         if (!runData.digging) {
3636                 infostream << "Started digging" << std::endl;
3637                 runData.dig_instantly = runData.dig_time_complete == 0;
3638                 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3639                         return;
3640                 client->interact(INTERACT_START_DIGGING, pointed);
3641                 runData.digging = true;
3642                 runData.btn_down_for_dig = true;
3643         }
3644
3645         if (!runData.dig_instantly) {
3646                 runData.dig_index = (float)crack_animation_length
3647                                 * runData.dig_time
3648                                 / runData.dig_time_complete;
3649         } else {
3650                 // This is for e.g. torches
3651                 runData.dig_index = crack_animation_length;
3652         }
3653
3654         SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3655
3656         if (sound_dig.exists() && params.diggable) {
3657                 if (sound_dig.name == "__group") {
3658                         if (!params.main_group.empty()) {
3659                                 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3660                                 soundmaker->m_player_leftpunch_sound.name =
3661                                                 std::string("default_dig_") +
3662                                                 params.main_group;
3663                         }
3664                 } else {
3665                         soundmaker->m_player_leftpunch_sound = sound_dig;
3666                 }
3667         }
3668
3669         // Don't show cracks if not diggable
3670         if (runData.dig_time_complete >= 100000.0) {
3671         } else if (runData.dig_index < crack_animation_length) {
3672                 //TimeTaker timer("client.setTempMod");
3673                 //infostream<<"dig_index="<<dig_index<<std::endl;
3674                 client->setCrack(runData.dig_index, nodepos);
3675         } else {
3676                 infostream << "Digging completed" << std::endl;
3677                 client->setCrack(-1, v3s16(0, 0, 0));
3678
3679                 runData.dig_time = 0;
3680                 runData.digging = false;
3681                 // we successfully dug, now block it from repeating if we want to be safe
3682                 if (g_settings->getBool("safe_dig_and_place"))
3683                         runData.digging_blocked = true;
3684
3685                 runData.nodig_delay_timer =
3686                                 runData.dig_time_complete / (float)crack_animation_length;
3687
3688                 // We don't want a corresponding delay to very time consuming nodes
3689                 // and nodes without digging time (e.g. torches) get a fixed delay.
3690                 if (runData.nodig_delay_timer > 0.3)
3691                         runData.nodig_delay_timer = 0.3;
3692                 else if (runData.dig_instantly)
3693                         runData.nodig_delay_timer = 0.15;
3694
3695                 bool is_valid_position;
3696                 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3697                 if (is_valid_position) {
3698                         if (client->modsLoaded() &&
3699                                         client->getScript()->on_dignode(nodepos, wasnode)) {
3700                                 return;
3701                         }
3702
3703                         const ContentFeatures &f = client->ndef()->get(wasnode);
3704                         if (f.node_dig_prediction == "air") {
3705                                 client->removeNode(nodepos);
3706                         } else if (!f.node_dig_prediction.empty()) {
3707                                 content_t id;
3708                                 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3709                                 if (found)
3710                                         client->addNode(nodepos, id, true);
3711                         }
3712                         // implicit else: no prediction
3713                 }
3714
3715                 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3716
3717                 if (m_cache_enable_particles) {
3718                         const ContentFeatures &features =
3719                                 client->getNodeDefManager()->get(wasnode);
3720                         client->getParticleManager()->addDiggingParticles(client,
3721                                 player, nodepos, wasnode, features);
3722                 }
3723
3724
3725                 // Send event to trigger sound
3726                 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3727         }
3728
3729         if (runData.dig_time_complete < 100000.0) {
3730                 runData.dig_time += dtime;
3731         } else {
3732                 runData.dig_time = 0;
3733                 client->setCrack(-1, nodepos);
3734         }
3735
3736         camera->setDigging(0);  // Dig animation
3737 }
3738
3739 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3740                 const CameraOrientation &cam)
3741 {
3742         TimeTaker tt_update("Game::updateFrame()");
3743         LocalPlayer *player = client->getEnv().getLocalPlayer();
3744
3745         /*
3746                 Fog range
3747         */
3748
3749         if (draw_control->range_all) {
3750                 runData.fog_range = 100000 * BS;
3751         } else {
3752                 runData.fog_range = draw_control->wanted_range * BS;
3753         }
3754
3755         /*
3756                 Calculate general brightness
3757         */
3758         u32 daynight_ratio = client->getEnv().getDayNightRatio();
3759         float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3760         float direct_brightness;
3761         bool sunlight_seen;
3762
3763         if (m_cache_enable_noclip && m_cache_enable_free_move) {
3764                 direct_brightness = time_brightness;
3765                 sunlight_seen = true;
3766         } else {
3767                 float old_brightness = sky->getBrightness();
3768                 direct_brightness = client->getEnv().getClientMap()
3769                                 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3770                                         daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3771                                     / 255.0;
3772         }
3773
3774         float time_of_day_smooth = runData.time_of_day_smooth;
3775         float time_of_day = client->getEnv().getTimeOfDayF();
3776
3777         static const float maxsm = 0.05f;
3778         static const float todsm = 0.05f;
3779
3780         if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3781                         std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3782                         std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3783                 time_of_day_smooth = time_of_day;
3784
3785         if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3786                 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3787                                 + (time_of_day + 1.0) * todsm;
3788         else
3789                 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3790                                 + time_of_day * todsm;
3791
3792         runData.time_of_day_smooth = time_of_day_smooth;
3793
3794         sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3795                         sunlight_seen, camera->getCameraMode(), player->getYaw(),
3796                         player->getPitch());
3797
3798         /*
3799                 Update clouds
3800         */
3801         if (clouds) {
3802                 if (sky->getCloudsVisible()) {
3803                         clouds->setVisible(true);
3804                         clouds->step(dtime);
3805                         // camera->getPosition is not enough for 3rd person views
3806                         v3f camera_node_position = camera->getCameraNode()->getPosition();
3807                         v3s16 camera_offset      = camera->getOffset();
3808                         camera_node_position.X   = camera_node_position.X + camera_offset.X * BS;
3809                         camera_node_position.Y   = camera_node_position.Y + camera_offset.Y * BS;
3810                         camera_node_position.Z   = camera_node_position.Z + camera_offset.Z * BS;
3811                         clouds->update(camera_node_position,
3812                                         sky->getCloudColor());
3813                         if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3814                                 // if inside clouds, and fog enabled, use that as sky
3815                                 // color(s)
3816                                 video::SColor clouds_dark = clouds->getColor()
3817                                                 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3818                                 sky->overrideColors(clouds_dark, clouds->getColor());
3819                                 sky->setInClouds(true);
3820                                 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3821                                 // do not draw clouds after all
3822                                 clouds->setVisible(false);
3823                         }
3824                 } else {
3825                         clouds->setVisible(false);
3826                 }
3827         }
3828
3829         /*
3830                 Update particles
3831         */
3832         client->getParticleManager()->step(dtime);
3833
3834         /*
3835                 Fog
3836         */
3837
3838         if (m_cache_enable_fog) {
3839                 driver->setFog(
3840                                 sky->getBgColor(),
3841                                 video::EFT_FOG_LINEAR,
3842                                 runData.fog_range * m_cache_fog_start,
3843                                 runData.fog_range * 1.0,
3844                                 0.01,
3845                                 false, // pixel fog
3846                                 true // range fog
3847                 );
3848         } else {
3849                 driver->setFog(
3850                                 sky->getBgColor(),
3851                                 video::EFT_FOG_LINEAR,
3852                                 100000 * BS,
3853                                 110000 * BS,
3854                                 0.01f,
3855                                 false, // pixel fog
3856                                 false // range fog
3857                 );
3858         }
3859
3860         /*
3861                 Damage camera tilt
3862         */
3863         if (player->hurt_tilt_timer > 0.0f) {
3864                 player->hurt_tilt_timer -= dtime * 6.0f;
3865
3866                 if (player->hurt_tilt_timer < 0.0f)
3867                         player->hurt_tilt_strength = 0.0f;
3868         }
3869
3870         /*
3871                 Update minimap pos and rotation
3872         */
3873         if (mapper && m_game_ui->m_flags.show_hud) {
3874                 mapper->setPos(floatToInt(player->getPosition(), BS));
3875                 mapper->setAngle(player->getYaw());
3876         }
3877
3878         /*
3879                 Get chat messages from client
3880         */
3881
3882         updateChat(dtime);
3883
3884         /*
3885                 Inventory
3886         */
3887
3888         if (player->getWieldIndex() != runData.new_playeritem)
3889                 client->setPlayerItem(runData.new_playeritem);
3890
3891         if (client->updateWieldedItem()) {
3892                 // Update wielded tool
3893                 ItemStack selected_item, hand_item;
3894                 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3895                 camera->wield(tool_item);
3896         }
3897
3898         /*
3899                 Update block draw list every 200ms or when camera direction has
3900                 changed much
3901         */
3902         runData.update_draw_list_timer += dtime;
3903
3904         float update_draw_list_delta = 0.2f;
3905
3906         v3f camera_direction = camera->getDirection();
3907         if (runData.update_draw_list_timer >= update_draw_list_delta
3908                         || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3909                         || m_camera_offset_changed
3910                         || client->getEnv().getClientMap().needsUpdateDrawList()) {
3911                 runData.update_draw_list_timer = 0;
3912                 client->getEnv().getClientMap().updateDrawList();
3913                 runData.update_draw_list_last_cam_dir = camera_direction;
3914         }
3915
3916         if (RenderingEngine::get_shadow_renderer()) {
3917                 updateShadows();
3918         }
3919
3920         m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3921
3922         /*
3923            make sure menu is on top
3924            1. Delete formspec menu reference if menu was removed
3925            2. Else, make sure formspec menu is on top
3926         */
3927         auto formspec = m_game_ui->getFormspecGUI();
3928         do { // breakable. only runs for one iteration
3929                 if (!formspec)
3930                         break;
3931
3932                 if (formspec->getReferenceCount() == 1) {
3933                         m_game_ui->deleteFormspec();
3934                         break;
3935                 }
3936
3937                 auto &loc = formspec->getFormspecLocation();
3938                 if (loc.type == InventoryLocation::NODEMETA) {
3939                         NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3940                         if (!meta || meta->getString("formspec").empty()) {
3941                                 formspec->quitMenu();
3942                                 break;
3943                         }
3944                 }
3945
3946                 if (isMenuActive())
3947                         guiroot->bringToFront(formspec);
3948         } while (false);
3949
3950         /*
3951                 ==================== Drawing begins ====================
3952         */
3953         const video::SColor skycolor = sky->getSkyColor();
3954
3955         TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
3956         driver->beginScene(true, true, skycolor);
3957
3958         bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3959                         (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3960                         (camera->getCameraMode() == CAMERA_MODE_FIRST));
3961         bool draw_crosshair = (
3962                         (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3963                         (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3964 #ifdef HAVE_TOUCHSCREENGUI
3965         try {
3966                 draw_crosshair = !g_settings->getBool("touchtarget");
3967         } catch (SettingNotFoundException) {
3968         }
3969 #endif
3970         m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3971                         m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3972
3973         /*
3974                 Profiler graph
3975         */
3976         v2u32 screensize = driver->getScreenSize();
3977
3978         if (m_game_ui->m_flags.show_profiler_graph)
3979                 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3980
3981         /*
3982                 Damage flash
3983         */
3984         if (runData.damage_flash > 0.0f) {
3985                 video::SColor color(runData.damage_flash, 180, 0, 0);
3986                 driver->draw2DRectangle(color,
3987                                         core::rect<s32>(0, 0, screensize.X, screensize.Y),
3988                                         NULL);
3989
3990                 runData.damage_flash -= 384.0f * dtime;
3991         }
3992
3993         /*
3994                 ==================== End scene ====================
3995         */
3996 #if IRRLICHT_VERSION_MT_REVISION < 5
3997         if (++m_reset_HW_buffer_counter > 500) {
3998                 /*
3999                   Periodically remove all mesh HW buffers.
4000
4001                   Work around for a quirk in Irrlicht where a HW buffer is only
4002                   released after 20000 iterations (triggered from endScene()).
4003
4004                   Without this, all loaded but unused meshes will retain their HW
4005                   buffers for at least 5 minutes, at which point looking up the HW buffers
4006                   becomes a bottleneck and the framerate drops (as much as 30%).
4007
4008                   Tests showed that numbers between 50 and 1000 are good, so picked 500.
4009                   There are no other public Irrlicht APIs that allow interacting with the
4010                   HW buffers without tracking the status of every individual mesh.
4011
4012                   The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4013                 */
4014                 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4015                 driver->removeAllHardwareBuffers();
4016                 m_reset_HW_buffer_counter = 0;
4017         }
4018 #endif
4019
4020         driver->endScene();
4021
4022         stats->drawtime = tt_draw.stop(true);
4023         g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
4024         g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
4025 }
4026
4027 /* Log times and stuff for visualization */
4028 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4029 {
4030         Profiler::GraphValues values;
4031         g_profiler->graphGet(values);
4032         graph->put(values);
4033 }
4034
4035 /****************************************************************************
4036  * Shadows
4037  *****************************************************************************/
4038 void Game::updateShadows()
4039 {
4040         ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4041         if (!shadow)
4042                 return;
4043
4044         float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4045
4046         float timeoftheday = getWickedTimeOfDay(in_timeofday);
4047         bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
4048         bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
4049         shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
4050
4051         timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
4052         const float offset_constant = 10000.0f;
4053
4054         v3f light(0.0f, 0.0f, -1.0f);
4055         light.rotateXZBy(90);
4056         light.rotateXYBy(timeoftheday * 360 - 90);
4057         light.rotateYZBy(sky->getSkyBodyOrbitTilt());
4058
4059         v3f sun_pos = light * offset_constant;
4060
4061         if (shadow->getDirectionalLightCount() == 0)
4062                 shadow->addDirectionalLight();
4063         shadow->getDirectionalLight().setDirection(sun_pos);
4064         shadow->setTimeOfDay(in_timeofday);
4065
4066         shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4067 }
4068
4069 /****************************************************************************
4070  Misc
4071  ****************************************************************************/
4072
4073 void FpsControl::reset()
4074 {
4075         last_time = porting::getTimeUs();
4076 }
4077
4078 /*
4079  * On some computers framerate doesn't seem to be automatically limited
4080  */
4081 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
4082 {
4083         const u64 frametime_min = 1000000.0f / (
4084                 device->isWindowFocused() && !g_menumgr.pausesGame()
4085                         ? g_settings->getFloat("fps_max")
4086                         : g_settings->getFloat("fps_max_unfocused"));
4087
4088         u64 time = porting::getTimeUs();
4089
4090         if (time > last_time) // Make sure time hasn't overflowed
4091                 busy_time = time - last_time;
4092         else
4093                 busy_time = 0;
4094
4095         if (busy_time < frametime_min) {
4096                 sleep_time = frametime_min - busy_time;
4097                 if (sleep_time > 1000)
4098                         sleep_ms(sleep_time / 1000);
4099         } else {
4100                 sleep_time = 0;
4101         }
4102
4103         // Read the timer again to accurately determine how long we actually slept,
4104         // rather than calculating it by adding sleep_time to time.
4105         time = porting::getTimeUs();
4106
4107         if (time > last_time) // Make sure last_time hasn't overflowed
4108                 *dtime = (time - last_time) / 1000000.0f;
4109         else
4110                 *dtime = 0;
4111
4112         last_time = time;
4113 }
4114
4115 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4116 {
4117         const wchar_t *wmsg = wgettext(msg);
4118         m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4119                 draw_clouds);
4120         delete[] wmsg;
4121 }
4122
4123 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4124 {
4125         ((Game *)data)->readSettings();
4126 }
4127
4128 void Game::readSettings()
4129 {
4130         m_cache_doubletap_jump               = g_settings->getBool("doubletap_jump");
4131         m_cache_enable_clouds                = g_settings->getBool("enable_clouds");
4132         m_cache_enable_joysticks             = g_settings->getBool("enable_joysticks");
4133         m_cache_enable_particles             = g_settings->getBool("enable_particles");
4134         m_cache_enable_fog                   = g_settings->getBool("enable_fog");
4135         m_cache_mouse_sensitivity            = g_settings->getFloat("mouse_sensitivity");
4136         m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4137         m_repeat_place_time                  = g_settings->getFloat("repeat_place_time");
4138
4139         m_cache_enable_noclip                = g_settings->getBool("noclip");
4140         m_cache_enable_free_move             = g_settings->getBool("free_move");
4141
4142         m_cache_fog_start                    = g_settings->getFloat("fog_start");
4143
4144         m_cache_cam_smoothing = 0;
4145         if (g_settings->getBool("cinematic"))
4146                 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4147         else
4148                 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4149
4150         m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4151         m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4152         m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4153
4154         m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4155 }
4156
4157 /****************************************************************************/
4158 /****************************************************************************
4159  Shutdown / cleanup
4160  ****************************************************************************/
4161 /****************************************************************************/
4162
4163 void Game::showDeathFormspec()
4164 {
4165         static std::string formspec_str =
4166                 std::string("formspec_version[1]") +
4167                 SIZE_TAG
4168                 "bgcolor[#320000b4;true]"
4169                 "label[4.85,1.35;" + gettext("You died") + "]"
4170                 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4171                 ;
4172
4173         /* Create menu */
4174         /* Note: FormspecFormSource and LocalFormspecHandler  *
4175          * are deleted by guiFormSpecMenu                     */
4176         FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4177         LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4178
4179         auto *&formspec = m_game_ui->getFormspecGUI();
4180         GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4181                 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4182         formspec->setFocus("btn_respawn");
4183 }
4184
4185 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4186 void Game::showPauseMenu()
4187 {
4188 #ifdef HAVE_TOUCHSCREENGUI
4189         static const std::string control_text = strgettext("Default Controls:\n"
4190                 "No menu visible:\n"
4191                 "- single tap: button activate\n"
4192                 "- double tap: place/use\n"
4193                 "- slide finger: look around\n"
4194                 "Menu/Inventory visible:\n"
4195                 "- double tap (outside):\n"
4196                 " -->close\n"
4197                 "- touch stack, touch slot:\n"
4198                 " --> move stack\n"
4199                 "- touch&drag, tap 2nd finger\n"
4200                 " --> place single item to slot\n"
4201                 );
4202 #else
4203         static const std::string control_text_template = strgettext("Controls:\n"
4204                 "- %s: move forwards\n"
4205                 "- %s: move backwards\n"
4206                 "- %s: move left\n"
4207                 "- %s: move right\n"
4208                 "- %s: jump/climb up\n"
4209                 "- %s: dig/punch\n"
4210                 "- %s: place/use\n"
4211                 "- %s: sneak/climb down\n"
4212                 "- %s: drop item\n"
4213                 "- %s: inventory\n"
4214                 "- Mouse: turn/look\n"
4215                 "- Mouse wheel: select item\n"
4216                 "- %s: chat\n"
4217         );
4218
4219         char control_text_buf[600];
4220
4221         porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4222                 GET_KEY_NAME(keymap_forward),
4223                 GET_KEY_NAME(keymap_backward),
4224                 GET_KEY_NAME(keymap_left),
4225                 GET_KEY_NAME(keymap_right),
4226                 GET_KEY_NAME(keymap_jump),
4227                 GET_KEY_NAME(keymap_dig),
4228                 GET_KEY_NAME(keymap_place),
4229                 GET_KEY_NAME(keymap_sneak),
4230                 GET_KEY_NAME(keymap_drop),
4231                 GET_KEY_NAME(keymap_inventory),
4232                 GET_KEY_NAME(keymap_chat)
4233         );
4234
4235         std::string control_text = std::string(control_text_buf);
4236         str_formspec_escape(control_text);
4237 #endif
4238
4239         float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4240         std::ostringstream os;
4241
4242         os << "formspec_version[1]" << SIZE_TAG
4243                 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4244                 << strgettext("Continue") << "]";
4245
4246         if (!simple_singleplayer_mode) {
4247                 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4248                         << strgettext("Change Password") << "]";
4249         } else {
4250                 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4251         }
4252
4253 #ifndef __ANDROID__
4254 #if USE_SOUND
4255         if (g_settings->getBool("enable_sound")) {
4256                 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4257                         << strgettext("Sound Volume") << "]";
4258         }
4259 #endif
4260         os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4261                 << strgettext("Change Keys")  << "]";
4262 #endif
4263         os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4264                 << strgettext("Exit to Menu") << "]";
4265         os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4266                 << strgettext("Exit to OS")   << "]"
4267                 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4268                 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4269                 << "\n"
4270                 <<  strgettext("Game info:") << "\n";
4271         const std::string &address = client->getAddressName();
4272         static const std::string mode = strgettext("- Mode: ");
4273         if (!simple_singleplayer_mode) {
4274                 Address serverAddress = client->getServerAddress();
4275                 if (!address.empty()) {
4276                         os << mode << strgettext("Remote server") << "\n"
4277                                         << strgettext("- Address: ") << address;
4278                 } else {
4279                         os << mode << strgettext("Hosting server");
4280                 }
4281                 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4282         } else {
4283                 os << mode << strgettext("Singleplayer") << "\n";
4284         }
4285         if (simple_singleplayer_mode || address.empty()) {
4286                 static const std::string on = strgettext("On");
4287                 static const std::string off = strgettext("Off");
4288                 // Note: Status of enable_damage and creative_mode settings is intentionally
4289                 // NOT shown here because the game might roll its own damage system and/or do
4290                 // a per-player Creative Mode, in which case writing it here would mislead.
4291                 bool damage = g_settings->getBool("enable_damage");
4292                 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4293                 if (!simple_singleplayer_mode) {
4294                         if (damage) {
4295                                 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4296                                 //~ PvP = Player versus Player
4297                                 os << strgettext("- PvP: ") << pvp << "\n";
4298                         }
4299                         os << strgettext("- Public: ") << announced << "\n";
4300                         std::string server_name = g_settings->get("server_name");
4301                         str_formspec_escape(server_name);
4302                         if (announced == on && !server_name.empty())
4303                                 os << strgettext("- Server Name: ") << server_name;
4304
4305                 }
4306         }
4307         os << ";]";
4308
4309         /* Create menu */
4310         /* Note: FormspecFormSource and LocalFormspecHandler  *
4311          * are deleted by guiFormSpecMenu                     */
4312         FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4313         LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4314
4315         auto *&formspec = m_game_ui->getFormspecGUI();
4316         GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4317                         &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4318         formspec->setFocus("btn_continue");
4319         formspec->doPause = true;
4320
4321         if (simple_singleplayer_mode)
4322                 pauseAnimation();
4323 }
4324
4325 /****************************************************************************/
4326 /****************************************************************************
4327  extern function for launching the game
4328  ****************************************************************************/
4329 /****************************************************************************/
4330
4331 void the_game(bool *kill,
4332                 InputHandler *input,
4333                 RenderingEngine *rendering_engine,
4334                 const GameStartData &start_data,
4335                 std::string &error_message,
4336                 ChatBackend &chat_backend,
4337                 bool *reconnect_requested) // Used for local game
4338 {
4339         Game game;
4340
4341         /* Make a copy of the server address because if a local singleplayer server
4342          * is created then this is updated and we don't want to change the value
4343          * passed to us by the calling function
4344          */
4345
4346         try {
4347
4348                 if (game.startup(kill, input, rendering_engine, start_data,
4349                                 error_message, reconnect_requested, &chat_backend)) {
4350                         game.run();
4351                 }
4352
4353         } catch (SerializationError &e) {
4354                 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
4355                 error_message = strgettext("A serialization error occurred:") +"\n"
4356                                 + e.what() + "\n\n" + ver_err;
4357                 errorstream << error_message << std::endl;
4358         } catch (ServerError &e) {
4359                 error_message = e.what();
4360                 errorstream << "ServerError: " << error_message << std::endl;
4361         } catch (ModError &e) {
4362                 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
4363                 error_message = std::string("ModError: ") + e.what() +
4364                                 strgettext("\nCheck debug.txt for details.");
4365                 errorstream << error_message << std::endl;
4366         }
4367         game.shutdown();
4368 }