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