The Battle for Wesnoth  1.19.5+dev
savegame.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
3  by Jörg Hinrichs, 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 
17 #include "savegame.hpp"
18 
19 #include "cursor.hpp"
20 #include "formatter.hpp"
21 #include "formula/string_utils.hpp"
22 #include "game_config_manager.hpp"
23 #include "game_config_view.hpp"
24 #include "game_end_exceptions.hpp"
25 #include "game_errors.hpp"
26 #include "game_version.hpp"
27 #include "gettext.hpp"
31 #include "gui/dialogs/message.hpp"
33 #include "gui/widgets/retval.hpp"
34 #include "log.hpp"
35 #include "persist_manager.hpp"
37 #include "resources.hpp"
38 #include "save_index.hpp"
39 #include "saved_game.hpp"
42 #include "utils/optimer.hpp"
43 #include "video.hpp" // only for faked
44 
45 #include <iomanip>
46 
47 static lg::log_domain log_engine("engine");
48 #define LOG_SAVE LOG_STREAM(info, log_engine)
49 #define ERR_SAVE LOG_STREAM(err, log_engine)
50 
51 static lg::log_domain log_enginerefac("enginerefac");
52 #define LOG_RG LOG_STREAM(info, log_enginerefac)
53 
54 namespace savegame
55 {
56 bool save_game_exists(std::string name, compression::format compressed)
57 {
58  name += compression::format_extension(compressed);
59  auto manager = save_index_class::default_saves_dir();
60  return filesystem::file_exists(manager->dir() + "/" + name);
61 }
62 
63 void clean_saves(const std::string& label)
64 {
65  const std::string prefix = label + "-" + _("Auto-Save");
66  LOG_SAVE << "Cleaning saves with prefix '" << prefix << "'";
67 
68  auto manager = save_index_class::default_saves_dir();
69  for(const auto& save : manager->get_saves_list()) {
70  if(save.name().compare(0, prefix.length(), prefix) == 0) {
71  LOG_SAVE << "Deleting savegame '" << save.name() << "'";
72  manager->delete_game(save.name());
73  }
74  }
75 }
76 
77 loadgame::loadgame(const std::shared_ptr<save_index_class>& index, saved_game& gamestate)
78  : game_config_(game_config_manager::get()->game_config())
79  , gamestate_(gamestate)
80  , load_data_{index}
81 {
82 }
83 
85 {
86  if(load_data_.summary["corrupt"].to_bool()) {
87  return false;
88  }
89 
90  std::string campaign_id = load_data_.summary["campaign"];
91 
92  for(const config& campaign : game_config_.child_range("campaign")) {
93  if(campaign["id"] != campaign_id) {
94  continue;
95  }
96 
97  gui2::dialogs::campaign_difficulty difficulty_dlg(campaign);
98 
99  // Return if canceled, since otherwise load_data_.difficulty will be set to 'CANCEL'
100  if(!difficulty_dlg.show()) {
101  return false;
102  }
103 
104  load_data_.difficulty = difficulty_dlg.selected_difficulty();
106 
107  // Exit loop
108  break;
109  }
110 
111  return true;
112 }
113 
114 // Called only by play_controller to handle in-game attempts to load. Instead of returning true,
115 // throws a "load_game_exception" to signal a resulting load game request.
117 {
118  if(video::headless()) {
119  return false;
120  }
121 
123  return false;
124  }
125 
126  if(load_data_.filename.empty()) {
127  return false;
128  }
129 
131  if(!show_difficulty_dialog()) {
132  return false;
133  }
134  }
135 
136  if(!load_data_.manager) {
137  ERR_SAVE << "Null pointer in save index";
138  return false;
139  }
140 
142 
143  // Confirm the integrity of the file before throwing the exception.
144  // Use the summary in the save_index for this.
145  const config& summary = load_data_.manager->get(load_data_.filename);
146 
147  if(summary["corrupt"].to_bool(false)) {
148  gui2::show_error_message(_("The file you have tried to load is corrupt: '"));
149  return false;
150  }
151 
152  if(!loadgame::check_version_compatibility(summary["version"].str())) {
153  return false;
154  }
155 
156  throw load_game_exception(std::move(load_data_));
157 }
158 
160 {
161  bool skip_version_check = true;
162 
163  if(load_data_.filename.empty()) {
165  return false;
166  }
167 
168  skip_version_check = false;
170  }
171 
172  if(load_data_.filename.empty()) {
173  return false;
174  }
175 
177  if(!show_difficulty_dialog()) {
178  return false;
179  }
180  }
181 
182  if(!load_data_.manager) {
183  ERR_SAVE << "Null pointer in save index";
184  return false;
185  }
186 
187  std::string error_log;
189 
191 
192  for(config& side : load_data_.load_config.child_range("side")) {
193  side.remove_attribute("is_local");
194  }
195 
196  if(!error_log.empty()) {
197  try {
199  _("Warning: The file you have tried to load is corrupt. Loading anyway.\n") + error_log);
200  } catch(const utf8::invalid_utf8_exception&) {
201  gui2::show_error_message(_("Warning: The file you have tried to load is corrupt. Loading anyway.\n")
202  + std::string("(UTF-8 ERROR)"));
203  }
204  }
205 
206  if(!load_data_.difficulty.empty()) {
208  }
209  // read classification to for loading the game_config config object.
211 
212  if(skip_version_check) {
213  return true;
214  }
215 
217 }
218 
220 {
222 }
223 
225 {
226  if(save_version == game_config::wesnoth_version) {
227  return true;
228  }
229 
231 
232  // Even minor version numbers indicate stable releases which are
233  // compatible with each other.
234  if(wesnoth_version.minor_version() % 2 == 0 && wesnoth_version.major_version() == save_version.major_version()
235  && wesnoth_version.minor_version() == save_version.minor_version()) {
236  return true;
237  }
238 
239  // Do not load if too old. If either the savegame or the current
240  // game has the version 'test', load. This 'test' version is never
241  // supposed to occur, except when Soliton is testing MP servers.
242  if(save_version < game_config::min_savegame_version && save_version != game_config::test_version
244  const std::string message
245  = _("This save is from an old, unsupported version ($version_number|) and cannot be loaded.");
246  utils::string_map symbols;
247  symbols["version_number"] = save_version.str();
249  return false;
250  }
251 
252  if(prefs::get().confirm_load_save_from_different_version()) {
253  const std::string message
254  = _("This save is from a different version of the game ($version_number|), and might not work with this "
255  "version.\n"
256  "\n"
257  "<b>Warning:</b> saves in the middle of campaigns are especially likely to fail, and you should either "
258  "use the old version or restart the campaign. Even when a saved game seems to load successfully, "
259  "subtler aspects like gameplay balance and story progression could be impacted. The difficulty, the "
260  "challenge, the <i>fun</i> may be missing.\n"
261  "\n"
262  "For example, the campaign may have been rebalanced with fewer enemies in the early scenarios, but "
263  "expecting your recall list to have correspondingly less experience in the late scenarios.\n"
264  "\n"
265  "Do you wish to continue?");
266  utils::string_map symbols;
267  symbols["version_number"] = save_version.str();
268  const int res = gui2::show_message(_("Load Game"), utils::interpolate_variables_into_string(message, &symbols),
270  return res == gui2::retval::OK;
271  }
272 
273  return true;
274 }
275 
277 {
279 }
280 
282 {
284  return false;
285  }
286 
288  if(load_data_.filename.empty()) {
289  return false;
290  }
291 
292  if(!load_data_.manager) {
293  ERR_SAVE << "Null pointer in save index";
294  return false;
295  }
296 
297  // read_save_file needs to be called before we can verify the classification so the data has
298  // been populated. Since we do that, we report any errors in that process first.
299  std::string error_log;
300  {
302  log_scope("load_game");
303 
306  }
307 
308  if(!error_log.empty()) {
309  gui2::show_error_message(_("The file you have tried to load is corrupt: '") + error_log);
310  return false;
311  }
312 
314  gui2::show_transient_message(_("Load Game"), _("Replays are not supported in multiplayer mode."));
315  return false;
316  }
317 
318  // We want to verify the game classification before setting the data, so we don't check on
319  // gamestate_.classification() and instead construct a game_classification object manually.
320  if(game_classification(load_data_.load_config).type != campaign_type::type::multiplayer) {
321  gui2::show_transient_error_message(_("This is not a multiplayer save."));
322  return false;
323  }
324 
325  set_gamestate();
326 
328 }
329 
331 {
332  auto replay_start = cfg.optional_child("replay_start");
333  if(!replay_start) {
334  return;
335  }
336 
337  auto era = replay_start->optional_child("era");
338  if(!era) {
339  return;
340  }
341 
342  auto snapshot = cfg.optional_child("snapshot");
343  if(!snapshot) {
344  return;
345  }
346 
347  snapshot->add_child("era", *era);
348 }
349 
350 savegame::savegame(saved_game& gamestate, const compression::format compress_saves, const std::string& title)
351  : filename_()
352  , title_(title)
353  , save_index_manager_(save_index_class::default_saves_dir())
354  , gamestate_(gamestate)
355  , error_message_(_("The game could not be saved: "))
356  , show_confirmation_(false)
357  , compress_saves_(compress_saves)
358 {
359 }
360 
361 bool savegame::save_game_automatic(bool ask_for_overwrite, const std::string& filename)
362 {
363  if(filename.empty()) {
364  filename_ = create_filename();
365  } else {
367  }
368 
369  if(ask_for_overwrite) {
370  if(!check_overwrite()) {
371  return save_game_interactive("", savegame::OK_CANCEL);
372  }
373  }
374 
375  return save_game();
376 }
377 
378 bool savegame::save_game_interactive(const std::string& message, DIALOG_TYPE dialog_type)
379 {
380  show_confirmation_ = true;
381  filename_ = create_filename();
382 
383  const int res = show_save_dialog(message, dialog_type);
384 
385  if(res == 2) {
386  throw_quit_game_exception(); // Quit game
387  }
388 
389  if(res == gui2::retval::OK && check_overwrite()) {
390  return save_game();
391  }
392 
393  return false;
394 }
395 
396 int savegame::show_save_dialog(const std::string& message, DIALOG_TYPE dialog_type)
397 {
398  int res = 0;
399 
400  if(dialog_type == OK_CANCEL) {
401  gui2::dialogs::game_save dlg(filename_, title_);
402  dlg.show();
403  res = dlg.get_retval();
404  } else if(dialog_type == YES_NO) {
405  gui2::dialogs::game_save_message dlg(filename_, title_, message);
406  dlg.show();
407  res = dlg.get_retval();
408  }
409 
410  if(!check_filename(filename_)) {
411  res = gui2::retval::CANCEL;
412  }
413 
414  return res;
415 }
416 
417 bool savegame::check_overwrite()
418 {
419  if(!save_game_exists(filename_, compress_saves_)) {
420  return true;
421  }
422 
423  std::ostringstream message;
424  message << _("Save already exists. Do you want to overwrite it?") << "\n" << _("Name: ") << filename_;
425  const int res = gui2::show_message(_("Overwrite?"), message.str(), gui2::dialogs::message::yes_no_buttons);
426  return res == gui2::retval::OK;
427 }
428 
429 bool savegame::check_filename(const std::string& filename)
430 {
432  gui2::show_error_message(_("Save names should not end with ‘.gz’ or ‘.bz2’. Please remove the extension."));
433  return false;
435  // This message is not all-inclusive. This is on purpose. Few people
436  // need to know about DOS device names or the 255 character limit.
437  gui2::show_error_message(_("Save names may not end with a dot, or contain two dots or any of the following characters:\n \" * / : < > ? \\ | ~"));
438  return false;
439  }
440 
441  return true;
442 }
443 
444 std::string savegame::create_filename(unsigned int turn_number) const
445 {
446  return create_initial_filename(turn_number);
447 }
448 
449 void savegame::before_save()
450 {
451 }
452 
453 bool savegame::save_game(const std::string& filename)
454 {
455  try {
456  utils::optional<const utils::ms_optimer> timer([this](const auto& timer) {
457  LOG_SAVE << "Milliseconds to save " << filename_ << ": " << timer;
458  });
459 
460  if(filename_.empty()) {
462  }
463 
464  before_save();
465 
466  write_game_to_disk(filename_);
467  if(resources::persist != nullptr) {
470  }
471 
472  // Create an entry in the save_index. Doing this here ensures all leader image paths
473  // sre expanded in a context-independent fashion and can appear in the Load Game dialog
474  // even if a campaign-specific sprite is used. This is because the image's full path is
475  // only available if the binary-path context its a part of is loaded. Without this, if
476  // a player saves a game and exits the game or reloads the cache, the leader image will
477  // only be available within that specific binary context (when playing another game from
478  // the came campaign, for example).
479  save_index_manager_->rebuild(filename_);
480 
481  // Log time before showing the confirmation
482  timer.reset();
483 
484  if(show_confirmation_) {
485  gui2::show_transient_message(_("Saved"), _("The game has been saved."));
486  }
487 
488  return true;
489  } catch(const game::save_game_failed& e) {
490  ERR_SAVE << error_message_ << e.message;
491  gui2::show_error_message(error_message_ + e.message);
492 
493  // do not bother retrying, since the user can just try to save the game again
494  // maybe show a yes-no dialog for "disable autosaves now"?
495  return false;
496  };
497 }
498 
499 void savegame::write_game_to_disk(const std::string& filename)
500 {
501  LOG_SAVE << "savegame::save_game";
502 
504  filename_ += compression::format_extension(compress_saves_);
505 
506  std::stringstream ss;
507  {
508  config_writer out(ss, compress_saves_);
509  write_game(out);
510  finish_save_game(out);
511  }
512 
513  filesystem::scoped_ostream os(open_save_game(filename_));
514  (*os) << ss.str();
515 
516  if(!os->good()) {
517  throw game::save_game_failed(_("Could not write to file"));
518  }
519 }
520 
521 void savegame::write_game(config_writer& out)
522 {
523  log_scope("write_game");
524 
525  out.write_key_val("version", game_config::wesnoth_version.str());
526 
527  gamestate_.write_general_info(out);
528 }
529 
530 void savegame::finish_save_game(const config_writer& out)
531 {
532  try {
533  if(!out.good()) {
534  throw game::save_game_failed(_("Could not write to file"));
535  }
536  } catch(const filesystem::io_exception& e) {
537  throw game::save_game_failed(e.what());
538  }
539 }
540 
541 // Throws game::save_game_failed
542 filesystem::scoped_ostream savegame::open_save_game(const std::string& label)
543 {
544  try {
545  return filesystem::ostream_file(save_index_manager_->dir() + "/" + label);
546  } catch(const filesystem::io_exception& e) {
547  throw game::save_game_failed(e.what());
548  }
549 }
550 
552  : savegame(gamestate, compress_saves)
553 {
555 }
556 
557 std::string scenariostart_savegame::create_initial_filename(unsigned int) const
558 {
559  return gamestate().classification().label;
560 }
561 
563 {
565  gamestate().write_carryover(out);
566 }
567 
569  : savegame(gamestate, compress_saves, _("Save Replay"))
570 {
571 }
572 
573 std::string replay_savegame::create_initial_filename(unsigned int) const
574 {
575  time_t t = std::time(nullptr);
576  tm tm = *std::localtime(&t);
577  auto time = std::put_time(&tm, "%Y%m%d-%H%M%S");
578 
579  // TRANSLATORS: This string is used as part of a filename, as in, "HttT-The Elves Besieged replay.gz"
580  return formatter() << gamestate().classification().label << " " << _("replay") << " " << time;
581 }
582 
584 {
586 
587  gamestate().write_carryover(out);
588  out.write_child("replay_start", gamestate().replay_start());
589 
590  out.open_child("replay");
591  gamestate().get_replay().write(out);
592  out.close_child("replay");
593 }
594 
596  : ingame_savegame(gamestate, compress_saves)
597 {
598  set_error_message(_("Could not auto save the game. Please save the game manually."));
599 }
600 
601 void autosave_savegame::autosave(const bool disable_autosave, const int autosave_max, const int infinite_autosaves)
602 {
603  if(disable_autosave)
604  return;
605 
607 
608  auto manager = save_index_class::default_saves_dir();
609  manager->delete_old_auto_saves(autosave_max, infinite_autosaves);
610 }
611 
612 std::string autosave_savegame::create_initial_filename(unsigned int turn_number) const
613 {
614  std::string filename;
615  if(gamestate().classification().label.empty()) {
616  filename = _("Auto-Save");
617  } else {
618  filename = gamestate().classification().label + "-" + _("Auto-Save") + std::to_string(turn_number);
619  }
620 
621  return filename;
622 }
623 
624 oos_savegame::oos_savegame(saved_game& gamestate, bool& ignore)
625  : ingame_savegame(gamestate, prefs::get().save_compression_format())
626  , ignore_(ignore)
627 {
628 }
629 
630 int oos_savegame::show_save_dialog(const std::string& message, DIALOG_TYPE /*dialog_type*/)
631 {
632  int res = 0;
633 
634  if(!ignore_) {
636  dlg.show();
637  res = dlg.get_retval();
638  }
639 
640  if(!check_filename(filename_)) {
641  res = gui2::retval::CANCEL;
642  }
643 
644  return res;
645 }
646 
648  : savegame(gamestate, compress_saves, _("Save Game"))
649 {
650 }
651 
652 std::string ingame_savegame::create_initial_filename(unsigned int turn_number) const
653 {
654  return formatter() << gamestate().classification().label << " " << _("Turn") << " " << turn_number;
655 }
656 
658 {
659  log_scope("write_game");
660 
661  if(!gamestate().get_starting_point().validate_wml()) {
662  throw game::save_game_failed(_("Game state is corrupted"));
663  }
664 
666 
667  gamestate().write_carryover(out);
668  out.write_child("snapshot", gamestate().get_starting_point());
669  out.write_child("replay_start", gamestate().replay_start());
670  out.open_child("replay");
671  gamestate().get_replay().write(out);
672  out.close_child("replay");
673 }
674 
675 // changes done during 1.11.0-dev
677 {
678  if(!cfg.has_child("snapshot")) {
679  return;
680  }
681 
682  const config& snapshot = cfg.mandatory_child("snapshot");
683  const config& replay_start = cfg.mandatory_child("replay_start");
684  const config& replay = cfg.mandatory_child("replay");
685 
686  if(!cfg.has_child("carryover_sides") && !cfg.has_child("carryover_sides_start")) {
688  // copy rng and menu items from toplevel to new carryover_sides
689  carryover["random_seed"] = cfg["random_seed"];
690  carryover["random_calls"] = cfg["random_calls"];
691 
692  for(const config& menu_item : cfg.child_range("menu_item")) {
693  carryover.add_child("menu_item", menu_item);
694  }
695 
696  carryover["difficulty"] = cfg["difficulty"];
697  carryover["random_mode"] = cfg["random_mode"];
698  // the scenario to be played is always stored as next_scenario in carryover_sides_start
699  carryover["next_scenario"] = cfg["scenario"];
700 
701  config carryover_start = carryover;
702 
703  // copy sides from either snapshot or replay_start to new carryover_sides
704  if(!snapshot.empty()) {
705  for(const config& side : snapshot.child_range("side")) {
706  carryover.add_child("side", side);
707  }
708  // for compatibility with old savegames that use player instead of side
709  for(const config& side : snapshot.child_range("player")) {
710  carryover.add_child("side", side);
711  }
712  // save the sides from replay_start in carryover_sides_start
713  for(const config& side : replay_start.child_range("side")) {
714  carryover_start.add_child("side", side);
715  }
716  // for compatibility with old savegames that use player instead of side
717  for(const config& side : replay_start.child_range("player")) {
718  carryover_start.add_child("side", side);
719  }
720  } else if(!replay_start.empty()) {
721  for(const config& side : replay_start.child_range("side")) {
722  carryover.add_child("side", side);
723  carryover_start.add_child("side", side);
724  }
725  // for compatibility with old savegames that use player instead of side
726  for(const config& side : replay_start.child_range("player")) {
727  carryover.add_child("side", side);
728  carryover_start.add_child("side", side);
729  }
730  }
731 
732  // get variables according to old hierarchy and copy them to new carryover_sides
733  if(!snapshot.empty()) {
734  if(auto variables_from_snapshot = snapshot.optional_child("variables")) {
735  carryover.add_child("variables", *variables_from_snapshot);
736  carryover_start.add_child("variables", replay_start.child_or_empty("variables"));
737  } else if(auto variables_from_cfg = cfg.optional_child("variables")) {
738  carryover.add_child("variables", *variables_from_cfg);
739  carryover_start.add_child("variables", *variables_from_cfg);
740  }
741  } else if(!replay_start.empty()) {
742  if(auto variables = replay_start.optional_child("variables")) {
743  carryover.add_child("variables", *variables);
744  carryover_start.add_child("variables", *variables);
745  }
746  } else {
747  carryover.add_child("variables", cfg.mandatory_child("variables"));
748  carryover_start.add_child("variables", cfg.mandatory_child("variables"));
749  }
750 
751  cfg.add_child("carryover_sides", carryover);
752  cfg.add_child("carryover_sides_start", carryover_start);
753  }
754 
755  // if replay and snapshot are empty we've got a start of scenario save and don't want replay_start either
756  if(replay.empty() && snapshot.empty()) {
757  LOG_RG << "removing replay_start";
758  cfg.clear_children("replay_start");
759  }
760 
761  // remove empty replay or snapshot so type of save can be detected more easily
762  if(replay.empty()) {
763  LOG_RG << "removing replay";
764  cfg.clear_children("replay");
765  }
766 
767  if(snapshot.empty()) {
768  LOG_RG << "removing snapshot";
769  cfg.clear_children("snapshot");
770  }
771 }
772 // changes done during 1.13.0-dev
774 {
775  if(auto carryover_sides_start = cfg.optional_child("carryover_sides_start")) {
776  if(!carryover_sides_start->has_attribute("next_underlying_unit_id")) {
777  carryover_sides_start["next_underlying_unit_id"] = cfg["next_underlying_unit_id"];
778  }
779  }
780 
781  if(cfg.child_or_empty("snapshot").empty()) {
782  cfg.clear_children("snapshot");
783  }
784 
785  if(cfg.child_or_empty("replay_start").empty()) {
786  cfg.clear_children("replay_start");
787  }
788 
789  if(auto snapshot = cfg.optional_child("snapshot")) {
790  // make [end_level] -> [end_level_data] since its alo called [end_level_data] in the carryover.
791  if(auto end_level = cfg.optional_child("end_level")) {
792  snapshot->add_child("end_level_data", *end_level);
793  snapshot->clear_children("end_level");
794  }
795  // if we have a snapshot then we already applied carryover so there is no reason to keep this data.
796  if(cfg.has_child("carryover_sides_start")) {
797  cfg.clear_children("carryover_sides_start");
798  }
799  }
800 
801  if(!cfg.has_child("snapshot") && !cfg.has_child("replay_start")) {
802  cfg.clear_children("carryover_sides");
803  }
804 
805  // This code is needed because for example otherwise it won't find the (empty) era
806  if(!cfg.has_child("multiplayer")) {
807  cfg.add_child("multiplayer",
808  config{
809  "mp_era",
810  "era_blank",
811  "mp_use_map_settings",
812  true,
813  });
814  }
815 }
816 
817 // changes done during 1.13.0+dev
819 {
820  if(auto multiplayer = cfg.optional_child("multiplayer")) {
821  if(multiplayer["mp_era"] == "era_blank") {
822  multiplayer["mp_era"] = "era_default";
823  }
824  }
825 
826  // This currently only fixes start-of-scenario saves.
827  if(auto carryover_sides_start = cfg.optional_child("carryover_sides_start")) {
828  for(config& side : carryover_sides_start->child_range("side")) {
829  for(config& unit : side.child_range("unit")) {
830  if(auto modifications = unit.optional_child("modifications")) {
831  for(config& advancement : modifications->child_range("advance")) {
832  modifications->add_child("advancement", advancement);
833  }
834  modifications->clear_children("advance");
835  }
836  }
837  }
838  }
839 
840  for(config& snapshot : cfg.child_range("snapshot")) {
841  if(snapshot.has_attribute("used_items")) {
842  config used_items;
843  for(const std::string& item : utils::split(snapshot["used_items"])) {
844  used_items[item] = true;
845  }
846 
847  snapshot.remove_attribute("used_items");
848  snapshot.add_child("used_items", used_items);
849  }
850  }
851 }
852 
853 // changes done during 1.15.3+dev
855 {
856  if(cfg["era_id"].empty()) {
857  cfg["era_id"] = cfg.child_or_empty("multiplayer")["mp_era"];
858  }
859 
860  if(cfg["active_mods"].empty()) {
861  cfg["active_mods"] = cfg.child_or_empty("multiplayer")["active_mods"];
862  }
863 }
864 
866 {
867  version_info loaded_version(cfg["version"]);
868  if(loaded_version < version_info("1.12.0")) {
870  }
871 
872  // '<= version_info("1.13.0")' doesn't work
873  // because version_info cannot handle 1.13.0-dev versions correctly.
874  if(loaded_version < version_info("1.13.1")) {
876  }
877 
878  if(loaded_version <= version_info("1.13.1")) {
880  }
881 
882  if(loaded_version < version_info("1.15.4")) {
884  }
885 
886  LOG_RG << "cfg after conversion " << cfg;
887 }
888 
889 } // namespace savegame
std::string filename_
Definition: action_wml.cpp:534
double t
Definition: astarsearch.cpp:63
Class for writing a config out to a file in pieces.
void close_child(const std::string &key)
bool good() const
void write_child(const std::string &key, const config &cfg)
void write_key_val(const std::string &key, const T &value)
This template function will work with any type that can be assigned to an attribute_value.
void open_child(const std::string &key)
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
const config & child_or_empty(config_key_type key) const
Returns the first child with the given key, or an empty config if there is none.
Definition: config.cpp:394
config & mandatory_child(config_key_type key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition: config.cpp:366
void clear_children(T... keys)
Definition: config.hpp:616
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:316
child_itors child_range(config_key_type key)
Definition: config.cpp:272
void remove_attribute(config_key_type key)
Definition: config.cpp:162
bool empty() const
Definition: config.cpp:849
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:384
config & add_child(config_key_type key)
Definition: config.cpp:440
std::ostringstream wrapper.
Definition: formatter.hpp:40
std::string version
Version game was created with.
campaign_type::type type
std::string label
Name of the game (e.g.
config_array_view child_range(config_key_type key) const
std::string selected_difficulty() const
Returns the selected difficulty define after displaying.
static bool execute(const game_config_view &cache_config, savegame::load_game_metadata &data)
Definition: game_load.cpp:58
@ yes_no_buttons
Shows a yes and no button.
Definition: message.hpp:81
bool show(const unsigned auto_close_time=0)
Shows the window.
int get_retval() const
Returns the cached window exit code.
static prefs & get()
void write(config_writer &out) const
bool empty() const
Definition: replay.cpp:657
game_classification & classification()
Definition: saved_game.hpp:56
replay_recorder_base & get_replay()
Definition: saved_game.hpp:140
void set_data(config &cfg)
destroys the passed config.
Definition: saved_game.cpp:761
void write_carryover(config_writer &out) const
Definition: saved_game.cpp:206
void autosave(const bool disable_autosave, const int autosave_max, const int infinite_autosaves)
Definition: savegame.cpp:601
autosave_savegame(saved_game &gamestate, const compression::format compress_saves)
Definition: savegame.cpp:595
virtual std::string create_initial_filename(unsigned int turn_number) const override
Create a filename for automatic saves.
Definition: savegame.cpp:612
Class for "normal" midgame saves.
Definition: savegame.hpp:254
ingame_savegame(saved_game &gamestate, const compression::format compress_saves)
Definition: savegame.cpp:647
void write_game(config_writer &out) override
Writing the savegame config to a file.
Definition: savegame.cpp:657
virtual std::string create_initial_filename(unsigned int turn_number) const override
Create a filename for automatic saves.
Definition: savegame.cpp:652
Exception used to signal that the user has decided to abortt a game, and to load another game instead...
Definition: savegame.hpp:85
void set_gamestate()
Generate the gamestate out of the loaded game config.
Definition: savegame.cpp:276
void copy_era(config &cfg)
Copy era information into the snapshot.
Definition: savegame.cpp:330
bool check_version_compatibility()
Call check_version_compatibility above, using the version of this savefile.
Definition: savegame.cpp:219
bool load_game_ingame()
Load a game without providing any information.
Definition: savegame.cpp:116
loadgame(const std::shared_ptr< save_index_class > &index, saved_game &gamestate)
Definition: savegame.cpp:77
static bool is_replay_save(const config &cfg)
Definition: savegame.hpp:127
load_game_metadata load_data_
Primary output information.
Definition: savegame.hpp:144
bool load_multiplayer_game()
Loading a game from within the multiplayer-create dialog.
Definition: savegame.cpp:281
bool show_difficulty_dialog()
Display the difficulty dialog.
Definition: savegame.cpp:84
bool load_game()
Load a game with pre-setting information for the load-game dialog.
Definition: savegame.cpp:159
const game_config_view & game_config_
Definition: savegame.hpp:140
saved_game & gamestate_
Definition: savegame.hpp:142
oos_savegame(saved_game &gamestate, bool &ignore)
Definition: savegame.cpp:624
virtual int show_save_dialog(const std::string &message, DIALOG_TYPE dialog_type) override
Display the save game dialog.
Definition: savegame.cpp:630
void write_game(config_writer &out) override
Writing the savegame config to a file.
Definition: savegame.cpp:583
virtual std::string create_initial_filename(unsigned int turn_number) const override
Create a filename for automatic saves.
Definition: savegame.cpp:573
replay_savegame(saved_game &gamestate, const compression::format compress_saves)
Definition: savegame.cpp:568
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:209
The base class for all savegame stuff.
Definition: savegame.hpp:155
bool check_filename(const std::string &filename)
Check, if the filename contains illegal constructs like ".gz".
Definition: savegame.cpp:429
std::string filename_
Filename of the savegame file on disk.
Definition: savegame.hpp:212
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:361
virtual void write_game(config_writer &out)
Writing the savegame config to a file.
Definition: savegame.cpp:521
std::string create_filename() const
Build the filename according to the specific savegame's needs.
Definition: savegame.hpp:181
const std::string & title() const
Definition: savegame.hpp:202
const std::string & filename() const
Definition: savegame.hpp:178
void set_error_message(const std::string &error_message)
Customize the standard error message.
Definition: savegame.hpp:200
const saved_game & gamestate() const
Definition: savegame.hpp:203
scenariostart_savegame(saved_game &gamestate, const compression::format compress_saves)
Definition: savegame.cpp:551
void write_game(config_writer &out) override
Writing the savegame config to a file.
Definition: savegame.cpp:562
virtual std::string create_initial_filename(unsigned int turn_number) const override
Create a filename for automatic saves.
Definition: savegame.cpp:557
This class represents a single unit of a specific type.
Definition: unit.hpp:133
Thrown by operations encountering invalid UTF-8 data.
Represents version numbers.
std::string str() const
Serializes the version number into string form.
unsigned int minor_version() const
Retrieves the minor version number (x2 in "x1.x2.x3").
unsigned int major_version() const
Retrieves the major version number (x1 in "x1.x2.x3").
Contains the exception interfaces used to signal completion of a scenario, campaign or turn.
void throw_quit_game_exception()
Interfaces for manipulating version numbers of engine, add-ons, etc.
static std::string _(const char *str)
Definition: gettext.hpp:93
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:200
Standard logging facilities (interface).
#define log_scope(description)
Definition: log.hpp:278
std::string format_extension(format compression_format)
Definition: compression.hpp:24
CURSOR_TYPE get()
Definition: cursor.cpp:216
@ WAIT
Definition: cursor.hpp:28
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:325
bool is_legal_user_file_name(const std::string &name, bool allow_whitespace=true)
Returns whether the given filename is a legal name for a user-created file.
bool is_compressed_file(const std::string &filename)
Definition: filesystem.hpp:292
filesystem::scoped_ostream ostream_file(const std::string &fname, std::ios_base::openmode mode, bool create_directory)
std::unique_ptr< std::ostream > scoped_ostream
Definition: filesystem.hpp:54
Game configuration data as global variables.
Definition: build_info.cpp:61
const version_info min_savegame_version(MIN_SAVEGAME_VERSION)
const version_info test_version("test")
const version_info wesnoth_version(VERSION)
bool disable_autosave
Definition: game_config.cpp:91
void show_transient_error_message(const std::string &message, const std::string &image, const bool message_use_markup)
Shows a transient error message to the user.
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.
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:201
void show_message(const std::string &title, const std::string &msg, const std::string &button_caption, const bool auto_close, const bool message_use_markup, const bool title_use_markup)
Shows a message to the user.
Definition: message.cpp:148
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
@ CANCEL
Dialog was closed with the CANCEL button.
Definition: retval.hpp:38
persist_manager * persist
Definition: resources.cpp:26
game_classification * classification
Definition: resources.cpp:34
void clean_saves(const std::string &label)
Delete all autosaves of a certain scenario from the default save directory.
Definition: savegame.cpp:63
static void convert_old_saves_1_13_0(config &cfg)
Definition: savegame.cpp:773
bool save_game_exists(std::string name, compression::format compressed)
Returns true if there is already a savegame with this name, looking only in the default save director...
Definition: savegame.cpp:56
void convert_old_saves(config &cfg)
converts saves from older versions of wesnoth
Definition: savegame.cpp:865
static void convert_old_saves_1_11_0(config &cfg)
Definition: savegame.cpp:676
static void convert_old_saves_1_13_1(config &cfg)
Definition: savegame.cpp:818
void read_save_file(const std::string &dir, const std::string &name, config &cfg, std::string *error_log)
Read the complete config information out of a savefile.
Definition: save_index.cpp:311
static void convert_old_saves_1_15_3(config &cfg)
Definition: savegame.cpp:854
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
std::string interpolate_variables_into_string(const std::string &str, const string_map *const symbols)
Function which will interpolate variables, starting with '$' in the string 'str' with the equivalent ...
std::map< std::string, t_string > string_map
std::vector< std::string > split(const config_attribute_value &val)
bool headless()
The game is running headless.
Definition: video.cpp:139
#define LOG_RG
Definition: savegame.cpp:52
#define LOG_SAVE
Definition: savegame.cpp:48
static lg::log_domain log_engine("engine")
static lg::log_domain log_enginerefac("enginerefac")
#define ERR_SAVE
Definition: savegame.cpp:49
std::string filename
Filename.
An exception object used when an IO error occurs.
Definition: filesystem.hpp:67
Error used when game saving fails.
Definition: game_errors.hpp:39
std::string difficulty
The difficulty the save is meant to be loaded with.
Definition: savegame.hpp:61
std::string filename
Name of the savefile to be loaded (not including the directory).
Definition: savegame.hpp:58
config summary
Summary config of the save selected in the load game dialog.
Definition: savegame.hpp:73
bool show_replay
State of the "show_replay" checkbox in the load-game dialog.
Definition: savegame.hpp:64
std::shared_ptr< save_index_class > manager
There may be different instances of the index for different directories.
Definition: savegame.hpp:55
config load_config
Config information of the savefile to be loaded.
Definition: savegame.hpp:76
bool select_difficulty
State of the "change_difficulty" checkbox in the load-game dialog.
Definition: savegame.hpp:70
#define e