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