The Battle for Wesnoth  1.19.15+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_config_manager.hpp" // for game_config_manager
28 #include "game_initialization/multiplayer.hpp" // for start_client, etc
29 #include "game_initialization/playcampaign.hpp" // for play_game, etc
30 #include "game_initialization/singleplayer.hpp" // for sp_create_mode
31 #include "generators/map_generator.hpp" // for mapgen_exception
32 #include "gettext.hpp" // for _
33 #include "gui/dialogs/language_selection.hpp" // for language_selection
35 #include "gui/dialogs/message.hpp" // for show error message
37 #include "gui/dialogs/title_screen.hpp" // for show_debug_clock_button
38 #include "gui/dialogs/transient_message.hpp" // for show_transient_message
39 #include "gui/widgets/settings.hpp" // for new_widgets
40 #include "language.hpp" // for language_def, etc
41 #include "log.hpp" // for LOG_STREAM, logger, general, etc
42 #include "map/exception.hpp"
44 #include "save_index.hpp"
46 #include "sdl/surface.hpp" // for surface
47 #include "serialization/compression.hpp" // for format::NONE
48 #include "tstring.hpp" // for operator==, operator!=
49 #include "video.hpp"
51 #include "wml_exception.hpp" // for wml_exception
52 
53 #ifdef __APPLE__
54 
55 //
56 // HACK: MacCompileStuff is currently on 1.86, so it could use the v2 API,
57 // but we need to update the libs manually to link against boost::process.
58 //
59 // -- vultraz, 2025-05-12
60 //
61 #if BOOST_VERSION > 108600
62 #error MacCompileStuff has been updated. Remove this block and the accompanying __APPLE__ checks below.
63 #endif
64 #include <boost/process/v1/child.hpp>
65 
66 #elif BOOST_VERSION >= 108600
67 
68 // boost::asio (via boost::process) complains about winsock.h otherwise
69 #ifdef _WIN32
70 #define WIN32_LEAN_AND_MEAN
71 #endif
72 #include <boost/process/v2/process.hpp>
73 
74 #else
75 
76 // process::v1 only. The v1 folders do not exist until 1.86
77 #ifdef _WIN32
78 #include <boost/process/windows.hpp>
79 #endif
80 #include <boost/process/child.hpp>
81 
82 #endif
83 
84 #include <algorithm> // for copy, max, min, stable_sort
85 #include <new>
86 #include <thread>
87 #include <utility> // for pair
88 
89 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
90 #include "gui/widgets/debug.hpp"
91 #endif
92 
93 static lg::log_domain log_config("config");
94 #define ERR_CONFIG LOG_STREAM(err, log_config)
95 #define WRN_CONFIG LOG_STREAM(warn, log_config)
96 #define LOG_CONFIG LOG_STREAM(info, log_config)
97 
98 #define ERR_GENERAL LOG_STREAM(err, lg::general())
99 #define LOG_GENERAL LOG_STREAM(info, lg::general())
100 #define WRN_GENERAL LOG_STREAM(warn, lg::general())
101 #define DBG_GENERAL LOG_STREAM(debug, lg::general())
102 
103 #define LOG_TEST FORCE_LOG_TO(lg::general(), log_config)
104 
105 static lg::log_domain log_mp_create("mp/create");
106 #define DBG_MP LOG_STREAM(debug, log_mp_create)
107 
108 static lg::log_domain log_network("network");
109 #define ERR_NET LOG_STREAM(err, log_network)
110 
111 static lg::log_domain log_enginerefac("enginerefac");
112 #define LOG_RG LOG_STREAM(info, log_enginerefac)
113 
115  : cmdline_opts_(cmdline_opts)
116  , font_manager_()
117  , image_manager_()
118  , main_event_context_()
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  return false;
632 }
633 
635 {
637  return true;
638  }
639 
640  state_.classification().type = campaign_type::type::multiplayer;
641  DBG_GENERAL << "Current campaign type: " << campaign_type::get_string(state_.classification().type);
642 
643  try {
645  } catch(const config::error& e) {
646  PLAIN_LOG << "Error loading game config: " << e.what();
647  return false;
648  }
649 
650  // A default output filename
651  std::string outfile = "wesnoth_image.png";
652 
653  // If a output path was given as an argument, use that instead
655  outfile = *cmdline_opts_.render_image_dst;
656  }
657 
659  exit(1);
660  }
661 
662  return false;
663 }
664 
666 {
667  return load_data_.has_value();
668 }
669 
671 {
672  return std::exchange(load_data_, utils::nullopt).value();
673 }
674 
676 {
677  DBG_GENERAL << "Current campaign type: " << campaign_type::get_string(state_.classification().type);
678  assert(!load_data_);
679 
680  // FIXME: evaluate which of these damn catch blocks are still necessary
681  try {
683 
684  } catch(const config::error& e) {
685  if(e.message.empty()) {
686  gui2::show_error_message(_("The file you have tried to load is corrupt"));
687  } else {
688  gui2::show_error_message(_("The file you have tried to load is corrupt: '") + e.message + '\'');
689  }
690 
691  return false;
692  } catch(const wml_exception& e) {
693  e.show();
694  return false;
695  } catch(const filesystem::io_exception& e) {
696  if(e.message.empty()) {
697  gui2::show_error_message(_("File I/O Error while reading the game"));
698  } else {
699  gui2::show_error_message(_("File I/O Error while reading the game: '") + e.message + '\'');
700  }
701 
702  return false;
703  } catch(const game::error& e) {
704  if(e.message.empty()) {
705  gui2::show_error_message(_("The file you have tried to load is corrupt"));
706  } else {
707  gui2::show_error_message(_("The file you have tried to load is corrupt: '") + e.message + '\'');
708  }
709 
710  return false;
711  }
712 
713  // If the player canceled loading, we won't have any load data at this point
714  return has_load_data();
715 }
716 
718 {
719  auto load_data = extract_load_data();
721 
722  play_replay_ = load_data.show_replay;
723 
724  // in case show_replay && is_start_of_scenario
725  // there won't be any turns to replay, but the
726  // user gets to watch the intro sequence again ...
727  if(load_data.show_replay && !state_.is_start_of_scenario()) {
729  }
730 
731  if(load_data.cancel_orders) {
733  }
734 
735  try {
737  } catch(const config::error&) {
738  return false;
739  }
740 
741  return true;
742 }
743 
745 {
746  state_.clear();
747  state_.classification().type = campaign_type::type::scenario;
748  play_replay_ = false;
749 
751 }
752 
754 {
756 }
757 
759  jump_to_campaign_.jump = false;
760  if(new_campaign()) {
763  return true;
764  }
765  return false;
766 }
767 
769 {
770  if(jump_to_campaign_.jump) {
771  return play_campaign();
772  }
773 
774  return true;
775 }
776 
778 {
779  if(!jump_to_multiplayer_) {
780  return true;
781  }
782 
783  jump_to_multiplayer_ = false;
785 }
786 
788 {
789  if(jump_to_editor_) {
790  // If no filename was specified, this will be an empty string.
791  const std::string to_open = std::exchange(jump_to_editor_, utils::nullopt).value();
792 
794  return false;
795  }
796  }
797 
798  return true;
799 }
800 
802 {
803  std::string wesnothd_program = "";
804  if(!prefs::get().get_mp_server_program_name().empty()) {
805  wesnothd_program = prefs::get().get_mp_server_program_name();
806  } else {
807  wesnothd_program = filesystem::get_wesnothd_name();
808  }
809 
810  std::string config = filesystem::get_user_data_dir() + "/lan_server.cfg";
812  // copy file if it isn't created yet
814  }
815 
816  LOG_GENERAL << "Starting wesnothd";
817  try
818  {
819 #if !defined(__APPLE__) && BOOST_VERSION >= 108600
820  boost::asio::io_context io_context;
821  auto c = boost::process::v2::process{io_context, wesnothd_program, { "-c", config }};
822 #else
823 #ifndef _WIN32
824  boost::process::child c(wesnothd_program, "-c", config);
825 #else
826  boost::process::child c(wesnothd_program, "-c", config, boost::process::windows::create_no_window);
827 #endif
828 #endif
829  c.detach();
830  // Give server a moment to start up
831  using namespace std::chrono_literals;
832  std::this_thread::sleep_for(50ms);
833  return;
834  }
835 #if defined(__APPLE__) || BOOST_VERSION < 108600
836  catch(const boost::process::process_error& e)
837 #else
838  catch(const std::exception& e)
839 #endif
840  {
842 
843  // Couldn't start server so throw error
844  WRN_GENERAL << "Failed to start server " << wesnothd_program << ":\n" << e.what();
845  throw game::mp_server_error("Starting MP server failed!");
846  }
847 }
848 
850 {
852  try {
853  if(mode == mp_mode::HOST) {
854  try {
855  start_wesnothd();
856  } catch(const game::mp_server_error&) {
858 
859  try {
860  start_wesnothd();
861  } catch(const game::mp_server_error&) {
862  return false;
863  }
864  }
865  }
866 
867  // If a server address wasn't specified, prompt for it now.
868  if(mode != mp_mode::LOCAL && multiplayer_server_.empty()) {
869  if(!gui2::dialogs::mp_connect::execute()) {
870  return false;
871  }
872 
873  // The prompt saves its input to preferences.
875 
876  if(multiplayer_server_ != prefs::get().builtin_servers_list().front().address) {
878  }
879  }
880 
881  // create_engine already calls game_config_manager::get()->load_config but maybe its better to have MULTIPLAYER
882  // defined while we are in the lobby.
884 
885  events::discard_input(); // prevent the "keylogger" effect
887 
888  if(mode == mp_mode::LOCAL) {
890  } else {
892  multiplayer_server_.clear();
893  }
894 
895  } catch(const wesnothd_rejected_client_error& e) {
896  gui2::show_error_message(e.message);
897  } catch(const game::mp_server_error& e) {
898  gui2::show_error_message(_("Error while starting server: ") + e.message);
899  } catch(const game::load_game_failed& e) {
900  gui2::show_error_message(_("The game could not be loaded: ") + e.message);
901  } catch(const game::game_error& e) {
902  gui2::show_error_message(_("Error while playing the game: ") + e.message);
903  } catch(const mapgen_exception& e) {
904  gui2::show_error_message(_("Map generator error: ") + e.message);
905  } catch(const wesnothd_error& e) {
906  if(!e.message.empty()) {
907  ERR_NET << "caught network error: " << e.message;
908 
909  std::string user_msg;
910  auto conn_err = dynamic_cast<const wesnothd_connection_error*>(&e);
911 
912  if(conn_err) {
913  // The wesnothd_connection_error subclass is only thrown with messages
914  // from boost::system::error_code which we can't translate ourselves.
915  // It's also the originator of the infamous EOF error that happens when
916  // the server dies. <https://github.com/wesnoth/wesnoth/issues/3005>. It
917  // will provide a translated string instead of that when it happens.
918  user_msg = !conn_err->user_message.empty()
919  ? conn_err->user_message
920  : _("Connection failed: ") + e.message;
921  } else {
922  // This will be a message from the server itself, which we can
923  // probably translate.
924  user_msg = translation::gettext(e.message.c_str());
925  }
926 
927  gui2::show_error_message(user_msg);
928  } else {
929  ERR_NET << "caught network error";
930  }
931  } catch(const config::error& e) {
932  if(!e.message.empty()) {
933  ERR_CONFIG << "caught config::error: " << e.message;
934  gui2::show_transient_message("", e.message);
935  } else {
936  ERR_CONFIG << "caught config::error";
937  }
938  } catch(const incorrect_map_format_error& e) {
939  gui2::show_error_message(_("The game map could not be loaded: ") + e.message);
940  } catch(savegame::load_game_exception& e) {
941  load_data_ = std::move(e.data_);
942  // this will make it so next time through the title screen loop, this game is loaded
943  } catch(const wml_exception& e) {
944  e.show();
945  } catch(const game::error& e) {
946  PLAIN_LOG << "caught game::error...";
947  gui2::show_error_message(_("Error: ") + e.message);
948  }
949 
950  return true;
951 }
952 
954 {
956  return true;
957  }
958 
959  DBG_MP << "starting multiplayer game from the commandline";
960 
962 
963  events::discard_input(); // prevent the "keylogger" effect
965 
966  try {
968  } catch(savegame::load_game_exception& e) {
969  load_data_ = std::move(e.data_);
970  return true;
971  }
972 
973  return false;
974 }
975 
977 {
978  if(!gui2::dialogs::language_selection::execute()) {
979  return false;
980  }
981 
984  }
985 
990 
991  return true;
992 }
993 
995 {
996  assert(!load_data_);
997  if(play_replay_) {
998  play_replay();
999  return;
1000  }
1001 
1002  gui2::dialogs::loading_screen::display([this, reload]() {
1004 
1005  if(reload == reload_mode::RELOAD_DATA) {
1006  try {
1009  } catch(const config::error&) {
1010  return;
1011  }
1012  }
1013  });
1014 
1015  try {
1016  campaign_controller ccontroller(state_);
1017  ccontroller.play_game();
1018  ai::manager::singleton_ = nullptr;
1019  } catch(savegame::load_game_exception& e) {
1020  load_data_ = std::move(e.data_);
1021  // this will make it so next time through the title screen loop, this game is loaded
1022  } catch(const wml_exception& e) {
1023  e.show();
1024  } catch(const mapgen_exception& e) {
1025  gui2::show_error_message(_("Map generator error: ") + e.message);
1026  }
1027 }
1028 
1030 {
1031  assert(!load_data_);
1032  try {
1033  campaign_controller ccontroller(state_);
1034  ccontroller.play_replay();
1035  } catch(savegame::load_game_exception& e) {
1036  load_data_ = std::move(e.data_);
1037  // this will make it so next time through the title screen loop, this game is loaded
1038  } catch(const wml_exception& e) {
1039  e.show();
1040  }
1041 }
1042 
1044 {
1046  while(true) {
1048 
1050 
1052 
1053  if(res != editor::EXIT_RELOAD_DATA) {
1054  return res;
1055  }
1056 
1058  }
1059 
1060  return editor::EXIT_ERROR; // not supposed to happen
1061 }
1062 
1064 {
1065  load_data_.reset();
1066 }
1067 
1069 {
1070  try {
1072  video::deinit();
1073  } catch(std::exception& e) {
1074  ERR_GENERAL << "Suppressing exception thrown during ~game_launcher: " << e.what();
1075  } catch(...) {
1076  ERR_GENERAL << "Suppressing exception " << utils::get_unknown_exception_type() << " thrown during ~game_launcher";
1077  }
1078 }
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:158
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.
static game_config_manager * get()
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)
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)
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:295
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).
Declarations for File-IO.
std::size_t i
Definition: function.cpp:1032
#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:178
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:882
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
std::string get_user_data_dir()
Definition: filesystem.cpp:872
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:341
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
Game configuration data as global variables.
Definition: build_info.cpp:61
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:683
save_result save_image(const locator &i_locator, const std::string &filename)
Definition: picture.cpp:910
@ 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:278
utils::optional< load_game_metadata > load_interactive()
Definition: savegame.cpp:205
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