The Battle for Wesnoth  1.19.24+dev
game_launcher.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
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_data_dir, etc
25 #include "game_classification.hpp" // for game_classification, etc
26 #include "game_config.hpp" // for path, etc
27 #include "game_initialization/multiplayer.hpp" // for start_client, etc
28 #include "game_initialization/playcampaign.hpp" // for play_game, etc
29 #include "game_initialization/singleplayer.hpp" // for sp_create_mode
30 #include "generators/map_generator.hpp" // for mapgen_exception
31 #include "gettext.hpp" // for _
32 #include "gui/dialogs/language_selection.hpp" // for language_selection
34 #include "gui/dialogs/message.hpp" // for show error message
36 #include "gui/dialogs/title_screen.hpp" // for show_debug_clock_button
37 #include "gui/dialogs/transient_message.hpp" // for show_transient_message
38 #include "gui/widgets/settings.hpp" // for new_widgets
39 #include "language.hpp" // for language_def, etc
40 #include "log.hpp" // for LOG_STREAM, logger, general, etc
41 #include "map/exception.hpp"
43 #include "save_index.hpp"
45 #include "sdl/surface.hpp" // for surface
46 #include "serialization/compression.hpp" // for format::NONE
47 #include "tstring.hpp" // for operator==, operator!=
48 #include "video.hpp"
50 #include "wml_exception.hpp" // for wml_exception
51 
52 #ifdef __APPLE__
53 #include <TargetConditionals.h>
54 #endif
55 
56 #if BOOST_VERSION >= 108600
57 
58 // boost::asio (via boost::process) complains about winsock.h otherwise
59 #ifdef _WIN32
60 #define WIN32_LEAN_AND_MEAN
61 #endif
62 #include <boost/process/v2/process.hpp>
63 
64 #else
65 
66 // process::v1 only. The v1 folders do not exist until 1.86
67 #ifdef _WIN32
68 #include <boost/process/windows.hpp>
69 #endif
70 #include <boost/process/child.hpp>
71 
72 #endif
73 
74 #include <algorithm> // for copy, max, min, stable_sort
75 #include <new>
76 #include <thread>
77 #include <utility> // for pair
78 
79 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
80 #include "gui/widgets/debug.hpp"
81 #endif
82 
83 static lg::log_domain log_config("config");
84 #define ERR_CONFIG LOG_STREAM(err, log_config)
85 #define WRN_CONFIG LOG_STREAM(warn, log_config)
86 #define LOG_CONFIG LOG_STREAM(info, log_config)
87 
88 #define ERR_GENERAL LOG_STREAM(err, lg::general())
89 #define LOG_GENERAL LOG_STREAM(info, lg::general())
90 #define WRN_GENERAL LOG_STREAM(warn, lg::general())
91 #define DBG_GENERAL LOG_STREAM(debug, lg::general())
92 
93 #define LOG_TEST FORCE_LOG_TO(lg::general(), log_config)
94 
95 static lg::log_domain log_mp_create("mp/create");
96 #define DBG_MP LOG_STREAM(debug, log_mp_create)
97 
98 static lg::log_domain log_network("network");
99 #define ERR_NET LOG_STREAM(err, log_network)
100 
101 static lg::log_domain log_enginerefac("enginerefac");
102 #define LOG_RG LOG_STREAM(info, log_enginerefac)
103 
105  : cmdline_opts_(cmdline_opts)
106  , config_manager_(cmdline_opts)
107  , font_manager_()
108  , image_manager_()
110  , hotkey_manager_()
111  , music_thinker_()
112  , music_muter_()
113  , test_scenarios_{"test"}
114  , screenshot_map_()
115  , screenshot_filename_()
116  , state_()
117  , play_replay_(false)
118  , multiplayer_server_()
119  , jump_to_multiplayer_(false)
120  , jump_to_campaign_{}
121  , jump_to_editor_()
122  , load_data_()
123 {
124  bool no_music = false;
125  bool no_sound = false;
126 
127  if(cmdline_opts_.core_id) {
128  prefs::get().set_core(*cmdline_opts_.core_id);
129  }
130  if(cmdline_opts_.campaign) {
131  jump_to_campaign_.jump = true;
133  PLAIN_LOG << "selected campaign id: [" << jump_to_campaign_.campaign_id << "]";
134 
137  PLAIN_LOG << "selected difficulty: [" << jump_to_campaign_.difficulty << "]";
138  } else {
139  jump_to_campaign_.difficulty = -1; // let the user choose the difficulty
140  }
141 
144  PLAIN_LOG << "selected scenario id: [" << jump_to_campaign_.scenario_id << "]";
145  }
146 
149  }
150  }
151  if(cmdline_opts_.clock)
153  if(cmdline_opts_.debug) {
155  game_config::mp_debug = true;
156  }
157 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
158  if(cmdline_opts_.debug_dot_domain)
159  gui2::debug_layout_graph::set_domain(*cmdline_opts_.debug_dot_domain);
160  if(cmdline_opts_.debug_dot_level)
161  gui2::debug_layout_graph::set_level(*cmdline_opts_.debug_dot_level);
162 #endif
165  if(cmdline_opts_.fps)
166  prefs::get().set_show_fps(true);
168  prefs::get().set_fullscreen(true);
169  if(cmdline_opts_.load) {
170 #ifdef __cpp_aggregate_paren_init
171  load_data_.emplace(
173 #else
176 #endif
177  try {
178  load_data_->read_file();
179  } catch(const game::load_game_failed&) {
180  load_data_.reset();
181  }
182  }
183  if(cmdline_opts_.max_fps) {
184  prefs::get().set_refresh_rate(std::clamp(*cmdline_opts_.max_fps, 1, 1000));
185  }
187  no_sound = true;
189  }
191  gui2::new_widgets = true;
193  no_music = true;
195  no_sound = true;
197  const int xres = std::get<0>(*cmdline_opts_.resolution);
198  const int yres = std::get<1>(*cmdline_opts_.resolution);
199  if(xres > 0 && yres > 0) {
200  prefs::get().set_resolution(point(xres, yres));
201  prefs::get().set_maximized(false);
202  }
203  }
205  // TODO it could be simplified to use cmdline_opts_ directly if there is no other way to enter screenshot mode
208  no_sound = true;
210  }
211  if (cmdline_opts_.server){
212  jump_to_multiplayer_ = true;
213  // Do we have any server specified ?
214  if(!cmdline_opts_.server->empty()) {
216  } else {
217  // Pick the first server in config
218  if(game_config::server_list.size() > 0) {
220  } else {
221  multiplayer_server_ = "";
222  }
223  }
224  if(cmdline_opts_.username) {
227  if(cmdline_opts_.password) {
229  prefs::get().set_password(*cmdline_opts.server, *cmdline_opts.username, *cmdline_opts_.password);
230  }
231  }
232  }
233  if(cmdline_opts_.test) {
234  if(!cmdline_opts_.test->empty()) {
236  }
237  }
238  if(!cmdline_opts_.unit_test.empty()) {
240  }
242  prefs::get().set_fullscreen(false);
244  load_data_->show_replay = true;
247 
248  if(!cmdline_opts.nobanner) {
249  PLAIN_LOG
250  << "\nGame data: " << game_config::path
251  << "\nUser data: " << filesystem::get_user_data_dir()
252  << "\nCache: " << filesystem::get_cache_dir()
253  << "\n";
254  }
255 
256  // disable sound in nosound mode, or when sound engine failed to initialize
257  if(no_sound || ((prefs::get().sound() || prefs::get().music_on() ||
259  !sound::init_sound())) {
260  prefs::get().set_sound(false);
261  prefs::get().set_music(false);
262  prefs::get().set_turn_bell(false);
263  prefs::get().set_ui_sound(false);
264  } else if(no_music) { // else disable the music in nomusic mode
265  prefs::get().set_music(false);
266  }
267 }
268 
270 {
271  if(!::load_language_list()) {
272  return false;
273  }
274 
275  language_def locale;
276  if(cmdline_opts_.language) {
277  std::vector<language_def> langs = get_languages(true);
278  for(const language_def& def : langs) {
279  if(def.localename == *cmdline_opts_.language) {
280  locale = def;
281  break;
282  }
283  }
284  if(locale.localename.empty()) {
285  PLAIN_LOG << "Language symbol '" << *cmdline_opts_.language << "' not found.";
286  return false;
287  }
288  } else {
289  locale = get_locale();
290  }
291  ::set_language(locale);
292 
293  return true;
294 }
295 
297 {
298  // Handle special commandline launch flags
303  {
309  {
310  PLAIN_LOG << "--nogui flag is only valid with --multiplayer or --screenshot or --plugin flags";
311  return false;
312  }
314  // Screenshots require a rendering context, and thus a window,
315  // so we create one but hidden.
317  } else {
318  // Other functions don't require a window at all.
320  }
321  return true;
322  }
323 
324  // Initialize video subsystem, and create a new window.
325  video::init();
326 
327  // Set window title and icon
329 
330 #if !(defined(__APPLE__))
331  surface icon(image::get_surface(image::locator{"icons/icon-game.png"}, image::UNSCALED));
332  if(icon != nullptr) {
334  }
335 #endif
336 
337 #ifndef __ANDROID__
338  SDL_StartTextInput(video::get_window());
339 #endif
340 
341  return true;
342 }
343 
345 {
346  bool error = false;
347 
348  if(!cmdline_opts_.nobanner) {
349  STREAMING_LOG << "Checking lua scripts... ";
350  }
351 
353  // load the "package" package, so that scripts can get what packages they want
355  }
356 
358  std::string filename = *cmdline_opts_.plugin_file;
359 
360  PLAIN_LOG << "Loading a plugin file'" << filename << "'...";
361 
363 
364  try {
365  if(sf->fail()) {
366  throw std::runtime_error("failed to open plugin file");
367  }
368 
369  /* Cancel all "jumps" to editor / campaign / multiplayer */
370  jump_to_multiplayer_ = false;
371  jump_to_editor_ = utils::nullopt;
372  jump_to_campaign_.jump = false;
373 
374  std::string full_plugin((std::istreambuf_iterator<char>(*sf)), std::istreambuf_iterator<char>());
375 
377 
378  std::size_t i = pm.add_plugin(filename, full_plugin);
379 
380  for(std::size_t j = 0; j < pm.size(); ++j) {
381  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
382  }
383 
384  PLAIN_LOG << "Starting a plugin...";
385  pm.start_plugin(i);
386 
387  for(std::size_t j = 0; j < pm.size(); ++j) {
388  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
389  }
390 
391  plugins_context pc("init");
392 
393  for(std::size_t repeat = 0; repeat < 5; ++repeat) {
394  PLAIN_LOG << "Playing a slice...";
395  pc.play_slice();
396 
397  for(std::size_t j = 0; j < pm.size(); ++j) {
398  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
399  }
400  }
401 
402  return true;
403  } catch(const std::exception& e) {
404  gui2::show_error_message(std::string("When loading a plugin, error:\n") + e.what());
405  error = true;
406  }
407  }
408 
409  if(!error && !cmdline_opts_.nobanner) {
410  PLAIN_LOG << "ok";
411  }
412 
413  return !error;
414 }
415 
416 void game_launcher::set_test(const std::string& id)
417 {
418  state_.clear();
419  state_.classification().type = campaign_type::type::test;
421  state_.classification().era_id = "era_default";
422 
423  state_.set_carryover_sides_start(config{"next_scenario", id});
424 }
425 
427 {
428  // This first_time variable was added in 70f3c80a3e2 so that using the GUI
429  // menu to load a game works. That seems to have edge-cases, for example if
430  // you try to load a game a second time then Wesnoth exits.
431  static bool first_time = true;
432 
433  if(!cmdline_opts_.test) {
434  return true;
435  }
436 
437  if(!first_time) {
438  return false;
439  }
440 
441  first_time = false;
442 
443  if(test_scenarios_.size() == 0) {
444  // shouldn't happen, as test_scenarios_ is initialised to {"test"}
445  PLAIN_LOG << "Error in the test handling code";
446  return false;
447  }
448 
449  if(test_scenarios_.size() > 1) {
450  PLAIN_LOG << "You can't run more than one unit test in interactive mode";
451  }
452 
453  set_test(test_scenarios_.at(0));
454 
456 
457  try {
458  campaign_controller ccontroller(state_);
459  ccontroller.play_game();
460  } catch(savegame::load_game_exception& e) {
461  load_data_ = std::move(e.data_);
462  return true;
463  }
464 
465  return false;
466 }
467 
468 /**
469  * Runs unit tests specified on the command line.
470  *
471  * If multiple unit tests were specified, then this will stop at the first test
472  * which returns a non-zero status.
473  */
474 // Same as play_test except that we return the results of play_game.
475 // \todo "same ... except" ... and many other changes, such as testing the replay
477 {
478  // There's no copy of play_test's first_time variable. That seems to be for handling
479  // the player loading a game via the GUI, which makes no sense in a non-interactive test.
480  if(cmdline_opts_.unit_test.empty()) {
482  }
483 
484  auto ret = unit_test_result::TEST_FAIL; // will only be returned if no test is run
485  for(const auto& scenario : test_scenarios_) {
486  set_test(scenario);
487  ret = single_unit_test();
488  const char* describe_result;
489  switch(ret) {
491  describe_result = "PASS TEST";
492  break;
494  describe_result = "FAIL TEST (INVALID REPLAY)";
495  break;
497  describe_result = "FAIL TEST (ERRORED REPLAY)";
498  break;
500  describe_result = "FAIL TEST (WML EXCEPTION)";
501  break;
503  describe_result = "FAIL TEST (DEFEAT)";
504  break;
506  describe_result = "PASS TEST (VICTORY)";
507  break;
509  describe_result = "BROKE STRICT (PASS)";
510  break;
512  describe_result = "BROKE STRICT (FAIL)";
513  break;
515  describe_result = "BROKE STRICT (DEFEAT)";
516  break;
518  describe_result = "BROKE STRICT (VICTORY)";
519  break;
520  default:
521  describe_result = "FAIL TEST (UNKNOWN)";
522  break;
523  }
524 
525  PLAIN_LOG << describe_result << " (" << int(ret) << "): " << scenario;
526  if(ret != unit_test_result::TEST_PASS) {
527  break;
528  }
529  }
530 
531  return ret;
532 }
533 
535 {
537 
538  level_result::type game_res = level_result::type::fail;
539  try {
540  campaign_controller ccontroller(state_, true);
541  game_res = ccontroller.play_game();
542  if(game_res == level_result::type::fail) {
543  if(lg::broke_strict()) {
545  } else {
547  }
548  }
549  } catch(const wml_exception& e) {
550  PLAIN_LOG << "Caught WML Exception:" << e.dev_message;
552  }
553 
555 
557  return pass_victory_or_defeat(game_res);
558  }
559 
561  save.save_game_automatic(false, "unit_test_replay");
562 
563 #ifdef __cpp_aggregate_paren_init
564  load_data_.emplace(
565  savegame::save_index_class::default_saves_dir(), save.filename(), "", true, true, false);
566 #else
568  savegame::save_index_class::default_saves_dir(), save.filename(), "", true, true, false};
569 #endif
570  load_data_->read_file();
571 
572  if(!load_prepared_game()) {
573  PLAIN_LOG << "Failed to load the replay!";
574  return unit_test_result::TEST_FAIL_LOADING_REPLAY; // failed to load replay
575  }
576 
577  try {
578  const bool was_strict_broken = lg::broke_strict();
579  campaign_controller ccontroller(state_, true);
580  ccontroller.play_replay();
581  if(!was_strict_broken && lg::broke_strict()) {
582  PLAIN_LOG << "Observed failure on replay";
584  }
585  } catch(const wml_exception& e) {
586  PLAIN_LOG << "WML Exception while playing replay: " << e.dev_message;
588  }
589 
590  return pass_victory_or_defeat(game_res);
591 }
592 
594 {
595  if(res == level_result::type::defeat) {
596  if(lg::broke_strict()) {
598  } else {
600  }
601  } else if(res == level_result::type::victory) {
602  if(lg::broke_strict()) {
604  } else {
606  }
607  }
608 
609  if(lg::broke_strict()) {
611  } else {
613  }
614 }
615 
617 {
619  return true;
620  }
621 
623 
625 
627  if (res != editor::EXIT_NORMAL) {
628  PLAIN_LOG << "Error while taking screenshot of map: " << screenshot_map_;
629  }
630  return false;
631 }
632 
634 {
636  return true;
637  }
638 
639  state_.classification().type = campaign_type::type::multiplayer;
640  DBG_GENERAL << "Current campaign type: " << campaign_type::get_string(state_.classification().type);
641 
642  try {
644  } catch(const config::error& e) {
645  PLAIN_LOG << "Error loading game config: " << e.what();
646  return false;
647  }
648 
649  // A default output filename
650  std::string outfile = "wesnoth_image.png";
651 
652  // If a output path was given as an argument, use that instead
654  outfile = *cmdline_opts_.render_image_dst;
655  }
656 
658  exit(1);
659  }
660 
661  return false;
662 }
663 
665 {
666  return load_data_.has_value();
667 }
668 
670 {
671  return std::exchange(load_data_, utils::nullopt).value();
672 }
673 
675 {
676  DBG_GENERAL << "Current campaign type: " << campaign_type::get_string(state_.classification().type);
677  assert(!load_data_);
678 
679  // FIXME: evaluate which of these damn catch blocks are still necessary
680  try {
682 
683  } catch(const config::error& e) {
684  if(e.message.empty()) {
685  gui2::show_error_message(_("The file you have tried to load is corrupt"));
686  } else {
687  gui2::show_error_message(_("The file you have tried to load is corrupt: '") + e.message + '\'');
688  }
689 
690  return false;
691  } catch(const wml_exception& e) {
692  e.show();
693  return false;
694  } catch(const filesystem::io_exception& e) {
695  if(e.message.empty()) {
696  gui2::show_error_message(_("File I/O Error while reading the game"));
697  } else {
698  gui2::show_error_message(_("File I/O Error while reading the game: '") + e.message + '\'');
699  }
700 
701  return false;
702  } catch(const game::error& e) {
703  if(e.message.empty()) {
704  gui2::show_error_message(_("The file you have tried to load is corrupt"));
705  } else {
706  gui2::show_error_message(_("The file you have tried to load is corrupt: '") + e.message + '\'');
707  }
708 
709  return false;
710  }
711 
712  // If the player canceled loading, we won't have any load data at this point
713  return has_load_data();
714 }
715 
717 {
718  auto load_data = extract_load_data();
720 
721  play_replay_ = load_data.show_replay;
722 
723  // in case show_replay && is_start_of_scenario
724  // there won't be any turns to replay, but the
725  // user gets to watch the intro sequence again ...
726  if(load_data.show_replay && !state_.is_start_of_scenario()) {
728  }
729 
730  if(load_data.cancel_orders) {
732  }
733 
734  try {
736  } catch(const config::error&) {
737  return false;
738  }
739 
740  return true;
741 }
742 
744 {
745  state_.clear();
746  state_.classification().type = campaign_type::type::scenario;
747  play_replay_ = false;
748 
750 }
751 
753 {
755 }
756 
758  jump_to_campaign_.jump = false;
759  if(new_campaign()) {
762  return true;
763  }
764  return false;
765 }
766 
768 {
769  if(jump_to_campaign_.jump) {
770  return play_campaign();
771  }
772 
773  return true;
774 }
775 
777 {
778  if(!jump_to_multiplayer_) {
779  return true;
780  }
781 
782  jump_to_multiplayer_ = false;
784 }
785 
787 {
788  if(jump_to_editor_) {
789  // If no filename was specified, this will be an empty string.
790  const std::string to_open = std::exchange(jump_to_editor_, utils::nullopt).value();
791 
793  return false;
794  }
795  }
796 
797  return true;
798 }
799 
801 {
802 #if defined(__APPLE__) && TARGET_OS_IPHONE
803  throw game::mp_server_error("Starting MP server is not supported on iOS builds.");
804 #else
805  std::string wesnothd_program = "";
806  if(!prefs::get().get_mp_server_program_name().empty()) {
807  wesnothd_program = prefs::get().get_mp_server_program_name();
808  } else {
809  wesnothd_program = filesystem::get_wesnothd_name();
810  }
811 
812  std::string config = filesystem::get_user_data_dir() + "/lan_server.cfg";
814  // copy file if it isn't created yet
816  }
817 
818  LOG_GENERAL << "Starting wesnothd";
819  try
820  {
821 #if BOOST_VERSION >= 108600
822  boost::asio::io_context io_context;
823  auto c = boost::process::v2::process{io_context, wesnothd_program, { "-c", config }};
824 #else
825 #ifndef _WIN32
826  boost::process::child c(wesnothd_program, "-c", config);
827 #else
828  boost::process::child c(wesnothd_program, "-c", config, boost::process::windows::create_no_window);
829 #endif
830 #endif
831  c.detach();
832  // Give server a moment to start up
833  using namespace std::chrono_literals;
834  std::this_thread::sleep_for(50ms);
835  return;
836  }
837 #if BOOST_VERSION >= 108600
838  catch(const std::exception& e)
839 #else
840  catch(const boost::process::process_error& e)
841 #endif
842  {
844 
845  // Couldn't start server so throw error
846  WRN_GENERAL << "Failed to start server " << wesnothd_program << ":\n" << e.what();
847  throw game::mp_server_error("Starting MP server failed!");
848  }
849 #endif
850 }
851 
853 {
855  try {
856  if(mode == mp_mode::HOST) {
857  try {
858  start_wesnothd();
859  } catch(const game::mp_server_error&) {
861 
862  try {
863  start_wesnothd();
864  } catch(const game::mp_server_error&) {
865  return false;
866  }
867  }
868  }
869 
870  // If a server address wasn't specified, prompt for it now.
871  if(mode != mp_mode::LOCAL && multiplayer_server_.empty()) {
872  if(!gui2::dialogs::mp_connect::execute()) {
873  return false;
874  }
875 
876  // The prompt saves its input to preferences.
878 
879  if(multiplayer_server_ != prefs::get().builtin_servers_list().front().address) {
881  }
882  }
883 
884  // create_engine already calls config_manager_.load_config but maybe its better to have MULTIPLAYER
885  // defined while we are in the lobby.
887 
888  events::discard_input(); // prevent the "keylogger" effect
890 
891  if(mode == mp_mode::LOCAL) {
893  } else {
895  multiplayer_server_.clear();
896  }
897 
898  } catch(const wesnothd_rejected_client_error& e) {
899  gui2::show_error_message(e.message);
900  } catch(const game::mp_server_error& e) {
901  gui2::show_error_message(_("Error while starting server: ") + e.message);
902  } catch(const game::load_game_failed& e) {
903  gui2::show_error_message(_("The game could not be loaded: ") + e.message);
904  } catch(const game::game_error& e) {
905  gui2::show_error_message(_("Error while playing the game: ") + e.message);
906  } catch(const mapgen_exception& e) {
907  gui2::show_error_message(_("Map generator error: ") + e.message);
908  } catch(const wesnothd_error& e) {
909  if(!e.message.empty()) {
910  ERR_NET << "caught network error: " << e.message;
911 
912  std::string user_msg;
913  auto conn_err = dynamic_cast<const wesnothd_connection_error*>(&e);
914 
915  if(conn_err) {
916  // The wesnothd_connection_error subclass is only thrown with messages
917  // from boost::system::error_code which we can't translate ourselves.
918  // It's also the originator of the infamous EOF error that happens when
919  // the server dies. <https://github.com/wesnoth/wesnoth/issues/3005>. It
920  // will provide a translated string instead of that when it happens.
921  user_msg = !conn_err->user_message.empty()
922  ? conn_err->user_message
923  : _("Connection failed: ") + e.message;
924  } else {
925  // This will be a message from the server itself, which we can
926  // probably translate.
927  user_msg = translation::gettext(e.message.c_str());
928  }
929 
930  gui2::show_error_message(user_msg);
931  } else {
932  ERR_NET << "caught network error";
933  }
934  } catch(const config::error& e) {
935  if(!e.message.empty()) {
936  ERR_CONFIG << "caught config::error: " << e.message;
937  gui2::show_transient_message("", e.message);
938  } else {
939  ERR_CONFIG << "caught config::error";
940  }
941  } catch(const incorrect_map_format_error& e) {
942  gui2::show_error_message(_("The game map could not be loaded: ") + e.message);
943  } catch(savegame::load_game_exception& e) {
944  load_data_ = std::move(e.data_);
945  // this will make it so next time through the title screen loop, this game is loaded
946  } catch(const wml_exception& e) {
947  e.show();
948  } catch(const game::error& e) {
949  PLAIN_LOG << "caught game::error...";
950  gui2::show_error_message(_("Error: ") + e.message);
951  }
952 
953  return true;
954 }
955 
957 {
959  return true;
960  }
961 
962  DBG_MP << "starting multiplayer game from the commandline";
963 
965 
966  events::discard_input(); // prevent the "keylogger" effect
968 
969  try {
971  } catch(savegame::load_game_exception& e) {
972  load_data_ = std::move(e.data_);
973  return true;
974  }
975 
976  return false;
977 }
978 
980 {
981  if(!gui2::dialogs::language_selection::execute()) {
982  return false;
983  }
984 
987  }
988 
993 
994  return true;
995 }
996 
998 {
999  assert(!load_data_);
1000  if(play_replay_) {
1001  play_replay();
1002  return;
1003  }
1004 
1005  gui2::dialogs::loading_screen::display([this, reload]() {
1007 
1008  if(reload == reload_mode::RELOAD_DATA) {
1009  try {
1012  } catch(const config::error&) {
1013  return;
1014  }
1015  }
1016  });
1017 
1018  try {
1019  campaign_controller ccontroller(state_);
1020  ccontroller.play_game();
1021  ai::manager::singleton_ = nullptr;
1022  } catch(savegame::load_game_exception& e) {
1023  load_data_ = std::move(e.data_);
1024  // this will make it so next time through the title screen loop, this game is loaded
1025  } catch(const wml_exception& e) {
1026  e.show();
1027  } catch(const mapgen_exception& e) {
1028  gui2::show_error_message(_("Map generator error: ") + e.message);
1029  }
1030 }
1031 
1033 {
1034  assert(!load_data_);
1035  try {
1036  campaign_controller ccontroller(state_);
1037  ccontroller.play_replay();
1038  } catch(savegame::load_game_exception& e) {
1039  load_data_ = std::move(e.data_);
1040  // this will make it so next time through the title screen loop, this game is loaded
1041  } catch(const wml_exception& e) {
1042  e.show();
1043  }
1044 }
1045 
1047 {
1049  while(true) {
1051 
1053 
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 }
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands.
static manager * singleton_
Definition: manager.hpp:422
level_result::type play_game()
level_result::type play_replay()
bool nogui
True if –nogui was given on the command line.
utils::optional< std::pair< int, int > > resolution
Pair of AxB values specified after –resolution.
bool headless_unit_test
True if –unit is used and –showgui is not present.
utils::optional< std::string > screenshot_map_file
Map file to make a screenshot of.
utils::optional< std::string > test
Non-empty if –test was given on the command line.
bool windowed
True if –windowed was given on the command line.
utils::optional< std::string > language
Non-empty if –language was given on the command line.
bool noreplaycheck
True if –noreplaycheck was given on the command line.
utils::optional< std::string > screenshot_output_file
Output file to put screenshot in.
utils::optional< int > max_fps
Max FPS specified by –max-fps option.
utils::optional< std::string > core_id
Non-empty if –core was given on the command line.
utils::optional< int > campaign_difficulty
Non-empty if –campaign-difficulty was given on the command line.
bool debug
True if –debug was given on the command line.
bool nosound
True if –nosound was given on the command line.
bool multiplayer
True if –multiplayer was given on the command line.
utils::optional< std::string > server
Non-empty if –server was given on the command line.
bool script_unsafe_mode
Whether to load the "package" package for the scripting environment.
utils::optional< unsigned int > translation_percent
Non-empty if –all-translations or –translations-over is given on the command line.
utils::optional< std::string > plugin_file
File to load a lua plugin script from.
bool nobanner
True if –nobanner was given on the command line.
bool nomusic
True if –nomusic was given on the command line.
bool clock
True if –clock was given on the command line.
utils::optional< std::string > render_image_dst
Output file to put rendered image path in.
utils::optional< std::string > render_image
Image path to render.
utils::optional< std::string > campaign
Non-empty if –campaign was given on the command line.
bool new_widgets
True if –new-widgets was given on the command line.
bool fps
True if –fps was given on the command line.
utils::optional< std::string > username
Non-empty if –username was given on the command line.
bool campaign_skip_story
True if –skip-story was given on the command line.
bool with_replay
True if –with-replay was given on the command line.
bool screenshot
True if –screenshot was given on the command line.
std::vector< std::string > unit_test
Non-empty if –unit was given on the command line.
utils::optional< std::string > editor
Non-empty if –editor was given on the command line.
utils::optional< std::string > password
Non-empty if –password was given on the command line.
bool fullscreen
True if –fullscreen was given on the command line.
utils::optional< std::string > campaign_scenario
Non-empty if –campaign-scenario was given on the command line.
utils::optional< std::string > load
Non-empty if –load was given on the command line.
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
campaign_type::type type
std::string label
Name of the game (e.g.
std::string campaign_define
If there is a define the campaign uses to customize data.
void load_game_config_for_game(const game_classification &classification, const std::string &scenario_id)
void load_game_config_for_create(bool is_mp, bool is_test=false)
const game_config_view & game_config() const
game_launcher(const commandline_options &cmdline_opts)
bool load_prepared_game()
utils::optional< std::string > jump_to_editor_
unit_test_result single_unit_test()
Internal to the implementation of unit_test().
std::string multiplayer_server_
std::string jump_to_campaign_id() const
Return the ID of the campaign to jump to (skipping the main menu).
bool play_screenshot_mode()
jump_to_campaign_info jump_to_campaign_
std::string screenshot_filename_
void launch_game(reload_mode reload=reload_mode::RELOAD_DATA)
bool goto_multiplayer()
bool change_language()
saved_game state_
savegame::load_game_metadata extract_load_data()
Returns the load_game_metadata object stored in load_data_.
bool load_game_prompt()
unit_test_result pass_victory_or_defeat(level_result::type res)
game_config_manager config_manager_
bool has_load_data() const
bool play_render_image_mode()
void clear_loaded_game()
bool init_lua_script()
std::vector< std::string > test_scenarios_
unit_test_result
Status code after running a unit test, should match the run_wml_tests script and the documentation fo...
std::string screenshot_map_
const commandline_options & cmdline_opts_
utils::optional< savegame::load_game_metadata > load_data_
void set_test(const std::string &id)
bool play_multiplayer_commandline()
unit_test_result unit_test()
Runs unit tests specified on the command line.
bool play_multiplayer(mp_mode mode)
editor::EXIT_STATUS start_editor()
static void progress(loading_stage stage=loading_stage::none)
Report what is being loaded to the loading screen.
static void display(const std::function< void()> &f)
Generic locator abstracting the location of an image.
Definition: picture.hpp:59
void load_package()
Loads the package library into lua environment.
void play_slice()
Definition: context.cpp:103
std::size_t size()
Definition: manager.cpp:68
std::string get_name(std::size_t idx)
Definition: manager.cpp:94
static plugins_manager * get()
Definition: manager.cpp:58
void start_plugin(std::size_t idx)
Definition: manager.cpp:101
std::string get_detailed_status(std::size_t idx)
Definition: manager.cpp:83
lua_kernel_base * get_kernel_base()
Definition: manager.cpp:63
std::size_t add_plugin(const std::string &name, const std::string &prog)
Definition: manager.cpp:118
void set_network_host(const std::string &host)
bool set_music(bool ison)
std::string network_host()
bool set_ui_sound(bool ison)
void set_login(const std::string &login)
static prefs & get()
void show_wesnothd_server_search()
void set_password(const std::string &server, const std::string &login, const std::string &key)
bool set_turn_bell(bool ison)
void set_show_fps(bool value)
bool set_sound(bool ison)
void set_resolution(const point &res)
static void disable_preferences_save()
std::string get_mp_server_program_name()
void set_mp_server_program_name(const std::string &)
game_classification & classification()
Definition: saved_game.hpp:55
void set_skip_story(bool skip_story)
Definition: saved_game.hpp:146
bool is_start_of_scenario() const
Definition: saved_game.hpp:109
void set_carryover_sides_start(config carryover_sides_start)
Definition: saved_game.cpp:165
std::string get_scenario_id() const
Definition: saved_game.cpp:698
void clear()
Definition: saved_game.cpp:832
statistics_record::campaign_stats_t & statistics()
Definition: saved_game.hpp:142
void cancel_orders()
Definition: saved_game.cpp:735
Exception used to signal that the user has decided to abort a game, and to load another game instead.
Definition: savegame.hpp:88
Class for replay saves (either manually or automatically).
Definition: savegame.hpp:244
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:202
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:274
const std::string & filename() const
Definition: savegame.hpp:154
static void reset_translations()
Definition: tstring.cpp:665
Definitions for the interface to Wesnoth Markup Language (WML).
const events::event_context main_event_context_
Declarations for File-IO.
std::size_t i
Definition: function.cpp:1031
#define ERR_GENERAL
#define DBG_GENERAL
static lg::log_domain log_enginerefac("enginerefac")
#define DBG_MP
static lg::log_domain log_network("network")
static lg::log_domain log_mp_create("mp/create")
#define ERR_CONFIG
#define WRN_GENERAL
#define ERR_NET
#define LOG_GENERAL
static lg::log_domain log_config("config")
static std::string _(const char *str)
Definition: gettext.hpp:97
const language_def & get_locale()
Definition: language.cpp:313
std::vector< language_def > get_languages(bool all)
Return a list of available translations.
Definition: language.cpp:162
void init_textdomains(const game_config_view &cfg)
Initializes the list of textdomains from a configuration object.
Definition: language.cpp:333
bool load_language_list()
Definition: language.cpp:141
void set_min_translation_percent(int percent)
Definition: language.cpp:192
Standard logging facilities (interface).
#define STREAMING_LOG
Definition: log.hpp:297
#define PLAIN_LOG
Definition: log.hpp:296
@ NORMAL
Definition: cursor.hpp:28
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
Definition: cursor.cpp:172
void point(int x, int y)
Draw a single point.
Definition: draw.cpp:228
@ EXIT_ERROR
Definition: editor_main.hpp:27
@ EXIT_NORMAL
Definition: editor_main.hpp:24
@ EXIT_QUIT_TO_DESKTOP
Definition: editor_main.hpp:25
@ EXIT_RELOAD_DATA
Definition: editor_main.hpp:26
EXIT_STATUS start(bool clear_id, const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
void discard_input()
Discards all input events.
Definition: events.cpp:765
std::string get_cache_dir()
Definition: filesystem.cpp:880
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
std::string get_user_data_dir()
Definition: filesystem.cpp:870
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:344
utils::optional< std::string > get_wml_location(const std::string &path, const utils::optional< std::string > &current_dir)
Returns a translated path to the actual file or directory, if it exists.
std::string read_file(const std::string &fname)
Basic disk I/O - read file.
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:52
void write_file(const std::string &fname, const std::string &data, std::ios_base::openmode mode)
Throws io_exception if an error occurs.
std::string get_wesnothd_name()
std::string normalize_path(const std::string &fpath, bool normalize_separators, bool resolve_dot_entries)
Returns the absolute path of a file.
bool load_font_config()
Definition: font_config.cpp:78
std::string turn_bell
std::string path
Definition: filesystem.cpp:106
std::string get_default_title_string()
std::vector< server_info > server_list
Definition: game_config.cpp:76
void set_debug(bool new_debug)
Definition: game_config.cpp:97
bool show_debug_clock_button
Do we wish to show the button for the debug clock.
static bool sound()
static bool ui_sound_on()
static bool music_on()
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.
bool new_widgets
Do we wish to use the new library or not.
Definition: settings.cpp:23
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:201
void flush_cache()
Purges all image caches.
Definition: picture.cpp:210
surface get_surface(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image surface suitable for software manipulation.
Definition: picture.cpp:691
save_result save_image(const locator &i_locator, const std::string &filename)
Definition: picture.cpp:921
@ UNSCALED
Unmodified original-size image.
Definition: picture.hpp:173
bool broke_strict()
Definition: log.cpp:430
void start_local_game()
Starts a multiplayer game in single-user mode.
void start_local_game_commandline(const commandline_options &cmdline_opts)
Starts a multiplayer game in single-user mode using command line settings.
void start_client(const std::string &host)
Pubic entry points for the MP workflow.
void clean_saves(const std::string &label)
Delete all autosaves of a certain scenario from the default save directory.
Definition: savegame.cpp:64
void set_gamestate(saved_game &gamestate, load_game_metadata &load_data)
Definition: savegame.cpp:257
utils::optional< load_game_metadata > load_interactive()
Definition: savegame.cpp:185
bool init_sound()
Definition: sound.cpp:340
void close_sound()
Definition: sound.cpp:427
void flush_cache()
Definition: sound.cpp:1028
bool select_campaign(saved_game &state, jump_to_campaign_info jump_to_campaign)
void process(int mousex, int mousey)
Definition: tooltips.cpp:334
void set_language(const std::string &language, const std::vector< std::string > *)
Definition: gettext.cpp:494
static std::string gettext(const char *str)
Definition: gettext.hpp:62
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:81
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition: general.cpp:23
void set_window_title(const std::string &title)
Sets the title of the main window.
Definition: video.cpp:676
void set_window_icon(surface &icon)
Sets the icon of the main window.
Definition: video.cpp:682
SDL_Window * get_window()
Definition: video.cpp:697
void init(fake type)
Initialize the video subsystem.
Definition: video.cpp:93
void deinit()
Deinitialize the video subsystem.
Definition: video.cpp:122
This file contains the settings handling of the widget library.
std::string filename
Filename.
An exception object used when an IO error occurs.
Definition: filesystem.hpp:56
Base class for all the errors encountered by the engine.
Definition: exceptions.hpp:29
Error used for any general game error, e.g.
Definition: game_errors.hpp:47
Error used when game loading fails.
Definition: game_errors.hpp:31
std::string campaign_id
The ID of the campaign to launch.
bool jump
Whether the game should immediately start a campaign.
std::string scenario_id
The ID of the scenario within the campaign to jump to.
bool skip_story
Whether the story screen should be skipped.
int difficulty
The difficulty at which to launch the campaign.
std::string localename
Definition: language.hpp:32
void clear_current_scenario()
Delete the current scenario from the stats.
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46
An error occurred inside the underlying network communication code (boost asio) TODO: find a short na...
std::string user_message
User-friendly and potentially translated message for use in the UI.
An error occurred during when trying to communicate with the wesnothd server.
Error used when the client is rejected by the MP server.
Helper class, don't construct this directly.
mock_char c
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define e