The Battle for Wesnoth  1.19.21+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 
55 #if !TARGET_OS_IPHONE
56 //
57 // HACK: MacCompileStuff is currently on 1.86, so it could use the v2 API,
58 // but macOS packaging still links against the old boost::process v1 layout.
59 //
60 // -- vultraz, 2025-05-12
61 //
62 #if BOOST_VERSION > 108600
63 #error MacCompileStuff has been updated. Remove this block and the accompanying __APPLE__ checks below.
64 #endif
65 #include <boost/process/v1/child.hpp>
66 #endif
67 
68 #elif BOOST_VERSION >= 108600
69 
70 // boost::asio (via boost::process) complains about winsock.h otherwise
71 #ifdef _WIN32
72 #define WIN32_LEAN_AND_MEAN
73 #endif
74 #include <boost/process/v2/process.hpp>
75 
76 #else
77 
78 // process::v1 only. The v1 folders do not exist until 1.86
79 #ifdef _WIN32
80 #include <boost/process/windows.hpp>
81 #endif
82 #include <boost/process/child.hpp>
83 
84 #endif
85 
86 #include <algorithm> // for copy, max, min, stable_sort
87 #include <new>
88 #include <thread>
89 #include <utility> // for pair
90 
91 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
92 #include "gui/widgets/debug.hpp"
93 #endif
94 
95 static lg::log_domain log_config("config");
96 #define ERR_CONFIG LOG_STREAM(err, log_config)
97 #define WRN_CONFIG LOG_STREAM(warn, log_config)
98 #define LOG_CONFIG LOG_STREAM(info, log_config)
99 
100 #define ERR_GENERAL LOG_STREAM(err, lg::general())
101 #define LOG_GENERAL LOG_STREAM(info, lg::general())
102 #define WRN_GENERAL LOG_STREAM(warn, lg::general())
103 #define DBG_GENERAL LOG_STREAM(debug, lg::general())
104 
105 #define LOG_TEST FORCE_LOG_TO(lg::general(), log_config)
106 
107 static lg::log_domain log_mp_create("mp/create");
108 #define DBG_MP LOG_STREAM(debug, log_mp_create)
109 
110 static lg::log_domain log_network("network");
111 #define ERR_NET LOG_STREAM(err, log_network)
112 
113 static lg::log_domain log_enginerefac("enginerefac");
114 #define LOG_RG LOG_STREAM(info, log_enginerefac)
115 
117  : cmdline_opts_(cmdline_opts)
118  , config_manager_(cmdline_opts)
119  , font_manager_()
120  , image_manager_()
122  , hotkey_manager_()
123  , music_thinker_()
124  , music_muter_()
125  , test_scenarios_{"test"}
126  , screenshot_map_()
127  , screenshot_filename_()
128  , state_()
129  , play_replay_(false)
130  , multiplayer_server_()
131  , jump_to_multiplayer_(false)
132  , jump_to_campaign_{}
133  , jump_to_editor_()
134  , load_data_()
135 {
136  bool no_music = false;
137  bool no_sound = false;
138 
139  if(cmdline_opts_.core_id) {
140  prefs::get().set_core(*cmdline_opts_.core_id);
141  }
142  if(cmdline_opts_.campaign) {
143  jump_to_campaign_.jump = true;
145  PLAIN_LOG << "selected campaign id: [" << jump_to_campaign_.campaign_id << "]";
146 
149  PLAIN_LOG << "selected difficulty: [" << jump_to_campaign_.difficulty << "]";
150  } else {
151  jump_to_campaign_.difficulty = -1; // let the user choose the difficulty
152  }
153 
156  PLAIN_LOG << "selected scenario id: [" << jump_to_campaign_.scenario_id << "]";
157  }
158 
161  }
162  }
163  if(cmdline_opts_.clock)
165  if(cmdline_opts_.debug) {
167  game_config::mp_debug = true;
168  }
169 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
170  if(cmdline_opts_.debug_dot_domain)
171  gui2::debug_layout_graph::set_domain(*cmdline_opts_.debug_dot_domain);
172  if(cmdline_opts_.debug_dot_level)
173  gui2::debug_layout_graph::set_level(*cmdline_opts_.debug_dot_level);
174 #endif
177  if(cmdline_opts_.fps)
178  prefs::get().set_show_fps(true);
180  prefs::get().set_fullscreen(true);
181  if(cmdline_opts_.load) {
182 #ifdef __cpp_aggregate_paren_init
183  load_data_.emplace(
185 #else
188 #endif
189  try {
190  load_data_->read_file();
191  } catch(const game::load_game_failed&) {
192  load_data_.reset();
193  }
194  }
195  if(cmdline_opts_.max_fps) {
196  prefs::get().set_refresh_rate(std::clamp(*cmdline_opts_.max_fps, 1, 1000));
197  }
199  no_sound = true;
201  }
203  gui2::new_widgets = true;
205  no_music = true;
207  no_sound = true;
209  const int xres = std::get<0>(*cmdline_opts_.resolution);
210  const int yres = std::get<1>(*cmdline_opts_.resolution);
211  if(xres > 0 && yres > 0) {
212  prefs::get().set_resolution(point(xres, yres));
213  prefs::get().set_maximized(false);
214  }
215  }
217  // TODO it could be simplified to use cmdline_opts_ directly if there is no other way to enter screenshot mode
220  no_sound = true;
222  }
223  if (cmdline_opts_.server){
224  jump_to_multiplayer_ = true;
225  // Do we have any server specified ?
226  if(!cmdline_opts_.server->empty()) {
228  } else {
229  // Pick the first server in config
230  if(game_config::server_list.size() > 0) {
232  } else {
233  multiplayer_server_ = "";
234  }
235  }
236  if(cmdline_opts_.username) {
239  if(cmdline_opts_.password) {
241  prefs::get().set_password(*cmdline_opts.server, *cmdline_opts.username, *cmdline_opts_.password);
242  }
243  }
244  }
245  if(cmdline_opts_.test) {
246  if(!cmdline_opts_.test->empty()) {
248  }
249  }
250  if(!cmdline_opts_.unit_test.empty()) {
252  }
254  prefs::get().set_fullscreen(false);
256  load_data_->show_replay = true;
259 
260  if(!cmdline_opts.nobanner) {
261  PLAIN_LOG
262  << "\nGame data: " << game_config::path
263  << "\nUser data: " << filesystem::get_user_data_dir()
264  << "\nCache: " << filesystem::get_cache_dir()
265  << "\n";
266  }
267 
268  // disable sound in nosound mode, or when sound engine failed to initialize
269  if(no_sound || ((prefs::get().sound() || prefs::get().music_on() ||
271  !sound::init_sound())) {
272  prefs::get().set_sound(false);
273  prefs::get().set_music(false);
274  prefs::get().set_turn_bell(false);
275  prefs::get().set_ui_sound(false);
276  } else if(no_music) { // else disable the music in nomusic mode
277  prefs::get().set_music(false);
278  }
279 }
280 
282 {
283  if(!::load_language_list()) {
284  return false;
285  }
286 
287  language_def locale;
288  if(cmdline_opts_.language) {
289  std::vector<language_def> langs = get_languages(true);
290  for(const language_def& def : langs) {
291  if(def.localename == *cmdline_opts_.language) {
292  locale = def;
293  break;
294  }
295  }
296  if(locale.localename.empty()) {
297  PLAIN_LOG << "Language symbol '" << *cmdline_opts_.language << "' not found.";
298  return false;
299  }
300  } else {
301  locale = get_locale();
302  }
303  ::set_language(locale);
304 
305  return true;
306 }
307 
309 {
310  // Handle special commandline launch flags
315  {
321  {
322  PLAIN_LOG << "--nogui flag is only valid with --multiplayer or --screenshot or --plugin flags";
323  return false;
324  }
326  // Screenshots require a rendering context, and thus a window,
327  // so we create one but hidden.
329  } else {
330  // Other functions don't require a window at all.
332  }
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(image::locator{"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 
365  std::string filename = *cmdline_opts_.plugin_file;
366 
367  PLAIN_LOG << "Loading a plugin file'" << filename << "'...";
368 
370 
371  try {
372  if(sf->fail()) {
373  throw std::runtime_error("failed to open plugin file");
374  }
375 
376  /* Cancel all "jumps" to editor / campaign / multiplayer */
377  jump_to_multiplayer_ = false;
378  jump_to_editor_ = utils::nullopt;
379  jump_to_campaign_.jump = false;
380 
381  std::string full_plugin((std::istreambuf_iterator<char>(*sf)), std::istreambuf_iterator<char>());
382 
384 
385  std::size_t i = pm.add_plugin(filename, full_plugin);
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  PLAIN_LOG << "Starting a plugin...";
392  pm.start_plugin(i);
393 
394  for(std::size_t j = 0; j < pm.size(); ++j) {
395  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
396  }
397 
398  plugins_context pc("init");
399 
400  for(std::size_t repeat = 0; repeat < 5; ++repeat) {
401  PLAIN_LOG << "Playing a slice...";
402  pc.play_slice();
403 
404  for(std::size_t j = 0; j < pm.size(); ++j) {
405  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
406  }
407  }
408 
409  return true;
410  } catch(const std::exception& e) {
411  gui2::show_error_message(std::string("When loading a plugin, error:\n") + e.what());
412  error = true;
413  }
414  }
415 
416  if(!error && !cmdline_opts_.nobanner) {
417  PLAIN_LOG << "ok";
418  }
419 
420  return !error;
421 }
422 
423 void game_launcher::set_test(const std::string& id)
424 {
425  state_.clear();
426  state_.classification().type = campaign_type::type::test;
428  state_.classification().era_id = "era_default";
429 
430  state_.set_carryover_sides_start(config{"next_scenario", id});
431 }
432 
434 {
435  // This first_time variable was added in 70f3c80a3e2 so that using the GUI
436  // menu to load a game works. That seems to have edge-cases, for example if
437  // you try to load a game a second time then Wesnoth exits.
438  static bool first_time = true;
439 
440  if(!cmdline_opts_.test) {
441  return true;
442  }
443 
444  if(!first_time) {
445  return false;
446  }
447 
448  first_time = false;
449 
450  if(test_scenarios_.size() == 0) {
451  // shouldn't happen, as test_scenarios_ is initialised to {"test"}
452  PLAIN_LOG << "Error in the test handling code";
453  return false;
454  }
455 
456  if(test_scenarios_.size() > 1) {
457  PLAIN_LOG << "You can't run more than one unit test in interactive mode";
458  }
459 
460  set_test(test_scenarios_.at(0));
461 
463 
464  try {
465  campaign_controller ccontroller(state_);
466  ccontroller.play_game();
467  } catch(savegame::load_game_exception& e) {
468  load_data_ = std::move(e.data_);
469  return true;
470  }
471 
472  return false;
473 }
474 
475 /**
476  * Runs unit tests specified on the command line.
477  *
478  * If multiple unit tests were specified, then this will stop at the first test
479  * which returns a non-zero status.
480  */
481 // Same as play_test except that we return the results of play_game.
482 // \todo "same ... except" ... and many other changes, such as testing the replay
484 {
485  // There's no copy of play_test's first_time variable. That seems to be for handling
486  // the player loading a game via the GUI, which makes no sense in a non-interactive test.
487  if(cmdline_opts_.unit_test.empty()) {
489  }
490 
491  auto ret = unit_test_result::TEST_FAIL; // will only be returned if no test is run
492  for(const auto& scenario : test_scenarios_) {
493  set_test(scenario);
494  ret = single_unit_test();
495  const char* describe_result;
496  switch(ret) {
498  describe_result = "PASS TEST";
499  break;
501  describe_result = "FAIL TEST (INVALID REPLAY)";
502  break;
504  describe_result = "FAIL TEST (ERRORED REPLAY)";
505  break;
507  describe_result = "FAIL TEST (WML EXCEPTION)";
508  break;
510  describe_result = "FAIL TEST (DEFEAT)";
511  break;
513  describe_result = "PASS TEST (VICTORY)";
514  break;
516  describe_result = "BROKE STRICT (PASS)";
517  break;
519  describe_result = "BROKE STRICT (FAIL)";
520  break;
522  describe_result = "BROKE STRICT (DEFEAT)";
523  break;
525  describe_result = "BROKE STRICT (VICTORY)";
526  break;
527  default:
528  describe_result = "FAIL TEST (UNKNOWN)";
529  break;
530  }
531 
532  PLAIN_LOG << describe_result << " (" << int(ret) << "): " << scenario;
533  if(ret != unit_test_result::TEST_PASS) {
534  break;
535  }
536  }
537 
538  return ret;
539 }
540 
542 {
544 
545  level_result::type game_res = level_result::type::fail;
546  try {
547  campaign_controller ccontroller(state_, true);
548  game_res = ccontroller.play_game();
549  if(game_res == level_result::type::fail) {
550  if(lg::broke_strict()) {
552  } else {
554  }
555  }
556  } catch(const wml_exception& e) {
557  PLAIN_LOG << "Caught WML Exception:" << e.dev_message;
559  }
560 
562 
564  return pass_victory_or_defeat(game_res);
565  }
566 
568  save.save_game_automatic(false, "unit_test_replay");
569 
570 #ifdef __cpp_aggregate_paren_init
571  load_data_.emplace(
572  savegame::save_index_class::default_saves_dir(), save.filename(), "", true, true, false);
573 #else
575  savegame::save_index_class::default_saves_dir(), save.filename(), "", true, true, false};
576 #endif
577  load_data_->read_file();
578 
579  if(!load_prepared_game()) {
580  PLAIN_LOG << "Failed to load the replay!";
581  return unit_test_result::TEST_FAIL_LOADING_REPLAY; // failed to load replay
582  }
583 
584  try {
585  const bool was_strict_broken = lg::broke_strict();
586  campaign_controller ccontroller(state_, true);
587  ccontroller.play_replay();
588  if(!was_strict_broken && lg::broke_strict()) {
589  PLAIN_LOG << "Observed failure on replay";
591  }
592  } catch(const wml_exception& e) {
593  PLAIN_LOG << "WML Exception while playing replay: " << e.dev_message;
595  }
596 
597  return pass_victory_or_defeat(game_res);
598 }
599 
601 {
602  if(res == level_result::type::defeat) {
603  if(lg::broke_strict()) {
605  } else {
607  }
608  } else if(res == level_result::type::victory) {
609  if(lg::broke_strict()) {
611  } else {
613  }
614  }
615 
616  if(lg::broke_strict()) {
618  } else {
620  }
621 }
622 
624 {
626  return true;
627  }
628 
630 
632 
634  if (res != editor::EXIT_NORMAL) {
635  PLAIN_LOG << "Error while taking screenshot of map: " << screenshot_map_;
636  }
637  return false;
638 }
639 
641 {
643  return true;
644  }
645 
646  state_.classification().type = campaign_type::type::multiplayer;
647  DBG_GENERAL << "Current campaign type: " << campaign_type::get_string(state_.classification().type);
648 
649  try {
651  } catch(const config::error& e) {
652  PLAIN_LOG << "Error loading game config: " << e.what();
653  return false;
654  }
655 
656  // A default output filename
657  std::string outfile = "wesnoth_image.png";
658 
659  // If a output path was given as an argument, use that instead
661  outfile = *cmdline_opts_.render_image_dst;
662  }
663 
665  exit(1);
666  }
667 
668  return false;
669 }
670 
672 {
673  return load_data_.has_value();
674 }
675 
677 {
678  return std::exchange(load_data_, utils::nullopt).value();
679 }
680 
682 {
683  DBG_GENERAL << "Current campaign type: " << campaign_type::get_string(state_.classification().type);
684  assert(!load_data_);
685 
686  // FIXME: evaluate which of these damn catch blocks are still necessary
687  try {
689 
690  } catch(const config::error& e) {
691  if(e.message.empty()) {
692  gui2::show_error_message(_("The file you have tried to load is corrupt"));
693  } else {
694  gui2::show_error_message(_("The file you have tried to load is corrupt: '") + e.message + '\'');
695  }
696 
697  return false;
698  } catch(const wml_exception& e) {
699  e.show();
700  return false;
701  } catch(const filesystem::io_exception& e) {
702  if(e.message.empty()) {
703  gui2::show_error_message(_("File I/O Error while reading the game"));
704  } else {
705  gui2::show_error_message(_("File I/O Error while reading the game: '") + e.message + '\'');
706  }
707 
708  return false;
709  } catch(const game::error& e) {
710  if(e.message.empty()) {
711  gui2::show_error_message(_("The file you have tried to load is corrupt"));
712  } else {
713  gui2::show_error_message(_("The file you have tried to load is corrupt: '") + e.message + '\'');
714  }
715 
716  return false;
717  }
718 
719  // If the player canceled loading, we won't have any load data at this point
720  return has_load_data();
721 }
722 
724 {
725  auto load_data = extract_load_data();
727 
728  play_replay_ = load_data.show_replay;
729 
730  // in case show_replay && is_start_of_scenario
731  // there won't be any turns to replay, but the
732  // user gets to watch the intro sequence again ...
733  if(load_data.show_replay && !state_.is_start_of_scenario()) {
735  }
736 
737  if(load_data.cancel_orders) {
739  }
740 
741  try {
743  } catch(const config::error&) {
744  return false;
745  }
746 
747  return true;
748 }
749 
751 {
752  state_.clear();
753  state_.classification().type = campaign_type::type::scenario;
754  play_replay_ = false;
755 
757 }
758 
760 {
762 }
763 
765  jump_to_campaign_.jump = false;
766  if(new_campaign()) {
769  return true;
770  }
771  return false;
772 }
773 
775 {
776  if(jump_to_campaign_.jump) {
777  return play_campaign();
778  }
779 
780  return true;
781 }
782 
784 {
785  if(!jump_to_multiplayer_) {
786  return true;
787  }
788 
789  jump_to_multiplayer_ = false;
791 }
792 
794 {
795  if(jump_to_editor_) {
796  // If no filename was specified, this will be an empty string.
797  const std::string to_open = std::exchange(jump_to_editor_, utils::nullopt).value();
798 
800  return false;
801  }
802  }
803 
804  return true;
805 }
806 
808 {
809 #if defined(__APPLE__) && TARGET_OS_IPHONE
810  throw game::mp_server_error("Starting MP server is not supported on iOS builds.");
811 #else
812  std::string wesnothd_program = "";
813  if(!prefs::get().get_mp_server_program_name().empty()) {
814  wesnothd_program = prefs::get().get_mp_server_program_name();
815  } else {
816  wesnothd_program = filesystem::get_wesnothd_name();
817  }
818 
819  std::string config = filesystem::get_user_data_dir() + "/lan_server.cfg";
821  // copy file if it isn't created yet
823  }
824 
825  LOG_GENERAL << "Starting wesnothd";
826  try
827  {
828 #if !defined(__APPLE__) && BOOST_VERSION >= 108600
829  boost::asio::io_context io_context;
830  auto c = boost::process::v2::process{io_context, wesnothd_program, { "-c", config }};
831 #else
832 #ifndef _WIN32
833  boost::process::child c(wesnothd_program, "-c", config);
834 #else
835  boost::process::child c(wesnothd_program, "-c", config, boost::process::windows::create_no_window);
836 #endif
837 #endif
838  c.detach();
839  // Give server a moment to start up
840  using namespace std::chrono_literals;
841  std::this_thread::sleep_for(50ms);
842  return;
843  }
844 #if defined(__APPLE__) || BOOST_VERSION < 108600
845  catch(const boost::process::process_error& e)
846 #else
847  catch(const std::exception& e)
848 #endif
849  {
851 
852  // Couldn't start server so throw error
853  WRN_GENERAL << "Failed to start server " << wesnothd_program << ":\n" << e.what();
854  throw game::mp_server_error("Starting MP server failed!");
855  }
856 #endif
857 }
858 
860 {
862  try {
863  if(mode == mp_mode::HOST) {
864  try {
865  start_wesnothd();
866  } catch(const game::mp_server_error&) {
868 
869  try {
870  start_wesnothd();
871  } catch(const game::mp_server_error&) {
872  return false;
873  }
874  }
875  }
876 
877  // If a server address wasn't specified, prompt for it now.
878  if(mode != mp_mode::LOCAL && multiplayer_server_.empty()) {
879  if(!gui2::dialogs::mp_connect::execute()) {
880  return false;
881  }
882 
883  // The prompt saves its input to preferences.
885 
886  if(multiplayer_server_ != prefs::get().builtin_servers_list().front().address) {
888  }
889  }
890 
891  // create_engine already calls config_manager_.load_config but maybe its better to have MULTIPLAYER
892  // defined while we are in the lobby.
894 
895  events::discard_input(); // prevent the "keylogger" effect
897 
898  if(mode == mp_mode::LOCAL) {
900  } else {
902  multiplayer_server_.clear();
903  }
904 
905  } catch(const wesnothd_rejected_client_error& e) {
906  gui2::show_error_message(e.message);
907  } catch(const game::mp_server_error& e) {
908  gui2::show_error_message(_("Error while starting server: ") + e.message);
909  } catch(const game::load_game_failed& e) {
910  gui2::show_error_message(_("The game could not be loaded: ") + e.message);
911  } catch(const game::game_error& e) {
912  gui2::show_error_message(_("Error while playing the game: ") + e.message);
913  } catch(const mapgen_exception& e) {
914  gui2::show_error_message(_("Map generator error: ") + e.message);
915  } catch(const wesnothd_error& e) {
916  if(!e.message.empty()) {
917  ERR_NET << "caught network error: " << e.message;
918 
919  std::string user_msg;
920  auto conn_err = dynamic_cast<const wesnothd_connection_error*>(&e);
921 
922  if(conn_err) {
923  // The wesnothd_connection_error subclass is only thrown with messages
924  // from boost::system::error_code which we can't translate ourselves.
925  // It's also the originator of the infamous EOF error that happens when
926  // the server dies. <https://github.com/wesnoth/wesnoth/issues/3005>. It
927  // will provide a translated string instead of that when it happens.
928  user_msg = !conn_err->user_message.empty()
929  ? conn_err->user_message
930  : _("Connection failed: ") + e.message;
931  } else {
932  // This will be a message from the server itself, which we can
933  // probably translate.
934  user_msg = translation::gettext(e.message.c_str());
935  }
936 
937  gui2::show_error_message(user_msg);
938  } else {
939  ERR_NET << "caught network error";
940  }
941  } catch(const config::error& e) {
942  if(!e.message.empty()) {
943  ERR_CONFIG << "caught config::error: " << e.message;
944  gui2::show_transient_message("", e.message);
945  } else {
946  ERR_CONFIG << "caught config::error";
947  }
948  } catch(const incorrect_map_format_error& e) {
949  gui2::show_error_message(_("The game map could not be loaded: ") + e.message);
950  } catch(savegame::load_game_exception& e) {
951  load_data_ = std::move(e.data_);
952  // this will make it so next time through the title screen loop, this game is loaded
953  } catch(const wml_exception& e) {
954  e.show();
955  } catch(const game::error& e) {
956  PLAIN_LOG << "caught game::error...";
957  gui2::show_error_message(_("Error: ") + e.message);
958  }
959 
960  return true;
961 }
962 
964 {
966  return true;
967  }
968 
969  DBG_MP << "starting multiplayer game from the commandline";
970 
972 
973  events::discard_input(); // prevent the "keylogger" effect
975 
976  try {
978  } catch(savegame::load_game_exception& e) {
979  load_data_ = std::move(e.data_);
980  return true;
981  }
982 
983  return false;
984 }
985 
987 {
988  if(!gui2::dialogs::language_selection::execute()) {
989  return false;
990  }
991 
994  }
995 
1000 
1001  return true;
1002 }
1003 
1005 {
1006  assert(!load_data_);
1007  if(play_replay_) {
1008  play_replay();
1009  return;
1010  }
1011 
1012  gui2::dialogs::loading_screen::display([this, reload]() {
1014 
1015  if(reload == reload_mode::RELOAD_DATA) {
1016  try {
1019  } catch(const config::error&) {
1020  return;
1021  }
1022  }
1023  });
1024 
1025  try {
1026  campaign_controller ccontroller(state_);
1027  ccontroller.play_game();
1028  ai::manager::singleton_ = nullptr;
1029  } catch(savegame::load_game_exception& e) {
1030  load_data_ = std::move(e.data_);
1031  // this will make it so next time through the title screen loop, this game is loaded
1032  } catch(const wml_exception& e) {
1033  e.show();
1034  } catch(const mapgen_exception& e) {
1035  gui2::show_error_message(_("Map generator error: ") + e.message);
1036  }
1037 }
1038 
1040 {
1041  assert(!load_data_);
1042  try {
1043  campaign_controller ccontroller(state_);
1044  ccontroller.play_replay();
1045  } catch(savegame::load_game_exception& e) {
1046  load_data_ = std::move(e.data_);
1047  // this will make it so next time through the title screen loop, this game is loaded
1048  } catch(const wml_exception& e) {
1049  e.show();
1050  }
1051 }
1052 
1054 {
1056  while(true) {
1058 
1060 
1062 
1063  if(res != editor::EXIT_RELOAD_DATA) {
1064  return res;
1065  }
1066 
1068  }
1069 
1070  return editor::EXIT_ERROR; // not supposed to happen
1071 }
1072 
1074 {
1075  load_data_.reset();
1076 }
1077 
1079 {
1080  try {
1082  video::deinit();
1083  } catch(std::exception& e) {
1084  ERR_GENERAL << "Suppressing exception thrown during ~game_launcher: " << e.what();
1085  } catch(...) {
1086  ERR_GENERAL << "Suppressing exception " << utils::get_unknown_exception_type() << " thrown during ~game_launcher";
1087  }
1088 }
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:321
std::vector< language_def > get_languages(bool all)
Return a list of available translations.
Definition: language.cpp:130
void init_textdomains(const game_config_view &cfg)
Initializes the list of textdomains from a configuration object.
Definition: language.cpp:359
bool load_language_list()
Definition: language.cpp:111
void set_min_translation_percent(int percent)
Definition: language.cpp:152
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:214
@ 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:763
std::string get_cache_dir()
Definition: filesystem.cpp:885
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
std::string get_user_data_dir()
Definition: filesystem.cpp:875
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:53
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:917
@ 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:440
void close_sound()
Definition: sound.cpp:492
void flush_cache()
Definition: sound.cpp:192
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:671
void set_window_icon(surface &icon)
Sets the icon of the main window.
Definition: video.cpp:677
void init(fake type)
Initialize the video subsystem.
Definition: video.cpp:90
void deinit()
Deinitialize the video subsystem.
Definition: video.cpp:119
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:67
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