The Battle for Wesnoth  1.19.20+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 
54 //
55 // HACK: MacCompileStuff is currently on 1.86, so it could use the v2 API,
56 // but we need to update the libs manually to link against boost::process.
57 //
58 // -- vultraz, 2025-05-12
59 //
60 #if BOOST_VERSION > 108600
61 #error MacCompileStuff has been updated. Remove this block and the accompanying __APPLE__ checks below.
62 #endif
63 #include <boost/process/v1/child.hpp>
64 
65 #elif BOOST_VERSION >= 108600
66 
67 // boost::asio (via boost::process) complains about winsock.h otherwise
68 #ifdef _WIN32
69 #define WIN32_LEAN_AND_MEAN
70 #endif
71 #include <boost/process/v2/process.hpp>
72 
73 #else
74 
75 // process::v1 only. The v1 folders do not exist until 1.86
76 #ifdef _WIN32
77 #include <boost/process/windows.hpp>
78 #endif
79 #include <boost/process/child.hpp>
80 
81 #endif
82 
83 #include <algorithm> // for copy, max, min, stable_sort
84 #include <new>
85 #include <thread>
86 #include <utility> // for pair
87 
88 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
89 #include "gui/widgets/debug.hpp"
90 #endif
91 
92 static lg::log_domain log_config("config");
93 #define ERR_CONFIG LOG_STREAM(err, log_config)
94 #define WRN_CONFIG LOG_STREAM(warn, log_config)
95 #define LOG_CONFIG LOG_STREAM(info, log_config)
96 
97 #define ERR_GENERAL LOG_STREAM(err, lg::general())
98 #define LOG_GENERAL LOG_STREAM(info, lg::general())
99 #define WRN_GENERAL LOG_STREAM(warn, lg::general())
100 #define DBG_GENERAL LOG_STREAM(debug, lg::general())
101 
102 #define LOG_TEST FORCE_LOG_TO(lg::general(), log_config)
103 
104 static lg::log_domain log_mp_create("mp/create");
105 #define DBG_MP LOG_STREAM(debug, log_mp_create)
106 
107 static lg::log_domain log_network("network");
108 #define ERR_NET LOG_STREAM(err, log_network)
109 
110 static lg::log_domain log_enginerefac("enginerefac");
111 #define LOG_RG LOG_STREAM(info, log_enginerefac)
112 
114  : cmdline_opts_(cmdline_opts)
115  , config_manager_(cmdline_opts)
116  , font_manager_()
117  , image_manager_()
119  , hotkey_manager_()
120  , music_thinker_()
121  , music_muter_()
122  , test_scenarios_{"test"}
123  , screenshot_map_()
124  , screenshot_filename_()
125  , state_()
126  , play_replay_(false)
127  , multiplayer_server_()
128  , jump_to_multiplayer_(false)
129  , jump_to_campaign_{}
130  , jump_to_editor_()
131  , load_data_()
132 {
133  bool no_music = false;
134  bool no_sound = false;
135 
136  if(cmdline_opts_.core_id) {
137  prefs::get().set_core(*cmdline_opts_.core_id);
138  }
139  if(cmdline_opts_.campaign) {
140  jump_to_campaign_.jump = true;
142  PLAIN_LOG << "selected campaign id: [" << jump_to_campaign_.campaign_id << "]";
143 
146  PLAIN_LOG << "selected difficulty: [" << jump_to_campaign_.difficulty << "]";
147  } else {
148  jump_to_campaign_.difficulty = -1; // let the user choose the difficulty
149  }
150 
153  PLAIN_LOG << "selected scenario id: [" << jump_to_campaign_.scenario_id << "]";
154  }
155 
158  }
159  }
160  if(cmdline_opts_.clock)
162  if(cmdline_opts_.debug) {
164  game_config::mp_debug = true;
165  }
166 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
167  if(cmdline_opts_.debug_dot_domain)
168  gui2::debug_layout_graph::set_domain(*cmdline_opts_.debug_dot_domain);
169  if(cmdline_opts_.debug_dot_level)
170  gui2::debug_layout_graph::set_level(*cmdline_opts_.debug_dot_level);
171 #endif
174  if(cmdline_opts_.fps)
175  prefs::get().set_show_fps(true);
177  prefs::get().set_fullscreen(true);
178  if(cmdline_opts_.load) {
179 #ifdef __cpp_aggregate_paren_init
180  load_data_.emplace(
182 #else
185 #endif
186  try {
187  load_data_->read_file();
188  } catch(const game::load_game_failed&) {
189  load_data_.reset();
190  }
191  }
192  if(cmdline_opts_.max_fps) {
193  prefs::get().set_refresh_rate(std::clamp(*cmdline_opts_.max_fps, 1, 1000));
194  }
196  no_sound = true;
198  }
200  gui2::new_widgets = true;
202  no_music = true;
204  no_sound = true;
206  const int xres = std::get<0>(*cmdline_opts_.resolution);
207  const int yres = std::get<1>(*cmdline_opts_.resolution);
208  if(xres > 0 && yres > 0) {
209  prefs::get().set_resolution(point(xres, yres));
210  prefs::get().set_maximized(false);
211  }
212  }
214  // TODO it could be simplified to use cmdline_opts_ directly if there is no other way to enter screenshot mode
217  no_sound = true;
219  }
220  if (cmdline_opts_.server){
221  jump_to_multiplayer_ = true;
222  // Do we have any server specified ?
223  if(!cmdline_opts_.server->empty()) {
225  } else {
226  // Pick the first server in config
227  if(game_config::server_list.size() > 0) {
229  } else {
230  multiplayer_server_ = "";
231  }
232  }
233  if(cmdline_opts_.username) {
236  if(cmdline_opts_.password) {
238  prefs::get().set_password(*cmdline_opts.server, *cmdline_opts.username, *cmdline_opts_.password);
239  }
240  }
241  }
242  if(cmdline_opts_.test) {
243  if(!cmdline_opts_.test->empty()) {
245  }
246  }
247  if(!cmdline_opts_.unit_test.empty()) {
249  }
251  prefs::get().set_fullscreen(false);
253  load_data_->show_replay = true;
256 
257  if(!cmdline_opts.nobanner) {
258  PLAIN_LOG
259  << "\nGame data: " << game_config::path
260  << "\nUser data: " << filesystem::get_user_data_dir()
261  << "\nCache: " << filesystem::get_cache_dir()
262  << "\n";
263  }
264 
265  // disable sound in nosound mode, or when sound engine failed to initialize
266  if(no_sound || ((prefs::get().sound() || prefs::get().music_on() ||
268  !sound::init_sound())) {
269  prefs::get().set_sound(false);
270  prefs::get().set_music(false);
271  prefs::get().set_turn_bell(false);
272  prefs::get().set_ui_sound(false);
273  } else if(no_music) { // else disable the music in nomusic mode
274  prefs::get().set_music(false);
275  }
276 }
277 
279 {
280  if(!::load_language_list()) {
281  return false;
282  }
283 
284  language_def locale;
285  if(cmdline_opts_.language) {
286  std::vector<language_def> langs = get_languages(true);
287  for(const language_def& def : langs) {
288  if(def.localename == *cmdline_opts_.language) {
289  locale = def;
290  break;
291  }
292  }
293  if(locale.localename.empty()) {
294  PLAIN_LOG << "Language symbol '" << *cmdline_opts_.language << "' not found.";
295  return false;
296  }
297  } else {
298  locale = get_locale();
299  }
300  ::set_language(locale);
301 
302  return true;
303 }
304 
306 {
307  // Handle special commandline launch flags
312  {
318  {
319  PLAIN_LOG << "--nogui flag is only valid with --multiplayer or --screenshot or --plugin flags";
320  return false;
321  }
323  // Screenshots require a rendering context, and thus a window,
324  // so we create one but hidden.
326  } else {
327  // Other functions don't require a window at all.
329  }
330  return true;
331  }
332 
333  // Initialize video subsystem, and create a new window.
334  video::init();
335 
336  // Set window title and icon
338 
339 #if !(defined(__APPLE__))
340  surface icon(image::get_surface(image::locator{"icons/icon-game.png"}, image::UNSCALED));
341  if(icon != nullptr) {
343  }
344 #endif
345  return true;
346 }
347 
349 {
350  bool error = false;
351 
352  if(!cmdline_opts_.nobanner) {
353  STREAMING_LOG << "Checking lua scripts... ";
354  }
355 
357  // load the "package" package, so that scripts can get what packages they want
359  }
360 
362  std::string filename = *cmdline_opts_.plugin_file;
363 
364  PLAIN_LOG << "Loading a plugin file'" << filename << "'...";
365 
367 
368  try {
369  if(sf->fail()) {
370  throw std::runtime_error("failed to open plugin file");
371  }
372 
373  /* Cancel all "jumps" to editor / campaign / multiplayer */
374  jump_to_multiplayer_ = false;
375  jump_to_editor_ = utils::nullopt;
376  jump_to_campaign_.jump = false;
377 
378  std::string full_plugin((std::istreambuf_iterator<char>(*sf)), std::istreambuf_iterator<char>());
379 
381 
382  std::size_t i = pm.add_plugin(filename, full_plugin);
383 
384  for(std::size_t j = 0; j < pm.size(); ++j) {
385  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
386  }
387 
388  PLAIN_LOG << "Starting a plugin...";
389  pm.start_plugin(i);
390 
391  for(std::size_t j = 0; j < pm.size(); ++j) {
392  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
393  }
394 
395  plugins_context pc("init");
396 
397  for(std::size_t repeat = 0; repeat < 5; ++repeat) {
398  PLAIN_LOG << "Playing a slice...";
399  pc.play_slice();
400 
401  for(std::size_t j = 0; j < pm.size(); ++j) {
402  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
403  }
404  }
405 
406  return true;
407  } catch(const std::exception& e) {
408  gui2::show_error_message(std::string("When loading a plugin, error:\n") + e.what());
409  error = true;
410  }
411  }
412 
413  if(!error && !cmdline_opts_.nobanner) {
414  PLAIN_LOG << "ok";
415  }
416 
417  return !error;
418 }
419 
420 void game_launcher::set_test(const std::string& id)
421 {
422  state_.clear();
423  state_.classification().type = campaign_type::type::test;
425  state_.classification().era_id = "era_default";
426 
427  state_.set_carryover_sides_start(config{"next_scenario", id});
428 }
429 
431 {
432  // This first_time variable was added in 70f3c80a3e2 so that using the GUI
433  // menu to load a game works. That seems to have edge-cases, for example if
434  // you try to load a game a second time then Wesnoth exits.
435  static bool first_time = true;
436 
437  if(!cmdline_opts_.test) {
438  return true;
439  }
440 
441  if(!first_time) {
442  return false;
443  }
444 
445  first_time = false;
446 
447  if(test_scenarios_.size() == 0) {
448  // shouldn't happen, as test_scenarios_ is initialised to {"test"}
449  PLAIN_LOG << "Error in the test handling code";
450  return false;
451  }
452 
453  if(test_scenarios_.size() > 1) {
454  PLAIN_LOG << "You can't run more than one unit test in interactive mode";
455  }
456 
457  set_test(test_scenarios_.at(0));
458 
460 
461  try {
462  campaign_controller ccontroller(state_);
463  ccontroller.play_game();
464  } catch(savegame::load_game_exception& e) {
465  load_data_ = std::move(e.data_);
466  return true;
467  }
468 
469  return false;
470 }
471 
472 /**
473  * Runs unit tests specified on the command line.
474  *
475  * If multiple unit tests were specified, then this will stop at the first test
476  * which returns a non-zero status.
477  */
478 // Same as play_test except that we return the results of play_game.
479 // \todo "same ... except" ... and many other changes, such as testing the replay
481 {
482  // There's no copy of play_test's first_time variable. That seems to be for handling
483  // the player loading a game via the GUI, which makes no sense in a non-interactive test.
484  if(cmdline_opts_.unit_test.empty()) {
486  }
487 
488  auto ret = unit_test_result::TEST_FAIL; // will only be returned if no test is run
489  for(const auto& scenario : test_scenarios_) {
490  set_test(scenario);
491  ret = single_unit_test();
492  const char* describe_result;
493  switch(ret) {
495  describe_result = "PASS TEST";
496  break;
498  describe_result = "FAIL TEST (INVALID REPLAY)";
499  break;
501  describe_result = "FAIL TEST (ERRORED REPLAY)";
502  break;
504  describe_result = "FAIL TEST (WML EXCEPTION)";
505  break;
507  describe_result = "FAIL TEST (DEFEAT)";
508  break;
510  describe_result = "PASS TEST (VICTORY)";
511  break;
513  describe_result = "BROKE STRICT (PASS)";
514  break;
516  describe_result = "BROKE STRICT (FAIL)";
517  break;
519  describe_result = "BROKE STRICT (DEFEAT)";
520  break;
522  describe_result = "BROKE STRICT (VICTORY)";
523  break;
524  default:
525  describe_result = "FAIL TEST (UNKNOWN)";
526  break;
527  }
528 
529  PLAIN_LOG << describe_result << " (" << int(ret) << "): " << scenario;
530  if(ret != unit_test_result::TEST_PASS) {
531  break;
532  }
533  }
534 
535  return ret;
536 }
537 
539 {
541 
542  level_result::type game_res = level_result::type::fail;
543  try {
544  campaign_controller ccontroller(state_, true);
545  game_res = ccontroller.play_game();
546  if(game_res == level_result::type::fail) {
547  if(lg::broke_strict()) {
549  } else {
551  }
552  }
553  } catch(const wml_exception& e) {
554  PLAIN_LOG << "Caught WML Exception:" << e.dev_message;
556  }
557 
559 
561  return pass_victory_or_defeat(game_res);
562  }
563 
565  save.save_game_automatic(false, "unit_test_replay");
566 
567 #ifdef __cpp_aggregate_paren_init
568  load_data_.emplace(
569  savegame::save_index_class::default_saves_dir(), save.filename(), "", true, true, false);
570 #else
572  savegame::save_index_class::default_saves_dir(), save.filename(), "", true, true, false};
573 #endif
574  load_data_->read_file();
575 
576  if(!load_prepared_game()) {
577  PLAIN_LOG << "Failed to load the replay!";
578  return unit_test_result::TEST_FAIL_LOADING_REPLAY; // failed to load replay
579  }
580 
581  try {
582  const bool was_strict_broken = lg::broke_strict();
583  campaign_controller ccontroller(state_, true);
584  ccontroller.play_replay();
585  if(!was_strict_broken && lg::broke_strict()) {
586  PLAIN_LOG << "Observed failure on replay";
588  }
589  } catch(const wml_exception& e) {
590  PLAIN_LOG << "WML Exception while playing replay: " << e.dev_message;
592  }
593 
594  return pass_victory_or_defeat(game_res);
595 }
596 
598 {
599  if(res == level_result::type::defeat) {
600  if(lg::broke_strict()) {
602  } else {
604  }
605  } else if(res == level_result::type::victory) {
606  if(lg::broke_strict()) {
608  } else {
610  }
611  }
612 
613  if(lg::broke_strict()) {
615  } else {
617  }
618 }
619 
621 {
623  return true;
624  }
625 
627 
629 
631  if (res != editor::EXIT_NORMAL) {
632  PLAIN_LOG << "Error while taking screenshot of map: " << screenshot_map_;
633  }
634  return false;
635 }
636 
638 {
640  return true;
641  }
642 
643  state_.classification().type = campaign_type::type::multiplayer;
644  DBG_GENERAL << "Current campaign type: " << campaign_type::get_string(state_.classification().type);
645 
646  try {
648  } catch(const config::error& e) {
649  PLAIN_LOG << "Error loading game config: " << e.what();
650  return false;
651  }
652 
653  // A default output filename
654  std::string outfile = "wesnoth_image.png";
655 
656  // If a output path was given as an argument, use that instead
658  outfile = *cmdline_opts_.render_image_dst;
659  }
660 
662  exit(1);
663  }
664 
665  return false;
666 }
667 
669 {
670  return load_data_.has_value();
671 }
672 
674 {
675  return std::exchange(load_data_, utils::nullopt).value();
676 }
677 
679 {
680  DBG_GENERAL << "Current campaign type: " << campaign_type::get_string(state_.classification().type);
681  assert(!load_data_);
682 
683  // FIXME: evaluate which of these damn catch blocks are still necessary
684  try {
686 
687  } catch(const config::error& e) {
688  if(e.message.empty()) {
689  gui2::show_error_message(_("The file you have tried to load is corrupt"));
690  } else {
691  gui2::show_error_message(_("The file you have tried to load is corrupt: '") + e.message + '\'');
692  }
693 
694  return false;
695  } catch(const wml_exception& e) {
696  e.show();
697  return false;
698  } catch(const filesystem::io_exception& e) {
699  if(e.message.empty()) {
700  gui2::show_error_message(_("File I/O Error while reading the game"));
701  } else {
702  gui2::show_error_message(_("File I/O Error while reading the game: '") + e.message + '\'');
703  }
704 
705  return false;
706  } catch(const game::error& e) {
707  if(e.message.empty()) {
708  gui2::show_error_message(_("The file you have tried to load is corrupt"));
709  } else {
710  gui2::show_error_message(_("The file you have tried to load is corrupt: '") + e.message + '\'');
711  }
712 
713  return false;
714  }
715 
716  // If the player canceled loading, we won't have any load data at this point
717  return has_load_data();
718 }
719 
721 {
722  auto load_data = extract_load_data();
724 
725  play_replay_ = load_data.show_replay;
726 
727  // in case show_replay && is_start_of_scenario
728  // there won't be any turns to replay, but the
729  // user gets to watch the intro sequence again ...
730  if(load_data.show_replay && !state_.is_start_of_scenario()) {
732  }
733 
734  if(load_data.cancel_orders) {
736  }
737 
738  try {
740  } catch(const config::error&) {
741  return false;
742  }
743 
744  return true;
745 }
746 
748 {
749  state_.clear();
750  state_.classification().type = campaign_type::type::scenario;
751  play_replay_ = false;
752 
754 }
755 
757 {
759 }
760 
762  jump_to_campaign_.jump = false;
763  if(new_campaign()) {
766  return true;
767  }
768  return false;
769 }
770 
772 {
773  if(jump_to_campaign_.jump) {
774  return play_campaign();
775  }
776 
777  return true;
778 }
779 
781 {
782  if(!jump_to_multiplayer_) {
783  return true;
784  }
785 
786  jump_to_multiplayer_ = false;
788 }
789 
791 {
792  if(jump_to_editor_) {
793  // If no filename was specified, this will be an empty string.
794  const std::string to_open = std::exchange(jump_to_editor_, utils::nullopt).value();
795 
797  return false;
798  }
799  }
800 
801  return true;
802 }
803 
805 {
806  std::string wesnothd_program = "";
807  if(!prefs::get().get_mp_server_program_name().empty()) {
808  wesnothd_program = prefs::get().get_mp_server_program_name();
809  } else {
810  wesnothd_program = filesystem::get_wesnothd_name();
811  }
812 
813  std::string config = filesystem::get_user_data_dir() + "/lan_server.cfg";
815  // copy file if it isn't created yet
817  }
818 
819  LOG_GENERAL << "Starting wesnothd";
820  try
821  {
822 #if !defined(__APPLE__) && BOOST_VERSION >= 108600
823  boost::asio::io_context io_context;
824  auto c = boost::process::v2::process{io_context, wesnothd_program, { "-c", config }};
825 #else
826 #ifndef _WIN32
827  boost::process::child c(wesnothd_program, "-c", config);
828 #else
829  boost::process::child c(wesnothd_program, "-c", config, boost::process::windows::create_no_window);
830 #endif
831 #endif
832  c.detach();
833  // Give server a moment to start up
834  using namespace std::chrono_literals;
835  std::this_thread::sleep_for(50ms);
836  return;
837  }
838 #if defined(__APPLE__) || BOOST_VERSION < 108600
839  catch(const boost::process::process_error& e)
840 #else
841  catch(const std::exception& e)
842 #endif
843  {
845 
846  // Couldn't start server so throw error
847  WRN_GENERAL << "Failed to start server " << wesnothd_program << ":\n" << e.what();
848  throw game::mp_server_error("Starting MP server failed!");
849  }
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:329
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:367
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