The Battle for Wesnoth  1.17.14+dev
game_launcher.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2022
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "game_launcher.hpp"
17 #include "game_errors.hpp"
18 
19 #include "ai/manager.hpp" // for manager
20 #include "commandline_options.hpp" // for commandline_options
21 #include "config.hpp" // for config, etc
22 #include "cursor.hpp" // for set, CURSOR_TYPE::NORMAL
23 #include "exceptions.hpp" // for error
24 #include "filesystem.hpp" // for get_user_config_dir, etc
25 #include "game_classification.hpp" // for game_classification, etc
26 #include "game_config.hpp" // for path, no_delay, revision, etc
27 #include "game_config_manager.hpp" // for game_config_manager
29 #include "game_initialization/multiplayer.hpp" // for start_client, etc
30 #include "game_initialization/playcampaign.hpp" // for play_game, etc
31 #include "game_initialization/singleplayer.hpp" // for sp_create_mode
32 #include "generators/map_generator.hpp" // for mapgen_exception
33 #include "gettext.hpp" // for _
34 #include "gui/dialogs/language_selection.hpp" // for language_selection
36 #include "gui/dialogs/message.hpp" // for show error message
38 #include "gui/dialogs/multiplayer/mp_host_game_prompt.hpp" // for host game prompt
40 #include "gui/dialogs/title_screen.hpp" // for show_debug_clock_button
41 #include "gui/dialogs/transient_message.hpp" // for show_transient_message
42 #include "gui/widgets/retval.hpp" // for window, etc
43 #include "gui/widgets/settings.hpp" // for new_widgets
44 #include "language.hpp" // for language_def, etc
45 #include "log.hpp" // for LOG_STREAM, logger, general, etc
46 #include "map/exception.hpp"
48 #include "preferences/display.hpp"
49 #include "preferences/general.hpp" // for disable_preferences_save, etc
50 #include "save_index.hpp"
52 #include "sdl/surface.hpp" // for surface
53 #include "serialization/compression.hpp" // for format::NONE
54 #include "serialization/string_utils.hpp" // for split
55 #include "statistics.hpp"
56 #include "tstring.hpp" // for operator==, operator!=
57 #include "video.hpp"
59 #include "wml_exception.hpp" // for wml_exception
60 
61 #include <algorithm> // for copy, max, min, stable_sort
62 #include <cstdlib> // for system
63 #include <new>
64 #include <utility> // for pair
65 
66 #include <SDL2/SDL.h> // for SDL_INIT_JOYSTICK, etc
67 
68 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
69 #include "gui/widgets/debug.hpp"
70 #endif
71 
72 // For wesnothd launch code.
73 #ifdef _WIN32
74 
75 #define WIN32_LEAN_AND_MEAN
76 #include <windows.h>
77 
78 #endif // _WIN32
79 
81 
82 static lg::log_domain log_config("config");
83 #define ERR_CONFIG LOG_STREAM(err, log_config)
84 #define WRN_CONFIG LOG_STREAM(warn, log_config)
85 #define LOG_CONFIG LOG_STREAM(info, log_config)
86 
87 #define ERR_GENERAL LOG_STREAM(err, lg::general())
88 #define LOG_GENERAL LOG_STREAM(info, lg::general())
89 #define WRN_GENERAL LOG_STREAM(warn, lg::general())
90 #define DBG_GENERAL LOG_STREAM(debug, lg::general())
91 
92 #define LOG_TEST FORCE_LOG_TO(lg::general(), log_config)
93 
94 static lg::log_domain log_mp_create("mp/create");
95 #define DBG_MP LOG_STREAM(debug, log_mp_create)
96 
97 static lg::log_domain log_network("network");
98 #define ERR_NET LOG_STREAM(err, log_network)
99 
100 static lg::log_domain log_enginerefac("enginerefac");
101 #define LOG_RG LOG_STREAM(info, log_enginerefac)
102 
104  : cmdline_opts_(cmdline_opts)
105  , font_manager_()
106  , prefs_manager_()
107  , image_manager_()
108  , main_event_context_()
109  , hotkey_manager_()
110  , music_thinker_()
111  , music_muter_()
112  , test_scenarios_{"test"}
113  , screenshot_map_()
115  , state_()
116  , play_replay_(false)
118  , jump_to_multiplayer_(false)
120  , jump_to_editor_(false)
121  , load_data_()
122 {
123  bool no_music = false;
124  bool no_sound = false;
125 
126  // The path can be hardcoded and it might be a relative path.
127  if(!game_config::path.empty() &&
128 #ifdef _WIN32
129  // use c_str to ensure that index 1 points to valid element since c_str() returns null-terminated string
130  game_config::path.c_str()[1] != ':'
131 #else
132  game_config::path[0] != '/'
133 #endif
134  )
135  {
137  // font_manager_.update_font_path()
138  // To update the font path, destroy and recreate the manager
140  new (&font_manager_) font::manager();
141  }
142 
143  if(cmdline_opts_.core_id) {
145  }
146  if(cmdline_opts_.campaign) {
147  jump_to_campaign_.jump = true;
149  PLAIN_LOG << "selected campaign id: [" << jump_to_campaign_.campaign_id << "]";
150 
153  PLAIN_LOG << "selected difficulty: [" << jump_to_campaign_.difficulty << "]";
154  } else {
155  jump_to_campaign_.difficulty = -1; // let the user choose the difficulty
156  }
157 
160  PLAIN_LOG << "selected scenario id: [" << jump_to_campaign_.scenario_id << "]";
161  }
162 
165  }
166  }
167  if(cmdline_opts_.clock)
169  if(cmdline_opts_.debug) {
171  game_config::mp_debug = true;
172  }
173 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
174  if(cmdline_opts_.debug_dot_domain)
175  gui2::debug_layout_graph::set_domain(*cmdline_opts_.debug_dot_domain);
176  if(cmdline_opts_.debug_dot_level)
178 #endif
179  if(cmdline_opts_.editor) {
180  jump_to_editor_ = true;
181  if(!cmdline_opts_.editor->empty()) {
184  }
185  }
186  if(cmdline_opts_.fps)
189  start_in_fullscreen_ = true;
190  if(cmdline_opts_.load)
193  if(cmdline_opts_.max_fps) {
194  int fps = std::clamp(*cmdline_opts_.max_fps, 1, 1000);
195  fps = 1000 / fps;
196  // increase the delay to avoid going above the maximum
197  if(1000 % fps != 0) {
198  ++fps;
199  }
201  }
203  no_sound = true;
205  }
207  gui2::new_widgets = true;
209  game_config::no_delay = true;
211  no_music = true;
213  no_sound = true;
215  const int xres = std::get<0>(*cmdline_opts_.resolution);
216  const int yres = std::get<1>(*cmdline_opts_.resolution);
217  if(xres > 0 && yres > 0) {
218  preferences::_set_resolution(point(xres, yres));
220  }
221  }
223  // TODO it could be simplified to use cmdline_opts_ directly if there is no other way to enter screenshot mode
226  no_sound = true;
228  }
229  if (cmdline_opts_.server){
230  jump_to_multiplayer_ = true;
231  // Do we have any server specified ?
232  if(!cmdline_opts_.server->empty()) {
234  } else {
235  // Pick the first server in config
236  if(game_config::server_list.size() > 0) {
238  } else {
239  multiplayer_server_ = "";
240  }
241  }
242  if(cmdline_opts_.username) {
245  if(cmdline_opts_.password) {
247  preferences::set_password(*cmdline_opts.server, *cmdline_opts.username, *cmdline_opts_.password);
248  }
249  }
250  }
251  if(cmdline_opts_.test) {
252  if(!cmdline_opts_.test->empty()) {
254  }
255  }
256  if(!cmdline_opts_.unit_test.empty()) {
258  }
260  start_in_fullscreen_ = false;
262  load_data_->show_replay = true;
265 
266  if(!cmdline_opts.nobanner) {
267  PLAIN_LOG
268  << "\nData directory: " << game_config::path
269  << "\nUser configuration directory: " << filesystem::get_user_config_dir()
270  << "\nUser data directory: " << filesystem::get_user_data_dir()
271  << "\nCache directory: " << filesystem::get_cache_dir()
272  << "\n\n";
273  }
274 
275  // disable sound in nosound mode, or when sound engine failed to initialize
276  if(no_sound || ((preferences::sound_on() || preferences::music_on() ||
278  !sound::init_sound())) {
279  preferences::set_sound(false);
280  preferences::set_music(false);
283  } else if(no_music) { // else disable the music in nomusic mode
284  preferences::set_music(false);
285  }
286 }
287 
289 {
290  if(!::load_language_list()) {
291  return false;
292  }
293 
294  language_def locale;
295  if(cmdline_opts_.language) {
296  std::vector<language_def> langs = get_languages(true);
297  for(const language_def& def : langs) {
298  if(def.localename == *cmdline_opts_.language) {
299  locale = def;
300  break;
301  }
302  }
303  if(locale.localename.empty()) {
304  PLAIN_LOG << "Language symbol '" << *cmdline_opts_.language << "' not found.";
305  return false;
306  }
307  } else {
308  locale = get_locale();
309  }
310  ::set_language(locale);
311 
312  return true;
313 }
314 
316 {
317  // Handle special commandline launch flags
321  {
327  {
328  PLAIN_LOG << "--nogui flag is only valid with --multiplayer or --screenshot or --plugin flags";
329  return false;
330  }
332  game_config::no_delay = true;
333  return true;
334  }
335 
336  // Initialize video subsystem, and create a new window.
337  video::init();
338 
339  // Set window title and icon
341 
342 #if !(defined(__APPLE__))
343  surface icon(image::get_surface("icons/icon-game.png", image::UNSCALED));
344  if(icon != nullptr) {
346  }
347 #endif
348  return true;
349 }
350 
352 {
353  bool error = false;
354 
355  if(!cmdline_opts_.nobanner) {
356  STREAMING_LOG << "Checking lua scripts... ";
357  }
358 
360  // load the "package" package, so that scripts can get what packages they want
362  }
363 
364  // get the application lua kernel, load and execute script file, if script file is present
367 
368  if(!sf->fail()) {
369  /* Cancel all "jumps" to editor / campaign / multiplayer */
370  jump_to_multiplayer_ = false;
371  jump_to_editor_ = false;
372  jump_to_campaign_.jump = false;
373 
374  std::string full_script((std::istreambuf_iterator<char>(*sf)), std::istreambuf_iterator<char>());
375 
376  PLAIN_LOG << "\nRunning lua script: " << *cmdline_opts_.script_file;
377 
379  } else {
380  PLAIN_LOG << "Encountered failure when opening script '" << *cmdline_opts_.script_file << '\'';
381  error = true;
382  }
383  }
384 
386  std::string filename = *cmdline_opts_.plugin_file;
387 
388  PLAIN_LOG << "Loading a plugin file'" << filename << "'...";
389 
391 
392  try {
393  if(sf->fail()) {
394  throw std::runtime_error("failed to open plugin file");
395  }
396 
397  /* Cancel all "jumps" to editor / campaign / multiplayer */
398  jump_to_multiplayer_ = false;
399  jump_to_editor_ = false;
400  jump_to_campaign_.jump = false;
401 
402  std::string full_plugin((std::istreambuf_iterator<char>(*sf)), std::istreambuf_iterator<char>());
403 
405 
406  std::size_t i = pm.add_plugin(filename, full_plugin);
407 
408  for(std::size_t j = 0; j < pm.size(); ++j) {
409  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
410  }
411 
412  PLAIN_LOG << "Starting a plugin...";
413  pm.start_plugin(i);
414 
415  for(std::size_t j = 0; j < pm.size(); ++j) {
416  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
417  }
418 
419  plugins_context pc("init");
420 
421  for(std::size_t repeat = 0; repeat < 5; ++repeat) {
422  PLAIN_LOG << "Playing a slice...";
423  pc.play_slice();
424 
425  for(std::size_t j = 0; j < pm.size(); ++j) {
426  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
427  }
428  }
429 
430  return true;
431  } catch(const std::exception& e) {
432  gui2::show_error_message(std::string("When loading a plugin, error:\n") + e.what());
433  error = true;
434  }
435  }
436 
437  if(!error && !cmdline_opts_.nobanner) {
438  PLAIN_LOG << "ok";
439  }
440 
441  return !error;
442 }
443 
444 void game_launcher::set_test(const std::string& id)
445 {
446  state_.clear();
447  state_.classification().type = campaign_type::type::test;
449  state_.classification().era_id = "era_default";
450 
451  state_.set_carryover_sides_start(config{"next_scenario", id});
452 }
453 
455 {
456  // This first_time variable was added in 70f3c80a3e2 so that using the GUI
457  // menu to load a game works. That seems to have edge-cases, for example if
458  // you try to load a game a second time then Wesnoth exits.
459  static bool first_time = true;
460 
461  if(!cmdline_opts_.test) {
462  return true;
463  }
464 
465  if(!first_time) {
466  return false;
467  }
468 
469  first_time = false;
470 
471  if(test_scenarios_.size() == 0) {
472  // shouldn't happen, as test_scenarios_ is initialised to {"test"}
473  PLAIN_LOG << "Error in the test handling code";
474  return false;
475  }
476 
477  if(test_scenarios_.size() > 1) {
478  PLAIN_LOG << "You can't run more than one unit test in interactive mode";
479  }
480 
481  set_test(test_scenarios_.at(0));
482 
484 
485  try {
486  campaign_controller ccontroller(state_);
487  ccontroller.play_game();
488  } catch(savegame::load_game_exception& e) {
489  load_data_ = std::move(e.data_);
490  return true;
491  }
492 
493  return false;
494 }
495 
496 /**
497  * Runs unit tests specified on the command line.
498  *
499  * If multiple unit tests were specified, then this will stop at the first test
500  * which returns a non-zero status.
501  */
502 // Same as play_test except that we return the results of play_game.
503 // \todo "same ... except" ... and many other changes, such as testing the replay
505 {
506  // There's no copy of play_test's first_time variable. That seems to be for handling
507  // the player loading a game via the GUI, which makes no sense in a non-interactive test.
508  if(cmdline_opts_.unit_test.empty()) {
510  }
511 
512  auto ret = unit_test_result::TEST_FAIL; // will only be returned if no test is run
513  for(const auto& scenario : test_scenarios_) {
514  set_test(scenario);
515  ret = single_unit_test();
516  const char* describe_result;
517  switch(ret) {
519  describe_result = "PASS TEST";
520  break;
522  describe_result = "FAIL TEST (INVALID REPLAY)";
523  break;
525  describe_result = "FAIL TEST (ERRORED REPLAY)";
526  break;
528  describe_result = "FAIL TEST (WML EXCEPTION)";
529  break;
531  describe_result = "FAIL TEST (DEFEAT)";
532  break;
534  describe_result = "PASS TEST (VICTORY)";
535  break;
537  describe_result = "BROKE STRICT (PASS)";
538  break;
540  describe_result = "BROKE STRICT (FAIL)";
541  break;
543  describe_result = "BROKE STRICT (DEFEAT)";
544  break;
546  describe_result = "BROKE STRICT (VICTORY)";
547  break;
548  default:
549  describe_result = "FAIL TEST (UNKNOWN)";
550  break;
551  }
552 
553  PLAIN_LOG << describe_result << " (" << int(ret) << "): " << scenario;
554  if(ret != unit_test_result::TEST_PASS) {
555  break;
556  }
557  }
558 
559  return ret;
560 }
561 
563 {
565 
566  level_result::type game_res = level_result::type::fail;
567  try {
568  campaign_controller ccontroller(state_, true);
569  game_res = ccontroller.play_game();
570  if(game_res == level_result::type::fail) {
571  if(lg::broke_strict()) {
573  } else {
575  }
576  }
577  } catch(const wml_exception& e) {
578  PLAIN_LOG << "Caught WML Exception:" << e.dev_message;
580  }
581 
583 
585  return pass_victory_or_defeat(game_res);
586  }
587 
589  save.save_game_automatic(false, "unit_test_replay");
590 
592  savegame::save_index_class::default_saves_dir(), save.filename(), "", true, true, false};
593 
594  if(!load_game()) {
595  PLAIN_LOG << "Failed to load the replay!";
596  return unit_test_result::TEST_FAIL_LOADING_REPLAY; // failed to load replay
597  }
598 
599  try {
600  const bool was_strict_broken = lg::broke_strict();
601  campaign_controller ccontroller(state_, true);
602  ccontroller.play_replay();
603  if(!was_strict_broken && lg::broke_strict()) {
604  PLAIN_LOG << "Observed failure on replay";
606  }
607  } catch(const wml_exception& e) {
608  PLAIN_LOG << "WML Exception while playing replay: " << e.dev_message;
610  }
611 
612  return pass_victory_or_defeat(game_res);
613 }
614 
616 {
617  if(res == level_result::type::defeat) {
618  if(lg::broke_strict()) {
620  } else {
622  }
623  } else if(res == level_result::type::victory) {
624  if(lg::broke_strict()) {
626  } else {
628  }
629  }
630 
631  if(lg::broke_strict()) {
633  } else {
635  }
636 }
637 
639 {
641  return true;
642  }
643 
645 
647 
649  return false;
650 }
651 
653 {
655  return true;
656  }
657 
658  state_.classification().type = campaign_type::type::multiplayer;
659  DBG_GENERAL << "Current campaign type: " << campaign_type::get_string(state_.classification().type);
660 
661  try {
663  } catch(const config::error& e) {
664  PLAIN_LOG << "Error loading game config: " << e.what();
665  return false;
666  }
667 
668  // A default output filename
669  std::string outfile = "wesnoth_image.png";
670 
671  // If a output path was given as an argument, use that instead
673  outfile = *cmdline_opts_.render_image_dst;
674  }
675 
677  exit(1);
678  }
679 
680  return false;
681 }
682 
684 {
685  return load_data_.has_value();
686 }
687 
689 {
690  assert(game_config_manager::get());
691 
692  DBG_GENERAL << "Current campaign type: " << campaign_type::get_string(state_.classification().type);
693 
695  if(load_data_) {
696  load.data() = std::move(*load_data_);
698  }
699 
700  try {
701  if(!load.load_game()) {
702  return false;
703  }
704 
705  load.set_gamestate();
706  try {
708  } catch(const config::error&) {
709  return false;
710  }
711 
712  } catch(const config::error& e) {
713  if(e.message.empty()) {
714  gui2::show_error_message(_("The file you have tried to load is corrupt"));
715  } else {
716  gui2::show_error_message(_("The file you have tried to load is corrupt: '") + e.message + '\'');
717  }
718 
719  return false;
720  } catch(const wml_exception& e) {
721  e.show();
722  return false;
723  } catch(const filesystem::io_exception& e) {
724  if(e.message.empty()) {
725  gui2::show_error_message(_("File I/O Error while reading the game"));
726  } else {
727  gui2::show_error_message(_("File I/O Error while reading the game: '") + e.message + '\'');
728  }
729 
730  return false;
731  } catch(const game::error& e) {
732  if(e.message.empty()) {
733  gui2::show_error_message(_("The file you have tried to load is corrupt"));
734  } else {
735  gui2::show_error_message(_("The file you have tried to load is corrupt: '") + e.message + '\'');
736  }
737 
738  return false;
739  }
740 
741  play_replay_ = load.data().show_replay;
742  LOG_CONFIG << "is middle game savefile: " << (state_.is_mid_game_save() ? "yes" : "no");
743  LOG_CONFIG << "show replay: " << (play_replay_ ? "yes" : "no");
744  // in case load.data().show_replay && !state_.is_mid_game_save()
745  // there won't be any turns to replay, but the
746  // user gets to watch the intro sequence again ...
747 
748  if(state_.is_mid_game_save() && load.data().show_replay) {
750  }
751 
754  }
755 
756  if(load.data().cancel_orders) {
758  }
759 
760  return true;
761 }
762 
764 {
765  state_.clear();
766  state_.classification().type = campaign_type::type::scenario;
767  play_replay_ = false;
768 
770 }
771 
773 {
775 }
776 
778 {
779  if(jump_to_campaign_.jump) {
780  if(new_campaign()) {
782  jump_to_campaign_.jump = false;
784  } else {
785  jump_to_campaign_.jump = false;
786  return false;
787  }
788  }
789 
790  return true;
791 }
792 
794 {
796  jump_to_multiplayer_ = false;
798  ;
799  } else {
800  return false;
801  }
802  }
803 
804  return true;
805 }
806 
808 {
809  if(jump_to_editor_) {
810  jump_to_editor_ = false;
811 
812  const std::string to_open = load_data_ ? filesystem::normalize_path(load_data_->filename) : "";
814 
816  return false;
817  }
818  }
819 
820  return true;
821 }
822 
824 {
825  const std::string wesnothd_program = preferences::get_mp_server_program_name().empty()
828 
829  std::string config = filesystem::get_user_config_dir() + "/lan_server.cfg";
830  if (!filesystem::file_exists(config)) {
831  // copy file if it isn't created yet
833  }
834 
835 #ifndef _WIN32
836  std::string command = "\"" + wesnothd_program +"\" -c \"" + config + "\" -d -t 2 -T 5";
837 #else
838  // start wesnoth as background job
839  std::string command = "cmd /C start \"wesnoth server\" /B \"" + wesnothd_program + "\" -c \"" + config + "\" -t 2 -T 5";
840  // Make sure wesnothd's console output is visible on the console window by
841  // disabling SDL's stdio redirection code for this and future child
842  // processes. No need to bother cleaning this up because it's only
843  // meaningful to SDL applications during pre-main initialization.
844  SetEnvironmentVariableA("SDL_STDIO_REDIRECT", "0");
845 #endif
846  LOG_GENERAL << "Starting wesnothd: "<< command;
847  if (std::system(command.c_str()) == 0) {
848  // Give server a moment to start up
849  SDL_Delay(50);
850  return;
851  }
853 
854  // Couldn't start server so throw error
855  WRN_GENERAL << "Failed to run server start script";
856  throw game::mp_server_error("Starting MP server failed!");
857 }
858 
860 {
861  try {
862  if(mode == mp_mode::HOST) {
863  try {
864  start_wesnothd();
865  } catch(const game::mp_server_error&) {
867 
868  try {
869  start_wesnothd();
870  } catch(const game::mp_server_error&) {
871  return false;
872  }
873  }
874  }
875 
876  // If a server address wasn't specified, prompt for it now.
877  if(mode != mp_mode::LOCAL && multiplayer_server_.empty()) {
878  if(!gui2::dialogs::mp_connect::execute()) {
879  return false;
880  }
881 
882  // The prompt saves its input to preferences.
884 
885  if(multiplayer_server_ != preferences::builtin_servers_list().front().address) {
887  }
888  }
889 
890  // create_engine already calls game_config_manager::get()->load_config but maybe its better to have MULTIPLAYER
891  // defined while we are in the lobby.
893 
894  events::discard_input(); // prevent the "keylogger" effect
896 
897  if(mode == mp_mode::LOCAL) {
899  } else {
901  multiplayer_server_.clear();
902  }
903 
904  } catch(const wesnothd_rejected_client_error& e) {
906  } catch(const game::mp_server_error& e) {
907  gui2::show_error_message(_("Error while starting server: ") + e.message);
908  } catch(const game::load_game_failed& e) {
909  gui2::show_error_message(_("The game could not be loaded: ") + e.message);
910  } catch(const game::game_error& e) {
911  gui2::show_error_message(_("Error while playing the game: ") + e.message);
912  } catch(const mapgen_exception& e) {
913  gui2::show_error_message(_("Map generator error: ") + e.message);
914  } catch(const wesnothd_error& e) {
915  if(!e.message.empty()) {
916  ERR_NET << "caught network error: " << e.message;
917 
918  std::string user_msg;
919  auto conn_err = dynamic_cast<const wesnothd_connection_error*>(&e);
920 
921  if(conn_err) {
922  // The wesnothd_connection_error subclass is only thrown with messages
923  // from boost::system::error_code which we can't translate ourselves.
924  // It's also the originator of the infamous EOF error that happens when
925  // the server dies. <https://github.com/wesnoth/wesnoth/issues/3005>. It
926  // will provide a translated string instead of that when it happens.
927  user_msg = !conn_err->user_message.empty()
928  ? conn_err->user_message
929  : _("Connection failed: ") + e.message;
930  } else {
931  // This will be a message from the server itself, which we can
932  // probably translate.
933  user_msg = translation::gettext(e.message.c_str());
934  }
935 
936  gui2::show_error_message(user_msg);
937  } else {
938  ERR_NET << "caught network error";
939  }
940  } catch(const config::error& e) {
941  if(!e.message.empty()) {
942  ERR_CONFIG << "caught config::error: " << e.message;
944  } else {
945  ERR_CONFIG << "caught config::error";
946  }
947  } catch(const incorrect_map_format_error& e) {
948  gui2::show_error_message(_("The game map could not be loaded: ") + e.message);
949  } catch(savegame::load_game_exception& e) {
950  load_data_ = std::move(e.data_);
951  // this will make it so next time through the title screen loop, this game is loaded
952  } catch(const wml_exception& e) {
953  e.show();
954  } catch(const game::error& e) {
955  PLAIN_LOG << "caught game::error...";
956  gui2::show_error_message(_("Error: ") + e.message);
957  }
958 
959  return true;
960 }
961 
963 {
965  return true;
966  }
967 
968  DBG_MP << "starting multiplayer game from the commandline";
969 
971 
972  events::discard_input(); // prevent the "keylogger" effect
974 
976 
977  return false;
978 }
979 
981 {
982  if(!gui2::dialogs::language_selection::execute()) {
983  return false;
984  }
985 
988  }
989 
994 
995  return true;
996 }
997 
999 {
1000  assert(!load_data_);
1001  if(play_replay_) {
1002  play_replay();
1003  return;
1004  }
1005 
1006  gui2::dialogs::loading_screen::display([this, reload]() {
1008 
1009  if(reload == reload_mode::RELOAD_DATA) {
1010  try {
1013  } catch(const config::error&) {
1014  return;
1015  }
1016  }
1017  });
1018 
1019  try {
1020  campaign_controller ccontroller(state_);
1021  ccontroller.play_game();
1022  ai::manager::singleton_ = nullptr;
1023  } catch(savegame::load_game_exception& e) {
1024  load_data_ = std::move(e.data_);
1025  // this will make it so next time through the title screen loop, this game is loaded
1026  } catch(const wml_exception& e) {
1027  e.show();
1028  } catch(const mapgen_exception& e) {
1029  gui2::show_error_message(_("Map generator error: ") + e.message);
1030  }
1031 }
1032 
1034 {
1035  assert(!load_data_);
1036  try {
1037  campaign_controller ccontroller(state_);
1038  ccontroller.play_replay();
1039  } catch(savegame::load_game_exception& e) {
1040  load_data_ = std::move(e.data_);
1041  // this will make it so next time through the title screen loop, this game is loaded
1042  } catch(const wml_exception& e) {
1043  e.show();
1044  }
1045 }
1046 
1048 {
1049  while(true) {
1051 
1053 
1054  editor::EXIT_STATUS res = editor::start(filename);
1055 
1056  if(res != editor::EXIT_RELOAD_DATA) {
1057  return res;
1058  }
1059 
1061  }
1062 
1063  return editor::EXIT_ERROR; // not supposed to happen
1064 }
1065 
1067 {
1068  load_data_.reset();
1069 }
1070 
1072 {
1073  try {
1075  video::deinit();
1076  } catch(std::exception& e) {
1077  ERR_GENERAL << "Suppressing exception thrown during ~game_launcher: " << e.what();
1078  } catch(...) {
1079  ERR_GENERAL << "Suppressing exception " << utils::get_unknown_exception_type() << " thrown during ~game_launcher";
1080  }
1081 }
void init(fake type)
Initialize the video subsystem.
Definition: video.cpp:86
bool has_load_data() const
An error occurred inside the underlying network communication code (boost asio) TODO: find a short na...
An error occurred during when trying to communicate with the wesnothd server.
bool new_widgets
Do we wish to use the new library or not.
Definition: settings.cpp:23
bool set_sound(bool ison)
Definition: general.cpp:733
void close_sound()
Definition: sound.cpp:495
game_launcher(const commandline_options &cmdline_opts)
std::string get_program_invocation(const std::string &program_name)
Returns the appropriate invocation for a Wesnoth-related binary, assuming that it is located in the s...
static lg::log_domain log_mp_create("mp/create")
std::optional< std::string > core_id
Non-empty if –core was given on the command line.
bool turn_bell()
Definition: general.cpp:675
#define PLAIN_LOG
Definition: log.hpp:260
void discard_input()
Discards all input events.
Definition: events.cpp:799
static void reset_translations()
Definition: tstring.cpp:652
Error used when the client is rejected by the MP server.
bool is_mid_game_save() const
Definition: saved_game.hpp:105
bool nogui
True if –nogui was given on the command line.
bool play_multiplayer(mp_mode mode)
std::string scenario_id
The ID of the scenario within the campaign to jump to.
The class for loading a savefile.
Definition: savegame.hpp:99
bool script_unsafe_mode
Whether to load the "package" package for the scripting environment.
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
Definition: cursor.cpp:176
void _set_maximized(bool ison)
Definition: general.cpp:450
std::string label
Name of the game (e.g.
void disable_preferences_save()
Definition: general.cpp:231
std::optional< std::string > test
Non-empty if –test was given on the command line.
void set_window_title(const std::string &title)
Sets the title of the main window.
Definition: video.cpp:621
std::vector< game_tip > load(const config &cfg)
Loads the tips from a config.
Definition: tips.cpp:36
Add a special kind of assert to validate whether the input from WML doesn&#39;t contain any problems that...
#define DBG_MP
std::optional< std::string > password
Non-empty if –password was given on the command line.
const std::string & filename() const
Definition: savegame.hpp:177
level_result::type play_game()
Error used when game loading fails.
Definition: game_errors.hpp:31
std::optional< unsigned int > translation_percent
Non-empty if –all-translations or –translations-over is given on the command line.
void show_wesnothd_server_search()
Definition: display.cpp:99
static std::shared_ptr< save_index_class > default_saves_dir()
Returns an instance for managing saves in filesystem::get_saves_dir()
Definition: save_index.cpp:210
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:262
surface get_surface(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image surface suitable for software manipulation.
Definition: picture.cpp:824
std::string network_host()
Definition: game.cpp:387
static void progress(loading_stage stage=loading_stage::none)
Report what is being loaded to the loading screen.
void unify_controllers()
Definition: saved_game.cpp:703
const language_def & get_locale()
Definition: language.cpp:329
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
void load_game_config_for_game(const game_classification &classification, const std::string &scenario_id)
void _set_resolution(const point &res)
Definition: general.cpp:444
void deinit()
Deinitialize the video subsystem.
Definition: video.cpp:112
void set_language(const std::string &language, const std::vector< std::string > *)
Definition: gettext.cpp:492
save_result save_image(const locator &i_locator, const std::string &filename)
Definition: picture.cpp:1087
std::string get_default_title_string()
void set_min_translation_percent(int percent)
Definition: language.cpp:144
void clear()
Definition: saved_game.cpp:785
bool fps
True if –fps was given on the command line.
std::vector< std::string > unit_test
Non-empty if –unit was given on the command line.
unit_test_result unit_test()
Runs unit tests specified on the command line.
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition: general.cpp:23
bool change_language()
void set_network_host(const std::string &host)
Definition: game.cpp:397
std::string normalize_path(const std::string &fpath, bool normalize_separators, bool resolve_dot_entries)
Returns the absolute path of a file.
bool noreplaycheck
True if –noreplaycheck was given on the command line.
std::optional< std::string > load
Non-empty if –load was given on the command line.
void init_textdomains(const game_config_view &cfg)
Initializes the list of textdomains from a configuration object.
Definition: language.cpp:367
std::optional< int > max_fps
Max FPS specified by –max-fps option.
static std::string _(const char *str)
Definition: gettext.hpp:93
std::string jump_to_campaign_id() const
Return the ID of the campaign to jump to (skipping the main menu).
Definitions for the interface to Wesnoth Markup Language (WML).
bool sound_on()
Definition: general.cpp:728
#define DBG_GENERAL
load_game_metadata data_
Definition: savegame.hpp:92
std::string user_message
User-friendly and potentially translated message for use in the UI.
jump_to_campaign_info jump_to_campaign_
void flush_cache()
Purges all image caches.
Definition: picture.cpp:234
bool init_sound()
Definition: sound.cpp:443
std::string get_scenario_id() const
Definition: saved_game.cpp:649
static lg::log_domain log_config("config")
bool broke_strict()
Definition: log.cpp:309
std::vector< std::string > test_scenarios_
std::string get_cwd()
Definition: filesystem.cpp:893
bool with_replay
True if –with-replay was given on the command line.
void set_mp_server_program_name(const std::string &path)
Definition: game.cpp:501
void show() const
Shows the error in a dialog.
bool play_render_image_mode()
bool headless_unit_test
True if –unit is used and –showgui is not present.
static game_config_manager * get()
unit_test_result
Status code after running a unit test, should match the run_wml_tests script and the documentation fo...
#define ERR_CONFIG
font::manager font_manager_
bool skip_story
Whether the story screen should be skipped.
std::optional< std::string > render_image_dst
Output file to put rendered image path in.
const std::vector< game_config::server_info > & builtin_servers_list()
Definition: game.cpp:356
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
static lg::log_domain log_enginerefac("enginerefac")
void start_plugin(std::size_t idx)
Definition: manager.cpp:102
std::optional< savegame::load_game_metadata > load_data_
level_result::type play_replay()
std::string get_name(std::size_t idx)
Definition: manager.cpp:95
This file contains the settings handling of the widget library.
#define STREAMING_LOG
Definition: log.hpp:261
EXIT_STATUS start(const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
Definition: editor_main.cpp:30
void start_local_game_commandline(const commandline_options &cmdline_opts)
Starts a multiplayer game in single-user mode using command line settings.
bool nodelay
True if –nodelay was given on the command line.
std::string get_user_data_dir()
Definition: filesystem.cpp:801
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup)
Shows a transient message to the user.
static lg::log_domain log_network("network")
std::string multiplayer_server_
std::string campaign_define
If there is a define the campaign uses to customize data.
editor::EXIT_STATUS start_editor()
void set_login(const std::string &login)
std::optional< int > campaign_difficulty
Non-empty if –campaign-difficulty was given on the command line.
std::string localename
Definition: language.hpp:55
language_list get_languages(bool all)
Return a list of available translations.
Definition: language.cpp:127
void load_package()
Loads the package library into lua environment.
bool windowed
True if –windowed was given on the command line.
void set_show_fps(bool value)
Definition: general.cpp:900
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:39
void set_test(const std::string &id)
void set_password(const std::string &server, const std::string &login, const std::string &key)
void set_core_id(const std::string &core_id)
Definition: general.cpp:338
std::string path
Definition: game_config.cpp:39
bool multiplayer
True if –multiplayer was given on the command line.
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands...
bool UI_sound_on()
Definition: general.cpp:699
const char * what() const noexcept
Definition: exceptions.hpp:36
bool clock
True if –clock was given on the command line.
Error used for any general game error, e.g.
Definition: game_errors.hpp:47
std::string read_file(const std::string &fname)
Basic disk I/O - read file.
bool nobanner
True if –nobanner was given on the command line.
std::string screenshot_map_
#define LOG_CONFIG
void set_window_icon(surface &icon)
Sets the icon of the main window.
Definition: video.cpp:627
void set_draw_delay(int value)
Definition: general.cpp:910
void flush_cache()
Definition: sound.cpp:195
void set_level(const std::string &value)
Definition: game.cpp:696
bool load_font_config()
Definition: font_config.cpp:89
std::optional< std::string > render_image
Image path to render.
Helper class, don&#39;t construct this directly.
bool nomusic
True if –nomusic was given on the command line.
std::string dev_message
The message for developers telling which problem was triggered, this shouldn&#39;t be translated...
std::optional< std::string > campaign
Non-empty if –campaign was given on the command line.
std::string get_cache_dir()
Definition: filesystem.cpp:811
Exception used to signal that the user has decided to abortt a game, and to load another game instead...
Definition: savegame.hpp:83
void run(char const *prog, const std::string &name, int nArgs=0)
Runs a plain script.
void write_file(const std::string &fname, const std::string &data, std::ios_base::openmode mode)
Throws io_exception if an error occurs.
#define WRN_GENERAL
static std::string gettext(const char *str)
Definition: gettext.hpp:60
bool debug
True if –debug was given on the command line.
std::size_t i
Definition: function.cpp:968
std::size_t add_plugin(const std::string &name, const std::string &prog)
Definition: manager.cpp:119
campaign_type::type type
std::string get_mp_server_program_name()
Definition: game.cpp:510
std::string get_wml_location(const std::string &filename, const std::string &current_dir)
Returns a complete path to the actual WML file or directory or an empty string if the file isn&#39;t pres...
void clean_saves(const std::string &label)
Delete all autosaves of a certain scenario from the default save directory.
Definition: savegame.cpp:69
void load_game_config_for_create(bool is_mp, bool is_test=false)
Game configuration data as global variables.
Definition: build_info.cpp:60
bool new_widgets
True if –new-widgets was given on the command line.
std::optional< std::string > screenshot_output_file
Output file to put screenshot in.
#define ERR_NET
An exception object used when an IO error occurs.
Definition: filesystem.hpp:48
bool music_on()
Definition: general.cpp:751
bool load_language_list()
Definition: language.cpp:104
#define ERR_GENERAL
bool set_UI_sound(bool ison)
Definition: general.cpp:704
bool goto_multiplayer()
Declarations for File-IO.
std::optional< std::string > script_file
File to load lua script from.
std::optional< std::string > username
Non-empty if –username was given on the command line.
unit_test_result pass_victory_or_defeat(level_result::type res)
Class for replay saves (either manually or automatically).
Definition: savegame.hpp:266
bool campaign_skip_story
True if –skip-story was given on the command line.
unit_test_result single_unit_test()
Internal to the implementation of unit_test().
static manager * singleton_
Definition: manager.hpp:442
std::optional< std::string > server
Non-empty if –server was given on the command line.
std::string get_user_config_dir()
Definition: filesystem.cpp:772
bool screenshot
True if –screenshot was given on the command line.
void cancel_orders()
Definition: saved_game.cpp:686
std::string get_detailed_status(std::size_t idx)
Definition: manager.cpp:84
static void display(std::function< void()> f)
const commandline_options & cmdline_opts_
bool select_campaign(saved_game &state, jump_to_campaign_info jump_to_campaign)
Base class for all the errors encountered by the engine.
Definition: exceptions.hpp:28
game_classification & classification()
Definition: saved_game.hpp:55
std::string screenshot_filename_
void set_carryover_sides_start(config carryover_sides_start)
Definition: saved_game.cpp:162
saved_game state_
std::optional< std::string > screenshot_map_file
Map file to make a screenshot of.
bool set_music(bool ison)
Definition: general.cpp:756
Standard logging facilities (interface).
void set_skip_story(bool skip_story)
Definition: saved_game.hpp:140
bool set_turn_bell(bool ison)
Definition: general.cpp:680
void clear_loaded_game()
std::string message
Definition: exceptions.hpp:30
bool fullscreen
True if –fullscreen was given on the command line.
bool jump
Whether the game should immediately start a campaign.
std::optional< std::string > editor
Non-empty if –editor was given on the command line.
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:204
void point(int x, int y)
Draw a single point.
Definition: draw.cpp:193
#define e
std::size_t size()
Definition: manager.cpp:69
void launch_game(reload_mode reload=reload_mode::RELOAD_DATA)
int difficulty
The difficulty at which to launch the campaign.
bool nosound
True if –nosound was given on the command line.
bool play_screenshot_mode()
lua_kernel_base * get_kernel_base()
Definition: manager.cpp:64
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:60
std::optional< std::string > campaign_scenario
Non-empty if –campaign-scenario was given on the command line.
void clear_current_scenario()
Delete the current scenario from the stats.
Definition: statistics.cpp:789
Unmodified original-size image.
Definition: picture.hpp:231
void start_client(const std::string &host)
Pubic entry points for the MP workflow.
std::optional< std::pair< int, int > > resolution
Pair of AxB values specified after –resolution.
bool init_lua_script()
std::vector< server_info > server_list
Definition: game_config.cpp:94
std::optional< std::string > plugin_file
File to load a lua plugin (similar to a script) from.
bool save_game_automatic(bool ask_for_overwrite=false, const std::string &filename="")
Saves a game without user interaction, unless the file exists and it should be asked to overwrite it...
Definition: savegame.cpp:367
bool play_multiplayer_commandline()
void set_debug(bool new_debug)
static plugins_manager * get()
Definition: manager.cpp:59
void play_slice()
Definition: context.cpp:97
#define LOG_GENERAL
bool show_debug_clock_button
Do we wish to show the button for the debug clock.
std::string campaign_id
The ID of the campaign to launch.
void start_local_game()
Starts a multiplayer game in single-user mode.
std::optional< std::string > language
Non-empty if –language was given on the command line.
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46