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