]> git.lizzy.rs Git - minetest.git/blob - src/client/clientlauncher.cpp
94d2a38c9d1eea97025b2fb6194c0e7f7a3601e5
[minetest.git] / src / client / clientlauncher.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 "gui/mainmenumanager.h"
21 #include "clouds.h"
22 #include "server.h"
23 #include "filesys.h"
24 #include "gui/guiMainMenu.h"
25 #include "game.h"
26 #include "player.h"
27 #include "chat.h"
28 #include "gettext.h"
29 #include "profiler.h"
30 #include "serverlist.h"
31 #include "gui/guiEngine.h"
32 #include "fontengine.h"
33 #include "clientlauncher.h"
34 #include "version.h"
35 #include "renderingengine.h"
36 #include "network/networkexceptions.h"
37
38 #if USE_SOUND
39         #include "sound_openal.h"
40 #endif
41
42 /* mainmenumanager.h
43  */
44 gui::IGUIEnvironment *guienv = nullptr;
45 gui::IGUIStaticText *guiroot = nullptr;
46 MainMenuManager g_menumgr;
47
48 bool isMenuActive()
49 {
50         return g_menumgr.menuCount() != 0;
51 }
52
53 // Passed to menus to allow disconnecting and exiting
54 MainGameCallback *g_gamecallback = nullptr;
55
56 #if 0
57 // This can be helpful for the next code cleanup
58 static void dump_start_data(const GameStartData &data)
59 {
60         std::cout <<
61                 "\ndedicated   " << (int)data.is_dedicated_server <<
62                 "\nport        " << data.socket_port <<
63                 "\nworld_path  " << data.world_spec.path <<
64                 "\nworld game  " << data.world_spec.gameid <<
65                 "\ngame path   " << data.game_spec.path <<
66                 "\nplayer name " << data.name <<
67                 "\naddress     " << data.address << std::endl;
68 }
69 #endif
70
71 ClientLauncher::~ClientLauncher()
72 {
73         delete input;
74
75         delete receiver;
76
77         delete g_fontengine;
78         delete g_gamecallback;
79
80         delete m_rendering_engine;
81
82 #if USE_SOUND
83         g_sound_manager_singleton.reset();
84 #endif
85 }
86
87
88 bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
89 {
90         /* This function is called when a client must be started.
91          * Covered cases:
92          *   - Singleplayer (address but map provided)
93          *   - Join server (no map but address provided)
94          *   - Local server (for main menu only)
95         */
96
97         init_args(start_data, cmd_args);
98
99 #if USE_SOUND
100         if (g_settings->getBool("enable_sound"))
101                 g_sound_manager_singleton = createSoundManagerSingleton();
102 #endif
103
104         if (!init_engine()) {
105                 errorstream << "Could not initialize game engine." << std::endl;
106                 return false;
107         }
108
109         // Speed tests (done after irrlicht is loaded to get timer)
110         if (cmd_args.getFlag("speedtests")) {
111                 dstream << "Running speed tests" << std::endl;
112                 speed_tests();
113                 return true;
114         }
115
116         if (m_rendering_engine->get_video_driver() == NULL) {
117                 errorstream << "Could not initialize video driver." << std::endl;
118                 return false;
119         }
120
121         m_rendering_engine->setupTopLevelWindow(PROJECT_NAME_C);
122
123         /*
124                 This changes the minimum allowed number of vertices in a VBO.
125                 Default is 500.
126         */
127         //driver->setMinHardwareBufferVertexCount(50);
128
129         // Create game callback for menus
130         g_gamecallback = new MainGameCallback();
131
132         m_rendering_engine->setResizable(true);
133
134         init_input();
135
136         m_rendering_engine->get_scene_manager()->getParameters()->
137                 setAttribute(scene::ALLOW_ZWRITE_ON_TRANSPARENT, true);
138
139         guienv = m_rendering_engine->get_gui_env();
140         skin = guienv->getSkin();
141         skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255, 255, 255, 255));
142         skin->setColor(gui::EGDC_3D_LIGHT, video::SColor(0, 0, 0, 0));
143         skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255, 30, 30, 30));
144         skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255, 0, 0, 0));
145         skin->setColor(gui::EGDC_HIGH_LIGHT, video::SColor(255, 70, 120, 50));
146         skin->setColor(gui::EGDC_HIGH_LIGHT_TEXT, video::SColor(255, 255, 255, 255));
147 #ifdef HAVE_TOUCHSCREENGUI
148         float density = RenderingEngine::getDisplayDensity();
149         skin->setSize(gui::EGDS_CHECK_BOX_WIDTH, (s32)(17.0f * density));
150         skin->setSize(gui::EGDS_SCROLLBAR_SIZE, (s32)(14.0f * density));
151         skin->setSize(gui::EGDS_WINDOW_BUTTON_WIDTH, (s32)(15.0f * density));
152         if (density > 1.5f) {
153                 std::string sprite_path = porting::path_user + "/textures/base/pack/";
154                 if (density > 3.5f)
155                         sprite_path.append("checkbox_64.png");
156                 else if (density > 2.0f)
157                         sprite_path.append("checkbox_32.png");
158                 else
159                         sprite_path.append("checkbox_16.png");
160                 // Texture dimensions should be a power of 2
161                 gui::IGUISpriteBank *sprites = skin->getSpriteBank();
162                 video::IVideoDriver *driver = m_rendering_engine->get_video_driver();
163                 video::ITexture *sprite_texture = driver->getTexture(sprite_path.c_str());
164                 if (sprite_texture) {
165                         s32 sprite_id = sprites->addTextureAsSprite(sprite_texture);
166                         if (sprite_id != -1)
167                                 skin->setIcon(gui::EGDI_CHECK_BOX_CHECKED, sprite_id);
168                 }
169         }
170 #endif
171         g_fontengine = new FontEngine(guienv);
172         FATAL_ERROR_IF(g_fontengine == NULL, "Font engine creation failed.");
173
174         // Irrlicht 1.8 input colours
175         skin->setColor(gui::EGDC_EDITABLE, video::SColor(255, 128, 128, 128));
176         skin->setColor(gui::EGDC_FOCUSED_EDITABLE, video::SColor(255, 96, 134, 49));
177
178         // Create the menu clouds
179         if (!g_menucloudsmgr)
180                 g_menucloudsmgr = m_rendering_engine->get_scene_manager()->createNewSceneManager();
181         if (!g_menuclouds)
182                 g_menuclouds = new Clouds(g_menucloudsmgr, -1, rand());
183         g_menuclouds->setHeight(100.0f);
184         g_menuclouds->update(v3f(0, 0, 0), video::SColor(255, 240, 240, 255));
185         scene::ICameraSceneNode* camera;
186         camera = g_menucloudsmgr->addCameraSceneNode(NULL, v3f(0, 0, 0), v3f(0, 60, 100));
187         camera->setFarValue(10000);
188
189         /*
190                 GUI stuff
191         */
192
193         ChatBackend chat_backend;
194
195         // If an error occurs, this is set to something by menu().
196         // It is then displayed before the menu shows on the next call to menu()
197         std::string error_message;
198         bool reconnect_requested = false;
199
200         bool first_loop = true;
201
202         /*
203                 Menu-game loop
204         */
205         bool retval = true;
206         bool *kill = porting::signal_handler_killstatus();
207
208         while (m_rendering_engine->run() && !*kill &&
209                 !g_gamecallback->shutdown_requested) {
210                 // Set the window caption
211                 const wchar_t *text = wgettext("Main Menu");
212                 m_rendering_engine->get_raw_device()->
213                         setWindowCaption((utf8_to_wide(PROJECT_NAME_C) +
214                         L" " + utf8_to_wide(g_version_hash) +
215                         L" [" + text + L"]").c_str());
216                 delete[] text;
217
218                 try {   // This is used for catching disconnects
219
220                         m_rendering_engine->get_gui_env()->clear();
221
222                         /*
223                                 We need some kind of a root node to be able to add
224                                 custom gui elements directly on the screen.
225                                 Otherwise they won't be automatically drawn.
226                         */
227                         guiroot = m_rendering_engine->get_gui_env()->addStaticText(L"",
228                                 core::rect<s32>(0, 0, 10000, 10000));
229
230                         bool game_has_run = launch_game(error_message, reconnect_requested,
231                                 start_data, cmd_args);
232
233                         // Reset the reconnect_requested flag
234                         reconnect_requested = false;
235
236                         // If skip_main_menu, we only want to startup once
237                         if (skip_main_menu && !first_loop)
238                                 break;
239
240                         first_loop = false;
241
242                         if (!game_has_run) {
243                                 if (skip_main_menu)
244                                         break;
245
246                                 continue;
247                         }
248
249                         // Break out of menu-game loop to shut down cleanly
250                         if (!m_rendering_engine->run() || *kill) {
251                                 if (!g_settings_path.empty())
252                                         g_settings->updateConfigFile(g_settings_path.c_str());
253                                 break;
254                         }
255
256                         m_rendering_engine->get_video_driver()->setTextureCreationFlag(
257                                         video::ETCF_CREATE_MIP_MAPS, g_settings->getBool("mip_map"));
258
259 #ifdef HAVE_TOUCHSCREENGUI
260                         receiver->m_touchscreengui = new TouchScreenGUI(m_rendering_engine->get_raw_device(), receiver);
261                         g_touchscreengui = receiver->m_touchscreengui;
262 #endif
263
264                         the_game(
265                                 kill,
266                                 input,
267                                 m_rendering_engine,
268                                 start_data,
269                                 error_message,
270                                 chat_backend,
271                                 &reconnect_requested
272                         );
273                 } //try
274                 catch (con::PeerNotFoundException &e) {
275                         error_message = gettext("Connection error (timed out?)");
276                         errorstream << error_message << std::endl;
277                 }
278
279 #ifdef NDEBUG
280                 catch (std::exception &e) {
281                         std::string error_message = "Some exception: \"";
282                         error_message += e.what();
283                         error_message += "\"";
284                         errorstream << error_message << std::endl;
285                 }
286 #endif
287
288                 m_rendering_engine->get_scene_manager()->clear();
289
290 #ifdef HAVE_TOUCHSCREENGUI
291                 delete g_touchscreengui;
292                 g_touchscreengui = NULL;
293                 receiver->m_touchscreengui = NULL;
294 #endif
295
296                 // If no main menu, show error and exit
297                 if (skip_main_menu) {
298                         if (!error_message.empty()) {
299                                 verbosestream << "error_message = "
300                                               << error_message << std::endl;
301                                 retval = false;
302                         }
303                         break;
304                 }
305         } // Menu-game loop
306
307         g_menuclouds->drop();
308         g_menucloudsmgr->drop();
309
310         return retval;
311 }
312
313 void ClientLauncher::init_args(GameStartData &start_data, const Settings &cmd_args)
314 {
315         skip_main_menu = cmd_args.getFlag("go");
316
317         start_data.address = g_settings->get("address");
318         if (cmd_args.exists("address")) {
319                 // Join a remote server
320                 start_data.address = cmd_args.get("address");
321                 start_data.world_path.clear();
322                 start_data.name = g_settings->get("name");
323         }
324         if (!start_data.world_path.empty()) {
325                 // Start a singleplayer instance
326                 start_data.address = "";
327         }
328
329         if (cmd_args.exists("name"))
330                 start_data.name = cmd_args.get("name");
331
332         random_input = g_settings->getBool("random_input")
333                         || cmd_args.getFlag("random-input");
334 }
335
336 bool ClientLauncher::init_engine()
337 {
338         receiver = new MyEventReceiver();
339         m_rendering_engine = new RenderingEngine(receiver);
340         return m_rendering_engine->get_raw_device() != nullptr;
341 }
342
343 void ClientLauncher::init_input()
344 {
345         if (random_input)
346                 input = new RandomInputHandler();
347         else
348                 input = new RealInputHandler(receiver);
349
350         if (g_settings->getBool("enable_joysticks")) {
351                 irr::core::array<irr::SJoystickInfo> infos;
352                 std::vector<irr::SJoystickInfo> joystick_infos;
353
354                 // Make sure this is called maximum once per
355                 // irrlicht device, otherwise it will give you
356                 // multiple events for the same joystick.
357                 if (m_rendering_engine->get_raw_device()->activateJoysticks(infos)) {
358                         infostream << "Joystick support enabled" << std::endl;
359                         joystick_infos.reserve(infos.size());
360                         for (u32 i = 0; i < infos.size(); i++) {
361                                 joystick_infos.push_back(infos[i]);
362                         }
363                         input->joystick.onJoystickConnect(joystick_infos);
364                 } else {
365                         errorstream << "Could not activate joystick support." << std::endl;
366                 }
367         }
368 }
369
370 bool ClientLauncher::launch_game(std::string &error_message,
371                 bool reconnect_requested, GameStartData &start_data,
372                 const Settings &cmd_args)
373 {
374         // Prepare and check the start data to launch a game
375         std::string error_message_lua = error_message;
376         error_message.clear();
377
378         if (cmd_args.exists("password"))
379                 start_data.password = cmd_args.get("password");
380
381         if (cmd_args.exists("password-file")) {
382                 std::ifstream passfile(cmd_args.get("password-file"));
383                 if (passfile.good()) {
384                         getline(passfile, start_data.password);
385                 } else {
386                         error_message = gettext("Provided password file "
387                                         "failed to open: ")
388                                         + cmd_args.get("password-file");
389                         errorstream << error_message << std::endl;
390                         return false;
391                 }
392         }
393
394         // If a world was commanded, append and select it
395         // This is provieded by "get_world_from_cmdline()", main.cpp
396         if (!start_data.world_path.empty()) {
397                 auto &spec = start_data.world_spec;
398
399                 spec.path = start_data.world_path;
400                 spec.gameid = getWorldGameId(spec.path, true);
401                 spec.name = _("[--world parameter]");
402
403                 if (spec.gameid.empty()) {      // Create new
404                         spec.gameid = g_settings->get("default_game");
405                         spec.name += " [new]";
406                 }
407         }
408
409         /* Show the GUI menu
410          */
411         std::string server_name, server_description;
412         if (!skip_main_menu) {
413                 // Initialize menu data
414                 // TODO: Re-use existing structs (GameStartData)
415                 MainMenuData menudata;
416                 menudata.address                         = start_data.address;
417                 menudata.name                            = start_data.name;
418                 menudata.password                        = start_data.password;
419                 menudata.port                            = itos(start_data.socket_port);
420                 menudata.script_data.errormessage        = std::move(error_message_lua);
421                 menudata.script_data.reconnect_requested = reconnect_requested;
422
423                 main_menu(&menudata);
424
425                 // Skip further loading if there was an exit signal.
426                 if (*porting::signal_handler_killstatus())
427                         return false;
428
429                 if (!menudata.script_data.errormessage.empty()) {
430                         /* The calling function will pass this back into this function upon the
431                          * next iteration (if any) causing it to be displayed by the GUI
432                          */
433                         error_message = menudata.script_data.errormessage;
434                         return false;
435                 }
436
437                 int newport = stoi(menudata.port);
438                 if (newport != 0)
439                         start_data.socket_port = newport;
440
441                 // Update world information using main menu data
442                 std::vector<WorldSpec> worldspecs = getAvailableWorlds();
443
444                 int world_index = menudata.selected_world;
445                 if (world_index >= 0 && world_index < (int)worldspecs.size()) {
446                         g_settings->set("selected_world_path",
447                                         worldspecs[world_index].path);
448                         start_data.world_spec = worldspecs[world_index];
449                 }
450
451                 start_data.name = menudata.name;
452                 start_data.password = menudata.password;
453                 start_data.address = std::move(menudata.address);
454                 start_data.allow_login_or_register = menudata.allow_login_or_register;
455                 server_name = menudata.servername;
456                 server_description = menudata.serverdescription;
457
458                 start_data.local_server = !menudata.simple_singleplayer_mode &&
459                         start_data.address.empty();
460         } else {
461                 start_data.local_server = !start_data.world_path.empty() &&
462                         start_data.address.empty() && !start_data.name.empty();
463         }
464
465         if (!m_rendering_engine->run())
466                 return false;
467
468         if (!start_data.isSinglePlayer() && start_data.name.empty()) {
469                 error_message = gettext("Please choose a name!");
470                 errorstream << error_message << std::endl;
471                 return false;
472         }
473
474         // If using simple singleplayer mode, override
475         if (start_data.isSinglePlayer()) {
476                 start_data.name = "singleplayer";
477                 start_data.password = "";
478                 start_data.socket_port = myrand_range(49152, 65535);
479         } else {
480                 g_settings->set("name", start_data.name);
481         }
482
483         if (start_data.name.length() > PLAYERNAME_SIZE - 1) {
484                 error_message = gettext("Player name too long.");
485                 start_data.name.resize(PLAYERNAME_SIZE);
486                 g_settings->set("name", start_data.name);
487                 return false;
488         }
489
490         auto &worldspec = start_data.world_spec;
491         infostream << "Selected world: " << worldspec.name
492                    << " [" << worldspec.path << "]" << std::endl;
493
494         if (start_data.address.empty()) {
495                 // For singleplayer and local server
496                 if (worldspec.path.empty()) {
497                         error_message = gettext("No world selected and no address "
498                                         "provided. Nothing to do.");
499                         errorstream << error_message << std::endl;
500                         return false;
501                 }
502
503                 if (!fs::PathExists(worldspec.path)) {
504                         error_message = gettext("Provided world path doesn't exist: ")
505                                         + worldspec.path;
506                         errorstream << error_message << std::endl;
507                         return false;
508                 }
509
510                 // Load gamespec for required game
511                 start_data.game_spec = findWorldSubgame(worldspec.path);
512                 if (!start_data.game_spec.isValid()) {
513                         error_message = gettext("Could not find or load game: ")
514                                         + worldspec.gameid;
515                         errorstream << error_message << std::endl;
516                         return false;
517                 }
518
519                 if (porting::signal_handler_killstatus())
520                         return true;
521
522                 if (!start_data.game_spec.isValid()) {
523                         error_message = gettext("Invalid gamespec.");
524                         error_message += " (world.gameid=" + worldspec.gameid + ")";
525                         errorstream << error_message << std::endl;
526                         return false;
527                 }
528         }
529
530         start_data.world_path = start_data.world_spec.path;
531         return true;
532 }
533
534 void ClientLauncher::main_menu(MainMenuData *menudata)
535 {
536         bool *kill = porting::signal_handler_killstatus();
537         video::IVideoDriver *driver = m_rendering_engine->get_video_driver();
538
539         infostream << "Waiting for other menus" << std::endl;
540         while (m_rendering_engine->run() && !*kill) {
541                 if (!isMenuActive())
542                         break;
543                 driver->beginScene(true, true, video::SColor(255, 128, 128, 128));
544                 m_rendering_engine->get_gui_env()->drawAll();
545                 driver->endScene();
546                 // On some computers framerate doesn't seem to be automatically limited
547                 sleep_ms(25);
548         }
549         infostream << "Waited for other menus" << std::endl;
550
551         // Cursor can be non-visible when coming from the game
552 #ifndef ANDROID
553         m_rendering_engine->get_raw_device()->getCursorControl()->setVisible(true);
554 #endif
555         // Set absolute mouse mode
556 #if IRRLICHT_VERSION_MT_REVISION >= 9
557         m_rendering_engine->get_raw_device()->getCursorControl()->setRelativeMode(false);
558 #endif
559
560         /* show main menu */
561         GUIEngine mymenu(&input->joystick, guiroot, m_rendering_engine, &g_menumgr, menudata, *kill);
562
563         /* leave scene manager in a clean state */
564         m_rendering_engine->get_scene_manager()->clear();
565 }
566
567 void ClientLauncher::speed_tests()
568 {
569         // volatile to avoid some potential compiler optimisations
570         volatile static s16 temp16;
571         volatile static f32 tempf;
572         // Silence compiler warning
573         (void)temp16;
574         static v3f tempv3f1;
575         static v3f tempv3f2;
576         static std::string tempstring;
577         static std::string tempstring2;
578
579         tempv3f1 = v3f();
580         tempv3f2 = v3f();
581         tempstring.clear();
582         tempstring2.clear();
583
584         {
585                 infostream << "The following test should take around 20ms." << std::endl;
586                 TimeTaker timer("Testing std::string speed");
587                 const u32 jj = 10000;
588                 for (u32 j = 0; j < jj; j++) {
589                         tempstring.clear();
590                         tempstring2.clear();
591                         const u32 ii = 10;
592                         for (u32 i = 0; i < ii; i++) {
593                                 tempstring2 += "asd";
594                         }
595                         for (u32 i = 0; i < ii+1; i++) {
596                                 tempstring += "asd";
597                                 if (tempstring == tempstring2)
598                                         break;
599                         }
600                 }
601         }
602
603         infostream << "All of the following tests should take around 100ms each."
604                    << std::endl;
605
606         {
607                 TimeTaker timer("Testing floating-point conversion speed");
608                 tempf = 0.001;
609                 for (u32 i = 0; i < 4000000; i++) {
610                         temp16 += tempf;
611                         tempf += 0.001;
612                 }
613         }
614
615         {
616                 TimeTaker timer("Testing floating-point vector speed");
617
618                 tempv3f1 = v3f(1, 2, 3);
619                 tempv3f2 = v3f(4, 5, 6);
620                 for (u32 i = 0; i < 10000000; i++) {
621                         tempf += tempv3f1.dotProduct(tempv3f2);
622                         tempv3f2 += v3f(7, 8, 9);
623                 }
624         }
625
626         {
627                 TimeTaker timer("Testing std::map speed");
628
629                 std::map<v2s16, f32> map1;
630                 tempf = -324;
631                 const s16 ii = 300;
632                 for (s16 y = 0; y < ii; y++) {
633                         for (s16 x = 0; x < ii; x++) {
634                                 map1[v2s16(x, y)] =  tempf;
635                                 tempf += 1;
636                         }
637                 }
638                 for (s16 y = ii - 1; y >= 0; y--) {
639                         for (s16 x = 0; x < ii; x++) {
640                                 tempf = map1[v2s16(x, y)];
641                         }
642                 }
643         }
644
645         {
646                 infostream << "Around 5000/ms should do well here." << std::endl;
647                 TimeTaker timer("Testing mutex speed");
648
649                 std::mutex m;
650                 u32 n = 0;
651                 u32 i = 0;
652                 do {
653                         n += 10000;
654                         for (; i < n; i++) {
655                                 m.lock();
656                                 m.unlock();
657                         }
658                 }
659                 // Do at least 10ms
660                 while(timer.getTimerTime() < 10);
661
662                 u32 dtime = timer.stop();
663                 u32 per_ms = n / dtime;
664                 infostream << "Done. " << dtime << "ms, " << per_ms << "/ms" << std::endl;
665         }
666 }