The Battle for Wesnoth  1.19.12+dev
game_launcher.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "game_launcher.hpp"
17 #include "game_errors.hpp"
18 
19 #include "ai/manager.hpp" // for manager
20 #include "commandline_options.hpp" // for commandline_options
21 #include "config.hpp" // for config, etc
22 #include "cursor.hpp" // for set, CURSOR_TYPE::NORMAL
23 #include "exceptions.hpp" // for error
24 #include "filesystem.hpp" // for get_user_data_dir, etc
25 #include "game_classification.hpp" // for game_classification, etc
26 #include "game_config.hpp" // for path, etc
27 #include "game_config_manager.hpp" // for game_config_manager
28 #include "game_initialization/multiplayer.hpp" // for start_client, etc
29 #include "game_initialization/playcampaign.hpp" // for play_game, etc
30 #include "game_initialization/singleplayer.hpp" // for sp_create_mode
31 #include "generators/map_generator.hpp" // for mapgen_exception
32 #include "gettext.hpp" // for _
33 #include "gui/dialogs/language_selection.hpp" // for language_selection
35 #include "gui/dialogs/message.hpp" // for show error message
37 #include "gui/dialogs/title_screen.hpp" // for show_debug_clock_button
38 #include "gui/dialogs/transient_message.hpp" // for show_transient_message
39 #include "gui/widgets/settings.hpp" // for new_widgets
40 #include "language.hpp" // for language_def, etc
41 #include "log.hpp" // for LOG_STREAM, logger, general, etc
42 #include "map/exception.hpp"
44 #include "save_index.hpp"
46 #include "sdl/surface.hpp" // for surface
47 #include "serialization/compression.hpp" // for format::NONE
48 #include "tstring.hpp" // for operator==, operator!=
49 #include "video.hpp"
51 #include "wml_exception.hpp" // for wml_exception
52 
53 #ifdef __APPLE__
54 
55 //
56 // HACK: MacCompileStuff is currently on 1.86, so it could use the v2 API,
57 // but we need to update the libs manually to link against boost::process.
58 //
59 // -- vultraz, 2025-05-12
60 //
61 #if BOOST_VERSION > 108600
62 #error MacCompileStuff has been updated. Remove this block and the accompanying __APPLE__ checks below.
63 #endif
64 #include <boost/process/v1/child.hpp>
65 
66 #elif BOOST_VERSION >= 108600
67 
68 // boost::asio (via boost::process) complains about winsock.h otherwise
69 #ifdef _WIN32
70 #define WIN32_LEAN_AND_MEAN
71 #endif
72 #include <boost/process/v2/process.hpp>
73 
74 #else
75 
76 // process::v1 only. The v1 folders do not exist until 1.86
77 #ifdef _WIN32
78 #include <boost/process/windows.hpp>
79 #endif
80 #include <boost/process/child.hpp>
81 
82 #endif
83 
84 #include <algorithm> // for copy, max, min, stable_sort
85 #include <cstdlib> // for system
86 #include <new>
87 #include <thread>
88 #include <utility> // for pair
89 
90 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
91 #include "gui/widgets/debug.hpp"
92 #endif
93 
94 static lg::log_domain log_config("config");
95 #define ERR_CONFIG LOG_STREAM(err, log_config)
96 #define WRN_CONFIG LOG_STREAM(warn, log_config)
97 #define LOG_CONFIG LOG_STREAM(info, log_config)
98 
99 #define ERR_GENERAL LOG_STREAM(err, lg::general())
100 #define LOG_GENERAL LOG_STREAM(info, lg::general())
101 #define WRN_GENERAL LOG_STREAM(warn, lg::general())
102 #define DBG_GENERAL LOG_STREAM(debug, lg::general())
103 
104 #define LOG_TEST FORCE_LOG_TO(lg::general(), log_config)
105 
106 static lg::log_domain log_mp_create("mp/create");
107 #define DBG_MP LOG_STREAM(debug, log_mp_create)
108 
109 static lg::log_domain log_network("network");
110 #define ERR_NET LOG_STREAM(err, log_network)
111 
112 static lg::log_domain log_enginerefac("enginerefac");
113 #define LOG_RG LOG_STREAM(info, log_enginerefac)
114 
116  : cmdline_opts_(cmdline_opts)
117  , font_manager_()
118  , image_manager_()
119  , main_event_context_()
120  , hotkey_manager_()
121  , music_thinker_()
122  , music_muter_()
123  , test_scenarios_{"test"}
124  , screenshot_map_()
125  , screenshot_filename_()
126  , state_()
127  , play_replay_(false)
128  , multiplayer_server_()
129  , jump_to_multiplayer_(false)
130  , jump_to_campaign_{}
131  , jump_to_editor_(false)
132  , load_data_()
133 {
134  bool no_music = false;
135  bool no_sound = false;
136 
137  if(cmdline_opts_.core_id) {
138  prefs::get().set_core(*cmdline_opts_.core_id);
139  }
140  if(cmdline_opts_.campaign) {
141  jump_to_campaign_.jump = true;
143  PLAIN_LOG << "selected campaign id: [" << jump_to_campaign_.campaign_id << "]";
144 
147  PLAIN_LOG << "selected difficulty: [" << jump_to_campaign_.difficulty << "]";
148  } else {
149  jump_to_campaign_.difficulty = -1; // let the user choose the difficulty
150  }
151 
154  PLAIN_LOG << "selected scenario id: [" << jump_to_campaign_.scenario_id << "]";
155  }
156 
159  }
160  }
161  if(cmdline_opts_.clock)
163  if(cmdline_opts_.debug) {
165  game_config::mp_debug = true;
166  }
167 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
168  if(cmdline_opts_.debug_dot_domain)
169  gui2::debug_layout_graph::set_domain(*cmdline_opts_.debug_dot_domain);
170  if(cmdline_opts_.debug_dot_level)
171  gui2::debug_layout_graph::set_level(*cmdline_opts_.debug_dot_level);
172 #endif
173  if(cmdline_opts_.editor) {
174  jump_to_editor_ = true;
175  if(!cmdline_opts_.editor->empty()) {
178  }
179  }
180  if(cmdline_opts_.fps)
181  prefs::get().set_show_fps(true);
183  prefs::get().set_fullscreen(true);
184  if(cmdline_opts_.load)
187  if(cmdline_opts_.max_fps) {
188  prefs::get().set_refresh_rate(std::clamp(*cmdline_opts_.max_fps, 1, 1000));
189  }
191  no_sound = true;
193  }
195  gui2::new_widgets = true;
197  no_music = true;
199  no_sound = true;
201  const int xres = std::get<0>(*cmdline_opts_.resolution);
202  const int yres = std::get<1>(*cmdline_opts_.resolution);
203  if(xres > 0 && yres > 0) {
204  prefs::get().set_resolution(point(xres, yres));
205  prefs::get().set_maximized(false);
206  }
207  }
209  // TODO it could be simplified to use cmdline_opts_ directly if there is no other way to enter screenshot mode
212  no_sound = true;
214  }
215  if (cmdline_opts_.server){
216  jump_to_multiplayer_ = true;
217  // Do we have any server specified ?
218  if(!cmdline_opts_.server->empty()) {
220  } else {
221  // Pick the first server in config
222  if(game_config::server_list.size() > 0) {
224  } else {
225  multiplayer_server_ = "";
226  }
227  }
228  if(cmdline_opts_.username) {
231  if(cmdline_opts_.password) {
233  prefs::get().set_password(*cmdline_opts.server, *cmdline_opts.username, *cmdline_opts_.password);
234  }
235  }
236  }
237  if(cmdline_opts_.test) {
238  if(!cmdline_opts_.test->empty()) {
240  }
241  }
242  if(!cmdline_opts_.unit_test.empty()) {
244  }
246  prefs::get().set_fullscreen(false);
248  load_data_->show_replay = true;
251 
252  if(!cmdline_opts.nobanner) {
253  PLAIN_LOG
254  << "\nGame data: " << game_config::path
255  << "\nUser data: " << filesystem::get_user_data_dir()
256  << "\nCache: " << filesystem::get_cache_dir()
257  << "\n";
258  }
259 
260  // disable sound in nosound mode, or when sound engine failed to initialize
261  if(no_sound || ((prefs::get().sound() || prefs::get().music_on() ||
263  !sound::init_sound())) {
264  prefs::get().set_sound(false);
265  prefs::get().set_music(false);
266  prefs::get().set_turn_bell(false);
267  prefs::get().set_ui_sound(false);
268  } else if(no_music) { // else disable the music in nomusic mode
269  prefs::get().set_music(false);
270  }
271 }
272 
274 {
275  if(!::load_language_list()) {
276  return false;
277  }
278 
279  language_def locale;
280  if(cmdline_opts_.language) {
281  std::vector<language_def> langs = get_languages(true);
282  for(const language_def& def : langs) {
283  if(def.localename == *cmdline_opts_.language) {
284  locale = def;
285  break;
286  }
287  }
288  if(locale.localename.empty()) {
289  PLAIN_LOG << "Language symbol '" << *cmdline_opts_.language << "' not found.";
290  return false;
291  }
292  } else {
293  locale = get_locale();
294  }
295  ::set_language(locale);
296 
297  return true;
298 }
299 
301 {
302  // Handle special commandline launch flags
307  {
313  {
314  PLAIN_LOG << "--nogui flag is only valid with --multiplayer or --screenshot or --plugin flags";
315  return false;
316  }
318  // Screenshots require a rendering context, and thus a window,
319  // so we create one but hidden.
321  } else {
322  // Other functions don't require a window at all.
324  }
325  return true;
326  }
327 
328  // Initialize video subsystem, and create a new window.
329  video::init();
330 
331  // Set window title and icon
333 
334 #if !(defined(__APPLE__))
335  surface icon(image::get_surface(image::locator{"icons/icon-game.png"}, image::UNSCALED));
336  if(icon != nullptr) {
338  }
339 #endif
340  return true;
341 }
342 
344 {
345  bool error = false;
346 
347  if(!cmdline_opts_.nobanner) {
348  STREAMING_LOG << "Checking lua scripts... ";
349  }
350 
352  // load the "package" package, so that scripts can get what packages they want
354  }
355 
357  std::string filename = *cmdline_opts_.plugin_file;
358 
359  PLAIN_LOG << "Loading a plugin file'" << filename << "'...";
360 
362 
363  try {
364  if(sf->fail()) {
365  throw std::runtime_error("failed to open plugin file");
366  }
367 
368  /* Cancel all "jumps" to editor / campaign / multiplayer */
369  jump_to_multiplayer_ = false;
370  jump_to_editor_ = false;
371  jump_to_campaign_.jump = false;
372 
373  std::string full_plugin((std::istreambuf_iterator<char>(*sf)), std::istreambuf_iterator<char>());
374 
376 
377  std::size_t i = pm.add_plugin(filename, full_plugin);
378 
379  for(std::size_t j = 0; j < pm.size(); ++j) {
380  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
381  }
382 
383  PLAIN_LOG << "Starting a plugin...";
384  pm.start_plugin(i);
385 
386  for(std::size_t j = 0; j < pm.size(); ++j) {
387  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
388  }
389 
390  plugins_context pc("init");
391 
392  for(std::size_t repeat = 0; repeat < 5; ++repeat) {
393  PLAIN_LOG << "Playing a slice...";
394  pc.play_slice();
395 
396  for(std::size_t j = 0; j < pm.size(); ++j) {
397  PLAIN_LOG << j << ": " << pm.get_name(j) << " -- " << pm.get_detailed_status(j);
398  }
399  }
400 
401  return true;
402  } catch(const std::exception& e) {
403  gui2::show_error_message(std::string("When loading a plugin, error:\n") + e.what());
404  error = true;
405  }
406  }
407 
408  if(!error && !cmdline_opts_.nobanner) {
409  PLAIN_LOG << "ok";
410  }
411 
412  return !error;
413 }
414 
415 void game_launcher::set_test(const std::string& id)
416 {
417  state_.clear();
418  state_.classification().type = campaign_type::type::test;
420  state_.classification().era_id = "era_default";
421 
422  state_.set_carryover_sides_start(config{"next_scenario", id});
423 }
424 
426 {
427  // This first_time variable was added in 70f3c80a3e2 so that using the GUI
428  // menu to load a game works. That seems to have edge-cases, for example if
429  // you try to load a game a second time then Wesnoth exits.
430  static bool first_time = true;
431 
432  if(!cmdline_opts_.test) {
433  return true;
434  }
435 
436  if(!first_time) {
437  return false;
438  }
439 
440  first_time = false;
441 
442  if(test_scenarios_.size() == 0) {
443  // shouldn't happen, as test_scenarios_ is initialised to {"test"}
444  PLAIN_LOG << "Error in the test handling code";
445  return false;
446  }
447 
448  if(test_scenarios_.size() > 1) {
449  PLAIN_LOG << "You can't run more than one unit test in interactive mode";
450  }
451 
452  set_test(test_scenarios_.at(0));
453 
455 
456  try {
457  campaign_controller ccontroller(state_);
458  ccontroller.play_game();
459  } catch(savegame::load_game_exception& e) {
460  load_data_ = std::move(e.data_);
461  return true;
462  }
463 
464  return false;
465 }
466 
467 /**
468  * Runs unit tests specified on the command line.
469  *
470  * If multiple unit tests were specified, then this will stop at the first test
471  * which returns a non-zero status.
472  */
473 // Same as play_test except that we return the results of play_game.
474 // \todo "same ... except" ... and many other changes, such as testing the replay
476 {
477  // There's no copy of play_test's first_time variable. That seems to be for handling
478  // the player loading a game via the GUI, which makes no sense in a non-interactive test.
479  if(cmdline_opts_.unit_test.empty()) {
481  }
482 
483  auto ret = unit_test_result::TEST_FAIL; // will only be returned if no test is run
484  for(const auto& scenario : test_scenarios_) {
485  set_test(scenario);
486  ret = single_unit_test();
487  const char* describe_result;
488  switch(ret) {
490  describe_result = "PASS TEST";
491  break;
493  describe_result = "FAIL TEST (INVALID REPLAY)";
494  break;
496  describe_result = "FAIL TEST (ERRORED REPLAY)";
497  break;
499  describe_result = "FAIL TEST (WML EXCEPTION)";
500  break;
502  describe_result = "FAIL TEST (DEFEAT)";
503  break;
505  describe_result = "PASS TEST (VICTORY)";
506  break;
508  describe_result = "BROKE STRICT (PASS)";
509  break;
511  describe_result = "BROKE STRICT (FAIL)";
512  break;
514  describe_result = "BROKE STRICT (DEFEAT)";
515  break;
517  describe_result = "BROKE STRICT (VICTORY)";
518  break;
519  default:
520  describe_result = "FAIL TEST (UNKNOWN)";
521  break;
522  }
523 
524  PLAIN_LOG << describe_result << " (" << int(ret) << "): " << scenario;
525  if(ret != unit_test_result::TEST_PASS) {
526  break;
527  }
528  }
529 
530  return ret;
531 }
532 
534 {
536 
537  level_result::type game_res = level_result::type::fail;
538  try {
539  campaign_controller ccontroller(state_, true);
540  game_res = ccontroller.play_game();
541  if(game_res == level_result::type::fail) {
542  if(lg::broke_strict()) {
544  } else {
546  }
547  }
548  } catch(const wml_exception& e) {
549  PLAIN_LOG << "Caught WML Exception:" << e.dev_message;
551  }
552 
554 
556  return pass_victory_or_defeat(game_res);
557  }
558 
560  save.save_game_automatic(false, "unit_test_replay");
561 
563  savegame::save_index_class::default_saves_dir(), save.filename(), "", true, true, false};
564 
565  if(!load_game()) {
566  PLAIN_LOG << "Failed to load the replay!";
567  return unit_test_result::TEST_FAIL_LOADING_REPLAY; // failed to load replay
568  }
569 
570  try {
571  const bool was_strict_broken = lg::broke_strict();
572  campaign_controller ccontroller(state_, true);
573  ccontroller.play_replay();
574  if(!was_strict_broken && lg::broke_strict()) {
575  PLAIN_LOG << "Observed failure on replay";
577  }
578  } catch(const wml_exception& e) {
579  PLAIN_LOG << "WML Exception while playing replay: " << e.dev_message;
581  }
582 
583  return pass_victory_or_defeat(game_res);
584 }
585 
587 {
588  if(res == level_result::type::defeat) {
589  if(lg::broke_strict()) {
591  } else {
593  }
594  } else if(res == level_result::type::victory) {
595  if(lg::broke_strict()) {
597  } else {
599  }
600  }
601 
602  if(lg::broke_strict()) {
604  } else {
606  }
607 }
608 
610 {
612  return true;
613  }
614 
616 
618 
620  return false;
621 }
622 
624 {
626  return true;
627  }
628 
629  state_.classification().type = campaign_type::type::multiplayer;
630  DBG_GENERAL << "Current campaign type: " << campaign_type::get_string(state_.classification().type);
631 
632  try {
634  } catch(const config::error& e) {
635  PLAIN_LOG << "Error loading game config: " << e.what();
636  return false;
637  }
638 
639  // A default output filename
640  std::string outfile = "wesnoth_image.png";
641 
642  // If a output path was given as an argument, use that instead
644  outfile = *cmdline_opts_.render_image_dst;
645  }
646 
648  exit(1);
649  }
650 
651  return false;
652 }
653 
655 {
656  return load_data_.has_value();
657 }
658 
660 {
661  assert(game_config_manager::get());
662 
663  DBG_GENERAL << "Current campaign type: " << campaign_type::get_string(state_.classification().type);
664 
666  if(load_data_) {
667  load.data() = std::move(*load_data_);
669  }
670 
671  try {
672  if(!load.load_game()) {
673  return false;
674  }
675 
676  load.set_gamestate();
677  try {
679  } catch(const config::error&) {
680  return false;
681  }
682 
683  } catch(const config::error& e) {
684  if(e.message.empty()) {
685  gui2::show_error_message(_("The file you have tried to load is corrupt"));
686  } else {
687  gui2::show_error_message(_("The file you have tried to load is corrupt: '") + e.message + '\'');
688  }
689 
690  return false;
691  } catch(const wml_exception& e) {
692  e.show();
693  return false;
694  } catch(const filesystem::io_exception& e) {
695  if(e.message.empty()) {
696  gui2::show_error_message(_("File I/O Error while reading the game"));
697  } else {
698  gui2::show_error_message(_("File I/O Error while reading the game: '") + e.message + '\'');
699  }
700 
701  return false;
702  } catch(const game::error& e) {
703  if(e.message.empty()) {
704  gui2::show_error_message(_("The file you have tried to load is corrupt"));
705  } else {
706  gui2::show_error_message(_("The file you have tried to load is corrupt: '") + e.message + '\'');
707  }
708 
709  return false;
710  }
711 
712  play_replay_ = load.data().show_replay;
713  LOG_CONFIG << "is middle game savefile: " << (state_.is_mid_game_save() ? "yes" : "no");
714  LOG_CONFIG << "show replay: " << (play_replay_ ? "yes" : "no");
715  // in case load.data().show_replay && state_.is_start_of_scenario
716  // there won't be any turns to replay, but the
717  // user gets to watch the intro sequence again ...
718 
719  if(!state_.is_start_of_scenario() && load.data().show_replay) {
721  }
722 
725  }
726 
727  if(load.data().cancel_orders) {
729  }
730 
731  return true;
732 }
733 
735 {
736  state_.clear();
737  state_.classification().type = campaign_type::type::scenario;
738  play_replay_ = false;
739 
741 }
742 
744 {
746 }
747 
749  jump_to_campaign_.jump = false;
750  if(new_campaign()) {
753  return true;
754  }
755  return false;
756 }
757 
759 {
760  if(jump_to_campaign_.jump) {
761  return play_campaign();
762  }
763 
764  return true;
765 }
766 
768 {
769  if(!jump_to_multiplayer_) {
770  return true;
771  }
772 
773  jump_to_multiplayer_ = false;
775 }
776 
778 {
779  if(jump_to_editor_) {
780  jump_to_editor_ = false;
781 
782  const std::string to_open = load_data_ ? filesystem::normalize_path(load_data_->filename) : "";
784 
786  return false;
787  }
788  }
789 
790  return true;
791 }
792 
794 {
795  std::string wesnothd_program = "";
796  if(!prefs::get().get_mp_server_program_name().empty()) {
797  wesnothd_program = prefs::get().get_mp_server_program_name();
798  } else {
799  wesnothd_program = filesystem::get_wesnothd_name();
800  }
801 
802  std::string config = filesystem::get_user_data_dir() + "/lan_server.cfg";
804  // copy file if it isn't created yet
806  }
807 
808  LOG_GENERAL << "Starting wesnothd";
809  try
810  {
811 #if !defined(__APPLE__) && BOOST_VERSION >= 108600
812  boost::asio::io_context io_context;
813  auto c = boost::process::v2::process{io_context, wesnothd_program, { "-c", config }};
814 #else
815 #ifndef _WIN32
816  boost::process::child c(wesnothd_program, "-c", config);
817 #else
818  boost::process::child c(wesnothd_program, "-c", config, boost::process::windows::create_no_window);
819 #endif
820 #endif
821  c.detach();
822  // Give server a moment to start up
823  using namespace std::chrono_literals;
824  std::this_thread::sleep_for(50ms);
825  return;
826  }
827 #if defined(__APPLE__) || BOOST_VERSION < 108600
828  catch(const boost::process::process_error& e)
829 #else
830  catch(const std::exception& e)
831 #endif
832  {
834 
835  // Couldn't start server so throw error
836  WRN_GENERAL << "Failed to start server " << wesnothd_program << ":\n" << e.what();
837  throw game::mp_server_error("Starting MP server failed!");
838  }
839 }
840 
842 {
844  try {
845  if(mode == mp_mode::HOST) {
846  try {
847  start_wesnothd();
848  } catch(const game::mp_server_error&) {
850 
851  try {
852  start_wesnothd();
853  } catch(const game::mp_server_error&) {
854  return false;
855  }
856  }
857  }
858 
859  // If a server address wasn't specified, prompt for it now.
860  if(mode != mp_mode::LOCAL && multiplayer_server_.empty()) {
861  if(!gui2::dialogs::mp_connect::execute()) {
862  return false;
863  }
864 
865  // The prompt saves its input to preferences.
867 
868  if(multiplayer_server_ != prefs::get().builtin_servers_list().front().address) {
870  }
871  }
872 
873  // create_engine already calls game_config_manager::get()->load_config but maybe its better to have MULTIPLAYER
874  // defined while we are in the lobby.
876 
877  events::discard_input(); // prevent the "keylogger" effect
879 
880  if(mode == mp_mode::LOCAL) {
882  } else {
884  multiplayer_server_.clear();
885  }
886 
887  } catch(const wesnothd_rejected_client_error& e) {
888  gui2::show_error_message(e.message);
889  } catch(const game::mp_server_error& e) {
890  gui2::show_error_message(_("Error while starting server: ") + e.message);
891  } catch(const game::load_game_failed& e) {
892  gui2::show_error_message(_("The game could not be loaded: ") + e.message);
893  } catch(const game::game_error& e) {
894  gui2::show_error_message(_("Error while playing the game: ") + e.message);
895  } catch(const mapgen_exception& e) {
896  gui2::show_error_message(_("Map generator error: ") + e.message);
897  } catch(const wesnothd_error& e) {
898  if(!e.message.empty()) {
899  ERR_NET << "caught network error: " << e.message;
900 
901  std::string user_msg;
902  auto conn_err = dynamic_cast<const wesnothd_connection_error*>(&e);
903 
904  if(conn_err) {
905  // The wesnothd_connection_error subclass is only thrown with messages
906  // from boost::system::error_code which we can't translate ourselves.
907  // It's also the originator of the infamous EOF error that happens when
908  // the server dies. <https://github.com/wesnoth/wesnoth/issues/3005>. It
909  // will provide a translated string instead of that when it happens.
910  user_msg = !conn_err->user_message.empty()
911  ? conn_err->user_message
912  : _("Connection failed: ") + e.message;
913  } else {
914  // This will be a message from the server itself, which we can
915  // probably translate.
916  user_msg = translation::gettext(e.message.c_str());
917  }
918 
919  gui2::show_error_message(user_msg);
920  } else {
921  ERR_NET << "caught network error";
922  }
923  } catch(const config::error& e) {
924  if(!e.message.empty()) {
925  ERR_CONFIG << "caught config::error: " << e.message;
926  gui2::show_transient_message("", e.message);
927  } else {
928  ERR_CONFIG << "caught config::error";
929  }
930  } catch(const incorrect_map_format_error& e) {
931  gui2::show_error_message(_("The game map could not be loaded: ") + e.message);
932  } catch(savegame::load_game_exception& e) {
933  load_data_ = std::move(e.data_);
934  // this will make it so next time through the title screen loop, this game is loaded
935  } catch(const wml_exception& e) {
936  e.show();
937  } catch(const game::error& e) {
938  PLAIN_LOG << "caught game::error...";
939  gui2::show_error_message(_("Error: ") + e.message);
940  }
941 
942  return true;
943 }
944 
946 {
948  return true;
949  }
950 
951  DBG_MP << "starting multiplayer game from the commandline";
952 
954 
955  events::discard_input(); // prevent the "keylogger" effect
957 
958  try {
960  } catch(savegame::load_game_exception& e) {
961  load_data_ = std::move(e.data_);
962  return true;
963  }
964 
965  return false;
966 }
967 
969 {
970  if(!gui2::dialogs::language_selection::execute()) {
971  return false;
972  }
973 
976  }
977 
982 
983  return true;
984 }
985 
987 {
988  assert(!load_data_);
989  if(play_replay_) {
990  play_replay();
991  return;
992  }
993 
994  gui2::dialogs::loading_screen::display([this, reload]() {
996 
997  if(reload == reload_mode::RELOAD_DATA) {
998  try {
1001  } catch(const config::error&) {
1002  return;
1003  }
1004  }
1005  });
1006 
1007  try {
1008  campaign_controller ccontroller(state_);
1009  ccontroller.play_game();
1010  ai::manager::singleton_ = nullptr;
1011  } catch(savegame::load_game_exception& e) {
1012  load_data_ = std::move(e.data_);
1013  // this will make it so next time through the title screen loop, this game is loaded
1014  } catch(const wml_exception& e) {
1015  e.show();
1016  } catch(const mapgen_exception& e) {
1017  gui2::show_error_message(_("Map generator error: ") + e.message);
1018  }
1019 }
1020 
1022 {
1023  assert(!load_data_);
1024  try {
1025  campaign_controller ccontroller(state_);
1026  ccontroller.play_replay();
1027  } catch(savegame::load_game_exception& e) {
1028  load_data_ = std::move(e.data_);
1029  // this will make it so next time through the title screen loop, this game is loaded
1030  } catch(const wml_exception& e) {
1031  e.show();
1032  }
1033 }
1034 
1036 {
1038  while(true) {
1040 
1042 
1044 
1045  if(res != editor::EXIT_RELOAD_DATA) {
1046  return res;
1047  }
1048 
1050  }
1051 
1052  return editor::EXIT_ERROR; // not supposed to happen
1053 }
1054 
1056 {
1057  load_data_.reset();
1058 }
1059 
1061 {
1062  try {
1064  video::deinit();
1065  } catch(std::exception& e) {
1066  ERR_GENERAL << "Suppressing exception thrown during ~game_launcher: " << e.what();
1067  } catch(...) {
1068  ERR_GENERAL << "Suppressing exception " << utils::get_unknown_exception_type() << " thrown during ~game_launcher";
1069  }
1070 }
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands.
static manager * singleton_
Definition: manager.hpp:437
level_result::type play_game()
level_result::type play_replay()
bool nogui
True if –nogui was given on the command line.
utils::optional< std::pair< int, int > > resolution
Pair of AxB values specified after –resolution.
bool headless_unit_test
True if –unit is used and –showgui is not present.
utils::optional< std::string > screenshot_map_file
Map file to make a screenshot of.
utils::optional< std::string > test
Non-empty if –test was given on the command line.
bool windowed
True if –windowed was given on the command line.
utils::optional< std::string > language
Non-empty if –language was given on the command line.
bool noreplaycheck
True if –noreplaycheck was given on the command line.
utils::optional< std::string > screenshot_output_file
Output file to put screenshot in.
utils::optional< int > max_fps
Max FPS specified by –max-fps option.
utils::optional< std::string > core_id
Non-empty if –core was given on the command line.
utils::optional< int > campaign_difficulty
Non-empty if –campaign-difficulty was given on the command line.
bool debug
True if –debug was given on the command line.
bool nosound
True if –nosound was given on the command line.
bool multiplayer
True if –multiplayer was given on the command line.
utils::optional< std::string > server
Non-empty if –server was given on the command line.
bool script_unsafe_mode
Whether to load the "package" package for the scripting environment.
utils::optional< unsigned int > translation_percent
Non-empty if –all-translations or –translations-over is given on the command line.
utils::optional< std::string > plugin_file
File to load a lua plugin script from.
bool nobanner
True if –nobanner was given on the command line.
bool nomusic
True if –nomusic was given on the command line.
bool clock
True if –clock was given on the command line.
utils::optional< std::string > render_image_dst
Output file to put rendered image path in.
utils::optional< std::string > render_image
Image path to render.
utils::optional< std::string > campaign
Non-empty if –campaign was given on the command line.
bool new_widgets
True if –new-widgets was given on the command line.
bool fps
True if –fps was given on the command line.
utils::optional< std::string > username
Non-empty if –username was given on the command line.
bool campaign_skip_story
True if –skip-story was given on the command line.
bool with_replay
True if –with-replay was given on the command line.
bool screenshot
True if –screenshot was given on the command line.
std::vector< std::string > unit_test
Non-empty if –unit was given on the command line.
utils::optional< std::string > editor
Non-empty if –editor was given on the command line.
utils::optional< std::string > password
Non-empty if –password was given on the command line.
bool fullscreen
True if –fullscreen was given on the command line.
utils::optional< std::string > campaign_scenario
Non-empty if –campaign-scenario was given on the command line.
utils::optional< std::string > load
Non-empty if –load was given on the command line.
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
campaign_type::type type
std::string label
Name of the game (e.g.
std::string campaign_define
If there is a define the campaign uses to customize data.
static game_config_manager * get()
void load_game_config_for_game(const game_classification &classification, const std::string &scenario_id)
void load_game_config_for_create(bool is_mp, bool is_test=false)
game_launcher(const commandline_options &cmdline_opts)
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_
unit_test_result pass_victory_or_defeat(level_result::type res)
bool has_load_data() const
bool play_render_image_mode()
void clear_loaded_game()
bool init_lua_script()
std::vector< std::string > test_scenarios_
unit_test_result
Status code after running a unit test, should match the run_wml_tests script and the documentation fo...
std::string screenshot_map_
const commandline_options & cmdline_opts_
utils::optional< savegame::load_game_metadata > load_data_
void set_test(const std::string &id)
bool play_multiplayer_commandline()
unit_test_result unit_test()
Runs unit tests specified on the command line.
bool play_multiplayer(mp_mode mode)
editor::EXIT_STATUS start_editor()
static void progress(loading_stage stage=loading_stage::none)
Report what is being loaded to the loading screen.
static void display(const std::function< void()> &f)
Generic locator abstracting the location of an image.
Definition: picture.hpp:59
void load_package()
Loads the package library into lua environment.
void play_slice()
Definition: context.cpp:103
std::size_t size()
Definition: manager.cpp:68
std::string get_name(std::size_t idx)
Definition: manager.cpp:94
static plugins_manager * get()
Definition: manager.cpp:58
void start_plugin(std::size_t idx)
Definition: manager.cpp:101
std::string get_detailed_status(std::size_t idx)
Definition: manager.cpp:83
lua_kernel_base * get_kernel_base()
Definition: manager.cpp:63
std::size_t add_plugin(const std::string &name, const std::string &prog)
Definition: manager.cpp:118
void set_network_host(const std::string &host)
bool set_music(bool ison)
std::string network_host()
bool set_ui_sound(bool ison)
void set_login(const std::string &login)
static prefs & get()
void show_wesnothd_server_search()
void set_password(const std::string &server, const std::string &login, const std::string &key)
bool set_turn_bell(bool ison)
void set_show_fps(bool value)
bool set_sound(bool ison)
void set_resolution(const point &res)
static void disable_preferences_save()
std::string get_mp_server_program_name()
void set_mp_server_program_name(const std::string &)
game_classification & classification()
Definition: saved_game.hpp: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:752
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:833
statistics_record::campaign_stats_t & statistics()
Definition: saved_game.hpp:143
void cancel_orders()
Definition: saved_game.cpp:735
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:362
const std::string & filename() const
Definition: savegame.hpp:178
static void reset_translations()
Definition: tstring.cpp:665
Definitions for the interface to Wesnoth Markup Language (WML).
Declarations for File-IO.
std::size_t i
Definition: function.cpp:1030
#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:97
const language_def & get_locale()
Definition: language.cpp:329
std::vector< language_def > get_languages(bool all)
Return a list of available translations.
Definition: language.cpp:130
void init_textdomains(const game_config_view &cfg)
Initializes the list of textdomains from a configuration object.
Definition: language.cpp:367
bool load_language_list()
Definition: language.cpp:111
void set_min_translation_percent(int percent)
Definition: language.cpp:152
Standard logging facilities (interface).
#define STREAMING_LOG
Definition: log.hpp:297
#define PLAIN_LOG
Definition: log.hpp:296
@ NORMAL
Definition: cursor.hpp:28
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
Definition: cursor.cpp:178
void point(int x, int y)
Draw a single point.
Definition: draw.cpp:211
@ 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:772
std::string get_cache_dir()
Definition: filesystem.cpp:875
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
std::string get_user_data_dir()
Definition: filesystem.cpp:865
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:337
utils::optional< std::string > get_wml_location(const std::string &path, const utils::optional< std::string > &current_dir)
Returns a translated path to the actual file or directory, if it exists.
std::string read_file(const std::string &fname)
Basic disk I/O - read file.
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:53
void write_file(const std::string &fname, const std::string &data, std::ios_base::openmode mode)
Throws io_exception if an error occurs.
std::string get_wesnothd_name()
std::string normalize_path(const std::string &fpath, bool normalize_separators, bool resolve_dot_entries)
Returns the absolute path of a file.
bool load_font_config()
Definition: font_config.cpp:78
std::string turn_bell
Game configuration data as global variables.
Definition: build_info.cpp:61
std::string path
Definition: filesystem.cpp:102
std::string get_default_title_string()
std::vector< server_info > server_list
Definition: game_config.cpp:76
void set_debug(bool new_debug)
Definition: game_config.cpp:97
bool show_debug_clock_button
Do we wish to show the button for the debug clock.
static bool sound()
static bool ui_sound_on()
static bool music_on()
std::vector< game_tip > load(const config &cfg)
Loads the tips from a config.
Definition: tips.cpp:37
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:679
save_result save_image(const locator &i_locator, const std::string &filename)
Definition: picture.cpp:906
@ UNSCALED
Unmodified original-size image.
Definition: picture.hpp:173
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:64
bool init_sound()
Definition: sound.cpp:440
void close_sound()
Definition: sound.cpp:492
void flush_cache()
Definition: sound.cpp:192
bool select_campaign(saved_game &state, jump_to_campaign_info jump_to_campaign)
void process(int mousex, int mousey)
Definition: tooltips.cpp:334
void set_language(const std::string &language, const std::vector< std::string > *)
Definition: gettext.cpp:494
static std::string gettext(const char *str)
Definition: gettext.hpp:62
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp: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:647
void set_window_icon(surface &icon)
Sets the icon of the main window.
Definition: video.cpp:653
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.
std::string filename
Filename.
An exception object used when an IO error occurs.
Definition: filesystem.hpp:67
Base class for all the errors encountered by the engine.
Definition: exceptions.hpp:29
Error used for any general game error, e.g.
Definition: game_errors.hpp:47
Error used when game loading fails.
Definition: game_errors.hpp:31
std::string campaign_id
The ID of the campaign to launch.
bool jump
Whether the game should immediately start a campaign.
std::string scenario_id
The ID of the scenario within the campaign to jump to.
bool skip_story
Whether the story screen should be skipped.
int difficulty
The difficulty at which to launch the campaign.
std::string localename
Definition: language.hpp:32
void clear_current_scenario()
Delete the current scenario from the stats.
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46
An error occurred inside the underlying network communication code (boost asio) TODO: find a short na...
std::string user_message
User-friendly and potentially translated message for use in the UI.
An error occurred during when trying to communicate with the wesnothd server.
Error used when the client is rejected by the MP server.
Helper class, don't construct this directly.
mock_char c
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define e