]> git.lizzy.rs Git - dragonfireclient.git/blob - src/client/game.cpp
Properly keep noclip state in Game and ClientMap
[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
1747         // debug UI and wireframe
1748         bool has_debug = client->checkPrivilege("debug");
1749         bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
1750
1751         if (m_game_ui->m_flags.show_basic_debug) {
1752                 if (!has_basic_debug)
1753                         m_game_ui->m_flags.show_basic_debug = false;
1754         } else if (m_game_ui->m_flags.show_minimal_debug) {
1755                 if (has_basic_debug)
1756                         m_game_ui->m_flags.show_basic_debug = true;
1757         }
1758         if (!has_basic_debug)
1759                 hud->disableBlockBounds();
1760         if (!has_debug)
1761                 draw_control->show_wireframe = false;
1762
1763         // noclip
1764         draw_control->allow_noclip = m_cache_enable_noclip && client->checkPrivilege("noclip");
1765 }
1766
1767 void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
1768                 f32 dtime)
1769 {
1770         float profiler_print_interval =
1771                         g_settings->getFloat("profiler_print_interval");
1772         bool print_to_log = true;
1773
1774         if (profiler_print_interval == 0) {
1775                 print_to_log = false;
1776                 profiler_print_interval = 3;
1777         }
1778
1779         if (profiler_interval.step(dtime, profiler_print_interval)) {
1780                 if (print_to_log) {
1781                         infostream << "Profiler:" << std::endl;
1782                         g_profiler->print(infostream);
1783                 }
1784
1785                 m_game_ui->updateProfiler();
1786                 g_profiler->clear();
1787         }
1788
1789         // Update update graphs
1790         g_profiler->graphAdd("Time non-rendering [us]",
1791                 draw_times.busy_time - stats.drawtime);
1792
1793         g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time);
1794         g_profiler->graphAdd("FPS", 1.0f / dtime);
1795 }
1796
1797 void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
1798                 f32 dtime)
1799 {
1800
1801         f32 jitter;
1802         Jitter *jp;
1803
1804         /* Time average and jitter calculation
1805          */
1806         jp = &stats->dtime_jitter;
1807         jp->avg = jp->avg * 0.96 + dtime * 0.04;
1808
1809         jitter = dtime - jp->avg;
1810
1811         if (jitter > jp->max)
1812                 jp->max = jitter;
1813
1814         jp->counter += dtime;
1815
1816         if (jp->counter > 0.0) {
1817                 jp->counter -= 3.0;
1818                 jp->max_sample = jp->max;
1819                 jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
1820                 jp->max = 0.0;
1821         }
1822
1823         /* Busytime average and jitter calculation
1824          */
1825         jp = &stats->busy_time_jitter;
1826         jp->avg = jp->avg + draw_times.getBusyMs() * 0.02;
1827
1828         jitter = draw_times.getBusyMs() - jp->avg;
1829
1830         if (jitter > jp->max)
1831                 jp->max = jitter;
1832         if (jitter < jp->min)
1833                 jp->min = jitter;
1834
1835         jp->counter += dtime;
1836
1837         if (jp->counter > 0.0) {
1838                 jp->counter -= 3.0;
1839                 jp->max_sample = jp->max;
1840                 jp->min_sample = jp->min;
1841                 jp->max = 0.0;
1842                 jp->min = 0.0;
1843         }
1844 }
1845
1846
1847
1848 /****************************************************************************
1849  Input handling
1850  ****************************************************************************/
1851
1852 void Game::processUserInput(f32 dtime)
1853 {
1854         // Reset input if window not active or some menu is active
1855         if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console)) {
1856                 input->clear();
1857 #ifdef HAVE_TOUCHSCREENGUI
1858                 g_touchscreengui->hide();
1859 #endif
1860         }
1861 #ifdef HAVE_TOUCHSCREENGUI
1862         else if (g_touchscreengui) {
1863                 /* on touchscreengui step may generate own input events which ain't
1864                  * what we want in case we just did clear them */
1865                 g_touchscreengui->show();
1866                 g_touchscreengui->step(dtime);
1867         }
1868 #endif
1869
1870         if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
1871                 gui_chat_console->closeConsoleAtOnce();
1872         }
1873
1874         // Input handler step() (used by the random input generator)
1875         input->step(dtime);
1876
1877 #ifdef __ANDROID__
1878         auto formspec = m_game_ui->getFormspecGUI();
1879         if (formspec)
1880                 formspec->getAndroidUIInput();
1881         else
1882                 handleAndroidChatInput();
1883 #endif
1884
1885         // Increase timer for double tap of "keymap_jump"
1886         if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f)
1887                 runData.jump_timer += dtime;
1888
1889         processKeyInput();
1890         processItemSelection(&runData.new_playeritem);
1891 }
1892
1893
1894 void Game::processKeyInput()
1895 {
1896         if (wasKeyDown(KeyType::DROP)) {
1897                 dropSelectedItem(isKeyDown(KeyType::SNEAK));
1898         } else if (wasKeyDown(KeyType::AUTOFORWARD)) {
1899                 toggleAutoforward();
1900         } else if (wasKeyDown(KeyType::BACKWARD)) {
1901                 if (g_settings->getBool("continuous_forward"))
1902                         toggleAutoforward();
1903         } else if (wasKeyDown(KeyType::INVENTORY)) {
1904                 openInventory();
1905         } else if (input->cancelPressed()) {
1906 #ifdef __ANDROID__
1907                 m_android_chat_open = false;
1908 #endif
1909                 if (!gui_chat_console->isOpenInhibited()) {
1910                         showPauseMenu();
1911                 }
1912         } else if (wasKeyDown(KeyType::CHAT)) {
1913                 openConsole(0.2, L"");
1914         } else if (wasKeyDown(KeyType::CMD)) {
1915                 openConsole(0.2, L"/");
1916         } else if (wasKeyDown(KeyType::CMD_LOCAL)) {
1917                 if (client->modsLoaded())
1918                         openConsole(0.2, L".");
1919                 else
1920                         m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
1921         } else if (wasKeyDown(KeyType::CONSOLE)) {
1922                 openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f));
1923         } else if (wasKeyDown(KeyType::FREEMOVE)) {
1924                 toggleFreeMove();
1925         } else if (wasKeyDown(KeyType::JUMP)) {
1926                 toggleFreeMoveAlt();
1927         } else if (wasKeyDown(KeyType::PITCHMOVE)) {
1928                 togglePitchMove();
1929         } else if (wasKeyDown(KeyType::FASTMOVE)) {
1930                 toggleFast();
1931         } else if (wasKeyDown(KeyType::NOCLIP)) {
1932                 toggleNoClip();
1933 #if USE_SOUND
1934         } else if (wasKeyDown(KeyType::MUTE)) {
1935                 if (g_settings->getBool("enable_sound")) {
1936                         bool new_mute_sound = !g_settings->getBool("mute_sound");
1937                         g_settings->setBool("mute_sound", new_mute_sound);
1938                         if (new_mute_sound)
1939                                 m_game_ui->showTranslatedStatusText("Sound muted");
1940                         else
1941                                 m_game_ui->showTranslatedStatusText("Sound unmuted");
1942                 } else {
1943                         m_game_ui->showTranslatedStatusText("Sound system is disabled");
1944                 }
1945         } else if (wasKeyDown(KeyType::INC_VOLUME)) {
1946                 if (g_settings->getBool("enable_sound")) {
1947                         float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
1948                         g_settings->setFloat("sound_volume", new_volume);
1949                         std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1950                         m_game_ui->showStatusText(msg);
1951                 } else {
1952                         m_game_ui->showTranslatedStatusText("Sound system is disabled");
1953                 }
1954         } else if (wasKeyDown(KeyType::DEC_VOLUME)) {
1955                 if (g_settings->getBool("enable_sound")) {
1956                         float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
1957                         g_settings->setFloat("sound_volume", new_volume);
1958                         std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100));
1959                         m_game_ui->showStatusText(msg);
1960                 } else {
1961                         m_game_ui->showTranslatedStatusText("Sound system is disabled");
1962                 }
1963 #else
1964         } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
1965                         || wasKeyDown(KeyType::DEC_VOLUME)) {
1966                 m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
1967 #endif
1968         } else if (wasKeyDown(KeyType::CINEMATIC)) {
1969                 toggleCinematic();
1970         } else if (wasKeyDown(KeyType::SCREENSHOT)) {
1971                 client->makeScreenshot();
1972         } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
1973                 toggleBlockBounds();
1974         } else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
1975                 m_game_ui->toggleHud();
1976         } else if (wasKeyDown(KeyType::MINIMAP)) {
1977                 toggleMinimap(isKeyDown(KeyType::SNEAK));
1978         } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) {
1979                 m_game_ui->toggleChat();
1980         } else if (wasKeyDown(KeyType::TOGGLE_FOG)) {
1981                 toggleFog();
1982         } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) {
1983                 toggleUpdateCamera();
1984         } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) {
1985                 toggleDebug();
1986         } else if (wasKeyDown(KeyType::TOGGLE_PROFILER)) {
1987                 m_game_ui->toggleProfiler();
1988         } else if (wasKeyDown(KeyType::INCREASE_VIEWING_RANGE)) {
1989                 increaseViewRange();
1990         } else if (wasKeyDown(KeyType::DECREASE_VIEWING_RANGE)) {
1991                 decreaseViewRange();
1992         } else if (wasKeyDown(KeyType::RANGESELECT)) {
1993                 toggleFullViewRange();
1994         } else if (wasKeyDown(KeyType::ZOOM)) {
1995                 checkZoomEnabled();
1996         } else if (wasKeyDown(KeyType::QUICKTUNE_NEXT)) {
1997                 quicktune->next();
1998         } else if (wasKeyDown(KeyType::QUICKTUNE_PREV)) {
1999                 quicktune->prev();
2000         } else if (wasKeyDown(KeyType::QUICKTUNE_INC)) {
2001                 quicktune->inc();
2002         } else if (wasKeyDown(KeyType::QUICKTUNE_DEC)) {
2003                 quicktune->dec();
2004         }
2005
2006         if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) {
2007                 runData.reset_jump_timer = false;
2008                 runData.jump_timer = 0.0f;
2009         }
2010
2011         if (quicktune->hasMessage()) {
2012                 m_game_ui->showStatusText(utf8_to_wide(quicktune->getMessage()));
2013         }
2014 }
2015
2016 void Game::processItemSelection(u16 *new_playeritem)
2017 {
2018         LocalPlayer *player = client->getEnv().getLocalPlayer();
2019
2020         /* Item selection using mouse wheel
2021          */
2022         *new_playeritem = player->getWieldIndex();
2023
2024         s32 wheel = input->getMouseWheel();
2025         u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2026                     player->hud_hotbar_itemcount - 1);
2027
2028         s32 dir = wheel;
2029
2030         if (wasKeyDown(KeyType::HOTBAR_NEXT))
2031                 dir = -1;
2032
2033         if (wasKeyDown(KeyType::HOTBAR_PREV))
2034                 dir = 1;
2035
2036         if (dir < 0)
2037                 *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2038         else if (dir > 0)
2039                 *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2040         // else dir == 0
2041
2042         /* Item selection using hotbar slot keys
2043          */
2044         for (u16 i = 0; i <= max_item; i++) {
2045                 if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
2046                         *new_playeritem = i;
2047                         break;
2048                 }
2049         }
2050 }
2051
2052
2053 void Game::dropSelectedItem(bool single_item)
2054 {
2055         IDropAction *a = new IDropAction();
2056         a->count = single_item ? 1 : 0;
2057         a->from_inv.setCurrentPlayer();
2058         a->from_list = "main";
2059         a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
2060         client->inventoryAction(a);
2061 }
2062
2063
2064 void Game::openInventory()
2065 {
2066         /*
2067          * Don't permit to open inventory is CAO or player doesn't exists.
2068          * This prevent showing an empty inventory at player load
2069          */
2070
2071         LocalPlayer *player = client->getEnv().getLocalPlayer();
2072         if (!player || !player->getCAO())
2073                 return;
2074
2075         infostream << "Game: Launching inventory" << std::endl;
2076
2077         PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2078
2079         InventoryLocation inventoryloc;
2080         inventoryloc.setCurrentPlayer();
2081
2082         if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
2083                 delete fs_src;
2084                 return;
2085         }
2086
2087         if (fs_src->getForm().empty()) {
2088                 delete fs_src;
2089                 return;
2090         }
2091
2092         TextDest *txt_dst = new TextDestPlayerInventory(client);
2093         auto *&formspec = m_game_ui->updateFormspec("");
2094         GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2095                 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2096
2097         formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2098 }
2099
2100
2101 void Game::openConsole(float scale, const wchar_t *line)
2102 {
2103         assert(scale > 0.0f && scale <= 1.0f);
2104
2105 #ifdef __ANDROID__
2106         porting::showInputDialog(gettext("ok"), "", "", 2);
2107         m_android_chat_open = true;
2108 #else
2109         if (gui_chat_console->isOpenInhibited())
2110                 return;
2111         gui_chat_console->openConsole(scale);
2112         if (line) {
2113                 gui_chat_console->setCloseOnEnter(true);
2114                 gui_chat_console->replaceAndAddToHistory(line);
2115         }
2116 #endif
2117 }
2118
2119 #ifdef __ANDROID__
2120 void Game::handleAndroidChatInput()
2121 {
2122         if (m_android_chat_open && porting::getInputDialogState() == 0) {
2123                 std::string text = porting::getInputDialogValue();
2124                 client->typeChatMessage(utf8_to_wide(text));
2125                 m_android_chat_open = false;
2126         }
2127 }
2128 #endif
2129
2130
2131 void Game::toggleFreeMove()
2132 {
2133         bool free_move = !g_settings->getBool("free_move");
2134         g_settings->set("free_move", bool_to_cstr(free_move));
2135
2136         if (free_move) {
2137                 if (client->checkPrivilege("fly")) {
2138                         m_game_ui->showTranslatedStatusText("Fly mode enabled");
2139                 } else {
2140                         m_game_ui->showTranslatedStatusText("Fly mode enabled (note: no 'fly' privilege)");
2141                 }
2142         } else {
2143                 m_game_ui->showTranslatedStatusText("Fly mode disabled");
2144         }
2145 }
2146
2147 void Game::toggleFreeMoveAlt()
2148 {
2149         if (m_cache_doubletap_jump && runData.jump_timer < 0.2f)
2150                 toggleFreeMove();
2151
2152         runData.reset_jump_timer = true;
2153 }
2154
2155
2156 void Game::togglePitchMove()
2157 {
2158         bool pitch_move = !g_settings->getBool("pitch_move");
2159         g_settings->set("pitch_move", bool_to_cstr(pitch_move));
2160
2161         if (pitch_move) {
2162                 m_game_ui->showTranslatedStatusText("Pitch move mode enabled");
2163         } else {
2164                 m_game_ui->showTranslatedStatusText("Pitch move mode disabled");
2165         }
2166 }
2167
2168
2169 void Game::toggleFast()
2170 {
2171         bool fast_move = !g_settings->getBool("fast_move");
2172         bool has_fast_privs = client->checkPrivilege("fast");
2173         g_settings->set("fast_move", bool_to_cstr(fast_move));
2174
2175         if (fast_move) {
2176                 if (has_fast_privs) {
2177                         m_game_ui->showTranslatedStatusText("Fast mode enabled");
2178                 } else {
2179                         m_game_ui->showTranslatedStatusText("Fast mode enabled (note: no 'fast' privilege)");
2180                 }
2181         } else {
2182                 m_game_ui->showTranslatedStatusText("Fast mode disabled");
2183         }
2184
2185 #ifdef HAVE_TOUCHSCREENGUI
2186         m_cache_hold_aux1 = fast_move && has_fast_privs;
2187 #endif
2188 }
2189
2190
2191 void Game::toggleNoClip()
2192 {
2193         bool noclip = !g_settings->getBool("noclip");
2194         g_settings->set("noclip", bool_to_cstr(noclip));
2195
2196         if (noclip) {
2197                 if (client->checkPrivilege("noclip")) {
2198                         m_game_ui->showTranslatedStatusText("Noclip mode enabled");
2199                 } else {
2200                         m_game_ui->showTranslatedStatusText("Noclip mode enabled (note: no 'noclip' privilege)");
2201                 }
2202         } else {
2203                 m_game_ui->showTranslatedStatusText("Noclip mode disabled");
2204         }
2205 }
2206
2207 void Game::toggleCinematic()
2208 {
2209         bool cinematic = !g_settings->getBool("cinematic");
2210         g_settings->set("cinematic", bool_to_cstr(cinematic));
2211
2212         if (cinematic)
2213                 m_game_ui->showTranslatedStatusText("Cinematic mode enabled");
2214         else
2215                 m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
2216 }
2217
2218 void Game::toggleBlockBounds()
2219 {
2220         LocalPlayer *player = client->getEnv().getLocalPlayer();
2221         if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) {
2222                 m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)");
2223                 return;
2224         }
2225         enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds();
2226         switch (newmode) {
2227                 case Hud::BLOCK_BOUNDS_OFF:
2228                         m_game_ui->showTranslatedStatusText("Block bounds hidden");
2229                         break;
2230                 case Hud::BLOCK_BOUNDS_CURRENT:
2231                         m_game_ui->showTranslatedStatusText("Block bounds shown for current block");
2232                         break;
2233                 case Hud::BLOCK_BOUNDS_NEAR:
2234                         m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks");
2235                         break;
2236                 case Hud::BLOCK_BOUNDS_MAX:
2237                         m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks");
2238                         break;
2239                 default:
2240                         break;
2241         }
2242 }
2243
2244 // Autoforward by toggling continuous forward.
2245 void Game::toggleAutoforward()
2246 {
2247         bool autorun_enabled = !g_settings->getBool("continuous_forward");
2248         g_settings->set("continuous_forward", bool_to_cstr(autorun_enabled));
2249
2250         if (autorun_enabled)
2251                 m_game_ui->showTranslatedStatusText("Automatic forward enabled");
2252         else
2253                 m_game_ui->showTranslatedStatusText("Automatic forward disabled");
2254 }
2255
2256 void Game::toggleMinimap(bool shift_pressed)
2257 {
2258         if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
2259                 return;
2260
2261         if (shift_pressed)
2262                 mapper->toggleMinimapShape();
2263         else
2264                 mapper->nextMode();
2265
2266         // TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
2267
2268         // Not so satisying code to keep compatibility with old fixed mode system
2269         // -->
2270         u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
2271
2272         if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
2273                 m_game_ui->m_flags.show_minimap = false;
2274         } else {
2275
2276         // If radar is disabled, try to find a non radar mode or fall back to 0
2277                 if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
2278                         while (mapper->getModeIndex() &&
2279                                         mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
2280                                 mapper->nextMode();
2281
2282                 m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
2283                                 MINIMAP_TYPE_OFF;
2284         }
2285         // <--
2286         // End of 'not so satifying code'
2287         if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
2288                         (hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
2289                 m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
2290         else
2291                 m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
2292 }
2293
2294 void Game::toggleFog()
2295 {
2296         bool fog_enabled = g_settings->getBool("enable_fog");
2297         g_settings->setBool("enable_fog", !fog_enabled);
2298         if (fog_enabled)
2299                 m_game_ui->showTranslatedStatusText("Fog disabled");
2300         else
2301                 m_game_ui->showTranslatedStatusText("Fog enabled");
2302 }
2303
2304
2305 void Game::toggleDebug()
2306 {
2307         LocalPlayer *player = client->getEnv().getLocalPlayer();
2308         bool has_debug = client->checkPrivilege("debug");
2309         bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
2310         // Initial: No debug info
2311         // 1x toggle: Debug text
2312         // 2x toggle: Debug text with profiler graph
2313         // 3x toggle: Debug text and wireframe (needs "debug" priv)
2314         // Next toggle: Back to initial
2315         //
2316         // The debug text can be in 2 modes: minimal and basic.
2317         // * Minimal: Only technical client info that not gameplay-relevant
2318         // * Basic: Info that might give gameplay advantage, e.g. pos, angle
2319         // Basic mode is used when player has the debug HUD flag set,
2320         // otherwise the Minimal mode is used.
2321         if (!m_game_ui->m_flags.show_minimal_debug) {
2322                 m_game_ui->m_flags.show_minimal_debug = true;
2323                 if (has_basic_debug)
2324                         m_game_ui->m_flags.show_basic_debug = true;
2325                 m_game_ui->m_flags.show_profiler_graph = false;
2326                 draw_control->show_wireframe = false;
2327                 m_game_ui->showTranslatedStatusText("Debug info shown");
2328         } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) {
2329                 if (has_basic_debug)
2330                         m_game_ui->m_flags.show_basic_debug = true;
2331                 m_game_ui->m_flags.show_profiler_graph = true;
2332                 m_game_ui->showTranslatedStatusText("Profiler graph shown");
2333         } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) {
2334                 if (has_basic_debug)
2335                         m_game_ui->m_flags.show_basic_debug = true;
2336                 m_game_ui->m_flags.show_profiler_graph = false;
2337                 draw_control->show_wireframe = true;
2338                 m_game_ui->showTranslatedStatusText("Wireframe shown");
2339         } else {
2340                 m_game_ui->m_flags.show_minimal_debug = false;
2341                 m_game_ui->m_flags.show_basic_debug = false;
2342                 m_game_ui->m_flags.show_profiler_graph = false;
2343                 draw_control->show_wireframe = false;
2344                 if (has_debug) {
2345                         m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
2346                 } else {
2347                         m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
2348                 }
2349         }
2350 }
2351
2352
2353 void Game::toggleUpdateCamera()
2354 {
2355         m_flags.disable_camera_update = !m_flags.disable_camera_update;
2356         if (m_flags.disable_camera_update)
2357                 m_game_ui->showTranslatedStatusText("Camera update disabled");
2358         else
2359                 m_game_ui->showTranslatedStatusText("Camera update enabled");
2360 }
2361
2362
2363 void Game::increaseViewRange()
2364 {
2365         s16 range = g_settings->getS16("viewing_range");
2366         s16 range_new = range + 10;
2367
2368         if (range_new > 4000) {
2369                 range_new = 4000;
2370                 std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new);
2371                 m_game_ui->showStatusText(msg);
2372         } else {
2373                 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2374                 m_game_ui->showStatusText(msg);
2375         }
2376         g_settings->set("viewing_range", itos(range_new));
2377 }
2378
2379
2380 void Game::decreaseViewRange()
2381 {
2382         s16 range = g_settings->getS16("viewing_range");
2383         s16 range_new = range - 10;
2384
2385         if (range_new < 20) {
2386                 range_new = 20;
2387                 std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new);
2388                 m_game_ui->showStatusText(msg);
2389         } else {
2390                 std::wstring msg = fwgettext("Viewing range changed to %d", range_new);
2391                 m_game_ui->showStatusText(msg);
2392         }
2393         g_settings->set("viewing_range", itos(range_new));
2394 }
2395
2396
2397 void Game::toggleFullViewRange()
2398 {
2399         draw_control->range_all = !draw_control->range_all;
2400         if (draw_control->range_all)
2401                 m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range");
2402         else
2403                 m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range");
2404 }
2405
2406
2407 void Game::checkZoomEnabled()
2408 {
2409         LocalPlayer *player = client->getEnv().getLocalPlayer();
2410         if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
2411                 m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
2412 }
2413
2414 void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
2415 {
2416         if ((device->isWindowActive() && device->isWindowFocused()
2417                         && !isMenuActive()) || input->isRandom()) {
2418
2419 #ifndef __ANDROID__
2420                 if (!input->isRandom()) {
2421                         // Mac OSX gets upset if this is set every frame
2422                         if (device->getCursorControl()->isVisible())
2423                                 device->getCursorControl()->setVisible(false);
2424                 }
2425 #endif
2426
2427                 if (m_first_loop_after_window_activation) {
2428                         m_first_loop_after_window_activation = false;
2429
2430                         input->setMousePos(driver->getScreenSize().Width / 2,
2431                                 driver->getScreenSize().Height / 2);
2432                 } else {
2433                         updateCameraOrientation(cam, dtime);
2434                 }
2435
2436         } else {
2437
2438 #ifndef ANDROID
2439                 // Mac OSX gets upset if this is set every frame
2440                 if (!device->getCursorControl()->isVisible())
2441                         device->getCursorControl()->setVisible(true);
2442 #endif
2443
2444                 m_first_loop_after_window_activation = true;
2445
2446         }
2447 }
2448
2449 // Get the factor to multiply with sensitivity to get the same mouse/joystick
2450 // responsiveness independently of FOV.
2451 f32 Game::getSensitivityScaleFactor() const
2452 {
2453         f32 fov_y = client->getCamera()->getFovY();
2454
2455         // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
2456         // 16:9 aspect ratio to minimize disruption of existing sensitivity
2457         // settings.
2458         return tan(fov_y / 2.0f) * 1.3763818698f;
2459 }
2460
2461 void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
2462 {
2463 #ifdef HAVE_TOUCHSCREENGUI
2464         if (g_touchscreengui) {
2465                 cam->camera_yaw   += g_touchscreengui->getYawChange();
2466                 cam->camera_pitch  = g_touchscreengui->getPitch();
2467         } else {
2468 #endif
2469                 v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
2470                 v2s32 dist = input->getMousePos() - center;
2471
2472                 if (m_invert_mouse || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
2473                         dist.Y = -dist.Y;
2474                 }
2475
2476                 f32 sens_scale = getSensitivityScaleFactor();
2477                 cam->camera_yaw   -= dist.X * m_cache_mouse_sensitivity * sens_scale;
2478                 cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
2479
2480                 if (dist.X != 0 || dist.Y != 0)
2481                         input->setMousePos(center.X, center.Y);
2482 #ifdef HAVE_TOUCHSCREENGUI
2483         }
2484 #endif
2485
2486         if (m_cache_enable_joysticks) {
2487                 f32 sens_scale = getSensitivityScaleFactor();
2488                 f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale;
2489                 cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c;
2490                 cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c;
2491         }
2492
2493         cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
2494 }
2495
2496
2497 void Game::updatePlayerControl(const CameraOrientation &cam)
2498 {
2499         LocalPlayer *player = client->getEnv().getLocalPlayer();
2500
2501         //TimeTaker tt("update player control", NULL, PRECISION_NANO);
2502
2503         PlayerControl control(
2504                 isKeyDown(KeyType::FORWARD),
2505                 isKeyDown(KeyType::BACKWARD),
2506                 isKeyDown(KeyType::LEFT),
2507                 isKeyDown(KeyType::RIGHT),
2508                 isKeyDown(KeyType::JUMP) || player->getAutojump(),
2509                 isKeyDown(KeyType::AUX1),
2510                 isKeyDown(KeyType::SNEAK),
2511                 isKeyDown(KeyType::ZOOM),
2512                 isKeyDown(KeyType::DIG),
2513                 isKeyDown(KeyType::PLACE),
2514                 cam.camera_pitch,
2515                 cam.camera_yaw,
2516                 input->getMovementSpeed(),
2517                 input->getMovementDirection()
2518         );
2519
2520         // autoforward if set: move towards pointed position at maximum speed
2521         if (player->getPlayerSettings().continuous_forward &&
2522                         client->activeObjectsReceived() && !player->isDead()) {
2523                 control.movement_speed = 1.0f;
2524                 control.movement_direction = 0.0f;
2525         }
2526
2527 #ifdef HAVE_TOUCHSCREENGUI
2528         /* For touch, simulate holding down AUX1 (fast move) if the user has
2529          * the fast_move setting toggled on. If there is an aux1 key defined for
2530          * touch then its meaning is inverted (i.e. holding aux1 means walk and
2531          * not fast)
2532          */
2533         if (m_cache_hold_aux1) {
2534                 control.aux1 = control.aux1 ^ true;
2535         }
2536 #endif
2537
2538         client->setPlayerControl(control);
2539
2540         //tt.stop();
2541 }
2542
2543
2544 inline void Game::step(f32 *dtime)
2545 {
2546         bool can_be_and_is_paused =
2547                         (simple_singleplayer_mode && g_menumgr.pausesGame());
2548
2549         if (can_be_and_is_paused) { // This is for a singleplayer server
2550                 *dtime = 0;             // No time passes
2551         } else {
2552                 if (simple_singleplayer_mode && !paused_animated_nodes.empty())
2553                         resumeAnimation();
2554
2555                 if (server)
2556                         server->step(*dtime);
2557
2558                 client->step(*dtime);
2559         }
2560 }
2561
2562 static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
2563         if (!node)
2564                 return;
2565         for (auto &&child: node->getChildren())
2566                 pauseNodeAnimation(paused, child);
2567         if (node->getType() != scene::ESNT_ANIMATED_MESH)
2568                 return;
2569         auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
2570         float speed = animated_node->getAnimationSpeed();
2571         if (!speed)
2572                 return;
2573         paused.push_back({grab(animated_node), speed});
2574         animated_node->setAnimationSpeed(0.0f);
2575 }
2576
2577 void Game::pauseAnimation()
2578 {
2579         pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
2580 }
2581
2582 void Game::resumeAnimation()
2583 {
2584         for (auto &&pair: paused_animated_nodes)
2585                 pair.first->setAnimationSpeed(pair.second);
2586         paused_animated_nodes.clear();
2587 }
2588
2589 const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
2590         {&Game::handleClientEvent_None},
2591         {&Game::handleClientEvent_PlayerDamage},
2592         {&Game::handleClientEvent_PlayerForceMove},
2593         {&Game::handleClientEvent_Deathscreen},
2594         {&Game::handleClientEvent_ShowFormSpec},
2595         {&Game::handleClientEvent_ShowLocalFormSpec},
2596         {&Game::handleClientEvent_HandleParticleEvent},
2597         {&Game::handleClientEvent_HandleParticleEvent},
2598         {&Game::handleClientEvent_HandleParticleEvent},
2599         {&Game::handleClientEvent_HudAdd},
2600         {&Game::handleClientEvent_HudRemove},
2601         {&Game::handleClientEvent_HudChange},
2602         {&Game::handleClientEvent_SetSky},
2603         {&Game::handleClientEvent_SetSun},
2604         {&Game::handleClientEvent_SetMoon},
2605         {&Game::handleClientEvent_SetStars},
2606         {&Game::handleClientEvent_OverrideDayNigthRatio},
2607         {&Game::handleClientEvent_CloudParams},
2608 };
2609
2610 void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
2611 {
2612         FATAL_ERROR("ClientEvent type None received");
2613 }
2614
2615 void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
2616 {
2617         if (client->modsLoaded())
2618                 client->getScript()->on_damage_taken(event->player_damage.amount);
2619
2620         // Damage flash and hurt tilt are not used at death
2621         if (client->getHP() > 0) {
2622                 LocalPlayer *player = client->getEnv().getLocalPlayer();
2623
2624                 f32 hp_max = player->getCAO() ?
2625                         player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT;
2626                 f32 damage_ratio = event->player_damage.amount / hp_max;
2627
2628                 runData.damage_flash += 95.0f + 64.f * damage_ratio;
2629                 runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
2630
2631                 player->hurt_tilt_timer = 1.5f;
2632                 player->hurt_tilt_strength =
2633                         rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
2634         }
2635
2636         // Play damage sound
2637         client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_DAMAGE));
2638 }
2639
2640 void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam)
2641 {
2642         cam->camera_yaw = event->player_force_move.yaw;
2643         cam->camera_pitch = event->player_force_move.pitch;
2644 }
2645
2646 void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
2647 {
2648         // If client scripting is enabled, deathscreen is handled by CSM code in
2649         // builtin/client/init.lua
2650         if (client->modsLoaded())
2651                 client->getScript()->on_death();
2652         else
2653                 showDeathFormspec();
2654
2655         /* Handle visualization */
2656         LocalPlayer *player = client->getEnv().getLocalPlayer();
2657         runData.damage_flash = 0;
2658         player->hurt_tilt_timer = 0;
2659         player->hurt_tilt_strength = 0;
2660 }
2661
2662 void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
2663 {
2664         if (event->show_formspec.formspec->empty()) {
2665                 auto formspec = m_game_ui->getFormspecGUI();
2666                 if (formspec && (event->show_formspec.formname->empty()
2667                                 || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
2668                         formspec->quitMenu();
2669                 }
2670         } else {
2671                 FormspecFormSource *fs_src =
2672                         new FormspecFormSource(*(event->show_formspec.formspec));
2673                 TextDestPlayerInventory *txt_dst =
2674                         new TextDestPlayerInventory(client, *(event->show_formspec.formname));
2675
2676                 auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
2677                 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
2678                         &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2679         }
2680
2681         delete event->show_formspec.formspec;
2682         delete event->show_formspec.formname;
2683 }
2684
2685 void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam)
2686 {
2687         FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
2688         LocalFormspecHandler *txt_dst =
2689                 new LocalFormspecHandler(*event->show_formspec.formname, client);
2690         GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(),
2691                         &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
2692
2693         delete event->show_formspec.formspec;
2694         delete event->show_formspec.formname;
2695 }
2696
2697 void Game::handleClientEvent_HandleParticleEvent(ClientEvent *event,
2698                 CameraOrientation *cam)
2699 {
2700         LocalPlayer *player = client->getEnv().getLocalPlayer();
2701         client->getParticleManager()->handleParticleEvent(event, client, player);
2702 }
2703
2704 void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
2705 {
2706         LocalPlayer *player = client->getEnv().getLocalPlayer();
2707
2708         u32 server_id = event->hudadd->server_id;
2709         // ignore if we already have a HUD with that ID
2710         auto i = m_hud_server_to_client.find(server_id);
2711         if (i != m_hud_server_to_client.end()) {
2712                 delete event->hudadd;
2713                 return;
2714         }
2715
2716         HudElement *e = new HudElement;
2717         e->type   = static_cast<HudElementType>(event->hudadd->type);
2718         e->pos    = event->hudadd->pos;
2719         e->name   = event->hudadd->name;
2720         e->scale  = event->hudadd->scale;
2721         e->text   = event->hudadd->text;
2722         e->number = event->hudadd->number;
2723         e->item   = event->hudadd->item;
2724         e->dir    = event->hudadd->dir;
2725         e->align  = event->hudadd->align;
2726         e->offset = event->hudadd->offset;
2727         e->world_pos = event->hudadd->world_pos;
2728         e->size      = event->hudadd->size;
2729         e->z_index   = event->hudadd->z_index;
2730         e->text2     = event->hudadd->text2;
2731         e->style     = event->hudadd->style;
2732         m_hud_server_to_client[server_id] = player->addHud(e);
2733
2734         delete event->hudadd;
2735 }
2736
2737 void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
2738 {
2739         LocalPlayer *player = client->getEnv().getLocalPlayer();
2740
2741         auto i = m_hud_server_to_client.find(event->hudrm.id);
2742         if (i != m_hud_server_to_client.end()) {
2743                 HudElement *e = player->removeHud(i->second);
2744                 delete e;
2745                 m_hud_server_to_client.erase(i);
2746         }
2747
2748 }
2749
2750 void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam)
2751 {
2752         LocalPlayer *player = client->getEnv().getLocalPlayer();
2753
2754         HudElement *e = nullptr;
2755
2756         auto i = m_hud_server_to_client.find(event->hudchange->id);
2757         if (i != m_hud_server_to_client.end()) {
2758                 e = player->getHud(i->second);
2759         }
2760
2761         if (e == nullptr) {
2762                 delete event->hudchange;
2763                 return;
2764         }
2765
2766 #define CASE_SET(statval, prop, dataprop) \
2767         case statval: \
2768                 e->prop = event->hudchange->dataprop; \
2769                 break
2770
2771         switch (event->hudchange->stat) {
2772                 CASE_SET(HUD_STAT_POS, pos, v2fdata);
2773
2774                 CASE_SET(HUD_STAT_NAME, name, sdata);
2775
2776                 CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
2777
2778                 CASE_SET(HUD_STAT_TEXT, text, sdata);
2779
2780                 CASE_SET(HUD_STAT_NUMBER, number, data);
2781
2782                 CASE_SET(HUD_STAT_ITEM, item, data);
2783
2784                 CASE_SET(HUD_STAT_DIR, dir, data);
2785
2786                 CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
2787
2788                 CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
2789
2790                 CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
2791
2792                 CASE_SET(HUD_STAT_SIZE, size, v2s32data);
2793
2794                 CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
2795
2796                 CASE_SET(HUD_STAT_TEXT2, text2, sdata);
2797
2798                 CASE_SET(HUD_STAT_STYLE, style, data);
2799         }
2800
2801 #undef CASE_SET
2802
2803         delete event->hudchange;
2804 }
2805
2806 void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
2807 {
2808         sky->setVisible(false);
2809         // Whether clouds are visible in front of a custom skybox.
2810         sky->setCloudsEnabled(event->set_sky->clouds);
2811
2812         if (skybox) {
2813                 skybox->remove();
2814                 skybox = NULL;
2815         }
2816         // Clear the old textures out in case we switch rendering type.
2817         sky->clearSkyboxTextures();
2818         // Handle according to type
2819         if (event->set_sky->type == "regular") {
2820                 // Shows the mesh skybox
2821                 sky->setVisible(true);
2822                 // Update mesh based skybox colours if applicable.
2823                 sky->setSkyColors(event->set_sky->sky_color);
2824                 sky->setHorizonTint(
2825                         event->set_sky->fog_sun_tint,
2826                         event->set_sky->fog_moon_tint,
2827                         event->set_sky->fog_tint_type
2828                 );
2829         } else if (event->set_sky->type == "skybox" &&
2830                         event->set_sky->textures.size() == 6) {
2831                 // Disable the dyanmic mesh skybox:
2832                 sky->setVisible(false);
2833                 // Set fog colors:
2834                 sky->setFallbackBgColor(event->set_sky->bgcolor);
2835                 // Set sunrise and sunset fog tinting:
2836                 sky->setHorizonTint(
2837                         event->set_sky->fog_sun_tint,
2838                         event->set_sky->fog_moon_tint,
2839                         event->set_sky->fog_tint_type
2840                 );
2841                 // Add textures to skybox.
2842                 for (int i = 0; i < 6; i++)
2843                         sky->addTextureToSkybox(event->set_sky->textures[i], i, texture_src);
2844         } else {
2845                 // Handle everything else as plain color.
2846                 if (event->set_sky->type != "plain")
2847                         infostream << "Unknown sky type: "
2848                                 << (event->set_sky->type) << std::endl;
2849                 sky->setVisible(false);
2850                 sky->setFallbackBgColor(event->set_sky->bgcolor);
2851                 // Disable directional sun/moon tinting on plain or invalid skyboxes.
2852                 sky->setHorizonTint(
2853                         event->set_sky->bgcolor,
2854                         event->set_sky->bgcolor,
2855                         "custom"
2856                 );
2857         }
2858
2859         delete event->set_sky;
2860 }
2861
2862 void Game::handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam)
2863 {
2864         sky->setSunVisible(event->sun_params->visible);
2865         sky->setSunTexture(event->sun_params->texture,
2866                 event->sun_params->tonemap, texture_src);
2867         sky->setSunScale(event->sun_params->scale);
2868         sky->setSunriseVisible(event->sun_params->sunrise_visible);
2869         sky->setSunriseTexture(event->sun_params->sunrise, texture_src);
2870         delete event->sun_params;
2871 }
2872
2873 void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam)
2874 {
2875         sky->setMoonVisible(event->moon_params->visible);
2876         sky->setMoonTexture(event->moon_params->texture,
2877                 event->moon_params->tonemap, texture_src);
2878         sky->setMoonScale(event->moon_params->scale);
2879         delete event->moon_params;
2880 }
2881
2882 void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam)
2883 {
2884         sky->setStarsVisible(event->star_params->visible);
2885         sky->setStarCount(event->star_params->count);
2886         sky->setStarColor(event->star_params->starcolor);
2887         sky->setStarScale(event->star_params->scale);
2888         delete event->star_params;
2889 }
2890
2891 void Game::handleClientEvent_OverrideDayNigthRatio(ClientEvent *event,
2892                 CameraOrientation *cam)
2893 {
2894         client->getEnv().setDayNightRatioOverride(
2895                 event->override_day_night_ratio.do_override,
2896                 event->override_day_night_ratio.ratio_f * 1000.0f);
2897 }
2898
2899 void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam)
2900 {
2901         if (!clouds)
2902                 return;
2903
2904         clouds->setDensity(event->cloud_params.density);
2905         clouds->setColorBright(video::SColor(event->cloud_params.color_bright));
2906         clouds->setColorAmbient(video::SColor(event->cloud_params.color_ambient));
2907         clouds->setHeight(event->cloud_params.height);
2908         clouds->setThickness(event->cloud_params.thickness);
2909         clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y));
2910 }
2911
2912 void Game::processClientEvents(CameraOrientation *cam)
2913 {
2914         while (client->hasClientEvents()) {
2915                 std::unique_ptr<ClientEvent> event(client->getClientEvent());
2916                 FATAL_ERROR_IF(event->type >= CLIENTEVENT_MAX, "Invalid clientevent type");
2917                 const ClientEventHandler& evHandler = clientEventHandler[event->type];
2918                 (this->*evHandler.handler)(event.get(), cam);
2919         }
2920 }
2921
2922 void Game::updateChat(f32 dtime)
2923 {
2924         // Get new messages from error log buffer
2925         while (!m_chat_log_buf.empty())
2926                 chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
2927
2928         // Get new messages from client
2929         std::wstring message;
2930         while (client->getChatMessage(message)) {
2931                 chat_backend->addUnparsedMessage(message);
2932         }
2933
2934         // Remove old messages
2935         chat_backend->step(dtime);
2936
2937         // Display all messages in a static text element
2938         auto &buf = chat_backend->getRecentBuffer();
2939         if (buf.getLinesModified()) {
2940                 buf.resetLinesModified();
2941                 m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount());
2942         }
2943
2944         // Make sure that the size is still correct
2945         m_game_ui->updateChatSize();
2946 }
2947
2948 void Game::updateCamera(f32 dtime)
2949 {
2950         LocalPlayer *player = client->getEnv().getLocalPlayer();
2951
2952         /*
2953                 For interaction purposes, get info about the held item
2954                 - What item is it?
2955                 - Is it a usable item?
2956                 - Can it point to liquids?
2957         */
2958         ItemStack playeritem;
2959         {
2960                 ItemStack selected, hand;
2961                 playeritem = player->getWieldedItem(&selected, &hand);
2962         }
2963
2964         ToolCapabilities playeritem_toolcap =
2965                 playeritem.getToolCapabilities(itemdef_manager);
2966
2967         v3s16 old_camera_offset = camera->getOffset();
2968
2969         if (wasKeyDown(KeyType::CAMERA_MODE)) {
2970                 GenericCAO *playercao = player->getCAO();
2971
2972                 // If playercao not loaded, don't change camera
2973                 if (!playercao)
2974                         return;
2975
2976                 camera->toggleCameraMode();
2977
2978                 // Make the player visible depending on camera mode.
2979                 playercao->updateMeshCulling();
2980                 playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
2981         }
2982
2983         float full_punch_interval = playeritem_toolcap.full_punch_interval;
2984         float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval;
2985
2986         tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
2987         camera->update(player, dtime, tool_reload_ratio);
2988         camera->step(dtime);
2989
2990         v3f camera_position = camera->getPosition();
2991         v3f camera_direction = camera->getDirection();
2992         f32 camera_fov = camera->getFovMax();
2993         v3s16 camera_offset = camera->getOffset();
2994
2995         m_camera_offset_changed = (camera_offset != old_camera_offset);
2996
2997         if (!m_flags.disable_camera_update) {
2998                 client->getEnv().getClientMap().updateCamera(camera_position,
2999                                 camera_direction, camera_fov, camera_offset);
3000
3001                 if (m_camera_offset_changed) {
3002                         client->updateCameraOffset(camera_offset);
3003                         client->getEnv().updateCameraOffset(camera_offset);
3004
3005                         if (clouds)
3006                                 clouds->updateCameraOffset(camera_offset);
3007                 }
3008         }
3009 }
3010
3011
3012 void Game::updateSound(f32 dtime)
3013 {
3014         // Update sound listener
3015         v3s16 camera_offset = camera->getOffset();
3016         sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3017                               v3f(0, 0, 0), // velocity
3018                               camera->getDirection(),
3019                               camera->getCameraNode()->getUpVector());
3020
3021         bool mute_sound = g_settings->getBool("mute_sound");
3022         if (mute_sound) {
3023                 sound->setListenerGain(0.0f);
3024         } else {
3025                 // Check if volume is in the proper range, else fix it.
3026                 float old_volume = g_settings->getFloat("sound_volume");
3027                 float new_volume = rangelim(old_volume, 0.0f, 1.0f);
3028                 sound->setListenerGain(new_volume);
3029
3030                 if (old_volume != new_volume) {
3031                         g_settings->setFloat("sound_volume", new_volume);
3032                 }
3033         }
3034
3035         LocalPlayer *player = client->getEnv().getLocalPlayer();
3036
3037         // Tell the sound maker whether to make footstep sounds
3038         soundmaker->makes_footstep_sound = player->makes_footstep_sound;
3039
3040         //      Update sound maker
3041         if (player->makes_footstep_sound)
3042                 soundmaker->step(dtime);
3043
3044         ClientMap &map = client->getEnv().getClientMap();
3045         MapNode n = map.getNode(player->getFootstepNodePos());
3046         soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3047 }
3048
3049
3050 void Game::processPlayerInteraction(f32 dtime, bool show_hud)
3051 {
3052         LocalPlayer *player = client->getEnv().getLocalPlayer();
3053
3054         const v3f camera_direction = camera->getDirection();
3055         const v3s16 camera_offset  = camera->getOffset();
3056
3057         /*
3058                 Calculate what block is the crosshair pointing to
3059         */
3060
3061         ItemStack selected_item, hand_item;
3062         const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3063
3064         const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
3065         f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
3066
3067         core::line3d<f32> shootline;
3068
3069         switch (camera->getCameraMode()) {
3070         case CAMERA_MODE_FIRST:
3071                 // Shoot from camera position, with bobbing
3072                 shootline.start = camera->getPosition();
3073                 break;
3074         case CAMERA_MODE_THIRD:
3075                 // Shoot from player head, no bobbing
3076                 shootline.start = camera->getHeadPosition();
3077                 break;
3078         case CAMERA_MODE_THIRD_FRONT:
3079                 shootline.start = camera->getHeadPosition();
3080                 // prevent player pointing anything in front-view
3081                 d = 0;
3082                 break;
3083         }
3084         shootline.end = shootline.start + camera_direction * BS * d;
3085
3086 #ifdef HAVE_TOUCHSCREENGUI
3087
3088         if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3089                 shootline = g_touchscreengui->getShootline();
3090                 // Scale shootline to the acual distance the player can reach
3091                 shootline.end = shootline.start
3092                         + shootline.getVector().normalize() * BS * d;
3093                 shootline.start += intToFloat(camera_offset, BS);
3094                 shootline.end += intToFloat(camera_offset, BS);
3095         }
3096
3097 #endif
3098
3099         PointedThing pointed = updatePointedThing(shootline,
3100                         selected_def.liquids_pointable,
3101                         !runData.btn_down_for_dig,
3102                         camera_offset);
3103
3104         if (pointed != runData.pointed_old)
3105                 infostream << "Pointing at " << pointed.dump() << std::endl;
3106
3107         // Note that updating the selection mesh every frame is not particularly efficient,
3108         // but the halo rendering code is already inefficient so there's no point in optimizing it here
3109         hud->updateSelectionMesh(camera_offset);
3110
3111         // Allow digging again if button is not pressed
3112         if (runData.digging_blocked && !isKeyDown(KeyType::DIG))
3113                 runData.digging_blocked = false;
3114
3115         /*
3116                 Stop digging when
3117                 - releasing dig button
3118                 - pointing away from node
3119         */
3120         if (runData.digging) {
3121                 if (wasKeyReleased(KeyType::DIG)) {
3122                         infostream << "Dig button released (stopped digging)" << std::endl;
3123                         runData.digging = false;
3124                 } else if (pointed != runData.pointed_old) {
3125                         if (pointed.type == POINTEDTHING_NODE
3126                                         && runData.pointed_old.type == POINTEDTHING_NODE
3127                                         && pointed.node_undersurface
3128                                                         == runData.pointed_old.node_undersurface) {
3129                                 // Still pointing to the same node, but a different face.
3130                                 // Don't reset.
3131                         } else {
3132                                 infostream << "Pointing away from node (stopped digging)" << std::endl;
3133                                 runData.digging = false;
3134                                 hud->updateSelectionMesh(camera_offset);
3135                         }
3136                 }
3137
3138                 if (!runData.digging) {
3139                         client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
3140                         client->setCrack(-1, v3s16(0, 0, 0));
3141                         runData.dig_time = 0.0;
3142                 }
3143         } else if (runData.dig_instantly && wasKeyReleased(KeyType::DIG)) {
3144                 // Remove e.g. torches faster when clicking instead of holding dig button
3145                 runData.nodig_delay_timer = 0;
3146                 runData.dig_instantly = false;
3147         }
3148
3149         if (!runData.digging && runData.btn_down_for_dig && !isKeyDown(KeyType::DIG))
3150                 runData.btn_down_for_dig = false;
3151
3152         runData.punching = false;
3153
3154         soundmaker->m_player_leftpunch_sound.name = "";
3155
3156         // Prepare for repeating, unless we're not supposed to
3157         if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place"))
3158                 runData.repeat_place_timer += dtime;
3159         else
3160                 runData.repeat_place_timer = 0;
3161
3162         if (selected_def.usable && isKeyDown(KeyType::DIG)) {
3163                 if (wasKeyPressed(KeyType::DIG) && (!client->modsLoaded() ||
3164                                 !client->getScript()->on_item_use(selected_item, pointed)))
3165                         client->interact(INTERACT_USE, pointed);
3166         } else if (pointed.type == POINTEDTHING_NODE) {
3167                 handlePointingAtNode(pointed, selected_item, hand_item, dtime);
3168         } else if (pointed.type == POINTEDTHING_OBJECT) {
3169                 v3f player_position  = player->getPosition();
3170                 bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
3171                 handlePointingAtObject(pointed, tool_item, player_position,
3172                                 m_game_ui->m_flags.show_basic_debug && basic_debug_allowed);
3173         } else if (isKeyDown(KeyType::DIG)) {
3174                 // When button is held down in air, show continuous animation
3175                 runData.punching = true;
3176                 // Run callback even though item is not usable
3177                 if (wasKeyPressed(KeyType::DIG) && client->modsLoaded())
3178                         client->getScript()->on_item_use(selected_item, pointed);
3179         } else if (wasKeyPressed(KeyType::PLACE)) {
3180                 handlePointingAtNothing(selected_item);
3181         }
3182
3183         runData.pointed_old = pointed;
3184
3185         if (runData.punching || wasKeyPressed(KeyType::DIG))
3186                 camera->setDigging(0); // dig animation
3187
3188         input->clearWasKeyPressed();
3189         input->clearWasKeyReleased();
3190         // Ensure DIG & PLACE are marked as handled
3191         wasKeyDown(KeyType::DIG);
3192         wasKeyDown(KeyType::PLACE);
3193
3194         input->joystick.clearWasKeyPressed(KeyType::DIG);
3195         input->joystick.clearWasKeyPressed(KeyType::PLACE);
3196
3197         input->joystick.clearWasKeyReleased(KeyType::DIG);
3198         input->joystick.clearWasKeyReleased(KeyType::PLACE);
3199 }
3200
3201
3202 PointedThing Game::updatePointedThing(
3203         const core::line3d<f32> &shootline,
3204         bool liquids_pointable,
3205         bool look_for_object,
3206         const v3s16 &camera_offset)
3207 {
3208         std::vector<aabb3f> *selectionboxes = hud->getSelectionBoxes();
3209         selectionboxes->clear();
3210         hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0));
3211         static thread_local const bool show_entity_selectionbox = g_settings->getBool(
3212                 "show_entity_selectionbox");
3213
3214         ClientEnvironment &env = client->getEnv();
3215         ClientMap &map = env.getClientMap();
3216         const NodeDefManager *nodedef = map.getNodeDefManager();
3217
3218         runData.selected_object = NULL;
3219         hud->pointing_at_object = false;
3220
3221         RaycastState s(shootline, look_for_object, liquids_pointable);
3222         PointedThing result;
3223         env.continueRaycast(&s, &result);
3224         if (result.type == POINTEDTHING_OBJECT) {
3225                 hud->pointing_at_object = true;
3226
3227                 runData.selected_object = client->getEnv().getActiveObject(result.object_id);
3228                 aabb3f selection_box;
3229                 if (show_entity_selectionbox && runData.selected_object->doShowSelectionBox() &&
3230                                 runData.selected_object->getSelectionBox(&selection_box)) {
3231                         v3f pos = runData.selected_object->getPosition();
3232                         selectionboxes->push_back(aabb3f(selection_box));
3233                         hud->setSelectionPos(pos, camera_offset);
3234                 }
3235         } else if (result.type == POINTEDTHING_NODE) {
3236                 // Update selection boxes
3237                 MapNode n = map.getNode(result.node_undersurface);
3238                 std::vector<aabb3f> boxes;
3239                 n.getSelectionBoxes(nodedef, &boxes,
3240                         n.getNeighbors(result.node_undersurface, &map));
3241
3242                 f32 d = 0.002 * BS;
3243                 for (std::vector<aabb3f>::const_iterator i = boxes.begin();
3244                         i != boxes.end(); ++i) {
3245                         aabb3f box = *i;
3246                         box.MinEdge -= v3f(d, d, d);
3247                         box.MaxEdge += v3f(d, d, d);
3248                         selectionboxes->push_back(box);
3249                 }
3250                 hud->setSelectionPos(intToFloat(result.node_undersurface, BS),
3251                         camera_offset);
3252                 hud->setSelectedFaceNormal(v3f(
3253                         result.intersection_normal.X,
3254                         result.intersection_normal.Y,
3255                         result.intersection_normal.Z));
3256         }
3257
3258         // Update selection mesh light level and vertex colors
3259         if (!selectionboxes->empty()) {
3260                 v3f pf = hud->getSelectionPos();
3261                 v3s16 p = floatToInt(pf, BS);
3262
3263                 // Get selection mesh light level
3264                 MapNode n = map.getNode(p);
3265                 u16 node_light = getInteriorLight(n, -1, nodedef);
3266                 u16 light_level = node_light;
3267
3268                 for (const v3s16 &dir : g_6dirs) {
3269                         n = map.getNode(p + dir);
3270                         node_light = getInteriorLight(n, -1, nodedef);
3271                         if (node_light > light_level)
3272                                 light_level = node_light;
3273                 }
3274
3275                 u32 daynight_ratio = client->getEnv().getDayNightRatio();
3276                 video::SColor c;
3277                 final_color_blend(&c, light_level, daynight_ratio);
3278
3279                 // Modify final color a bit with time
3280                 u32 timer = porting::getTimeMs() % 5000;
3281                 float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5));
3282                 float sin_r = 0.08f * std::sin(timerf);
3283                 float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f);
3284                 float sin_b = 0.08f * std::sin(timerf + irr::core::PI);
3285                 c.setRed(core::clamp(core::round32(c.getRed() * (0.8 + sin_r)), 0, 255));
3286                 c.setGreen(core::clamp(core::round32(c.getGreen() * (0.8 + sin_g)), 0, 255));
3287                 c.setBlue(core::clamp(core::round32(c.getBlue() * (0.8 + sin_b)), 0, 255));
3288
3289                 // Set mesh final color
3290                 hud->setSelectionMeshColor(c);
3291         }
3292         return result;
3293 }
3294
3295
3296 void Game::handlePointingAtNothing(const ItemStack &playerItem)
3297 {
3298         infostream << "Attempted to place item while pointing at nothing" << std::endl;
3299         PointedThing fauxPointed;
3300         fauxPointed.type = POINTEDTHING_NOTHING;
3301         client->interact(INTERACT_ACTIVATE, fauxPointed);
3302 }
3303
3304
3305 void Game::handlePointingAtNode(const PointedThing &pointed,
3306         const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3307 {
3308         v3s16 nodepos = pointed.node_undersurface;
3309         v3s16 neighbourpos = pointed.node_abovesurface;
3310
3311         /*
3312                 Check information text of node
3313         */
3314
3315         ClientMap &map = client->getEnv().getClientMap();
3316
3317         if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG)
3318                         && !runData.digging_blocked
3319                         && client->checkPrivilege("interact")) {
3320                 handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
3321         }
3322
3323         // This should be done after digging handling
3324         NodeMetadata *meta = map.getNodeMetadata(nodepos);
3325
3326         if (meta) {
3327                 m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
3328                         meta->getString("infotext"))));
3329         } else {
3330                 MapNode n = map.getNode(nodepos);
3331
3332                 if (nodedef_manager->get(n).name == "unknown") {
3333                         m_game_ui->setInfoText(L"Unknown node");
3334                 }
3335         }
3336
3337         if ((wasKeyPressed(KeyType::PLACE) ||
3338                         runData.repeat_place_timer >= m_repeat_place_time) &&
3339                         client->checkPrivilege("interact")) {
3340                 runData.repeat_place_timer = 0;
3341                 infostream << "Place button pressed while looking at ground" << std::endl;
3342
3343                 // Placing animation (always shown for feedback)
3344                 camera->setDigging(1);
3345
3346                 soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
3347
3348                 // If the wielded item has node placement prediction,
3349                 // make that happen
3350                 // And also set the sound and send the interact
3351                 // But first check for meta formspec and rightclickable
3352                 auto &def = selected_item.getDefinition(itemdef_manager);
3353                 bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
3354                         pointed, meta);
3355
3356                 if (placed && client->modsLoaded())
3357                         client->getScript()->on_placenode(pointed, def);
3358         }
3359 }
3360
3361 bool Game::nodePlacement(const ItemDefinition &selected_def,
3362         const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
3363         const PointedThing &pointed, const NodeMetadata *meta)
3364 {
3365         const auto &prediction = selected_def.node_placement_prediction;
3366
3367         const NodeDefManager *nodedef = client->ndef();
3368         ClientMap &map = client->getEnv().getClientMap();
3369         MapNode node;
3370         bool is_valid_position;
3371
3372         node = map.getNode(nodepos, &is_valid_position);
3373         if (!is_valid_position) {
3374                 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3375                 return false;
3376         }
3377
3378         // formspec in meta
3379         if (meta && !meta->getString("formspec").empty() && !input->isRandom()
3380                         && !isKeyDown(KeyType::SNEAK)) {
3381                 // on_rightclick callbacks are called anyway
3382                 if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
3383                         client->interact(INTERACT_PLACE, pointed);
3384
3385                 infostream << "Launching custom inventory view" << std::endl;
3386
3387                 InventoryLocation inventoryloc;
3388                 inventoryloc.setNodeMeta(nodepos);
3389
3390                 NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3391                         &client->getEnv().getClientMap(), nodepos);
3392                 TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3393
3394                 auto *&formspec = m_game_ui->updateFormspec("");
3395                 GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
3396                         &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
3397
3398                 formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3399                 return false;
3400         }
3401
3402         // on_rightclick callback
3403         if (prediction.empty() || (nodedef->get(node).rightclickable &&
3404                         !isKeyDown(KeyType::SNEAK))) {
3405                 // Report to server
3406                 client->interact(INTERACT_PLACE, pointed);
3407                 return false;
3408         }
3409
3410         verbosestream << "Node placement prediction for "
3411                 << selected_def.name << " is " << prediction << std::endl;
3412         v3s16 p = neighbourpos;
3413
3414         // Place inside node itself if buildable_to
3415         MapNode n_under = map.getNode(nodepos, &is_valid_position);
3416         if (is_valid_position) {
3417                 if (nodedef->get(n_under).buildable_to) {
3418                         p = nodepos;
3419                 } else {
3420                         node = map.getNode(p, &is_valid_position);
3421                         if (is_valid_position && !nodedef->get(node).buildable_to) {
3422                                 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3423                                 // Report to server
3424                                 client->interact(INTERACT_PLACE, pointed);
3425                                 return false;
3426                         }
3427                 }
3428         }
3429
3430         // Find id of predicted node
3431         content_t id;
3432         bool found = nodedef->getId(prediction, id);
3433
3434         if (!found) {
3435                 errorstream << "Node placement prediction failed for "
3436                         << selected_def.name << " (places " << prediction
3437                         << ") - Name not known" << std::endl;
3438                 // Handle this as if prediction was empty
3439                 // Report to server
3440                 client->interact(INTERACT_PLACE, pointed);
3441                 return false;
3442         }
3443
3444         const ContentFeatures &predicted_f = nodedef->get(id);
3445
3446         // Predict param2 for facedir and wallmounted nodes
3447         // Compare core.item_place_node() for what the server does
3448         u8 param2 = 0;
3449
3450         const u8 place_param2 = selected_def.place_param2;
3451
3452         if (place_param2) {
3453                 param2 = place_param2;
3454         } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3455                         predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3456                 v3s16 dir = nodepos - neighbourpos;
3457
3458                 if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
3459                         param2 = dir.Y < 0 ? 1 : 0;
3460                 } else if (abs(dir.X) > abs(dir.Z)) {
3461                         param2 = dir.X < 0 ? 3 : 2;
3462                 } else {
3463                         param2 = dir.Z < 0 ? 5 : 4;
3464                 }
3465         } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
3466                         predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3467                 v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
3468
3469                 if (abs(dir.X) > abs(dir.Z)) {
3470                         param2 = dir.X < 0 ? 3 : 1;
3471                 } else {
3472                         param2 = dir.Z < 0 ? 2 : 0;
3473                 }
3474         }
3475
3476         // Check attachment if node is in group attached_node
3477         if (itemgroup_get(predicted_f.groups, "attached_node") != 0) {
3478                 const static v3s16 wallmounted_dirs[8] = {
3479                         v3s16(0, 1, 0),
3480                         v3s16(0, -1, 0),
3481                         v3s16(1, 0, 0),
3482                         v3s16(-1, 0, 0),
3483                         v3s16(0, 0, 1),
3484                         v3s16(0, 0, -1),
3485                 };
3486                 v3s16 pp;
3487
3488                 if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
3489                                 predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
3490                         pp = p + wallmounted_dirs[param2];
3491                 else
3492                         pp = p + v3s16(0, -1, 0);
3493
3494                 if (!nodedef->get(map.getNode(pp)).walkable) {
3495                         soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3496                         // Report to server
3497                         client->interact(INTERACT_PLACE, pointed);
3498                         return false;
3499                 }
3500         }
3501
3502         // Apply color
3503         if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR
3504                         || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
3505                         || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
3506                 const auto &indexstr = selected_item.metadata.
3507                         getString("palette_index", 0);
3508                 if (!indexstr.empty()) {
3509                         s32 index = mystoi(indexstr);
3510                         if (predicted_f.param_type_2 == CPT2_COLOR) {
3511                                 param2 = index;
3512                         } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
3513                                 // param2 = pure palette index + other
3514                                 param2 = (index & 0xf8) | (param2 & 0x07);
3515                         } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
3516                                 // param2 = pure palette index + other
3517                                 param2 = (index & 0xe0) | (param2 & 0x1f);
3518                         }
3519                 }
3520         }
3521
3522         // Add node to client map
3523         MapNode n(id, 0, param2);
3524
3525         try {
3526                 LocalPlayer *player = client->getEnv().getLocalPlayer();
3527
3528                 // Dont place node when player would be inside new node
3529                 // NOTE: This is to be eventually implemented by a mod as client-side Lua
3530                 if (!nodedef->get(n).walkable ||
3531                                 g_settings->getBool("enable_build_where_you_stand") ||
3532                                 (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
3533                                 (nodedef->get(n).walkable &&
3534                                         neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
3535                                         neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
3536                         // This triggers the required mesh update too
3537                         client->addNode(p, n);
3538                         // Report to server
3539                         client->interact(INTERACT_PLACE, pointed);
3540                         // A node is predicted, also play a sound
3541                         soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
3542                         return true;
3543                 } else {
3544                         soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3545                         return false;
3546                 }
3547         } catch (const InvalidPositionException &e) {
3548                 errorstream << "Node placement prediction failed for "
3549                         << selected_def.name << " (places "
3550                         << prediction << ") - Position not loaded" << std::endl;
3551                 soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
3552                 return false;
3553         }
3554 }
3555
3556 void Game::handlePointingAtObject(const PointedThing &pointed,
3557                 const ItemStack &tool_item, const v3f &player_position, bool show_debug)
3558 {
3559         std::wstring infotext = unescape_translate(
3560                 utf8_to_wide(runData.selected_object->infoText()));
3561
3562         if (show_debug) {
3563                 if (!infotext.empty()) {
3564                         infotext += L"\n";
3565                 }
3566                 infotext += utf8_to_wide(runData.selected_object->debugInfoText());
3567         }
3568
3569         m_game_ui->setInfoText(infotext);
3570
3571         if (isKeyDown(KeyType::DIG)) {
3572                 bool do_punch = false;
3573                 bool do_punch_damage = false;
3574
3575                 if (runData.object_hit_delay_timer <= 0.0) {
3576                         do_punch = true;
3577                         do_punch_damage = true;
3578                         runData.object_hit_delay_timer = object_hit_delay;
3579                 }
3580
3581                 if (wasKeyPressed(KeyType::DIG))
3582                         do_punch = true;
3583
3584                 if (do_punch) {
3585                         infostream << "Punched object" << std::endl;
3586                         runData.punching = true;
3587                 }
3588
3589                 if (do_punch_damage) {
3590                         // Report direct punch
3591                         v3f objpos = runData.selected_object->getPosition();
3592                         v3f dir = (objpos - player_position).normalize();
3593
3594                         bool disable_send = runData.selected_object->directReportPunch(
3595                                         dir, &tool_item, runData.time_from_last_punch);
3596                         runData.time_from_last_punch = 0;
3597
3598                         if (!disable_send)
3599                                 client->interact(INTERACT_START_DIGGING, pointed);
3600                 }
3601         } else if (wasKeyDown(KeyType::PLACE)) {
3602                 infostream << "Pressed place button while pointing at object" << std::endl;
3603                 client->interact(INTERACT_PLACE, pointed);  // place
3604         }
3605 }
3606
3607
3608 void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
3609                 const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
3610 {
3611         // See also: serverpackethandle.cpp, action == 2
3612         LocalPlayer *player = client->getEnv().getLocalPlayer();
3613         ClientMap &map = client->getEnv().getClientMap();
3614         MapNode n = client->getEnv().getClientMap().getNode(nodepos);
3615
3616         // NOTE: Similar piece of code exists on the server side for
3617         // cheat detection.
3618         // Get digging parameters
3619         DigParams params = getDigParams(nodedef_manager->get(n).groups,
3620                         &selected_item.getToolCapabilities(itemdef_manager),
3621                         selected_item.wear);
3622
3623         // If can't dig, try hand
3624         if (!params.diggable) {
3625                 params = getDigParams(nodedef_manager->get(n).groups,
3626                                 &hand_item.getToolCapabilities(itemdef_manager));
3627         }
3628
3629         if (!params.diggable) {
3630                 // I guess nobody will wait for this long
3631                 runData.dig_time_complete = 10000000.0;
3632         } else {
3633                 runData.dig_time_complete = params.time;
3634
3635                 if (m_cache_enable_particles) {
3636                         const ContentFeatures &features = client->getNodeDefManager()->get(n);
3637                         client->getParticleManager()->addNodeParticle(client,
3638                                         player, nodepos, n, features);
3639                 }
3640         }
3641
3642         if (!runData.digging) {
3643                 infostream << "Started digging" << std::endl;
3644                 runData.dig_instantly = runData.dig_time_complete == 0;
3645                 if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
3646                         return;
3647                 client->interact(INTERACT_START_DIGGING, pointed);
3648                 runData.digging = true;
3649                 runData.btn_down_for_dig = true;
3650         }
3651
3652         if (!runData.dig_instantly) {
3653                 runData.dig_index = (float)crack_animation_length
3654                                 * runData.dig_time
3655                                 / runData.dig_time_complete;
3656         } else {
3657                 // This is for e.g. torches
3658                 runData.dig_index = crack_animation_length;
3659         }
3660
3661         SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3662
3663         if (sound_dig.exists() && params.diggable) {
3664                 if (sound_dig.name == "__group") {
3665                         if (!params.main_group.empty()) {
3666                                 soundmaker->m_player_leftpunch_sound.gain = 0.5;
3667                                 soundmaker->m_player_leftpunch_sound.name =
3668                                                 std::string("default_dig_") +
3669                                                 params.main_group;
3670                         }
3671                 } else {
3672                         soundmaker->m_player_leftpunch_sound = sound_dig;
3673                 }
3674         }
3675
3676         // Don't show cracks if not diggable
3677         if (runData.dig_time_complete >= 100000.0) {
3678         } else if (runData.dig_index < crack_animation_length) {
3679                 //TimeTaker timer("client.setTempMod");
3680                 //infostream<<"dig_index="<<dig_index<<std::endl;
3681                 client->setCrack(runData.dig_index, nodepos);
3682         } else {
3683                 infostream << "Digging completed" << std::endl;
3684                 client->setCrack(-1, v3s16(0, 0, 0));
3685
3686                 runData.dig_time = 0;
3687                 runData.digging = false;
3688                 // we successfully dug, now block it from repeating if we want to be safe
3689                 if (g_settings->getBool("safe_dig_and_place"))
3690                         runData.digging_blocked = true;
3691
3692                 runData.nodig_delay_timer =
3693                                 runData.dig_time_complete / (float)crack_animation_length;
3694
3695                 // We don't want a corresponding delay to very time consuming nodes
3696                 // and nodes without digging time (e.g. torches) get a fixed delay.
3697                 if (runData.nodig_delay_timer > 0.3)
3698                         runData.nodig_delay_timer = 0.3;
3699                 else if (runData.dig_instantly)
3700                         runData.nodig_delay_timer = 0.15;
3701
3702                 bool is_valid_position;
3703                 MapNode wasnode = map.getNode(nodepos, &is_valid_position);
3704                 if (is_valid_position) {
3705                         if (client->modsLoaded() &&
3706                                         client->getScript()->on_dignode(nodepos, wasnode)) {
3707                                 return;
3708                         }
3709
3710                         const ContentFeatures &f = client->ndef()->get(wasnode);
3711                         if (f.node_dig_prediction == "air") {
3712                                 client->removeNode(nodepos);
3713                         } else if (!f.node_dig_prediction.empty()) {
3714                                 content_t id;
3715                                 bool found = client->ndef()->getId(f.node_dig_prediction, id);
3716                                 if (found)
3717                                         client->addNode(nodepos, id, true);
3718                         }
3719                         // implicit else: no prediction
3720                 }
3721
3722                 client->interact(INTERACT_DIGGING_COMPLETED, pointed);
3723
3724                 if (m_cache_enable_particles) {
3725                         const ContentFeatures &features =
3726                                 client->getNodeDefManager()->get(wasnode);
3727                         client->getParticleManager()->addDiggingParticles(client,
3728                                 player, nodepos, wasnode, features);
3729                 }
3730
3731
3732                 // Send event to trigger sound
3733                 client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode));
3734         }
3735
3736         if (runData.dig_time_complete < 100000.0) {
3737                 runData.dig_time += dtime;
3738         } else {
3739                 runData.dig_time = 0;
3740                 client->setCrack(-1, nodepos);
3741         }
3742
3743         camera->setDigging(0);  // Dig animation
3744 }
3745
3746 void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
3747                 const CameraOrientation &cam)
3748 {
3749         TimeTaker tt_update("Game::updateFrame()");
3750         LocalPlayer *player = client->getEnv().getLocalPlayer();
3751
3752         /*
3753                 Fog range
3754         */
3755
3756         if (draw_control->range_all) {
3757                 runData.fog_range = 100000 * BS;
3758         } else {
3759                 runData.fog_range = draw_control->wanted_range * BS;
3760         }
3761
3762         /*
3763                 Calculate general brightness
3764         */
3765         u32 daynight_ratio = client->getEnv().getDayNightRatio();
3766         float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3767         float direct_brightness;
3768         bool sunlight_seen;
3769
3770         // When in noclip mode force same sky brightness as above ground so you
3771         // can see properly
3772         if (draw_control->allow_noclip && m_cache_enable_free_move &&
3773                 client->checkPrivilege("fly")) {
3774                 direct_brightness = time_brightness;
3775                 sunlight_seen = true;
3776         } else {
3777                 float old_brightness = sky->getBrightness();
3778                 direct_brightness = client->getEnv().getClientMap()
3779                                 .getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
3780                                         daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3781                                     / 255.0;
3782         }
3783
3784         float time_of_day_smooth = runData.time_of_day_smooth;
3785         float time_of_day = client->getEnv().getTimeOfDayF();
3786
3787         static const float maxsm = 0.05f;
3788         static const float todsm = 0.05f;
3789
3790         if (std::fabs(time_of_day - time_of_day_smooth) > maxsm &&
3791                         std::fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3792                         std::fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3793                 time_of_day_smooth = time_of_day;
3794
3795         if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3796                 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3797                                 + (time_of_day + 1.0) * todsm;
3798         else
3799                 time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3800                                 + time_of_day * todsm;
3801
3802         runData.time_of_day_smooth = time_of_day_smooth;
3803
3804         sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3805                         sunlight_seen, camera->getCameraMode(), player->getYaw(),
3806                         player->getPitch());
3807
3808         /*
3809                 Update clouds
3810         */
3811         if (clouds) {
3812                 if (sky->getCloudsVisible()) {
3813                         clouds->setVisible(true);
3814                         clouds->step(dtime);
3815                         // camera->getPosition is not enough for 3rd person views
3816                         v3f camera_node_position = camera->getCameraNode()->getPosition();
3817                         v3s16 camera_offset      = camera->getOffset();
3818                         camera_node_position.X   = camera_node_position.X + camera_offset.X * BS;
3819                         camera_node_position.Y   = camera_node_position.Y + camera_offset.Y * BS;
3820                         camera_node_position.Z   = camera_node_position.Z + camera_offset.Z * BS;
3821                         clouds->update(camera_node_position,
3822                                         sky->getCloudColor());
3823                         if (clouds->isCameraInsideCloud() && m_cache_enable_fog) {
3824                                 // if inside clouds, and fog enabled, use that as sky
3825                                 // color(s)
3826                                 video::SColor clouds_dark = clouds->getColor()
3827                                                 .getInterpolated(video::SColor(255, 0, 0, 0), 0.9);
3828                                 sky->overrideColors(clouds_dark, clouds->getColor());
3829                                 sky->setInClouds(true);
3830                                 runData.fog_range = std::fmin(runData.fog_range * 0.5f, 32.0f * BS);
3831                                 // do not draw clouds after all
3832                                 clouds->setVisible(false);
3833                         }
3834                 } else {
3835                         clouds->setVisible(false);
3836                 }
3837         }
3838
3839         /*
3840                 Update particles
3841         */
3842         client->getParticleManager()->step(dtime);
3843
3844         /*
3845                 Fog
3846         */
3847
3848         if (m_cache_enable_fog) {
3849                 driver->setFog(
3850                                 sky->getBgColor(),
3851                                 video::EFT_FOG_LINEAR,
3852                                 runData.fog_range * m_cache_fog_start,
3853                                 runData.fog_range * 1.0,
3854                                 0.01,
3855                                 false, // pixel fog
3856                                 true // range fog
3857                 );
3858         } else {
3859                 driver->setFog(
3860                                 sky->getBgColor(),
3861                                 video::EFT_FOG_LINEAR,
3862                                 100000 * BS,
3863                                 110000 * BS,
3864                                 0.01f,
3865                                 false, // pixel fog
3866                                 false // range fog
3867                 );
3868         }
3869
3870         /*
3871                 Damage camera tilt
3872         */
3873         if (player->hurt_tilt_timer > 0.0f) {
3874                 player->hurt_tilt_timer -= dtime * 6.0f;
3875
3876                 if (player->hurt_tilt_timer < 0.0f)
3877                         player->hurt_tilt_strength = 0.0f;
3878         }
3879
3880         /*
3881                 Update minimap pos and rotation
3882         */
3883         if (mapper && m_game_ui->m_flags.show_hud) {
3884                 mapper->setPos(floatToInt(player->getPosition(), BS));
3885                 mapper->setAngle(player->getYaw());
3886         }
3887
3888         /*
3889                 Get chat messages from client
3890         */
3891
3892         updateChat(dtime);
3893
3894         /*
3895                 Inventory
3896         */
3897
3898         if (player->getWieldIndex() != runData.new_playeritem)
3899                 client->setPlayerItem(runData.new_playeritem);
3900
3901         if (client->updateWieldedItem()) {
3902                 // Update wielded tool
3903                 ItemStack selected_item, hand_item;
3904                 ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
3905                 camera->wield(tool_item);
3906         }
3907
3908         /*
3909                 Update block draw list every 200ms or when camera direction has
3910                 changed much
3911         */
3912         runData.update_draw_list_timer += dtime;
3913
3914         float update_draw_list_delta = 0.2f;
3915
3916         v3f camera_direction = camera->getDirection();
3917         if (runData.update_draw_list_timer >= update_draw_list_delta
3918                         || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3919                         || m_camera_offset_changed
3920                         || client->getEnv().getClientMap().needsUpdateDrawList()) {
3921                 runData.update_draw_list_timer = 0;
3922                 client->getEnv().getClientMap().updateDrawList();
3923                 runData.update_draw_list_last_cam_dir = camera_direction;
3924         }
3925
3926         if (RenderingEngine::get_shadow_renderer()) {
3927                 updateShadows();
3928         }
3929
3930         m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
3931
3932         /*
3933            make sure menu is on top
3934            1. Delete formspec menu reference if menu was removed
3935            2. Else, make sure formspec menu is on top
3936         */
3937         auto formspec = m_game_ui->getFormspecGUI();
3938         do { // breakable. only runs for one iteration
3939                 if (!formspec)
3940                         break;
3941
3942                 if (formspec->getReferenceCount() == 1) {
3943                         m_game_ui->deleteFormspec();
3944                         break;
3945                 }
3946
3947                 auto &loc = formspec->getFormspecLocation();
3948                 if (loc.type == InventoryLocation::NODEMETA) {
3949                         NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
3950                         if (!meta || meta->getString("formspec").empty()) {
3951                                 formspec->quitMenu();
3952                                 break;
3953                         }
3954                 }
3955
3956                 if (isMenuActive())
3957                         guiroot->bringToFront(formspec);
3958         } while (false);
3959
3960         /*
3961                 ==================== Drawing begins ====================
3962         */
3963         const video::SColor skycolor = sky->getSkyColor();
3964
3965         TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
3966         driver->beginScene(true, true, skycolor);
3967
3968         bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
3969                         (player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
3970                         (camera->getCameraMode() == CAMERA_MODE_FIRST));
3971         bool draw_crosshair = (
3972                         (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
3973                         (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
3974 #ifdef HAVE_TOUCHSCREENGUI
3975         try {
3976                 draw_crosshair = !g_settings->getBool("touchtarget");
3977         } catch (SettingNotFoundException) {
3978         }
3979 #endif
3980         m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
3981                         m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
3982
3983         /*
3984                 Profiler graph
3985         */
3986         v2u32 screensize = driver->getScreenSize();
3987
3988         if (m_game_ui->m_flags.show_profiler_graph)
3989                 graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
3990
3991         /*
3992                 Damage flash
3993         */
3994         if (runData.damage_flash > 0.0f) {
3995                 video::SColor color(runData.damage_flash, 180, 0, 0);
3996                 driver->draw2DRectangle(color,
3997                                         core::rect<s32>(0, 0, screensize.X, screensize.Y),
3998                                         NULL);
3999
4000                 runData.damage_flash -= 384.0f * dtime;
4001         }
4002
4003         /*
4004                 ==================== End scene ====================
4005         */
4006 #if IRRLICHT_VERSION_MT_REVISION < 5
4007         if (++m_reset_HW_buffer_counter > 500) {
4008                 /*
4009                   Periodically remove all mesh HW buffers.
4010
4011                   Work around for a quirk in Irrlicht where a HW buffer is only
4012                   released after 20000 iterations (triggered from endScene()).
4013
4014                   Without this, all loaded but unused meshes will retain their HW
4015                   buffers for at least 5 minutes, at which point looking up the HW buffers
4016                   becomes a bottleneck and the framerate drops (as much as 30%).
4017
4018                   Tests showed that numbers between 50 and 1000 are good, so picked 500.
4019                   There are no other public Irrlicht APIs that allow interacting with the
4020                   HW buffers without tracking the status of every individual mesh.
4021
4022                   The HW buffers for _visible_ meshes will be reinitialized in the next frame.
4023                 */
4024                 infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl;
4025                 driver->removeAllHardwareBuffers();
4026                 m_reset_HW_buffer_counter = 0;
4027         }
4028 #endif
4029
4030         driver->endScene();
4031
4032         stats->drawtime = tt_draw.stop(true);
4033         g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
4034         g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
4035 }
4036
4037 /* Log times and stuff for visualization */
4038 inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4039 {
4040         Profiler::GraphValues values;
4041         g_profiler->graphGet(values);
4042         graph->put(values);
4043 }
4044
4045 /****************************************************************************
4046  * Shadows
4047  *****************************************************************************/
4048 void Game::updateShadows()
4049 {
4050         ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer();
4051         if (!shadow)
4052                 return;
4053
4054         float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
4055
4056         float timeoftheday = getWickedTimeOfDay(in_timeofday);
4057         bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
4058         bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
4059         shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
4060
4061         timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
4062         const float offset_constant = 10000.0f;
4063
4064         v3f light(0.0f, 0.0f, -1.0f);
4065         light.rotateXZBy(90);
4066         light.rotateXYBy(timeoftheday * 360 - 90);
4067         light.rotateYZBy(sky->getSkyBodyOrbitTilt());
4068
4069         v3f sun_pos = light * offset_constant;
4070
4071         if (shadow->getDirectionalLightCount() == 0)
4072                 shadow->addDirectionalLight();
4073         shadow->getDirectionalLight().setDirection(sun_pos);
4074         shadow->setTimeOfDay(in_timeofday);
4075
4076         shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
4077 }
4078
4079 /****************************************************************************
4080  Misc
4081  ****************************************************************************/
4082
4083 void FpsControl::reset()
4084 {
4085         last_time = porting::getTimeUs();
4086 }
4087
4088 /*
4089  * On some computers framerate doesn't seem to be automatically limited
4090  */
4091 void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
4092 {
4093         const u64 frametime_min = 1000000.0f / (
4094                 device->isWindowFocused() && !g_menumgr.pausesGame()
4095                         ? g_settings->getFloat("fps_max")
4096                         : g_settings->getFloat("fps_max_unfocused"));
4097
4098         u64 time = porting::getTimeUs();
4099
4100         if (time > last_time) // Make sure time hasn't overflowed
4101                 busy_time = time - last_time;
4102         else
4103                 busy_time = 0;
4104
4105         if (busy_time < frametime_min) {
4106                 sleep_time = frametime_min - busy_time;
4107                 if (sleep_time > 1000)
4108                         sleep_ms(sleep_time / 1000);
4109         } else {
4110                 sleep_time = 0;
4111         }
4112
4113         // Read the timer again to accurately determine how long we actually slept,
4114         // rather than calculating it by adding sleep_time to time.
4115         time = porting::getTimeUs();
4116
4117         if (time > last_time) // Make sure last_time hasn't overflowed
4118                 *dtime = (time - last_time) / 1000000.0f;
4119         else
4120                 *dtime = 0;
4121
4122         last_time = time;
4123 }
4124
4125 void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds)
4126 {
4127         const wchar_t *wmsg = wgettext(msg);
4128         m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent,
4129                 draw_clouds);
4130         delete[] wmsg;
4131 }
4132
4133 void Game::settingChangedCallback(const std::string &setting_name, void *data)
4134 {
4135         ((Game *)data)->readSettings();
4136 }
4137
4138 void Game::readSettings()
4139 {
4140         m_cache_doubletap_jump               = g_settings->getBool("doubletap_jump");
4141         m_cache_enable_clouds                = g_settings->getBool("enable_clouds");
4142         m_cache_enable_joysticks             = g_settings->getBool("enable_joysticks");
4143         m_cache_enable_particles             = g_settings->getBool("enable_particles");
4144         m_cache_enable_fog                   = g_settings->getBool("enable_fog");
4145         m_cache_mouse_sensitivity            = g_settings->getFloat("mouse_sensitivity");
4146         m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity");
4147         m_repeat_place_time                  = g_settings->getFloat("repeat_place_time");
4148
4149         m_cache_enable_noclip                = g_settings->getBool("noclip");
4150         m_cache_enable_free_move             = g_settings->getBool("free_move");
4151
4152         m_cache_fog_start                    = g_settings->getFloat("fog_start");
4153
4154         m_cache_cam_smoothing = 0;
4155         if (g_settings->getBool("cinematic"))
4156                 m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
4157         else
4158                 m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
4159
4160         m_cache_fog_start = rangelim(m_cache_fog_start, 0.0f, 0.99f);
4161         m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f);
4162         m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
4163
4164         m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus");
4165 }
4166
4167 /****************************************************************************/
4168 /****************************************************************************
4169  Shutdown / cleanup
4170  ****************************************************************************/
4171 /****************************************************************************/
4172
4173 void Game::showDeathFormspec()
4174 {
4175         static std::string formspec_str =
4176                 std::string("formspec_version[1]") +
4177                 SIZE_TAG
4178                 "bgcolor[#320000b4;true]"
4179                 "label[4.85,1.35;" + gettext("You died") + "]"
4180                 "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
4181                 ;
4182
4183         /* Create menu */
4184         /* Note: FormspecFormSource and LocalFormspecHandler  *
4185          * are deleted by guiFormSpecMenu                     */
4186         FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
4187         LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
4188
4189         auto *&formspec = m_game_ui->getFormspecGUI();
4190         GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4191                 &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4192         formspec->setFocus("btn_respawn");
4193 }
4194
4195 #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
4196 void Game::showPauseMenu()
4197 {
4198 #ifdef HAVE_TOUCHSCREENGUI
4199         static const std::string control_text = strgettext("Default Controls:\n"
4200                 "No menu visible:\n"
4201                 "- single tap: button activate\n"
4202                 "- double tap: place/use\n"
4203                 "- slide finger: look around\n"
4204                 "Menu/Inventory visible:\n"
4205                 "- double tap (outside):\n"
4206                 " -->close\n"
4207                 "- touch stack, touch slot:\n"
4208                 " --> move stack\n"
4209                 "- touch&drag, tap 2nd finger\n"
4210                 " --> place single item to slot\n"
4211                 );
4212 #else
4213         static const std::string control_text_template = strgettext("Controls:\n"
4214                 "- %s: move forwards\n"
4215                 "- %s: move backwards\n"
4216                 "- %s: move left\n"
4217                 "- %s: move right\n"
4218                 "- %s: jump/climb up\n"
4219                 "- %s: dig/punch\n"
4220                 "- %s: place/use\n"
4221                 "- %s: sneak/climb down\n"
4222                 "- %s: drop item\n"
4223                 "- %s: inventory\n"
4224                 "- Mouse: turn/look\n"
4225                 "- Mouse wheel: select item\n"
4226                 "- %s: chat\n"
4227         );
4228
4229         char control_text_buf[600];
4230
4231         porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
4232                 GET_KEY_NAME(keymap_forward),
4233                 GET_KEY_NAME(keymap_backward),
4234                 GET_KEY_NAME(keymap_left),
4235                 GET_KEY_NAME(keymap_right),
4236                 GET_KEY_NAME(keymap_jump),
4237                 GET_KEY_NAME(keymap_dig),
4238                 GET_KEY_NAME(keymap_place),
4239                 GET_KEY_NAME(keymap_sneak),
4240                 GET_KEY_NAME(keymap_drop),
4241                 GET_KEY_NAME(keymap_inventory),
4242                 GET_KEY_NAME(keymap_chat)
4243         );
4244
4245         std::string control_text = std::string(control_text_buf);
4246         str_formspec_escape(control_text);
4247 #endif
4248
4249         float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
4250         std::ostringstream os;
4251
4252         os << "formspec_version[1]" << SIZE_TAG
4253                 << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
4254                 << strgettext("Continue") << "]";
4255
4256         if (!simple_singleplayer_mode) {
4257                 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
4258                         << strgettext("Change Password") << "]";
4259         } else {
4260                 os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]";
4261         }
4262
4263 #ifndef __ANDROID__
4264 #if USE_SOUND
4265         if (g_settings->getBool("enable_sound")) {
4266                 os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
4267                         << strgettext("Sound Volume") << "]";
4268         }
4269 #endif
4270         os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
4271                 << strgettext("Change Keys")  << "]";
4272 #endif
4273         os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
4274                 << strgettext("Exit to Menu") << "]";
4275         os              << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
4276                 << strgettext("Exit to OS")   << "]"
4277                 << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
4278                 << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
4279                 << "\n"
4280                 <<  strgettext("Game info:") << "\n";
4281         const std::string &address = client->getAddressName();
4282         static const std::string mode = strgettext("- Mode: ");
4283         if (!simple_singleplayer_mode) {
4284                 Address serverAddress = client->getServerAddress();
4285                 if (!address.empty()) {
4286                         os << mode << strgettext("Remote server") << "\n"
4287                                         << strgettext("- Address: ") << address;
4288                 } else {
4289                         os << mode << strgettext("Hosting server");
4290                 }
4291                 os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
4292         } else {
4293                 os << mode << strgettext("Singleplayer") << "\n";
4294         }
4295         if (simple_singleplayer_mode || address.empty()) {
4296                 static const std::string on = strgettext("On");
4297                 static const std::string off = strgettext("Off");
4298                 // Note: Status of enable_damage and creative_mode settings is intentionally
4299                 // NOT shown here because the game might roll its own damage system and/or do
4300                 // a per-player Creative Mode, in which case writing it here would mislead.
4301                 bool damage = g_settings->getBool("enable_damage");
4302                 const std::string &announced = g_settings->getBool("server_announce") ? on : off;
4303                 if (!simple_singleplayer_mode) {
4304                         if (damage) {
4305                                 const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off;
4306                                 //~ PvP = Player versus Player
4307                                 os << strgettext("- PvP: ") << pvp << "\n";
4308                         }
4309                         os << strgettext("- Public: ") << announced << "\n";
4310                         std::string server_name = g_settings->get("server_name");
4311                         str_formspec_escape(server_name);
4312                         if (announced == on && !server_name.empty())
4313                                 os << strgettext("- Server Name: ") << server_name;
4314
4315                 }
4316         }
4317         os << ";]";
4318
4319         /* Create menu */
4320         /* Note: FormspecFormSource and LocalFormspecHandler  *
4321          * are deleted by guiFormSpecMenu                     */
4322         FormspecFormSource *fs_src = new FormspecFormSource(os.str());
4323         LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
4324
4325         auto *&formspec = m_game_ui->getFormspecGUI();
4326         GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(),
4327                         &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound);
4328         formspec->setFocus("btn_continue");
4329         formspec->doPause = true;
4330
4331         if (simple_singleplayer_mode)
4332                 pauseAnimation();
4333 }
4334
4335 /****************************************************************************/
4336 /****************************************************************************
4337  extern function for launching the game
4338  ****************************************************************************/
4339 /****************************************************************************/
4340
4341 void the_game(bool *kill,
4342                 InputHandler *input,
4343                 RenderingEngine *rendering_engine,
4344                 const GameStartData &start_data,
4345                 std::string &error_message,
4346                 ChatBackend &chat_backend,
4347                 bool *reconnect_requested) // Used for local game
4348 {
4349         Game game;
4350
4351         /* Make a copy of the server address because if a local singleplayer server
4352          * is created then this is updated and we don't want to change the value
4353          * passed to us by the calling function
4354          */
4355
4356         try {
4357
4358                 if (game.startup(kill, input, rendering_engine, start_data,
4359                                 error_message, reconnect_requested, &chat_backend)) {
4360                         game.run();
4361                 }
4362
4363         } catch (SerializationError &e) {
4364                 const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C);
4365                 error_message = strgettext("A serialization error occurred:") +"\n"
4366                                 + e.what() + "\n\n" + ver_err;
4367                 errorstream << error_message << std::endl;
4368         } catch (ServerError &e) {
4369                 error_message = e.what();
4370                 errorstream << "ServerError: " << error_message << std::endl;
4371         } catch (ModError &e) {
4372                 // DO NOT TRANSLATE the `ModError`, it's used by ui.lua
4373                 error_message = std::string("ModError: ") + e.what() +
4374                                 strgettext("\nCheck debug.txt for details.");
4375                 errorstream << error_message << std::endl;
4376         }
4377         game.shutdown();
4378 }