]> git.lizzy.rs Git - dragonfireclient.git/blob - src/client/clientlauncher.cpp
Remove reference to a removed file in devtest (followup to #12157)
[dragonfireclient.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        = 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                 server_name = menudata.servername;
455                 server_description = menudata.serverdescription;
456
457                 start_data.local_server = !menudata.simple_singleplayer_mode &&
458                         start_data.address.empty();
459         } else {
460                 start_data.local_server = !start_data.world_path.empty() &&
461                         start_data.address.empty() && !start_data.name.empty();
462         }
463
464         if (!m_rendering_engine->run())
465                 return false;
466
467         if (!start_data.isSinglePlayer() && start_data.name.empty()) {
468                 error_message = gettext("Please choose a name!");
469                 errorstream << error_message << std::endl;
470                 return false;
471         }
472
473         // If using simple singleplayer mode, override
474         if (start_data.isSinglePlayer()) {
475                 start_data.name = "singleplayer";
476                 start_data.password = "";
477                 start_data.socket_port = myrand_range(49152, 65535);
478         } else {
479                 g_settings->set("name", start_data.name);
480         }
481
482         if (start_data.name.length() > PLAYERNAME_SIZE - 1) {
483                 error_message = gettext("Player name too long.");
484                 start_data.name.resize(PLAYERNAME_SIZE);
485                 g_settings->set("name", start_data.name);
486                 return false;
487         }
488
489         auto &worldspec = start_data.world_spec;
490         infostream << "Selected world: " << worldspec.name
491                    << " [" << worldspec.path << "]" << std::endl;
492
493         if (start_data.address.empty()) {
494                 // For singleplayer and local server
495                 if (worldspec.path.empty()) {
496                         error_message = gettext("No world selected and no address "
497                                         "provided. Nothing to do.");
498                         errorstream << error_message << std::endl;
499                         return false;
500                 }
501
502                 if (!fs::PathExists(worldspec.path)) {
503                         error_message = gettext("Provided world path doesn't exist: ")
504                                         + worldspec.path;
505                         errorstream << error_message << std::endl;
506                         return false;
507                 }
508
509                 // Load gamespec for required game
510                 start_data.game_spec = findWorldSubgame(worldspec.path);
511                 if (!start_data.game_spec.isValid()) {
512                         error_message = gettext("Could not find or load game: ")
513                                         + worldspec.gameid;
514                         errorstream << error_message << std::endl;
515                         return false;
516                 }
517
518                 if (porting::signal_handler_killstatus())
519                         return true;
520
521                 if (!start_data.game_spec.isValid()) {
522                         error_message = gettext("Invalid gamespec.");
523                         error_message += " (world.gameid=" + worldspec.gameid + ")";
524                         errorstream << error_message << std::endl;
525                         return false;
526                 }
527         }
528
529         start_data.world_path = start_data.world_spec.path;
530         return true;
531 }
532
533 void ClientLauncher::main_menu(MainMenuData *menudata)
534 {
535         bool *kill = porting::signal_handler_killstatus();
536         video::IVideoDriver *driver = m_rendering_engine->get_video_driver();
537
538         infostream << "Waiting for other menus" << std::endl;
539         while (m_rendering_engine->run() && !*kill) {
540                 if (!isMenuActive())
541                         break;
542                 driver->beginScene(true, true, video::SColor(255, 128, 128, 128));
543                 m_rendering_engine->get_gui_env()->drawAll();
544                 driver->endScene();
545                 // On some computers framerate doesn't seem to be automatically limited
546                 sleep_ms(25);
547         }
548         infostream << "Waited for other menus" << std::endl;
549
550         // Cursor can be non-visible when coming from the game
551 #ifndef ANDROID
552         m_rendering_engine->get_raw_device()->getCursorControl()->setVisible(true);
553 #endif
554
555         /* show main menu */
556         GUIEngine mymenu(&input->joystick, guiroot, m_rendering_engine, &g_menumgr, menudata, *kill);
557
558         /* leave scene manager in a clean state */
559         m_rendering_engine->get_scene_manager()->clear();
560 }
561
562 void ClientLauncher::speed_tests()
563 {
564         // volatile to avoid some potential compiler optimisations
565         volatile static s16 temp16;
566         volatile static f32 tempf;
567         static v3f tempv3f1;
568         static v3f tempv3f2;
569         static std::string tempstring;
570         static std::string tempstring2;
571
572         tempv3f1 = v3f();
573         tempv3f2 = v3f();
574         tempstring.clear();
575         tempstring2.clear();
576
577         {
578                 infostream << "The following test should take around 20ms." << std::endl;
579                 TimeTaker timer("Testing std::string speed");
580                 const u32 jj = 10000;
581                 for (u32 j = 0; j < jj; j++) {
582                         tempstring = "";
583                         tempstring2 = "";
584                         const u32 ii = 10;
585                         for (u32 i = 0; i < ii; i++) {
586                                 tempstring2 += "asd";
587                         }
588                         for (u32 i = 0; i < ii+1; i++) {
589                                 tempstring += "asd";
590                                 if (tempstring == tempstring2)
591                                         break;
592                         }
593                 }
594         }
595
596         infostream << "All of the following tests should take around 100ms each."
597                    << std::endl;
598
599         {
600                 TimeTaker timer("Testing floating-point conversion speed");
601                 tempf = 0.001;
602                 for (u32 i = 0; i < 4000000; i++) {
603                         temp16 += tempf;
604                         tempf += 0.001;
605                 }
606         }
607
608         {
609                 TimeTaker timer("Testing floating-point vector speed");
610
611                 tempv3f1 = v3f(1, 2, 3);
612                 tempv3f2 = v3f(4, 5, 6);
613                 for (u32 i = 0; i < 10000000; i++) {
614                         tempf += tempv3f1.dotProduct(tempv3f2);
615                         tempv3f2 += v3f(7, 8, 9);
616                 }
617         }
618
619         {
620                 TimeTaker timer("Testing std::map speed");
621
622                 std::map<v2s16, f32> map1;
623                 tempf = -324;
624                 const s16 ii = 300;
625                 for (s16 y = 0; y < ii; y++) {
626                         for (s16 x = 0; x < ii; x++) {
627                                 map1[v2s16(x, y)] =  tempf;
628                                 tempf += 1;
629                         }
630                 }
631                 for (s16 y = ii - 1; y >= 0; y--) {
632                         for (s16 x = 0; x < ii; x++) {
633                                 tempf = map1[v2s16(x, y)];
634                         }
635                 }
636         }
637
638         {
639                 infostream << "Around 5000/ms should do well here." << std::endl;
640                 TimeTaker timer("Testing mutex speed");
641
642                 std::mutex m;
643                 u32 n = 0;
644                 u32 i = 0;
645                 do {
646                         n += 10000;
647                         for (; i < n; i++) {
648                                 m.lock();
649                                 m.unlock();
650                         }
651                 }
652                 // Do at least 10ms
653                 while(timer.getTimerTime() < 10);
654
655                 u32 dtime = timer.stop();
656                 u32 per_ms = n / dtime;
657                 infostream << "Done. " << dtime << "ms, " << per_ms << "/ms" << std::endl;
658         }
659 }