The Battle for Wesnoth  1.19.1+dev
game_launcher.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
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, no_delay, revision, 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 #include <algorithm> // for copy, max, min, stable_sort
54 #ifdef _WIN32
55 #include <boost/process/windows.hpp>
56 #endif
57 #include <boost/process.hpp>
58 #include <cstdlib> // for system
59 #include <new>
60 #include <utility> // for pair
61 
62 
63 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
64 #include "gui/widgets/debug.hpp"
65 #endif
66 
67 
68 static lg::log_domain log_config("config");
69 #define ERR_CONFIG LOG_STREAM(err, log_config)
70 #define WRN_CONFIG LOG_STREAM(warn, log_config)
71 #define LOG_CONFIG LOG_STREAM(info, log_config)
72 
73 #define ERR_GENERAL LOG_STREAM(err, lg::general())
74 #define LOG_GENERAL LOG_STREAM(info, lg::general())
75 #define WRN_GENERAL LOG_STREAM(warn, lg::general())
76 #define DBG_GENERAL LOG_STREAM(debug, lg::general())
77 
78 #define LOG_TEST FORCE_LOG_TO(lg::general(), log_config)
79 
80 static lg::log_domain log_mp_create("mp/create");
81 #define DBG_MP LOG_STREAM(debug, log_mp_create)
82 
83 static lg::log_domain log_network("network");
84 #define ERR_NET LOG_STREAM(err, log_network)
85 
86 static lg::log_domain log_enginerefac("enginerefac");
87 #define LOG_RG LOG_STREAM(info, log_enginerefac)
88 
89 namespace bp = boost::process;
90 
92  : cmdline_opts_(cmdline_opts)
93  , font_manager_()
94  , image_manager_()
95  , main_event_context_()
96  , hotkey_manager_()
97  , music_thinker_()
98  , music_muter_()
99  , test_scenarios_{"test"}
100  , screenshot_map_()
101  , screenshot_filename_()
102  , state_()
103  , play_replay_(false)
104  , multiplayer_server_()
105  , jump_to_multiplayer_(false)
106  , jump_to_campaign_{}
107  , jump_to_editor_(false)
108  , load_data_()
109 {
110  bool no_music = false;
111  bool no_sound = false;
112 
113  // The path can be hardcoded and it might be a relative path.
114  if(!game_config::path.empty() &&
115 #ifdef _WIN32
116  // use c_str to ensure that index 1 points to valid element since c_str() returns null-terminated string
117  game_config::path.c_str()[1] != ':'
118 #else
119  game_config::path[0] != '/'
120 #endif
121  )
122  {
124  // font_manager_.update_font_path()
125  // To update the font path, destroy and recreate the manager
127  new (&font_manager_) font::manager();
128  }
129 
130  if(cmdline_opts_.core_id) {
132  }
133  if(cmdline_opts_.campaign) {
134  jump_to_campaign_.jump = true;
136  PLAIN_LOG << "selected campaign id: [" << jump_to_campaign_.campaign_id << "]";
137 
140  PLAIN_LOG << "selected difficulty: [" << jump_to_campaign_.difficulty << "]";
141  } else {
142  jump_to_campaign_.difficulty = -1; // let the user choose the difficulty
143  }
144 
147  PLAIN_LOG << "selected scenario id: [" << jump_to_campaign_.scenario_id << "]";
148  }
149 
152  }
153  }
154  if(cmdline_opts_.clock)
156  if(cmdline_opts_.debug) {
158  game_config::mp_debug = true;
159  }
160 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
161  if(cmdline_opts_.debug_dot_domain)
162  gui2::debug_layout_graph::set_domain(*cmdline_opts_.debug_dot_domain);
163  if(cmdline_opts_.debug_dot_level)
164  gui2::debug_layout_graph::set_level(*cmdline_opts_.debug_dot_level);
165 #endif
166  if(cmdline_opts_.editor) {
167  jump_to_editor_ = true;
168  if(!cmdline_opts_.editor->empty()) {
171  }
172  }
173  if(cmdline_opts_.fps)
174  prefs::get().set_show_fps(true);
176  start_in_fullscreen_ = true;
177  if(cmdline_opts_.load)
180  if(cmdline_opts_.max_fps) {
181  int fps = std::clamp(*cmdline_opts_.max_fps, 1, 1000);
182  fps = 1000 / fps;
183  // increase the delay to avoid going above the maximum
184  if(1000 % fps != 0) {
185  ++fps;
186  }
187  prefs::get().set_draw_delay(fps);
188  }
190  no_sound = true;
192  }
194  gui2::new_widgets = true;
196  game_config::no_delay = true;
198  no_music = true;
200  no_sound = true;
202  const int xres = std::get<0>(*cmdline_opts_.resolution);
203  const int yres = std::get<1>(*cmdline_opts_.resolution);
204  if(xres > 0 && yres > 0) {
205  prefs::get().set_resolution(point(xres, yres));
206  prefs::get().set_maximized(false);
207  }
208  }
210  // TODO it could be simplified to use cmdline_opts_ directly if there is no other way to enter screenshot mode
213  no_sound = true;
215  }
216  if (cmdline_opts_.server){
217  jump_to_multiplayer_ = true;
218  // Do we have any server specified ?
219  if(!cmdline_opts_.server->empty()) {
221  } else {
222  // Pick the first server in config
223  if(game_config::server_list.size() > 0) {
225  } else {
226  multiplayer_server_ = "";
227  }
228  }
229  if(cmdline_opts_.username) {
232  if(cmdline_opts_.password) {
234  prefs::get().set_password(*cmdline_opts.server, *cmdline_opts.username, *cmdline_opts_.password);
235  }
236  }
237  }
238  if(cmdline_opts_.test) {
239  if(!cmdline_opts_.test->empty()) {
241  }
242  }
243  if(!cmdline_opts_.unit_test.empty()) {
245  }
247  start_in_fullscreen_ = false;
249  load_data_->show_replay = true;
252 
253  if(!cmdline_opts.nobanner) {
254  PLAIN_LOG
255  << "\nData directory: " << game_config::path
256  << "\nUser data directory: " << filesystem::get_user_data_dir()
257  << "\nCache directory: " << filesystem::get_cache_dir()
258  << "\n\n";
259  }
260 
261  // disable sound in nosound mode, or when sound engine failed to initialize
262  if(no_sound || ((prefs::get().sound_on() || prefs::get().music_on() ||
264  !sound::init_sound())) {
265  prefs::get().set_sound(false);
266  prefs::get().set_music(false);
267  prefs::get().set_turn_bell(false);
268  prefs::get().set_ui_sound(false);
269  } else if(no_music) { // else disable the music in nomusic mode
270  prefs::get().set_music(false);
271  }
272 }
273 
275 {
276  if(!::load_language_list()) {
277  return false;
278  }
279 
280  language_def locale;
281  if(cmdline_opts_.language) {
282  std::vector<language_def> langs = get_languages(true);
283  for(const language_def& def : langs) {
284  if(def.localename == *cmdline_opts_.language) {
285  locale = def;
286  break;
287  }
288  }
289  if(locale.localename.empty()) {
290  PLAIN_LOG << "Language symbol '" << *cmdline_opts_.language << "' not found.";
291  return false;
292  }
293  } else {
294  locale = get_locale();
295  }
296  ::set_language(locale);
297 
298  return true;
299 }
300 
302 {
303  // Handle special commandline launch flags
308  {
314  {
315  PLAIN_LOG << "--nogui flag is only valid with --multiplayer or --screenshot or --plugin flags";
316  return false;
317  }
319  // Screenshots require a rendering context, and thus a window,
320  // so we create one but hidden.
322  } else {
323  // Other functions don't require a window at all.
325  }
326  game_config::no_delay = true;
327  return true;
328  }
329 
330  // Initialize video subsystem, and create a new window.
331  video::init();
332 
333  // Set window title and icon
335 
336 #if !(defined(__APPLE__))
337  surface icon(image::get_surface(image::locator{"icons/icon-game.png"}, image::UNSCALED));
338  if(icon != nullptr) {
340  }
341 #endif
342  return true;
343 }
344 
346 {
347  bool error = false;
348 
349  if(!cmdline_opts_.nobanner) {
350  STREAMING_LOG << "Checking lua scripts... ";
351  }
352 
354  // load the "package" package, so that scripts can get what packages they want
356  }
357 
358  // get the application lua kernel, load and execute script file, if script file is present
361 
362  if(!sf->fail()) {
363  /* Cancel all "jumps" to editor / campaign / multiplayer */
364  jump_to_multiplayer_ = false;
365  jump_to_editor_ = false;
366  jump_to_campaign_.jump = false;
367 
368  std::string full_script((std::istreambuf_iterator<char>(*sf)), std::istreambuf_iterator<char>());
369 
370  PLAIN_LOG << "\nRunning lua script: " << *cmdline_opts_.script_file;
371 
373  } else {
374  PLAIN_LOG << "Encountered failure when opening script '" << *cmdline_opts_.script_file << '\'';
375  error = true;
376  }
377  }
378 
380  std::string filename = *cmdline_opts_.plugin_file;
381 
382  PLAIN_LOG << "Loading a plugin file'" << filename << "'...";
383 
385 
386  try {
387  if(sf->fail()) {
388  throw std::runtime_error("failed to open plugin file");
389  }
390 
391  /* Cancel all "jumps" to editor / campaign / multiplayer */
392  jump_to_multiplayer_ = false;
393  jump_to_editor_ = false;
394  jump_to_campaign_.jump = false;
395 
396  std::string full_plugin((std::istreambuf_iterator<char>(*sf)), std::istreambuf_iterator<char>());
397 
399 
400  std::size_t i = pm.add_plugin(filename, full_plugin);
401 
402  for(std::size_t j = 0; j < pm.size(); ++j) {
403  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
404  }
405 
406  PLAIN_LOG << "Starting a plugin...";
407  pm.start_plugin(i);
408 
409  for(std::size_t j = 0; j < pm.size(); ++j) {
410  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
411  }
412 
413  plugins_context pc("init");
414 
415  for(std::size_t repeat = 0; repeat < 5; ++repeat) {
416  PLAIN_LOG << "Playing a slice...";
417  pc.play_slice();
418 
419  for(std::size_t j = 0; j < pm.size(); ++j) {
420  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
421  }
422  }
423 
424  return true;
425  } catch(const std::exception& e) {
426  gui2::show_error_message(std::string("When loading a plugin, error:\n") + e.what());
427  error = true;
428  }
429  }
430 
431  if(!error && !cmdline_opts_.nobanner) {
432  PLAIN_LOG << "ok";
433  }
434 
435  return !error;
436 }
437 
438 void game_launcher::set_test(const std::string& id)
439 {
440  state_.clear();
441  state_.classification().type = campaign_type::type::test;
443  state_.classification().era_id = "era_default";
444 
445  state_.set_carryover_sides_start(config{"next_scenario", id});
446 }
447 
449 {
450  // This first_time variable was added in 70f3c80a3e2 so that using the GUI
451  // menu to load a game works. That seems to have edge-cases, for example if
452  // you try to load a game a second time then Wesnoth exits.
453  static bool first_time = true;
454 
455  if(!cmdline_opts_.test) {
456  return true;
457  }
458 
459  if(!first_time) {
460  return false;
461  }
462 
463  first_time = false;
464 
465  if(test_scenarios_.size() == 0) {
466  // shouldn't happen, as test_scenarios_ is initialised to {"test"}
467  PLAIN_LOG << "Error in the test handling code";
468  return false;
469  }
470 
471  if(test_scenarios_.size() > 1) {
472  PLAIN_LOG << "You can't run more than one unit test in interactive mode";
473  }
474 
475  set_test(test_scenarios_.at(0));
476 
478 
479  try {
480  campaign_controller ccontroller(state_);
481  ccontroller.play_game();
482  } catch(savegame::load_game_exception& e) {
483  load_data_ = std::move(e.data_);
484  return true;
485  }
486 
487  return false;
488 }
489 
490 /**
491  * Runs unit tests specified on the command line.
492  *
493  * If multiple unit tests were specified, then this will stop at the first test
494  * which returns a non-zero status.
495  */
496 // Same as play_test except that we return the results of play_game.
497 // \todo "same ... except" ... and many other changes, such as testing the replay
499 {
500  // There's no copy of play_test's first_time variable. That seems to be for handling
501  // the player loading a game via the GUI, which makes no sense in a non-interactive test.
502  if(cmdline_opts_.unit_test.empty()) {
504  }
505 
506  auto ret = unit_test_result::TEST_FAIL; // will only be returned if no test is run
507  for(const auto& scenario : test_scenarios_) {
508  set_test(scenario);
509  ret = single_unit_test();
510  const char* describe_result;
511  switch(ret) {
513  describe_result = "PASS TEST";
514  break;
516  describe_result = "FAIL TEST (INVALID REPLAY)";
517  break;
519  describe_result = "FAIL TEST (ERRORED REPLAY)";
520  break;
522  describe_result = "FAIL TEST (WML EXCEPTION)";
523  break;
525  describe_result = "FAIL TEST (DEFEAT)";
526  break;
528  describe_result = "PASS TEST (VICTORY)";
529  break;
531  describe_result = "BROKE STRICT (PASS)";
532  break;
534  describe_result = "BROKE STRICT (FAIL)";
535  break;
537  describe_result = "BROKE STRICT (DEFEAT)";
538  break;
540  describe_result = "BROKE STRICT (VICTORY)";
541  break;
542  default:
543  describe_result = "FAIL TEST (UNKNOWN)";
544  break;
545  }
546 
547  PLAIN_LOG << describe_result << " (" << int(ret) << "): " << scenario;
548  if(ret != unit_test_result::TEST_PASS) {
549  break;
550  }
551  }
552 
553  return ret;
554 }
555 
557 {
559 
560  level_result::type game_res = level_result::type::fail;
561  try {
562  campaign_controller ccontroller(state_, true);
563  game_res = ccontroller.play_game();
564  if(game_res == level_result::type::fail) {
565  if(lg::broke_strict()) {
567  } else {
569  }
570  }
571  } catch(const wml_exception& e) {
572  PLAIN_LOG << "Caught WML Exception:" << e.dev_message;
574  }
575 
577 
579  return pass_victory_or_defeat(game_res);
580  }
581 
583  save.save_game_automatic(false, "unit_test_replay");
584 
586  savegame::save_index_class::default_saves_dir(), save.filename(), "", true, true, false};
587 
588  if(!load_game()) {
589  PLAIN_LOG << "Failed to load the replay!";
590  return unit_test_result::TEST_FAIL_LOADING_REPLAY; // failed to load replay
591  }
592 
593  try {
594  const bool was_strict_broken = lg::broke_strict();
595  campaign_controller ccontroller(state_, true);
596  ccontroller.play_replay();
597  if(!was_strict_broken && lg::broke_strict()) {
598  PLAIN_LOG << "Observed failure on replay";
600  }
601  } catch(const wml_exception& e) {
602  PLAIN_LOG << "WML Exception while playing replay: " << e.dev_message;
604  }
605 
606  return pass_victory_or_defeat(game_res);
607 }
608 
610 {
611  if(res == level_result::type::defeat) {
612  if(lg::broke_strict()) {
614  } else {
616  }
617  } else if(res == level_result::type::victory) {
618  if(lg::broke_strict()) {
620  } else {
622  }
623  }
624 
625  if(lg::broke_strict()) {
627  } else {
629  }
630 }
631 
633 {
635  return true;
636  }
637 
639 
641 
643  return false;
644 }
645 
647 {
649  return true;
650  }
651 
652  state_.classification().type = campaign_type::type::multiplayer;
653  DBG_GENERAL << "Current campaign type: " << campaign_type::get_string(state_.classification().type);
654 
655  try {
657  } catch(const config::error& e) {
658  PLAIN_LOG << "Error loading game config: " << e.what();
659  return false;
660  }
661 
662  // A default output filename
663  std::string outfile = "wesnoth_image.png";
664 
665  // If a output path was given as an argument, use that instead
667  outfile = *cmdline_opts_.render_image_dst;
668  }
669 
671  exit(1);
672  }
673 
674  return false;
675 }
676 
678 {
679  return load_data_.has_value();
680 }
681 
683 {
684  assert(game_config_manager::get());
685 
686  DBG_GENERAL << "Current campaign type: " << campaign_type::get_string(state_.classification().type);
687 
689  if(load_data_) {
690  load.data() = std::move(*load_data_);
692  }
693 
694  try {
695  if(!load.load_game()) {
696  return false;
697  }
698 
699  load.set_gamestate();
700  try {
702  } catch(const config::error&) {
703  return false;
704  }
705 
706  } catch(const config::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  } catch(const wml_exception& e) {
715  e.show();
716  return false;
717  } catch(const filesystem::io_exception& e) {
718  if(e.message.empty()) {
719  gui2::show_error_message(_("File I/O Error while reading the game"));
720  } else {
721  gui2::show_error_message(_("File I/O Error while reading the game: '") + e.message + '\'');
722  }
723 
724  return false;
725  } catch(const game::error& e) {
726  if(e.message.empty()) {
727  gui2::show_error_message(_("The file you have tried to load is corrupt"));
728  } else {
729  gui2::show_error_message(_("The file you have tried to load is corrupt: '") + e.message + '\'');
730  }
731 
732  return false;
733  }
734 
735  play_replay_ = load.data().show_replay;
736  LOG_CONFIG << "is middle game savefile: " << (state_.is_mid_game_save() ? "yes" : "no");
737  LOG_CONFIG << "show replay: " << (play_replay_ ? "yes" : "no");
738  // in case load.data().show_replay && state_.is_start_of_scenario
739  // there won't be any turns to replay, but the
740  // user gets to watch the intro sequence again ...
741 
742  if(!state_.is_start_of_scenario() && load.data().show_replay) {
744  }
745 
748  }
749 
750  if(load.data().cancel_orders) {
752  }
753 
754  return true;
755 }
756 
758 {
759  state_.clear();
760  state_.classification().type = campaign_type::type::scenario;
761  play_replay_ = false;
762 
764 }
765 
767 {
769 }
770 
772 {
773  if(jump_to_campaign_.jump) {
774  if(new_campaign()) {
776  jump_to_campaign_.jump = false;
778  } else {
779  jump_to_campaign_.jump = false;
780  return false;
781  }
782  }
783 
784  return true;
785 }
786 
788 {
790  jump_to_multiplayer_ = false;
792  ;
793  } else {
794  return false;
795  }
796  }
797 
798  return true;
799 }
800 
802 {
803  if(jump_to_editor_) {
804  jump_to_editor_ = false;
805 
806  const std::string to_open = load_data_ ? filesystem::normalize_path(load_data_->filename) : "";
808 
810  return false;
811  }
812  }
813 
814  return true;
815 }
816 
818 {
819  std::string wesnothd_program = "";
820  if(!prefs::get().get_mp_server_program_name().empty()) {
821  wesnothd_program = prefs::get().get_mp_server_program_name();
822  } else {
823  wesnothd_program = filesystem::get_wesnothd_name();
824  }
825 
826  std::string config = filesystem::get_user_data_dir() + "/lan_server.cfg";
828  // copy file if it isn't created yet
830  }
831 
832  LOG_GENERAL << "Starting wesnothd";
833  try
834  {
835 #ifndef _WIN32
836  bp::child c(wesnothd_program, "-c", config);
837 #else
838  bp::child c(wesnothd_program, "-c", config, bp::windows::create_no_window);
839 #endif
840  c.detach();
841  // Give server a moment to start up
842  SDL_Delay(50);
843  return;
844  }
845  catch(const bp::process_error& e)
846  {
848 
849  // Couldn't start server so throw error
850  WRN_GENERAL << "Failed to start server " << wesnothd_program << ":\n" << e.what();
851  throw game::mp_server_error("Starting MP server failed!");
852  }
853 }
854 
856 {
857  try {
858  if(mode == mp_mode::HOST) {
859  try {
860  start_wesnothd();
861  } catch(const game::mp_server_error&) {
863 
864  try {
865  start_wesnothd();
866  } catch(const game::mp_server_error&) {
867  return false;
868  }
869  }
870  }
871 
872  // If a server address wasn't specified, prompt for it now.
873  if(mode != mp_mode::LOCAL && multiplayer_server_.empty()) {
874  if(!gui2::dialogs::mp_connect::execute()) {
875  return false;
876  }
877 
878  // The prompt saves its input to preferences.
880 
881  if(multiplayer_server_ != prefs::get().builtin_servers_list().front().address) {
883  }
884  }
885 
886  // create_engine already calls game_config_manager::get()->load_config but maybe its better to have MULTIPLAYER
887  // defined while we are in the lobby.
889 
890  events::discard_input(); // prevent the "keylogger" effect
892 
893  if(mode == mp_mode::LOCAL) {
895  } else {
897  multiplayer_server_.clear();
898  }
899 
900  } catch(const wesnothd_rejected_client_error& e) {
901  gui2::show_error_message(e.message);
902  } catch(const game::mp_server_error& e) {
903  gui2::show_error_message(_("Error while starting server: ") + e.message);
904  } catch(const game::load_game_failed& e) {
905  gui2::show_error_message(_("The game could not be loaded: ") + e.message);
906  } catch(const game::game_error& e) {
907  gui2::show_error_message(_("Error while playing the game: ") + e.message);
908  } catch(const mapgen_exception& e) {
909  gui2::show_error_message(_("Map generator error: ") + e.message);
910  } catch(const wesnothd_error& e) {
911  if(!e.message.empty()) {
912  ERR_NET << "caught network error: " << e.message;
913 
914  std::string user_msg;
915  auto conn_err = dynamic_cast<const wesnothd_connection_error*>(&e);
916 
917  if(conn_err) {
918  // The wesnothd_connection_error subclass is only thrown with messages
919  // from boost::system::error_code which we can't translate ourselves.
920  // It's also the originator of the infamous EOF error that happens when
921  // the server dies. <https://github.com/wesnoth/wesnoth/issues/3005>. It
922  // will provide a translated string instead of that when it happens.
923  user_msg = !conn_err->user_message.empty()
924  ? conn_err->user_message
925  : _("Connection failed: ") + e.message;
926  } else {
927  // This will be a message from the server itself, which we can
928  // probably translate.
929  user_msg = translation::gettext(e.message.c_str());
930  }
931 
932  gui2::show_error_message(user_msg);
933  } else {
934  ERR_NET << "caught network error";
935  }
936  } catch(const config::error& e) {
937  if(!e.message.empty()) {
938  ERR_CONFIG << "caught config::error: " << e.message;
939  gui2::show_transient_message("", e.message);
940  } else {
941  ERR_CONFIG << "caught config::error";
942  }
943  } catch(const incorrect_map_format_error& e) {
944  gui2::show_error_message(_("The game map could not be loaded: ") + e.message);
945  } catch(savegame::load_game_exception& e) {
946  load_data_ = std::move(e.data_);
947  // this will make it so next time through the title screen loop, this game is loaded
948  } catch(const wml_exception& e) {
949  e.show();
950  } catch(const game::error& e) {
951  PLAIN_LOG << "caught game::error...";
952  gui2::show_error_message(_("Error: ") + e.message);
953  }
954 
955  return true;
956 }
957 
959 {
961  return true;
962  }
963 
964  DBG_MP << "starting multiplayer game from the commandline";
965 
967 
968  events::discard_input(); // prevent the "keylogger" effect
970 
971  try {
973  } catch(savegame::load_game_exception& e) {
974  load_data_ = std::move(e.data_);
975  return true;
976  }
977 
978  return false;
979 }
980 
982 {
983  if(!gui2::dialogs::language_selection::execute()) {
984  return false;
985  }
986 
989  }
990 
995 
996  return true;
997 }
998 
1000 {
1001  assert(!load_data_);
1002  if(play_replay_) {
1003  play_replay();
1004  return;
1005  }
1006 
1007  gui2::dialogs::loading_screen::display([this, reload]() {
1009 
1010  if(reload == reload_mode::RELOAD_DATA) {
1011  try {
1014  } catch(const config::error&) {
1015  return;
1016  }
1017  }
1018  });
1019 
1020  try {
1021  campaign_controller ccontroller(state_);
1022  ccontroller.play_game();
1023  ai::manager::singleton_ = nullptr;
1024  } catch(savegame::load_game_exception& e) {
1025  load_data_ = std::move(e.data_);
1026  // this will make it so next time through the title screen loop, this game is loaded
1027  } catch(const wml_exception& e) {
1028  e.show();
1029  } catch(const mapgen_exception& e) {
1030  gui2::show_error_message(_("Map generator error: ") + e.message);
1031  }
1032 }
1033 
1035 {
1036  assert(!load_data_);
1037  try {
1038  campaign_controller ccontroller(state_);
1039  ccontroller.play_replay();
1040  } catch(savegame::load_game_exception& e) {
1041  load_data_ = std::move(e.data_);
1042  // this will make it so next time through the title screen loop, this game is loaded
1043  } catch(const wml_exception& e) {
1044  e.show();
1045  }
1046 }
1047 
1049 {
1051  while(true) {
1053 
1055 
1056  res = editor::start(res != editor::EXIT_RELOAD_DATA, filename);
1057 
1058  if(res != editor::EXIT_RELOAD_DATA) {
1059  return res;
1060  }
1061 
1063  }
1064 
1065  return editor::EXIT_ERROR; // not supposed to happen
1066 }
1067 
1069 {
1070  load_data_.reset();
1071 }
1072 
1074 {
1075  try {
1077  video::deinit();
1078  } catch(std::exception& e) {
1079  ERR_GENERAL << "Suppressing exception thrown during ~game_launcher: " << e.what();
1080  } catch(...) {
1081  ERR_GENERAL << "Suppressing exception " << utils::get_unknown_exception_type() << " thrown during ~game_launcher";
1082  }
1083 }
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands.
static manager * singleton_
Definition: manager.hpp:439
level_result::type play_game()
level_result::type play_replay()
std::optional< std::string > render_image_dst
Output file to put rendered image path in.
bool nogui
True if –nogui was given on the command line.
std::optional< std::string > server
Non-empty if –server was given on the command line.
std::optional< std::string > plugin_file
File to load a lua plugin (similar to a script) from.
bool headless_unit_test
True if –unit is used and –showgui is not present.
bool windowed
True if –windowed was given on the command line.
bool noreplaycheck
True if –noreplaycheck was given on the command line.
std::optional< std::string > render_image
Image path to render.
std::optional< int > campaign_difficulty
Non-empty if –campaign-difficulty was given on the command line.
std::optional< unsigned int > translation_percent
Non-empty if –all-translations or –translations-over is given on the command line.
std::optional< std::string > campaign_scenario
Non-empty if –campaign-scenario was given on the command line.
std::optional< std::string > password
Non-empty if –password 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.
bool script_unsafe_mode
Whether to load the "package" package for the scripting environment.
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.
bool nodelay
True if –nodelay was given on the command line.
std::optional< std::string > username
Non-empty if –username was given on the command line.
bool new_widgets
True if –new-widgets was given on the command line.
std::optional< std::string > load
Non-empty if –load was given on the command line.
std::optional< std::string > campaign
Non-empty if –campaign was given on the command line.
bool fps
True if –fps was given on the command line.
std::optional< std::string > script_file
File to load lua script from.
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.
std::optional< std::string > core_id
Non-empty if –core 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.
bool fullscreen
True if –fullscreen was given on the command line.
std::optional< std::string > test
Non-empty if –test was given on the command line.
std::optional< std::string > language
Non-empty if –language was given on the command line.
std::optional< int > max_fps
Max FPS specified by –max-fps option.
std::optional< std::pair< int, int > > resolution
Pair of AxB values specified after –resolution.
std::optional< std::string > screenshot_map_file
Map file to make a screenshot of.
std::optional< std::string > screenshot_output_file
Output file to put screenshot in.
std::optional< std::string > editor
Non-empty if –editor 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:159
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)
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).
std::optional< savegame::load_game_metadata > load_data_
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_
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_
font::manager font_manager_
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(std::function< void()> f)
Generic locator abstracting the location of an image.
Definition: picture.hpp:63
void run(char const *prog, const std::string &name, int nArgs=0)
Runs a plain script.
void load_package()
Loads the package library into lua environment.
void play_slice()
Definition: context.cpp:96
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)
void set_core_id(const std::string &root)
std::string network_host()
void set_draw_delay(int value)
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 &)
void set_maximized(bool ison)
game_classification & classification()
Definition: saved_game.hpp:56
void set_skip_story(bool skip_story)
Definition: saved_game.hpp:147
bool is_mid_game_save() const
Definition: saved_game.hpp:106
bool is_start_of_scenario() const
Definition: saved_game.hpp:110
void unify_controllers()
Definition: saved_game.cpp:732
void set_carryover_sides_start(config carryover_sides_start)
Definition: saved_game.cpp:163
std::string get_scenario_id() const
Definition: saved_game.cpp:678
void clear()
Definition: saved_game.cpp:813
statistics_record::campaign_stats_t & statistics()
Definition: saved_game.hpp:143
void cancel_orders()
Definition: saved_game.cpp:715
Exception used to signal that the user has decided to abortt a game, and to load another game instead...
Definition: savegame.hpp:85
The class for loading a savefile.
Definition: savegame.hpp:101
Class for replay saves (either manually or automatically).
Definition: savegame.hpp:268
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:208
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:360
const std::string & filename() const
Definition: savegame.hpp:178
static void reset_translations()
Definition: tstring.cpp:651
Declarations for File-IO.
std::size_t i
Definition: function.cpp:968
#define LOG_CONFIG
#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:93
const language_def & get_locale()
Definition: language.cpp:333
void init_textdomains(const game_config_view &cfg)
Initializes the list of textdomains from a configuration object.
Definition: language.cpp:371
bool load_language_list()
Definition: language.cpp:103
language_list get_languages(bool all)
Return a list of available translations.
Definition: language.cpp:126
void set_min_translation_percent(int percent)
Definition: language.cpp:148
Standard logging facilities (interface).
#define STREAMING_LOG
Definition: log.hpp:299
#define PLAIN_LOG
Definition: log.hpp:298
@ NORMAL
Definition: cursor.hpp:28
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
Definition: cursor.cpp:176
void point(int x, int y)
Draw a single point.
Definition: draw.cpp:202
@ 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:807
std::string get_cache_dir()
Definition: filesystem.cpp:803
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
std::string get_user_data_dir()
Definition: filesystem.cpp:793
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:324
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()
Definition: filesystem.cpp:975
std::string get_cwd()
Definition: filesystem.cpp:885
std::string normalize_path(const std::string &fpath, bool normalize_separators, bool resolve_dot_entries)
Returns the absolute path of a file.
std::optional< std::string > get_wml_location(const std::string &filename, const std::string &current_dir)
Returns a complete path to the actual WML file or directory, if either exists.
bool load_font_config()
Definition: font_config.cpp:79
std::string turn_bell
Game configuration data as global variables.
Definition: build_info.cpp:61
std::string path
Definition: filesystem.cpp:89
std::string get_default_title_string()
std::vector< server_info > server_list
Definition: game_config.cpp:73
void set_debug(bool new_debug)
Definition: game_config.cpp:94
static bool sound_on()
bool show_debug_clock_button
Do we wish to show the button for the debug clock.
static bool ui_sound_on()
static bool music_on()
std::vector< game_tip > load(const config &cfg)
Loads the tips from a config.
Definition: tips.cpp:36
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:203
void flush_cache()
Purges all image caches.
Definition: picture.cpp:216
surface get_surface(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image surface suitable for software manipulation.
Definition: picture.cpp:673
save_result save_image(const locator &i_locator, const std::string &filename)
Definition: picture.cpp:924
@ UNSCALED
Unmodified original-size image.
Definition: picture.hpp:226
bool broke_strict()
Definition: log.cpp:397
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:62
bool init_sound()
Definition: sound.cpp:441
void close_sound()
Definition: sound.cpp:493
void flush_cache()
Definition: sound.cpp:193
bool select_campaign(saved_game &state, jump_to_campaign_info jump_to_campaign)
void process(int mousex, int mousey)
Definition: tooltips.cpp:278
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:60
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
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:634
void set_window_icon(surface &icon)
Sets the icon of the main window.
Definition: video.cpp:640
void init(fake type)
Initialize the video subsystem.
Definition: video.cpp:82
void deinit()
Deinitialize the video subsystem.
Definition: video.cpp:111
This file contains the settings handling of the widget library.
An exception object used when an IO error occurs.
Definition: filesystem.hpp:66
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:53
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