The Battle for Wesnoth  1.19.26+dev
game_launcher.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "game_launcher.hpp"
17 #include "game_errors.hpp"
18 
19 #include "ai/manager.hpp" // for manager
20 #include "commandline_options.hpp" // for commandline_options
21 #include "config.hpp" // for config, etc
22 #include "cursor.hpp" // for set, CURSOR_TYPE::NORMAL
23 #include "exceptions.hpp" // for error
24 #include "filesystem.hpp" // for get_user_data_dir, etc
25 #include "game_classification.hpp" // for game_classification, etc
26 #include "game_config.hpp" // for path, etc
27 #include "game_initialization/multiplayer.hpp" // for start_client, etc
28 #include "game_initialization/playcampaign.hpp" // for play_game, etc
29 #include "game_initialization/singleplayer.hpp" // for sp_create_mode
30 #include "generators/map_generator.hpp" // for mapgen_exception
31 #include "gettext.hpp" // for _
32 #include "gui/dialogs/language_selection.hpp" // for language_selection
34 #include "gui/dialogs/message.hpp" // for show error message
36 #include "gui/dialogs/title_screen.hpp" // for show_debug_clock_button
37 #include "gui/dialogs/transient_message.hpp" // for show_transient_message
38 #include "gui/widgets/settings.hpp" // for new_widgets
39 #include "language.hpp" // for language_def, etc
40 #include "log.hpp" // for LOG_STREAM, logger, general, etc
41 #include "map/exception.hpp"
43 #include "save_index.hpp"
45 #include "sdl/surface.hpp" // for surface
46 #include "serialization/compression.hpp" // for format::NONE
47 #include "tstring.hpp" // for operator==, operator!=
48 #include "video.hpp"
50 #include "wml_exception.hpp" // for wml_exception
51 
52 #ifdef __APPLE__
53 #include <TargetConditionals.h>
54 #endif
55 
56 #if BOOST_VERSION >= 108600
57 
58 // boost::asio (via boost::process) complains about winsock.h otherwise
59 #ifdef _WIN32
60 #define WIN32_LEAN_AND_MEAN
61 #endif
62 #include <boost/process/v2/process.hpp>
63 
64 #else
65 
66 // process::v1 only. The v1 folders do not exist until 1.86
67 #ifdef _WIN32
68 #include <boost/process/windows.hpp>
69 #endif
70 #include <boost/process/child.hpp>
71 
72 #endif
73 
74 #include <algorithm> // for copy, max, min, stable_sort
75 #include <new>
76 #include <thread>
77 #include <utility> // for pair
78 
79 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
80 #include "gui/widgets/debug.hpp"
81 #endif
82 
83 static lg::log_domain log_config("config");
84 #define ERR_CONFIG LOG_STREAM(err, log_config)
85 #define WRN_CONFIG LOG_STREAM(warn, log_config)
86 #define LOG_CONFIG LOG_STREAM(info, log_config)
87 
88 #define ERR_GENERAL LOG_STREAM(err, lg::general())
89 #define LOG_GENERAL LOG_STREAM(info, lg::general())
90 #define WRN_GENERAL LOG_STREAM(warn, lg::general())
91 #define DBG_GENERAL LOG_STREAM(debug, lg::general())
92 
93 #define LOG_TEST FORCE_LOG_TO(lg::general(), log_config)
94 
95 static lg::log_domain log_mp_create("mp/create");
96 #define DBG_MP LOG_STREAM(debug, log_mp_create)
97 
98 static lg::log_domain log_network("network");
99 #define ERR_NET LOG_STREAM(err, log_network)
100 
101 static lg::log_domain log_enginerefac("enginerefac");
102 #define LOG_RG LOG_STREAM(info, log_enginerefac)
103 
105  : cmdline_opts_(cmdline_opts)
106  , config_manager_(cmdline_opts)
107  , font_manager_()
108  , image_manager_()
110  , hotkey_manager_()
111  , music_thinker_()
112  , music_muter_()
113  , test_scenarios_{"test"}
114  , screenshot_map_()
115  , screenshot_filename_()
116  , state_()
117  , play_replay_(false)
118  , multiplayer_server_()
119  , jump_to_multiplayer_(false)
120  , jump_to_campaign_{}
121  , jump_to_editor_()
122  , load_data_()
123 {
124  bool no_music = false;
125  bool no_sound = false;
126 
127  if(cmdline_opts_.core_id) {
128  prefs::get().set_core(*cmdline_opts_.core_id);
129  }
130  if(cmdline_opts_.campaign) {
131  jump_to_campaign_.jump = true;
133  PLAIN_LOG << "selected campaign id: [" << jump_to_campaign_.campaign_id << "]";
134 
137  PLAIN_LOG << "selected difficulty: [" << jump_to_campaign_.difficulty << "]";
138  } else {
139  jump_to_campaign_.difficulty = -1; // let the user choose the difficulty
140  }
141 
144  PLAIN_LOG << "selected scenario id: [" << jump_to_campaign_.scenario_id << "]";
145  }
146 
149  }
150  }
151  if(cmdline_opts_.clock)
153  if(cmdline_opts_.debug) {
155  game_config::mp_debug = true;
156  }
157 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
158  if(cmdline_opts_.debug_dot_domain)
159  gui2::debug_layout_graph::set_domain(*cmdline_opts_.debug_dot_domain);
160  if(cmdline_opts_.debug_dot_level)
161  gui2::debug_layout_graph::set_level(*cmdline_opts_.debug_dot_level);
162 #endif
165  if(cmdline_opts_.fps)
166  prefs::get().set_show_fps(true);
168  prefs::get().set_fullscreen(true);
169  if(cmdline_opts_.load) {
170 #ifdef __cpp_aggregate_paren_init
171  load_data_.emplace(
173 #else
176 #endif
177  try {
178  load_data_->read_file();
179  } catch(const game::load_game_failed&) {
180  load_data_.reset();
181  }
182  }
183  if(cmdline_opts_.max_fps) {
184  prefs::get().set_refresh_rate(std::clamp(*cmdline_opts_.max_fps, 1, 1000));
185  }
187  no_sound = true;
189  }
191  gui2::new_widgets = true;
193  no_music = true;
195  no_sound = true;
197  const int xres = std::get<0>(*cmdline_opts_.resolution);
198  const int yres = std::get<1>(*cmdline_opts_.resolution);
199  if(xres > 0 && yres > 0) {
200  prefs::get().set_resolution(point(xres, yres));
201  prefs::get().set_maximized(false);
202  }
203  }
205  // TODO it could be simplified to use cmdline_opts_ directly if there is no other way to enter screenshot mode
208  no_sound = true;
210  }
211  if (cmdline_opts_.server){
212  jump_to_multiplayer_ = true;
213  // Do we have any server specified ?
214  if(!cmdline_opts_.server->empty()) {
216  } else {
217  // Pick the first server in config
218  if(game_config::server_list.size() > 0) {
220  } else {
221  multiplayer_server_ = "";
222  }
223  }
224  if(cmdline_opts_.username) {
227  if(cmdline_opts_.password) {
229  prefs::get().set_password(*cmdline_opts.server, *cmdline_opts.username, *cmdline_opts_.password);
230  }
231  }
232  }
233  if(cmdline_opts_.test) {
234  if(!cmdline_opts_.test->empty()) {
236  }
237  }
238  if(!cmdline_opts_.unit_test.empty()) {
240  }
242  prefs::get().set_fullscreen(false);
244  load_data_->show_replay = true;
247 
248  if(!cmdline_opts.nobanner) {
249  PLAIN_LOG
250  << "\nGame data: " << game_config::path
251  << "\nUser data: " << filesystem::get_user_data_dir()
252  << "\nCache: " << filesystem::get_cache_dir()
253  << "\n";
254  }
255 
256  // disable sound in nosound mode, or when sound engine failed to initialize
257  if(no_sound || !sound::init_sound()) {
258  prefs::get().set_sound(false);
259  prefs::get().set_music(false);
260  prefs::get().set_turn_bell(false);
261  prefs::get().set_ui_sound(false);
262  } else if(no_music) { // else disable the music in nomusic mode
263  prefs::get().set_music(false);
264  }
265 }
266 
268 {
269  if(!::load_language_list()) {
270  return false;
271  }
272 
273  language_def locale;
274  if(cmdline_opts_.language) {
275  std::vector<language_def> langs = get_languages(true);
276  for(const language_def& def : langs) {
277  if(def.localename == *cmdline_opts_.language) {
278  locale = def;
279  break;
280  }
281  }
282  if(locale.localename.empty()) {
283  PLAIN_LOG << "Language symbol '" << *cmdline_opts_.language << "' not found.";
284  return false;
285  }
286  } else {
287  locale = get_locale();
288  }
289  ::set_language(locale);
290 
291  return true;
292 }
293 
295 {
296  // Handle special commandline launch flags
301  {
307  {
308  PLAIN_LOG << "--nogui flag is only valid with --multiplayer or --screenshot or --plugin flags";
309  return false;
310  }
312  // Screenshots require a rendering context, and thus a window,
313  // so we create one but hidden.
315  } else {
316  // Other functions don't require a window at all.
318  }
319  return true;
320  }
321 
322  // Initialize video subsystem, and create a new window.
323  video::init();
324 
325  // Set window title and icon
327 
328 #if !(defined(__APPLE__))
329  surface icon(image::get_surface(image::locator{"icons/icon-game.png"}, image::UNSCALED));
330  if(icon != nullptr) {
332  }
333 #endif
334 
335 #ifndef __ANDROID__
336  SDL_StartTextInput(video::get_window());
337 #endif
338 
339  return true;
340 }
341 
343 {
344  bool error = false;
345 
346  if(!cmdline_opts_.nobanner) {
347  STREAMING_LOG << "Checking lua scripts... ";
348  }
349 
351  // load the "package" package, so that scripts can get what packages they want
353  }
354 
356  std::string filename = *cmdline_opts_.plugin_file;
357 
358  PLAIN_LOG << "Loading a plugin file'" << filename << "'...";
359 
361 
362  try {
363  if(sf->fail()) {
364  throw std::runtime_error("failed to open plugin file");
365  }
366 
367  /* Cancel all "jumps" to editor / campaign / multiplayer */
368  jump_to_multiplayer_ = false;
369  jump_to_editor_ = utils::nullopt;
370  jump_to_campaign_.jump = false;
371 
372  std::string full_plugin((std::istreambuf_iterator<char>(*sf)), std::istreambuf_iterator<char>());
373 
375 
376  std::size_t i = pm.add_plugin(filename, full_plugin);
377 
378  for(std::size_t j = 0; j < pm.size(); ++j) {
379  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
380  }
381 
382  PLAIN_LOG << "Starting a plugin...";
383  pm.start_plugin(i);
384 
385  for(std::size_t j = 0; j < pm.size(); ++j) {
386  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
387  }
388 
389  plugins_context pc("init");
390 
391  for(std::size_t repeat = 0; repeat < 5; ++repeat) {
392  PLAIN_LOG << "Playing a slice...";
393  pc.play_slice();
394 
395  for(std::size_t j = 0; j < pm.size(); ++j) {
396  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
397  }
398  }
399 
400  return true;
401  } catch(const std::exception& e) {
402  gui2::show_error_message(std::string("When loading a plugin, error:\n") + e.what());
403  error = true;
404  }
405  }
406 
407  if(!error && !cmdline_opts_.nobanner) {
408  PLAIN_LOG << "ok";
409  }
410 
411  return !error;
412 }
413 
414 void game_launcher::set_test(const std::string& id)
415 {
416  state_.clear();
417  state_.classification().type = campaign_type::type::test;
419  state_.classification().era_id = "era_default";
420 
421  state_.set_carryover_sides_start(config{"next_scenario", id});
422 }
423 
425 {
426  // This first_time variable was added in 70f3c80a3e2 so that using the GUI
427  // menu to load a game works. That seems to have edge-cases, for example if
428  // you try to load a game a second time then Wesnoth exits.
429  static bool first_time = true;
430 
431  if(!cmdline_opts_.test) {
432  return true;
433  }
434 
435  if(!first_time) {
436  return false;
437  }
438 
439  first_time = false;
440 
441  if(test_scenarios_.size() == 0) {
442  // shouldn't happen, as test_scenarios_ is initialised to {"test"}
443  PLAIN_LOG << "Error in the test handling code";
444  return false;
445  }
446 
447  if(test_scenarios_.size() > 1) {
448  PLAIN_LOG << "You can't run more than one unit test in interactive mode";
449  }
450 
451  set_test(test_scenarios_.at(0));
452 
454 
455  try {
456  campaign_controller ccontroller(state_);
457  ccontroller.play_game();
458  } catch(savegame::load_game_exception& e) {
459  load_data_ = std::move(e.data_);
460  return true;
461  }
462 
463  return false;
464 }
465 
466 /**
467  * Runs unit tests specified on the command line.
468  *
469  * If multiple unit tests were specified, then this will stop at the first test
470  * which returns a non-zero status.
471  */
472 // Same as play_test except that we return the results of play_game.
473 // \todo "same ... except" ... and many other changes, such as testing the replay
475 {
476  // There's no copy of play_test's first_time variable. That seems to be for handling
477  // the player loading a game via the GUI, which makes no sense in a non-interactive test.
478  if(cmdline_opts_.unit_test.empty()) {
480  }
481 
482  auto ret = unit_test_result::TEST_FAIL; // will only be returned if no test is run
483  for(const auto& scenario : test_scenarios_) {
484  set_test(scenario);
485  ret = single_unit_test();
486  const char* describe_result;
487  switch(ret) {
489  describe_result = "PASS TEST";
490  break;
492  describe_result = "FAIL TEST (INVALID REPLAY)";
493  break;
495  describe_result = "FAIL TEST (ERRORED REPLAY)";
496  break;
498  describe_result = "FAIL TEST (WML EXCEPTION)";
499  break;
501  describe_result = "FAIL TEST (DEFEAT)";
502  break;
504  describe_result = "PASS TEST (VICTORY)";
505  break;
507  describe_result = "BROKE STRICT (PASS)";
508  break;
510  describe_result = "BROKE STRICT (FAIL)";
511  break;
513  describe_result = "BROKE STRICT (DEFEAT)";
514  break;
516  describe_result = "BROKE STRICT (VICTORY)";
517  break;
518  default:
519  describe_result = "FAIL TEST (UNKNOWN)";
520  break;
521  }
522 
523  PLAIN_LOG << describe_result << " (" << int(ret) << "): " << scenario;
524  if(ret != unit_test_result::TEST_PASS) {
525  break;
526  }
527  }
528 
529  return ret;
530 }
531 
533 {
535 
536  level_result::type game_res = level_result::type::fail;
537  try {
538  campaign_controller ccontroller(state_, true);
539  game_res = ccontroller.play_game();
540  if(game_res == level_result::type::fail) {
541  if(lg::broke_strict()) {
543  } else {
545  }
546  }
547  } catch(const wml_exception& e) {
548  PLAIN_LOG << "Caught WML Exception:" << e.dev_message;
550  }
551 
553 
555  return pass_victory_or_defeat(game_res);
556  }
557 
559  save.save_game_automatic(false, "unit_test_replay");
560 
561 #ifdef __cpp_aggregate_paren_init
562  load_data_.emplace(
563  savegame::save_index_class::default_saves_dir(), save.filename(), "", true, true, false);
564 #else
566  savegame::save_index_class::default_saves_dir(), save.filename(), "", true, true, false};
567 #endif
568  load_data_->read_file();
569 
570  if(!load_prepared_game()) {
571  PLAIN_LOG << "Failed to load the replay!";
572  return unit_test_result::TEST_FAIL_LOADING_REPLAY; // failed to load replay
573  }
574 
575  try {
576  const bool was_strict_broken = lg::broke_strict();
577  campaign_controller ccontroller(state_, true);
578  ccontroller.play_replay();
579  if(!was_strict_broken && lg::broke_strict()) {
580  PLAIN_LOG << "Observed failure on replay";
582  }
583  } catch(const wml_exception& e) {
584  PLAIN_LOG << "WML Exception while playing replay: " << e.dev_message;
586  }
587 
588  return pass_victory_or_defeat(game_res);
589 }
590 
592 {
593  if(res == level_result::type::defeat) {
594  if(lg::broke_strict()) {
596  } else {
598  }
599  } else if(res == level_result::type::victory) {
600  if(lg::broke_strict()) {
602  } else {
604  }
605  }
606 
607  if(lg::broke_strict()) {
609  } else {
611  }
612 }
613 
615 {
617  return true;
618  }
619 
621 
623 
625  if (res != editor::EXIT_NORMAL) {
626  PLAIN_LOG << "Error while taking screenshot of map: " << screenshot_map_;
627  }
628  return false;
629 }
630 
632 {
634  return true;
635  }
636 
637  state_.classification().type = campaign_type::type::multiplayer;
638  DBG_GENERAL << "Current campaign type: " << campaign_type::get_string(state_.classification().type);
639 
640  try {
642  } catch(const config::error& e) {
643  PLAIN_LOG << "Error loading game config: " << e.what();
644  return false;
645  }
646 
647  // A default output filename
648  std::string outfile = "wesnoth_image.png";
649 
650  // If a output path was given as an argument, use that instead
652  outfile = *cmdline_opts_.render_image_dst;
653  }
654 
656  exit(1);
657  }
658 
659  return false;
660 }
661 
663 {
664  return load_data_.has_value();
665 }
666 
668 {
669  return std::exchange(load_data_, utils::nullopt).value();
670 }
671 
673 {
674  DBG_GENERAL << "Current campaign type: " << campaign_type::get_string(state_.classification().type);
675  assert(!load_data_);
676 
677  // FIXME: evaluate which of these damn catch blocks are still necessary
678  try {
680 
681  } catch(const config::error& e) {
682  if(e.message.empty()) {
683  gui2::show_error_message(_("The file you have tried to load is corrupt"));
684  } else {
685  gui2::show_error_message(_("The file you have tried to load is corrupt: '") + e.message + '\'');
686  }
687 
688  return false;
689  } catch(const wml_exception& e) {
690  e.show();
691  return false;
692  } catch(const filesystem::io_exception& e) {
693  if(e.message.empty()) {
694  gui2::show_error_message(_("File I/O Error while reading the game"));
695  } else {
696  gui2::show_error_message(_("File I/O Error while reading the game: '") + e.message + '\'');
697  }
698 
699  return false;
700  } catch(const game::error& e) {
701  if(e.message.empty()) {
702  gui2::show_error_message(_("The file you have tried to load is corrupt"));
703  } else {
704  gui2::show_error_message(_("The file you have tried to load is corrupt: '") + e.message + '\'');
705  }
706 
707  return false;
708  }
709 
710  // If the player canceled loading, we won't have any load data at this point
711  return has_load_data();
712 }
713 
715 {
716  auto load_data = extract_load_data();
718 
719  play_replay_ = load_data.show_replay;
720 
721  // in case show_replay && is_start_of_scenario
722  // there won't be any turns to replay, but the
723  // user gets to watch the intro sequence again ...
724  if(load_data.show_replay && !state_.is_start_of_scenario()) {
726  }
727 
728  if(load_data.cancel_orders) {
730  }
731 
732  try {
734  } catch(const config::error&) {
735  return false;
736  }
737 
738  return true;
739 }
740 
742 {
743  state_.clear();
744  state_.classification().type = campaign_type::type::scenario;
745  play_replay_ = false;
746 
748 }
749 
751 {
753 }
754 
756  jump_to_campaign_.jump = false;
757  if(new_campaign()) {
760  return true;
761  }
762  return false;
763 }
764 
766 {
767  if(jump_to_campaign_.jump) {
768  return play_campaign();
769  }
770 
771  return true;
772 }
773 
775 {
776  if(!jump_to_multiplayer_) {
777  return true;
778  }
779 
780  jump_to_multiplayer_ = false;
782 }
783 
785 {
786  if(jump_to_editor_) {
787  // If no filename was specified, this will be an empty string.
788  const std::string to_open = std::exchange(jump_to_editor_, utils::nullopt).value();
789 
791  return false;
792  }
793  }
794 
795  return true;
796 }
797 
799 {
800 #if defined(__APPLE__) && TARGET_OS_IPHONE
801  throw game::mp_server_error("Starting MP server is not supported on iOS builds.");
802 #else
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 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 BOOST_VERSION >= 108600
836  catch(const std::exception& e)
837 #else
838  catch(const boost::process::process_error& 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 #endif
848 }
849 
851 {
853  try {
854  if(mode == mp_mode::HOST) {
855  try {
856  start_wesnothd();
857  } catch(const game::mp_server_error&) {
859 
860  try {
861  start_wesnothd();
862  } catch(const game::mp_server_error&) {
863  return false;
864  }
865  }
866  }
867 
868  // If a server address wasn't specified, prompt for it now.
869  if(mode != mp_mode::LOCAL && multiplayer_server_.empty()) {
870  if(!gui2::dialogs::mp_connect::execute()) {
871  return false;
872  }
873 
874  // The prompt saves its input to preferences.
876 
877  if(multiplayer_server_ != prefs::get().builtin_servers_list().front().address) {
879  }
880  }
881 
882  // create_engine already calls config_manager_.load_config but maybe its better to have MULTIPLAYER
883  // defined while we are in the lobby.
885 
886  events::discard_input(); // prevent the "keylogger" effect
888 
889  if(mode == mp_mode::LOCAL) {
891  } else {
893  multiplayer_server_.clear();
894  }
895 
896  } catch(const wesnothd_rejected_client_error& e) {
897  gui2::show_error_message(e.message);
898  } catch(const game::mp_server_error& e) {
899  gui2::show_error_message(_("Error while starting server: ") + e.message);
900  } catch(const game::load_game_failed& e) {
901  gui2::show_error_message(_("The game could not be loaded: ") + e.message);
902  } catch(const game::game_error& e) {
903  gui2::show_error_message(_("Error while playing the game: ") + e.message);
904  } catch(const mapgen_exception& e) {
905  gui2::show_error_message(_("Map generator error: ") + e.message);
906  } catch(const wesnothd_error& e) {
907  if(!e.message.empty()) {
908  ERR_NET << "caught network error: " << e.message;
909 
910  std::string user_msg;
911  auto conn_err = dynamic_cast<const wesnothd_connection_error*>(&e);
912 
913  if(conn_err) {
914  // The wesnothd_connection_error subclass is only thrown with messages
915  // from boost::system::error_code which we can't translate ourselves.
916  // It's also the originator of the infamous EOF error that happens when
917  // the server dies. <https://github.com/wesnoth/wesnoth/issues/3005>. It
918  // will provide a translated string instead of that when it happens.
919  user_msg = !conn_err->user_message.empty()
920  ? conn_err->user_message
921  : _("Connection failed: ") + e.message;
922  } else {
923  // This will be a message from the server itself, which we can
924  // probably translate.
925  user_msg = translation::gettext(e.message.c_str());
926  }
927 
928  gui2::show_error_message(user_msg);
929  } else {
930  ERR_NET << "caught network error";
931  }
932  } catch(const config::error& e) {
933  if(!e.message.empty()) {
934  ERR_CONFIG << "caught config::error: " << e.message;
935  gui2::show_transient_message("", e.message);
936  } else {
937  ERR_CONFIG << "caught config::error";
938  }
939  } catch(const incorrect_map_format_error& e) {
940  gui2::show_error_message(_("The game map could not be loaded: ") + e.message);
941  } catch(savegame::load_game_exception& e) {
942  load_data_ = std::move(e.data_);
943  // this will make it so next time through the title screen loop, this game is loaded
944  } catch(const wml_exception& e) {
945  e.show();
946  } catch(const game::error& e) {
947  PLAIN_LOG << "caught game::error...";
948  gui2::show_error_message(_("Error: ") + e.message);
949  }
950 
951  return true;
952 }
953 
955 {
957  return true;
958  }
959 
960  DBG_MP << "starting multiplayer game from the commandline";
961 
963 
964  events::discard_input(); // prevent the "keylogger" effect
966 
967  try {
969  } catch(savegame::load_game_exception& e) {
970  load_data_ = std::move(e.data_);
971  return true;
972  }
973 
974  return false;
975 }
976 
978 {
979  if(!gui2::dialogs::language_selection::execute()) {
980  return false;
981  }
982 
985  }
986 
991 
992  return true;
993 }
994 
996 {
997  assert(!load_data_);
998  if(play_replay_) {
999  play_replay();
1000  return;
1001  }
1002 
1003  gui2::dialogs::loading_screen::display([this, reload]() {
1005 
1006  if(reload == reload_mode::RELOAD_DATA) {
1007  try {
1010  } catch(const config::error&) {
1011  return;
1012  }
1013  }
1014  });
1015 
1016  try {
1017  campaign_controller ccontroller(state_);
1018  ccontroller.play_game();
1019  ai::manager::singleton_ = nullptr;
1020  } catch(savegame::load_game_exception& e) {
1021  load_data_ = std::move(e.data_);
1022  // this will make it so next time through the title screen loop, this game is loaded
1023  } catch(const wml_exception& e) {
1024  e.show();
1025  } catch(const mapgen_exception& e) {
1026  gui2::show_error_message(_("Map generator error: ") + e.message);
1027  }
1028 }
1029 
1031 {
1032  assert(!load_data_);
1033  try {
1034  campaign_controller ccontroller(state_);
1035  ccontroller.play_replay();
1036  } catch(savegame::load_game_exception& e) {
1037  load_data_ = std::move(e.data_);
1038  // this will make it so next time through the title screen loop, this game is loaded
1039  } catch(const wml_exception& e) {
1040  e.show();
1041  }
1042 }
1043 
1045 {
1047  while(true) {
1049 
1051 
1053 
1054  if(res != editor::EXIT_RELOAD_DATA) {
1055  return res;
1056  }
1057 
1059  }
1060 
1061  return editor::EXIT_ERROR; // not supposed to happen
1062 }
1063 
1065 {
1066  load_data_.reset();
1067 }
1068 
1070 {
1071  try {
1073  video::deinit();
1074  } catch(std::exception& e) {
1075  ERR_GENERAL << "Suppressing exception thrown during ~game_launcher: " << e.what();
1076  } catch(...) {
1077  ERR_GENERAL << "Suppressing exception " << utils::get_unknown_exception_type() << " thrown during ~game_launcher";
1078  }
1079 }
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:100
const language_def & get_locale()
Definition: language.cpp:313
std::vector< language_def > get_languages(bool all)
Return a list of available translations.
Definition: language.cpp:162
void init_textdomains(const game_config_view &cfg)
Initializes the list of textdomains from a configuration object.
Definition: language.cpp:333
bool load_language_list()
Definition: language.cpp:141
void set_min_translation_percent(int percent)
Definition: language.cpp:192
Standard logging facilities (interface).
#define STREAMING_LOG
Definition: log.hpp:297
#define PLAIN_LOG
Definition: log.hpp:296
@ NORMAL
Definition: cursor.hpp:28
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
Definition: cursor.cpp:171
void point(int x, int y)
Draw a single point.
Definition: draw.cpp:228
@ EXIT_ERROR
Definition: editor_main.hpp:27
@ EXIT_NORMAL
Definition: editor_main.hpp:24
@ EXIT_QUIT_TO_DESKTOP
Definition: editor_main.hpp:25
@ EXIT_RELOAD_DATA
Definition: editor_main.hpp:26
EXIT_STATUS start(bool clear_id, const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
void discard_input()
Discards all input events.
Definition: events.cpp:758
std::string get_cache_dir()
Definition: filesystem.cpp:880
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
std::string get_user_data_dir()
Definition: filesystem.cpp:870
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:344
utils::optional< std::string > get_wml_location(const std::string &path, const utils::optional< std::string > &current_dir)
Returns a translated path to the actual file or directory, if it exists.
std::string read_file(const std::string &fname)
Basic disk I/O - read file.
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:52
void write_file(const std::string &fname, const std::string &data, std::ios_base::openmode mode)
Throws io_exception if an error occurs.
std::string get_wesnothd_name()
std::string normalize_path(const std::string &fpath, bool normalize_separators, bool resolve_dot_entries)
Returns the absolute path of a file.
bool load_font_config()
Definition: font_config.cpp:81
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:98
bool show_debug_clock_button
Do we wish to show the button for the debug clock.
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup)
Shows a transient message to the user.
bool new_widgets
Do we wish to use the new library or not.
Definition: settings.cpp:23
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:201
void flush_cache()
Purges all image caches.
Definition: picture.cpp:210
surface get_surface(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image surface suitable for software manipulation.
Definition: picture.cpp:691
save_result save_image(const locator &i_locator, const std::string &filename)
Definition: picture.cpp:921
@ UNSCALED
Unmodified original-size image.
Definition: picture.hpp:173
bool broke_strict()
Definition: log.cpp:430
void start_local_game()
Starts a multiplayer game in single-user mode.
void start_local_game_commandline(const commandline_options &cmdline_opts)
Starts a multiplayer game in single-user mode using command line settings.
void start_client(const std::string &host)
Pubic entry points for the MP workflow.
void clean_saves(const std::string &label)
Delete all autosaves of a certain scenario from the default save directory.
Definition: savegame.cpp:64
void set_gamestate(saved_game &gamestate, load_game_metadata &load_data)
Definition: savegame.cpp:257
utils::optional< load_game_metadata > load_interactive()
Definition: savegame.cpp:185
bool init_sound()
Definition: sound.cpp:422
void close_sound()
Definition: sound.cpp:485
void flush_cache()
Definition: sound.cpp:1060
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:669
void set_window_icon(surface &icon)
Sets the icon of the main window.
Definition: video.cpp:675
SDL_Window * get_window()
Definition: video.cpp:690
void init(fake type)
Initialize the video subsystem.
Definition: video.cpp:93
void deinit()
Deinitialize the video subsystem.
Definition: video.cpp:122
This file contains the settings handling of the widget library.
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition: sound.cpp:138
std::string filename
Filename.
An exception object used when an IO error occurs.
Definition: filesystem.hpp:56
Base class for all the errors encountered by the engine.
Definition: exceptions.hpp:29
Error used for any general game error, e.g.
Definition: game_errors.hpp:47
Error used when game loading fails.
Definition: game_errors.hpp:31
std::string campaign_id
The ID of the campaign to launch.
bool jump
Whether the game should immediately start a campaign.
std::string scenario_id
The ID of the scenario within the campaign to jump to.
bool skip_story
Whether the story screen should be skipped.
int difficulty
The difficulty at which to launch the campaign.
std::string localename
Definition: language.hpp:32
void clear_current_scenario()
Delete the current scenario from the stats.
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46
An error occurred inside the underlying network communication code (boost asio) TODO: find a short na...
std::string user_message
User-friendly and potentially translated message for use in the UI.
An error occurred during when trying to communicate with the wesnothd server.
Error used when the client is rejected by the MP server.
Helper class, don't construct this directly.
mock_char c
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define e