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