The Battle for Wesnoth  1.19.0-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_config_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
29 #include "game_initialization/multiplayer.hpp" // for start_client, etc
30 #include "game_initialization/playcampaign.hpp" // for play_game, etc
31 #include "game_initialization/singleplayer.hpp" // for sp_create_mode
32 #include "generators/map_generator.hpp" // for mapgen_exception
33 #include "gettext.hpp" // for _
34 #include "gui/dialogs/language_selection.hpp" // for language_selection
36 #include "gui/dialogs/message.hpp" // for show error message
38 #include "gui/dialogs/multiplayer/mp_host_game_prompt.hpp" // for host game prompt
40 #include "gui/dialogs/title_screen.hpp" // for show_debug_clock_button
41 #include "gui/dialogs/transient_message.hpp" // for show_transient_message
42 #include "gui/widgets/retval.hpp" // for window, etc
43 #include "gui/widgets/settings.hpp" // for new_widgets
44 #include "language.hpp" // for language_def, etc
45 #include "log.hpp" // for LOG_STREAM, logger, general, etc
46 #include "map/exception.hpp"
48 #include "preferences/display.hpp"
49 #include "preferences/general.hpp" // for disable_preferences_save, etc
50 #include "save_index.hpp"
52 #include "sdl/surface.hpp" // for surface
53 #include "serialization/compression.hpp" // for format::NONE
54 #include "serialization/string_utils.hpp" // for split
55 #include "tstring.hpp" // for operator==, operator!=
56 #include "video.hpp"
58 #include "wml_exception.hpp" // for wml_exception
59 
60 #include <algorithm> // for copy, max, min, stable_sort
61 #ifdef _WIN32
62 #include <boost/process/windows.hpp>
63 #endif
64 #include <boost/process.hpp>
65 #include <cstdlib> // for system
66 #include <new>
67 #include <utility> // for pair
68 
69 #include <SDL2/SDL.h> // for SDL_INIT_JOYSTICK, etc
70 
71 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
72 #include "gui/widgets/debug.hpp"
73 #endif
74 
76 
77 static lg::log_domain log_config("config");
78 #define ERR_CONFIG LOG_STREAM(err, log_config)
79 #define WRN_CONFIG LOG_STREAM(warn, log_config)
80 #define LOG_CONFIG LOG_STREAM(info, log_config)
81 
82 #define ERR_GENERAL LOG_STREAM(err, lg::general())
83 #define LOG_GENERAL LOG_STREAM(info, lg::general())
84 #define WRN_GENERAL LOG_STREAM(warn, lg::general())
85 #define DBG_GENERAL LOG_STREAM(debug, lg::general())
86 
87 #define LOG_TEST FORCE_LOG_TO(lg::general(), log_config)
88 
89 static lg::log_domain log_mp_create("mp/create");
90 #define DBG_MP LOG_STREAM(debug, log_mp_create)
91 
92 static lg::log_domain log_network("network");
93 #define ERR_NET LOG_STREAM(err, log_network)
94 
95 static lg::log_domain log_enginerefac("enginerefac");
96 #define LOG_RG LOG_STREAM(info, log_enginerefac)
97 
98 namespace bp = boost::process;
99 
101  : cmdline_opts_(cmdline_opts)
102  , font_manager_()
103  , prefs_manager_()
104  , image_manager_()
105  , main_event_context_()
106  , hotkey_manager_()
107  , music_thinker_()
108  , music_muter_()
109  , test_scenarios_{"test"}
110  , screenshot_map_()
111  , screenshot_filename_()
112  , state_()
113  , play_replay_(false)
114  , multiplayer_server_()
115  , jump_to_multiplayer_(false)
116  , jump_to_campaign_{}
117  , jump_to_editor_(false)
118  , load_data_()
119 {
120  bool no_music = false;
121  bool no_sound = false;
122 
123  // The path can be hardcoded and it might be a relative path.
124  if(!game_config::path.empty() &&
125 #ifdef _WIN32
126  // use c_str to ensure that index 1 points to valid element since c_str() returns null-terminated string
127  game_config::path.c_str()[1] != ':'
128 #else
129  game_config::path[0] != '/'
130 #endif
131  )
132  {
134  // font_manager_.update_font_path()
135  // To update the font path, destroy and recreate the manager
137  new (&font_manager_) font::manager();
138  }
139 
140  if(cmdline_opts_.core_id) {
142  }
143  if(cmdline_opts_.campaign) {
144  jump_to_campaign_.jump = true;
146  PLAIN_LOG << "selected campaign id: [" << jump_to_campaign_.campaign_id << "]";
147 
150  PLAIN_LOG << "selected difficulty: [" << jump_to_campaign_.difficulty << "]";
151  } else {
152  jump_to_campaign_.difficulty = -1; // let the user choose the difficulty
153  }
154 
157  PLAIN_LOG << "selected scenario id: [" << jump_to_campaign_.scenario_id << "]";
158  }
159 
162  }
163  }
164  if(cmdline_opts_.clock)
166  if(cmdline_opts_.debug) {
168  game_config::mp_debug = true;
169  }
170 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
171  if(cmdline_opts_.debug_dot_domain)
172  gui2::debug_layout_graph::set_domain(*cmdline_opts_.debug_dot_domain);
173  if(cmdline_opts_.debug_dot_level)
175 #endif
176  if(cmdline_opts_.editor) {
177  jump_to_editor_ = true;
178  if(!cmdline_opts_.editor->empty()) {
181  }
182  }
183  if(cmdline_opts_.fps)
186  start_in_fullscreen_ = true;
187  if(cmdline_opts_.load)
190  if(cmdline_opts_.max_fps) {
191  int fps = std::clamp(*cmdline_opts_.max_fps, 1, 1000);
192  fps = 1000 / fps;
193  // increase the delay to avoid going above the maximum
194  if(1000 % fps != 0) {
195  ++fps;
196  }
198  }
200  no_sound = true;
202  }
204  gui2::new_widgets = true;
206  game_config::no_delay = true;
208  no_music = true;
210  no_sound = true;
212  const int xres = std::get<0>(*cmdline_opts_.resolution);
213  const int yres = std::get<1>(*cmdline_opts_.resolution);
214  if(xres > 0 && yres > 0) {
215  preferences::_set_resolution(point(xres, yres));
217  }
218  }
220  // TODO it could be simplified to use cmdline_opts_ directly if there is no other way to enter screenshot mode
223  no_sound = true;
225  }
226  if (cmdline_opts_.server){
227  jump_to_multiplayer_ = true;
228  // Do we have any server specified ?
229  if(!cmdline_opts_.server->empty()) {
231  } else {
232  // Pick the first server in config
233  if(game_config::server_list.size() > 0) {
235  } else {
236  multiplayer_server_ = "";
237  }
238  }
239  if(cmdline_opts_.username) {
242  if(cmdline_opts_.password) {
244  preferences::set_password(*cmdline_opts.server, *cmdline_opts.username, *cmdline_opts_.password);
245  }
246  }
247  }
248  if(cmdline_opts_.test) {
249  if(!cmdline_opts_.test->empty()) {
251  }
252  }
253  if(!cmdline_opts_.unit_test.empty()) {
255  }
257  start_in_fullscreen_ = false;
259  load_data_->show_replay = true;
262 
263  if(!cmdline_opts.nobanner) {
264  PLAIN_LOG
265  << "\nData directory: " << game_config::path
266  << "\nUser configuration directory: " << filesystem::get_user_config_dir()
267  << "\nUser data directory: " << filesystem::get_user_data_dir()
268  << "\nCache directory: " << filesystem::get_cache_dir()
269  << "\n\n";
270  }
271 
272  // disable sound in nosound mode, or when sound engine failed to initialize
273  if(no_sound || ((preferences::sound_on() || preferences::music_on() ||
275  !sound::init_sound())) {
276  preferences::set_sound(false);
277  preferences::set_music(false);
280  } else if(no_music) { // else disable the music in nomusic mode
281  preferences::set_music(false);
282  }
283 }
284 
286 {
287  if(!::load_language_list()) {
288  return false;
289  }
290 
291  language_def locale;
292  if(cmdline_opts_.language) {
293  std::vector<language_def> langs = get_languages(true);
294  for(const language_def& def : langs) {
295  if(def.localename == *cmdline_opts_.language) {
296  locale = def;
297  break;
298  }
299  }
300  if(locale.localename.empty()) {
301  PLAIN_LOG << "Language symbol '" << *cmdline_opts_.language << "' not found.";
302  return false;
303  }
304  } else {
305  locale = get_locale();
306  }
307  ::set_language(locale);
308 
309  return true;
310 }
311 
313 {
314  // Handle special commandline launch flags
319  {
325  {
326  PLAIN_LOG << "--nogui flag is only valid with --multiplayer or --screenshot or --plugin flags";
327  return false;
328  }
330  // Screenshots require a rendering context, and thus a window,
331  // so we create one but hidden.
333  } else {
334  // Other functions don't require a window at all.
336  }
337  game_config::no_delay = true;
338  return true;
339  }
340 
341  // Initialize video subsystem, and create a new window.
342  video::init();
343 
344  // Set window title and icon
346 
347 #if !(defined(__APPLE__))
348  surface icon(image::get_surface(image::locator{"icons/icon-game.png"}, image::UNSCALED));
349  if(icon != nullptr) {
351  }
352 #endif
353  return true;
354 }
355 
357 {
358  bool error = false;
359 
360  if(!cmdline_opts_.nobanner) {
361  STREAMING_LOG << "Checking lua scripts... ";
362  }
363 
365  // load the "package" package, so that scripts can get what packages they want
367  }
368 
369  // get the application lua kernel, load and execute script file, if script file is present
372 
373  if(!sf->fail()) {
374  /* Cancel all "jumps" to editor / campaign / multiplayer */
375  jump_to_multiplayer_ = false;
376  jump_to_editor_ = false;
377  jump_to_campaign_.jump = false;
378 
379  std::string full_script((std::istreambuf_iterator<char>(*sf)), std::istreambuf_iterator<char>());
380 
381  PLAIN_LOG << "\nRunning lua script: " << *cmdline_opts_.script_file;
382 
384  } else {
385  PLAIN_LOG << "Encountered failure when opening script '" << *cmdline_opts_.script_file << '\'';
386  error = true;
387  }
388  }
389 
391  std::string filename = *cmdline_opts_.plugin_file;
392 
393  PLAIN_LOG << "Loading a plugin file'" << filename << "'...";
394 
396 
397  try {
398  if(sf->fail()) {
399  throw std::runtime_error("failed to open plugin file");
400  }
401 
402  /* Cancel all "jumps" to editor / campaign / multiplayer */
403  jump_to_multiplayer_ = false;
404  jump_to_editor_ = false;
405  jump_to_campaign_.jump = false;
406 
407  std::string full_plugin((std::istreambuf_iterator<char>(*sf)), std::istreambuf_iterator<char>());
408 
410 
411  std::size_t i = pm.add_plugin(filename, full_plugin);
412 
413  for(std::size_t j = 0; j < pm.size(); ++j) {
414  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
415  }
416 
417  PLAIN_LOG << "Starting a plugin...";
418  pm.start_plugin(i);
419 
420  for(std::size_t j = 0; j < pm.size(); ++j) {
421  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
422  }
423 
424  plugins_context pc("init");
425 
426  for(std::size_t repeat = 0; repeat < 5; ++repeat) {
427  PLAIN_LOG << "Playing a slice...";
428  pc.play_slice();
429 
430  for(std::size_t j = 0; j < pm.size(); ++j) {
431  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
432  }
433  }
434 
435  return true;
436  } catch(const std::exception& e) {
437  gui2::show_error_message(std::string("When loading a plugin, error:\n") + e.what());
438  error = true;
439  }
440  }
441 
442  if(!error && !cmdline_opts_.nobanner) {
443  PLAIN_LOG << "ok";
444  }
445 
446  return !error;
447 }
448 
449 void game_launcher::set_test(const std::string& id)
450 {
451  state_.clear();
452  state_.classification().type = campaign_type::type::test;
454  state_.classification().era_id = "era_default";
455 
456  state_.set_carryover_sides_start(config{"next_scenario", id});
457 }
458 
460 {
461  // This first_time variable was added in 70f3c80a3e2 so that using the GUI
462  // menu to load a game works. That seems to have edge-cases, for example if
463  // you try to load a game a second time then Wesnoth exits.
464  static bool first_time = true;
465 
466  if(!cmdline_opts_.test) {
467  return true;
468  }
469 
470  if(!first_time) {
471  return false;
472  }
473 
474  first_time = false;
475 
476  if(test_scenarios_.size() == 0) {
477  // shouldn't happen, as test_scenarios_ is initialised to {"test"}
478  PLAIN_LOG << "Error in the test handling code";
479  return false;
480  }
481 
482  if(test_scenarios_.size() > 1) {
483  PLAIN_LOG << "You can't run more than one unit test in interactive mode";
484  }
485 
486  set_test(test_scenarios_.at(0));
487 
489 
490  try {
491  campaign_controller ccontroller(state_);
492  ccontroller.play_game();
493  } catch(savegame::load_game_exception& e) {
494  load_data_ = std::move(e.data_);
495  return true;
496  }
497 
498  return false;
499 }
500 
501 /**
502  * Runs unit tests specified on the command line.
503  *
504  * If multiple unit tests were specified, then this will stop at the first test
505  * which returns a non-zero status.
506  */
507 // Same as play_test except that we return the results of play_game.
508 // \todo "same ... except" ... and many other changes, such as testing the replay
510 {
511  // There's no copy of play_test's first_time variable. That seems to be for handling
512  // the player loading a game via the GUI, which makes no sense in a non-interactive test.
513  if(cmdline_opts_.unit_test.empty()) {
515  }
516 
517  auto ret = unit_test_result::TEST_FAIL; // will only be returned if no test is run
518  for(const auto& scenario : test_scenarios_) {
519  set_test(scenario);
520  ret = single_unit_test();
521  const char* describe_result;
522  switch(ret) {
524  describe_result = "PASS TEST";
525  break;
527  describe_result = "FAIL TEST (INVALID REPLAY)";
528  break;
530  describe_result = "FAIL TEST (ERRORED REPLAY)";
531  break;
533  describe_result = "FAIL TEST (WML EXCEPTION)";
534  break;
536  describe_result = "FAIL TEST (DEFEAT)";
537  break;
539  describe_result = "PASS TEST (VICTORY)";
540  break;
542  describe_result = "BROKE STRICT (PASS)";
543  break;
545  describe_result = "BROKE STRICT (FAIL)";
546  break;
548  describe_result = "BROKE STRICT (DEFEAT)";
549  break;
551  describe_result = "BROKE STRICT (VICTORY)";
552  break;
553  default:
554  describe_result = "FAIL TEST (UNKNOWN)";
555  break;
556  }
557 
558  PLAIN_LOG << describe_result << " (" << int(ret) << "): " << scenario;
559  if(ret != unit_test_result::TEST_PASS) {
560  break;
561  }
562  }
563 
564  return ret;
565 }
566 
568 {
570 
571  level_result::type game_res = level_result::type::fail;
572  try {
573  campaign_controller ccontroller(state_, true);
574  game_res = ccontroller.play_game();
575  if(game_res == level_result::type::fail) {
576  if(lg::broke_strict()) {
578  } else {
580  }
581  }
582  } catch(const wml_exception& e) {
583  PLAIN_LOG << "Caught WML Exception:" << e.dev_message;
585  }
586 
588 
590  return pass_victory_or_defeat(game_res);
591  }
592 
594  save.save_game_automatic(false, "unit_test_replay");
595 
597  savegame::save_index_class::default_saves_dir(), save.filename(), "", true, true, false};
598 
599  if(!load_game()) {
600  PLAIN_LOG << "Failed to load the replay!";
601  return unit_test_result::TEST_FAIL_LOADING_REPLAY; // failed to load replay
602  }
603 
604  try {
605  const bool was_strict_broken = lg::broke_strict();
606  campaign_controller ccontroller(state_, true);
607  ccontroller.play_replay();
608  if(!was_strict_broken && lg::broke_strict()) {
609  PLAIN_LOG << "Observed failure on replay";
611  }
612  } catch(const wml_exception& e) {
613  PLAIN_LOG << "WML Exception while playing replay: " << e.dev_message;
615  }
616 
617  return pass_victory_or_defeat(game_res);
618 }
619 
621 {
622  if(res == level_result::type::defeat) {
623  if(lg::broke_strict()) {
625  } else {
627  }
628  } else if(res == level_result::type::victory) {
629  if(lg::broke_strict()) {
631  } else {
633  }
634  }
635 
636  if(lg::broke_strict()) {
638  } else {
640  }
641 }
642 
644 {
646  return true;
647  }
648 
650 
652 
654  return false;
655 }
656 
658 {
660  return true;
661  }
662 
663  state_.classification().type = campaign_type::type::multiplayer;
664  DBG_GENERAL << "Current campaign type: " << campaign_type::get_string(state_.classification().type);
665 
666  try {
668  } catch(const config::error& e) {
669  PLAIN_LOG << "Error loading game config: " << e.what();
670  return false;
671  }
672 
673  // A default output filename
674  std::string outfile = "wesnoth_image.png";
675 
676  // If a output path was given as an argument, use that instead
678  outfile = *cmdline_opts_.render_image_dst;
679  }
680 
682  exit(1);
683  }
684 
685  return false;
686 }
687 
689 {
690  return load_data_.has_value();
691 }
692 
694 {
695  assert(game_config_manager::get());
696 
697  DBG_GENERAL << "Current campaign type: " << campaign_type::get_string(state_.classification().type);
698 
700  if(load_data_) {
701  load.data() = std::move(*load_data_);
703  }
704 
705  try {
706  if(!load.load_game()) {
707  return false;
708  }
709 
710  load.set_gamestate();
711  try {
713  } catch(const config::error&) {
714  return false;
715  }
716 
717  } catch(const config::error& e) {
718  if(e.message.empty()) {
719  gui2::show_error_message(_("The file you have tried to load is corrupt"));
720  } else {
721  gui2::show_error_message(_("The file you have tried to load is corrupt: '") + e.message + '\'');
722  }
723 
724  return false;
725  } catch(const wml_exception& e) {
726  e.show();
727  return false;
728  } catch(const filesystem::io_exception& e) {
729  if(e.message.empty()) {
730  gui2::show_error_message(_("File I/O Error while reading the game"));
731  } else {
732  gui2::show_error_message(_("File I/O Error while reading the game: '") + e.message + '\'');
733  }
734 
735  return false;
736  } catch(const game::error& e) {
737  if(e.message.empty()) {
738  gui2::show_error_message(_("The file you have tried to load is corrupt"));
739  } else {
740  gui2::show_error_message(_("The file you have tried to load is corrupt: '") + e.message + '\'');
741  }
742 
743  return false;
744  }
745 
746  play_replay_ = load.data().show_replay;
747  LOG_CONFIG << "is middle game savefile: " << (state_.is_mid_game_save() ? "yes" : "no");
748  LOG_CONFIG << "show replay: " << (play_replay_ ? "yes" : "no");
749  // in case load.data().show_replay && state_.is_start_of_scenario
750  // there won't be any turns to replay, but the
751  // user gets to watch the intro sequence again ...
752 
753  if(!state_.is_start_of_scenario() && load.data().show_replay) {
755  }
756 
759  }
760 
761  if(load.data().cancel_orders) {
763  }
764 
765  return true;
766 }
767 
769 {
770  state_.clear();
771  state_.classification().type = campaign_type::type::scenario;
772  play_replay_ = false;
773 
775 }
776 
778 {
780 }
781 
783 {
784  if(jump_to_campaign_.jump) {
785  if(new_campaign()) {
787  jump_to_campaign_.jump = false;
789  } else {
790  jump_to_campaign_.jump = false;
791  return false;
792  }
793  }
794 
795  return true;
796 }
797 
799 {
801  jump_to_multiplayer_ = false;
803  ;
804  } else {
805  return false;
806  }
807  }
808 
809  return true;
810 }
811 
813 {
814  if(jump_to_editor_) {
815  jump_to_editor_ = false;
816 
817  const std::string to_open = load_data_ ? filesystem::normalize_path(load_data_->filename) : "";
819 
821  return false;
822  }
823  }
824 
825  return true;
826 }
827 
829 {
830  std::string wesnothd_program = preferences::get_mp_server_program_name().empty()
833 
834  std::string config = filesystem::get_user_config_dir() + "/lan_server.cfg";
836  // copy file if it isn't created yet
838  }
839 
840  LOG_GENERAL << "Starting wesnothd";
841  try
842  {
843 #ifndef _WIN32
844  bp::child c(wesnothd_program, "-c", config);
845 #else
846  bp::child c(wesnothd_program, "-c", config, bp::windows::create_no_window);
847 #endif
848  c.detach();
849  // Give server a moment to start up
850  SDL_Delay(50);
851  return;
852  }
853  catch(const bp::process_error& e)
854  {
856 
857  // Couldn't start server so throw error
858  WRN_GENERAL << "Failed to start server " << wesnothd_program << ":\n" << e.what();
859  throw game::mp_server_error("Starting MP server failed!");
860  }
861 }
862 
864 {
865  try {
866  if(mode == mp_mode::HOST) {
867  try {
868  start_wesnothd();
869  } catch(const game::mp_server_error&) {
871 
872  try {
873  start_wesnothd();
874  } catch(const game::mp_server_error&) {
875  return false;
876  }
877  }
878  }
879 
880  // If a server address wasn't specified, prompt for it now.
881  if(mode != mp_mode::LOCAL && multiplayer_server_.empty()) {
882  if(!gui2::dialogs::mp_connect::execute()) {
883  return false;
884  }
885 
886  // The prompt saves its input to preferences.
888 
889  if(multiplayer_server_ != preferences::builtin_servers_list().front().address) {
891  }
892  }
893 
894  // create_engine already calls game_config_manager::get()->load_config but maybe its better to have MULTIPLAYER
895  // defined while we are in the lobby.
897 
898  events::discard_input(); // prevent the "keylogger" effect
900 
901  if(mode == mp_mode::LOCAL) {
903  } else {
905  multiplayer_server_.clear();
906  }
907 
908  } catch(const wesnothd_rejected_client_error& e) {
909  gui2::show_error_message(e.message);
910  } catch(const game::mp_server_error& e) {
911  gui2::show_error_message(_("Error while starting server: ") + e.message);
912  } catch(const game::load_game_failed& e) {
913  gui2::show_error_message(_("The game could not be loaded: ") + e.message);
914  } catch(const game::game_error& e) {
915  gui2::show_error_message(_("Error while playing the game: ") + e.message);
916  } catch(const mapgen_exception& e) {
917  gui2::show_error_message(_("Map generator error: ") + e.message);
918  } catch(const wesnothd_error& e) {
919  if(!e.message.empty()) {
920  ERR_NET << "caught network error: " << e.message;
921 
922  std::string user_msg;
923  auto conn_err = dynamic_cast<const wesnothd_connection_error*>(&e);
924 
925  if(conn_err) {
926  // The wesnothd_connection_error subclass is only thrown with messages
927  // from boost::system::error_code which we can't translate ourselves.
928  // It's also the originator of the infamous EOF error that happens when
929  // the server dies. <https://github.com/wesnoth/wesnoth/issues/3005>. It
930  // will provide a translated string instead of that when it happens.
931  user_msg = !conn_err->user_message.empty()
932  ? conn_err->user_message
933  : _("Connection failed: ") + e.message;
934  } else {
935  // This will be a message from the server itself, which we can
936  // probably translate.
937  user_msg = translation::gettext(e.message.c_str());
938  }
939 
940  gui2::show_error_message(user_msg);
941  } else {
942  ERR_NET << "caught network error";
943  }
944  } catch(const config::error& e) {
945  if(!e.message.empty()) {
946  ERR_CONFIG << "caught config::error: " << e.message;
947  gui2::show_transient_message("", e.message);
948  } else {
949  ERR_CONFIG << "caught config::error";
950  }
951  } catch(const incorrect_map_format_error& e) {
952  gui2::show_error_message(_("The game map could not be loaded: ") + e.message);
953  } catch(savegame::load_game_exception& e) {
954  load_data_ = std::move(e.data_);
955  // this will make it so next time through the title screen loop, this game is loaded
956  } catch(const wml_exception& e) {
957  e.show();
958  } catch(const game::error& e) {
959  PLAIN_LOG << "caught game::error...";
960  gui2::show_error_message(_("Error: ") + e.message);
961  }
962 
963  return true;
964 }
965 
967 {
969  return true;
970  }
971 
972  DBG_MP << "starting multiplayer game from the commandline";
973 
975 
976  events::discard_input(); // prevent the "keylogger" effect
978 
979  try {
981  } catch(savegame::load_game_exception& e) {
982  load_data_ = std::move(e.data_);
983  return true;
984  }
985 
986  return false;
987 }
988 
990 {
991  if(!gui2::dialogs::language_selection::execute()) {
992  return false;
993  }
994 
997  }
998 
1003 
1004  return true;
1005 }
1006 
1008 {
1009  assert(!load_data_);
1010  if(play_replay_) {
1011  play_replay();
1012  return;
1013  }
1014 
1015  gui2::dialogs::loading_screen::display([this, reload]() {
1017 
1018  if(reload == reload_mode::RELOAD_DATA) {
1019  try {
1022  } catch(const config::error&) {
1023  return;
1024  }
1025  }
1026  });
1027 
1028  try {
1029  campaign_controller ccontroller(state_);
1030  ccontroller.play_game();
1031  ai::manager::singleton_ = nullptr;
1032  } catch(savegame::load_game_exception& e) {
1033  load_data_ = std::move(e.data_);
1034  // this will make it so next time through the title screen loop, this game is loaded
1035  } catch(const wml_exception& e) {
1036  e.show();
1037  } catch(const mapgen_exception& e) {
1038  gui2::show_error_message(_("Map generator error: ") + e.message);
1039  }
1040 }
1041 
1043 {
1044  assert(!load_data_);
1045  try {
1046  campaign_controller ccontroller(state_);
1047  ccontroller.play_replay();
1048  } catch(savegame::load_game_exception& e) {
1049  load_data_ = std::move(e.data_);
1050  // this will make it so next time through the title screen loop, this game is loaded
1051  } catch(const wml_exception& e) {
1052  e.show();
1053  }
1054 }
1055 
1057 {
1059  while(true) {
1061 
1063 
1064  res = editor::start(res != editor::EXIT_RELOAD_DATA, filename);
1065 
1066  if(res != editor::EXIT_RELOAD_DATA) {
1067  return res;
1068  }
1069 
1071  }
1072 
1073  return editor::EXIT_ERROR; // not supposed to happen
1074 }
1075 
1077 {
1078  load_data_.reset();
1079 }
1080 
1082 {
1083  try {
1085  video::deinit();
1086  } catch(std::exception& e) {
1087  ERR_GENERAL << "Suppressing exception thrown during ~game_launcher: " << e.what();
1088  } catch(...) {
1089  ERR_GENERAL << "Suppressing exception " << utils::get_unknown_exception_type() << " thrown during ~game_launcher";
1090  }
1091 }
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands.
static manager * singleton_
Definition: manager.hpp:442
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:64
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:97
std::size_t size()
Definition: manager.cpp:69
std::string get_name(std::size_t idx)
Definition: manager.cpp:95
static plugins_manager * get()
Definition: manager.cpp:59
void start_plugin(std::size_t idx)
Definition: manager.cpp:102
std::string get_detailed_status(std::size_t idx)
Definition: manager.cpp:84
lua_kernel_base * get_kernel_base()
Definition: manager.cpp:64
std::size_t add_plugin(const std::string &name, const std::string &prog)
Definition: manager.cpp:119
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:733
void set_carryover_sides_start(config carryover_sides_start)
Definition: saved_game.cpp:164
std::string get_scenario_id() const
Definition: saved_game.cpp:679
void clear()
Definition: saved_game.cpp:814
statistics_record::campaign_stats_t & statistics()
Definition: saved_game.hpp:143
void cancel_orders()
Definition: saved_game.cpp:716
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:210
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:366
const std::string & filename() const
Definition: savegame.hpp:178
static void reset_translations()
Definition: tstring.cpp:652
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:329
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:104
language_list get_languages(bool all)
Return a list of available translations.
Definition: language.cpp:127
void set_min_translation_percent(int percent)
Definition: language.cpp:144
Standard logging facilities (interface).
#define STREAMING_LOG
Definition: log.hpp:296
#define PLAIN_LOG
Definition: log.hpp:295
@ NORMAL
Definition: cursor.hpp:29
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:203
@ EXIT_ERROR
Definition: editor_main.hpp:28
@ EXIT_NORMAL
Definition: editor_main.hpp:25
@ EXIT_QUIT_TO_DESKTOP
Definition: editor_main.hpp:26
@ EXIT_RELOAD_DATA
Definition: editor_main.hpp:27
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:799
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_config_dir()
Definition: filesystem.cpp:841
std::string get_user_data_dir()
Definition: filesystem.cpp:870
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:318
std::string get_exe_dir()
Definition: filesystem.cpp:990
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 or an empty string if the file isn't pres...
std::string get_program_invocation(const std::string &program_name)
Returns the appropriate invocation for a Wesnoth-related binary, assuming that it is located in the s...
std::string read_file(const std::string &fname)
Basic disk I/O - read file.
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:50
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_cwd()
Definition: filesystem.cpp:962
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:89
Game configuration data as global variables.
Definition: build_info.cpp:63
std::string path
Definition: filesystem.cpp:83
std::string get_default_title_string()
std::vector< server_info > server_list
Definition: game_config.cpp:72
void set_debug(bool new_debug)
Definition: game_config.cpp:93
bool show_debug_clock_button
Do we wish to show the button for the debug clock.
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:204
void flush_cache()
Purges all image caches.
Definition: picture.cpp:221
surface get_surface(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image surface suitable for software manipulation.
Definition: picture.cpp:677
save_result save_image(const locator &i_locator, const std::string &filename)
Definition: picture.cpp:928
@ UNSCALED
Unmodified original-size image.
Definition: picture.hpp:227
bool broke_strict()
Definition: log.cpp:398
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 disable_preferences_save()
Definition: general.cpp:231
std::string get_mp_server_program_name()
Definition: game.cpp:510
void set_core_id(const std::string &core_id)
Definition: general.cpp:338
void set_mp_server_program_name(const std::string &path)
Definition: game.cpp:501
bool set_turn_bell(bool ison)
Definition: general.cpp:680
bool set_sound(bool ison)
Definition: general.cpp:733
bool music_on()
Definition: general.cpp:751
void set_draw_delay(int value)
Definition: general.cpp:902
void set_show_fps(bool value)
Definition: general.cpp:892
bool sound_on()
Definition: general.cpp:728
std::string network_host()
Definition: game.cpp:387
bool set_music(bool ison)
Definition: general.cpp:756
void set_network_host(const std::string &host)
Definition: game.cpp:397
bool UI_sound_on()
Definition: general.cpp:699
void _set_maximized(bool ison)
Definition: general.cpp:450
const std::vector< game_config::server_info > & builtin_servers_list()
Definition: game.cpp:356
bool set_UI_sound(bool ison)
Definition: general.cpp:704
void set_level(const std::string &value)
Definition: game.cpp:696
void set_login(const std::string &login)
bool turn_bell()
Definition: general.cpp:675
void set_password(const std::string &server, const std::string &login, const std::string &key)
void _set_resolution(const point &res)
Definition: general.cpp:444
void show_wesnothd_server_search()
Definition: display.cpp:99
void clean_saves(const std::string &label)
Delete all autosaves of a certain scenario from the default save directory.
Definition: savegame.cpp:68
bool init_sound()
Definition: sound.cpp:443
void close_sound()
Definition: sound.cpp:495
void flush_cache()
Definition: sound.cpp:195
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:87
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:638
void set_window_icon(surface &icon)
Sets the icon of the main window.
Definition: video.cpp:644
void init(fake type)
Initialize the video subsystem.
Definition: video.cpp:86
void deinit()
Deinitialize the video subsystem.
Definition: video.cpp:115
This file contains the settings handling of the widget library.
An exception object used when an IO error occurs.
Definition: filesystem.hpp:64
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:55
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