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