The Battle for Wesnoth  1.19.0-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"
36 #include "preferences/game.hpp"
37 #include "resources.hpp"
38 #include "save_index.hpp"
39 #include "saved_game.hpp"
42 #include "video.hpp" // only for faked
43 
44 #include <iomanip>
45 
46 static lg::log_domain log_engine("engine");
47 #define LOG_SAVE LOG_STREAM(info, log_engine)
48 #define ERR_SAVE LOG_STREAM(err, log_engine)
49 
50 static lg::log_domain log_enginerefac("enginerefac");
51 #define LOG_RG LOG_STREAM(info, log_enginerefac)
52 
53 namespace savegame
54 {
55 bool save_game_exists(std::string name, compression::format compressed)
56 {
57  name += compression::format_extension(compressed);
58  auto manager = save_index_class::default_saves_dir();
59  return filesystem::file_exists(manager->dir() + "/" + name);
60 }
61 
62 void clean_saves(const std::string& label)
63 {
64  const std::string prefix = label + "-" + _("Auto-Save");
65  LOG_SAVE << "Cleaning saves with prefix '" << prefix << "'";
66 
67  auto manager = save_index_class::default_saves_dir();
68  for(const auto& save : manager->get_saves_list()) {
69  if(save.name().compare(0, prefix.length(), prefix) == 0) {
70  LOG_SAVE << "Deleting savegame '" << save.name() << "'";
71  manager->delete_game(save.name());
72  }
73  }
74 }
75 
76 loadgame::loadgame(const std::shared_ptr<save_index_class>& index, saved_game& gamestate)
77  : game_config_(game_config_manager::get()->game_config())
78  , gamestate_(gamestate)
79  , load_data_{index}
80 {
81 }
82 
84 {
85  if(load_data_.summary["corrupt"].to_bool()) {
86  return false;
87  }
88 
89  std::string campaign_id = load_data_.summary["campaign"];
90 
91  for(const config& campaign : game_config_.child_range("campaign")) {
92  if(campaign["id"] != campaign_id) {
93  continue;
94  }
95 
96  gui2::dialogs::campaign_difficulty difficulty_dlg(campaign);
97 
98  // Return if canceled, since otherwise load_data_.difficulty will be set to 'CANCEL'
99  if(!difficulty_dlg.show()) {
100  return false;
101  }
102 
103  load_data_.difficulty = difficulty_dlg.selected_difficulty();
105 
106  // Exit loop
107  break;
108  }
109 
110  return true;
111 }
112 
113 // Called only by play_controller to handle in-game attempts to load. Instead of returning true,
114 // throws a "load_game_exception" to signal a resulting load game request.
116 {
117  if(video::headless()) {
118  return false;
119  }
120 
122  return false;
123  }
124 
125  if(load_data_.filename.empty()) {
126  return false;
127  }
128 
130  if(!show_difficulty_dialog()) {
131  return false;
132  }
133  }
134 
135  if(!load_data_.manager) {
136  ERR_SAVE << "Null pointer in save index";
137  return false;
138  }
139 
141 
142  // Confirm the integrity of the file before throwing the exception.
143  // Use the summary in the save_index for this.
144  const config& summary = load_data_.manager->get(load_data_.filename);
145 
146  if(summary["corrupt"].to_bool(false)) {
147  gui2::show_error_message(_("The file you have tried to load is corrupt: '"));
148  return false;
149  }
150 
151  if(!loadgame::check_version_compatibility(summary["version"].str())) {
152  return false;
153  }
154 
155  throw load_game_exception(std::move(load_data_));
156 }
157 
159 {
160  bool skip_version_check = true;
161 
162  if(load_data_.filename.empty()) {
164  return false;
165  }
166 
167  skip_version_check = false;
169  }
170 
171  if(load_data_.filename.empty()) {
172  return false;
173  }
174 
176  if(!show_difficulty_dialog()) {
177  return false;
178  }
179  }
180 
181  if(!load_data_.manager) {
182  ERR_SAVE << "Null pointer in save index";
183  return false;
184  }
185 
186  std::string error_log;
188 
190 
191  for(config& side : load_data_.load_config.child_range("side")) {
192  side.remove_attribute("is_local");
193  }
194 
195  if(!error_log.empty()) {
196  try {
198  _("Warning: The file you have tried to load is corrupt. Loading anyway.\n") + error_log);
199  } catch(const utf8::invalid_utf8_exception&) {
200  gui2::show_error_message(_("Warning: The file you have tried to load is corrupt. Loading anyway.\n")
201  + std::string("(UTF-8 ERROR)"));
202  }
203  }
204 
205  if(!load_data_.difficulty.empty()) {
207  }
208  // read classification to for loading the game_config config object.
210 
211  if(skip_version_check) {
212  return true;
213  }
214 
216 }
217 
219 {
221 }
222 
224 {
225  if(save_version == game_config::wesnoth_version) {
226  return true;
227  }
228 
230 
231  // Even minor version numbers indicate stable releases which are
232  // compatible with each other.
233  if(wesnoth_version.minor_version() % 2 == 0 && wesnoth_version.major_version() == save_version.major_version()
234  && wesnoth_version.minor_version() == save_version.minor_version()) {
235  return true;
236  }
237 
238  // Do not load if too old. If either the savegame or the current
239  // game has the version 'test', load. This 'test' version is never
240  // supposed to occur, except when Soliton is testing MP servers.
241  if(save_version < game_config::min_savegame_version && save_version != game_config::test_version
243  const std::string message
244  = _("This save is from an old, unsupported version ($version_number|) and cannot be loaded.");
245  utils::string_map symbols;
246  symbols["version_number"] = save_version.str();
248  return false;
249  }
250 
252  const std::string message
253  = _("This save is from a different version of the game ($version_number|), and might not work with this "
254  "version.\n"
255  "\n"
256  "<b>Warning:</b> saves in the middle of campaigns are especially likely to fail, and you should either "
257  "use the old version or restart the campaign. Even when a saved game seems to load successfully, "
258  "subtler aspects like gameplay balance and story progression could be impacted. The difficulty, the "
259  "challenge, the <i>fun</i> may be missing.\n"
260  "\n"
261  "For example, the campaign may have been rebalanced with fewer enemies in the early scenarios, but "
262  "expecting your recall list to have correspondingly less experience in the late scenarios.\n"
263  "\n"
264  "Do you wish to continue?");
265  utils::string_map symbols;
266  symbols["version_number"] = save_version.str();
267  const int res = gui2::show_message(_("Load Game"), utils::interpolate_variables_into_string(message, &symbols),
269  return res == gui2::retval::OK;
270  }
271 
272  return true;
273 }
274 
276 {
278 }
279 
281 {
283  return false;
284  }
285 
287  if(load_data_.filename.empty()) {
288  return false;
289  }
290 
291  if(!load_data_.manager) {
292  ERR_SAVE << "Null pointer in save index";
293  return false;
294  }
295 
296  // read_save_file needs to be called before we can verify the classification so the data has
297  // been populated. Since we do that, we report any errors in that process first.
298  std::string error_log;
299  {
301  log_scope("load_game");
302 
305  }
306 
307  if(!error_log.empty()) {
308  gui2::show_error_message(_("The file you have tried to load is corrupt: '") + error_log);
309  return false;
310  }
311 
313  gui2::show_transient_message(_("Load Game"), _("Replays are not supported in multiplayer mode."));
314  return false;
315  }
316 
317  // We want to verify the game classification before setting the data, so we don't check on
318  // gamestate_.classification() and instead construct a game_classification object manually.
319  if(game_classification(load_data_.load_config).type != campaign_type::type::multiplayer) {
320  gui2::show_transient_error_message(_("This is not a multiplayer save."));
321  return false;
322  }
323 
324  set_gamestate();
325 
327 }
328 
330 {
331  auto replay_start = cfg.optional_child("replay_start");
332  if(!replay_start) {
333  return;
334  }
335 
336  auto era = replay_start->optional_child("era");
337  if(!era) {
338  return;
339  }
340 
341  auto snapshot = cfg.optional_child("snapshot");
342  if(!snapshot) {
343  return;
344  }
345 
346  snapshot->add_child("era", *era);
347 }
348 
349 savegame::savegame(saved_game& gamestate, const compression::format compress_saves, const std::string& title)
350  : filename_()
351  , title_(title)
352  , save_index_manager_(save_index_class::default_saves_dir())
353  , gamestate_(gamestate)
354  , error_message_(_("The game could not be saved: "))
355  , show_confirmation_(false)
356  , compress_saves_(compress_saves)
357 {
358 }
359 
360 bool savegame::save_game_automatic(bool ask_for_overwrite, const std::string& filename)
361 {
362  if(filename.empty()) {
363  filename_ = create_filename();
364  } else {
365  filename_ = filename;
366  }
367 
368  if(ask_for_overwrite) {
369  if(!check_overwrite()) {
370  return save_game_interactive("", savegame::OK_CANCEL);
371  }
372  }
373 
374  return save_game();
375 }
376 
377 bool savegame::save_game_interactive(const std::string& message, DIALOG_TYPE dialog_type)
378 {
379  show_confirmation_ = true;
380  filename_ = create_filename();
381 
382  const int res = show_save_dialog(message, dialog_type);
383 
384  if(res == 2) {
385  throw_quit_game_exception(); // Quit game
386  }
387 
388  if(res == gui2::retval::OK && check_overwrite()) {
389  return save_game();
390  }
391 
392  return false;
393 }
394 
395 int savegame::show_save_dialog(const std::string& message, DIALOG_TYPE dialog_type)
396 {
397  int res = 0;
398 
399  if(dialog_type == OK_CANCEL) {
400  gui2::dialogs::game_save dlg(filename_, title_);
401  dlg.show();
402  res = dlg.get_retval();
403  } else if(dialog_type == YES_NO) {
404  gui2::dialogs::game_save_message dlg(filename_, title_, message);
405  dlg.show();
406  res = dlg.get_retval();
407  }
408 
409  if(!check_filename(filename_)) {
410  res = gui2::retval::CANCEL;
411  }
412 
413  return res;
414 }
415 
416 bool savegame::check_overwrite()
417 {
418  if(!save_game_exists(filename_, compress_saves_)) {
419  return true;
420  }
421 
422  std::ostringstream message;
423  message << _("Save already exists. Do you want to overwrite it?") << "\n" << _("Name: ") << filename_;
424  const int res = gui2::show_message(_("Overwrite?"), message.str(), gui2::dialogs::message::yes_no_buttons);
425  return res == gui2::retval::OK;
426 }
427 
428 bool savegame::check_filename(const std::string& filename)
429 {
430  if(filesystem::is_compressed_file(filename)) {
431  gui2::show_error_message(_("Save names should not end on '.gz' or '.bz2'. Please remove the extension."));
432  return false;
433  } else if(!filesystem::is_legal_user_file_name(filename)) {
434  // This message is not all-inclusive. This is on purpose. Few people
435  // need to know about DOS device names or the 255 character limit.
436  gui2::show_error_message(_("Save names may not end with a dot, or contain two dots or any of the following characters:\n \" * / : < > ? \\ | ~"));
437  return false;
438  }
439 
440  return true;
441 }
442 
443 std::string savegame::create_filename(unsigned int turn_number) const
444 {
445  return create_initial_filename(turn_number);
446 }
447 
448 void savegame::before_save()
449 {
450 }
451 
452 bool savegame::save_game(const std::string& filename)
453 {
454  try {
455  uint32_t start, end;
456  start = SDL_GetTicks();
457 
458  if(filename_.empty()) {
459  filename_ = filename;
460  }
461 
462  before_save();
463 
464  write_game_to_disk(filename_);
465  if(resources::persist != nullptr) {
468  }
469 
470  // Create an entry in the save_index. Doing this here ensures all leader image paths
471  // sre expanded in a context-independent fashion and can appear in the Load Game dialog
472  // even if a campaign-specific sprite is used. This is because the image's full path is
473  // only available if the binary-path context its a part of is loaded. Without this, if
474  // a player saves a game and exits the game or reloads the cache, the leader image will
475  // only be available within that specific binary context (when playing another game from
476  // the came campaign, for example).
477  save_index_manager_->rebuild(filename_);
478 
479  end = SDL_GetTicks();
480  LOG_SAVE << "Milliseconds to save " << filename_ << ": " << end - start;
481 
482  if(show_confirmation_) {
483  gui2::show_transient_message(_("Saved"), _("The game has been saved."));
484  }
485 
486  return true;
487  } catch(const game::save_game_failed& e) {
488  ERR_SAVE << error_message_ << e.message;
489  gui2::show_error_message(error_message_ + e.message);
490 
491  // do not bother retrying, since the user can just try to save the game again
492  // maybe show a yes-no dialog for "disable autosaves now"?
493  return false;
494  };
495 }
496 
497 void savegame::write_game_to_disk(const std::string& filename)
498 {
499  LOG_SAVE << "savegame::save_game";
500 
501  filename_ = filename;
502  filename_ += compression::format_extension(compress_saves_);
503 
504  std::stringstream ss;
505  {
506  config_writer out(ss, compress_saves_);
507  write_game(out);
508  finish_save_game(out);
509  }
510 
511  filesystem::scoped_ostream os(open_save_game(filename_));
512  (*os) << ss.str();
513 
514  if(!os->good()) {
515  throw game::save_game_failed(_("Could not write to file"));
516  }
517 }
518 
519 void savegame::write_game(config_writer& out)
520 {
521  log_scope("write_game");
522 
523  out.write_key_val("version", game_config::wesnoth_version.str());
524 
525  gamestate_.write_general_info(out);
526 }
527 
528 void savegame::finish_save_game(const config_writer& out)
529 {
530  try {
531  if(!out.good()) {
532  throw game::save_game_failed(_("Could not write to file"));
533  }
534  } catch(const filesystem::io_exception& e) {
535  throw game::save_game_failed(e.what());
536  }
537 }
538 
539 // Throws game::save_game_failed
540 filesystem::scoped_ostream savegame::open_save_game(const std::string& label)
541 {
542  try {
543  return filesystem::ostream_file(save_index_manager_->dir() + "/" + label);
544  } catch(const filesystem::io_exception& e) {
545  throw game::save_game_failed(e.what());
546  }
547 }
548 
550  : savegame(gamestate, compress_saves)
551 {
553 }
554 
555 std::string scenariostart_savegame::create_initial_filename(unsigned int) const
556 {
557  return gamestate().classification().label;
558 }
559 
561 {
563  gamestate().write_carryover(out);
564 }
565 
567  : savegame(gamestate, compress_saves, _("Save Replay"))
568 {
569 }
570 
571 std::string replay_savegame::create_initial_filename(unsigned int) const
572 {
573  time_t t = std::time(nullptr);
574  tm tm = *std::localtime(&t);
575  auto time = std::put_time(&tm, "%Y%m%d-%H%M%S");
576 
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)
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:538
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:159
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:395
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:367
void clear_children(T... keys)
Definition: config.hpp:642
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:317
child_itors child_range(config_key_type key)
Definition: config.cpp:273
void remove_attribute(config_key_type key)
Definition: config.cpp:160
bool empty() const
Definition: config.cpp:852
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Euivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:385
config & add_child(config_key_type key)
Definition: config.cpp:441
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
The campaign mode difficulty menu.
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:56
This shows the dialog to create a savegame file.
Definition: game_save.hpp:33
@ 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.
void write(config_writer &out) const
bool empty() const
Definition: replay.cpp:656
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:760
void write_carryover(config_writer &out) const
Definition: saved_game.cpp:205
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:275
void copy_era(config &cfg)
Copy era information into the snapshot.
Definition: savegame.cpp:329
bool check_version_compatibility()
Call check_version_compatibility above, using the version of this savefile.
Definition: savegame.cpp:218
bool load_game_ingame()
Load a game without providing any information.
Definition: savegame.cpp:115
loadgame(const std::shared_ptr< save_index_class > &index, saved_game &gamestate)
Definition: savegame.cpp:76
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:280
bool show_difficulty_dialog()
Display the difficulty dialog.
Definition: savegame.cpp:83
bool load_game()
Load a game with pre-setting information for the load-game dialog.
Definition: savegame.cpp:158
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:571
replay_savegame(saved_game &gamestate, const compression::format compress_saves)
Definition: savegame.cpp:566
static std::shared_ptr< save_index_class > default_saves_dir()
Returns an instance for managing saves in filesystem::get_saves_dir()
Definition: save_index.cpp:208
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:428
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:360
virtual void write_game(config_writer &out)
Writing the savegame config to a file.
Definition: savegame.cpp:519
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:549
void write_game(config_writer &out) override
Writing the savegame config to a file.
Definition: savegame.cpp:560
virtual std::string create_initial_filename(unsigned int turn_number) const override
Create a filename for automatic saves.
Definition: savegame.cpp:555
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:209
Standard logging facilities (interface).
#define log_scope(description)
Definition: log.hpp:274
std::string format_extension(format compression_format)
Definition: compression.hpp:24
CURSOR_TYPE get()
Definition: cursor.cpp:216
@ WAIT
Definition: cursor.hpp:28
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.
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:319
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:255
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:51
Game configuration data as global variables.
Definition: build_info.cpp:60
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:88
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:203
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:150
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
@ CANCEL
Dialog was closed with the CANCEL button.
Definition: retval.hpp:38
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:412
Modify, read and display user preferences.
compression::format save_compression_format()
Definition: game.cpp:840
std::string era()
Definition: game.cpp:678
bool confirm_load_save_from_different_version()
Definition: general.cpp:961
const std::vector< std::string > & modifications(bool mp)
Definition: game.cpp:708
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:62
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:55
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:312
static void convert_old_saves_1_15_3(config &cfg)
Definition: savegame.cpp:852
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:141
#define LOG_RG
Definition: savegame.cpp:51
#define LOG_SAVE
Definition: savegame.cpp:47
static lg::log_domain log_engine("engine")
static lg::log_domain log_enginerefac("enginerefac")
#define ERR_SAVE
Definition: savegame.cpp:48
An exception object used when an IO error occurs.
Definition: filesystem.hpp:64
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