The Battle for Wesnoth  1.15.13+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  } catch(const filesystem::io_exception& e) {
545  throw game::save_game_failed(e.what());
546  }
547 }
548 
549 // Throws game::save_game_failed
550 filesystem::scoped_ostream savegame::open_save_game(const std::string& label)
551 {
552  try {
553  return filesystem::ostream_file(save_index_manager_->dir() + "/" + label);
554  } catch(const filesystem::io_exception& e) {
555  throw game::save_game_failed(e.what());
556  }
557 }
558 
560  : savegame(gamestate, compress_saves)
561 {
563 }
564 
565 std::string scenariostart_savegame::create_initial_filename(unsigned int) const
566 {
567  return gamestate().classification().label;
568 }
569 
571 {
573  gamestate().write_carryover(out);
574 }
575 
577  : savegame(gamestate, compress_saves, _("Save Replay"))
578 {
579 }
580 
581 std::string replay_savegame::create_initial_filename(unsigned int) const
582 {
583  time_t t = std::time(nullptr);
584  tm tm = *std::localtime(&t);
585  auto time = std::put_time(&tm, "%Y%m%d-%H%M%S");
586 
587  // TRANSLATORS: This string is used as part of a filename, as in, "HttT-The Elves Besieged replay.gz"
588  return formatter() << gamestate().classification().label << " " << _("replay") << " " << time;
589 }
590 
592 {
594 
595  gamestate().write_carryover(out);
596  out.write_child("replay_start", gamestate().replay_start());
597 
598  out.open_child("replay");
599  gamestate().get_replay().write(out);
600  out.close_child("replay");
601 }
602 
604  : ingame_savegame(gamestate, compress_saves)
605 {
606  set_error_message(_("Could not auto save the game. Please save the game manually."));
607 }
608 
609 void autosave_savegame::autosave(const bool disable_autosave, const int autosave_max, const int infinite_autosaves)
610 {
611  if(disable_autosave)
612  return;
613 
615 
616  auto manager = save_index_class::default_saves_dir();
617  manager->delete_old_auto_saves(autosave_max, infinite_autosaves);
618 }
619 
620 std::string autosave_savegame::create_initial_filename(unsigned int turn_number) const
621 {
622  std::string filename;
623  if(gamestate().classification().label.empty()) {
624  filename = _("Auto-Save");
625  } else {
626  filename = gamestate().classification().label + "-" + _("Auto-Save") + std::to_string(turn_number);
627  }
628 
629  return filename;
630 }
631 
634  , ignore_(ignore)
635 {
636 }
637 
638 int oos_savegame::show_save_dialog(const std::string& message, DIALOG_TYPE /*dialog_type*/)
639 {
640  int res = 0;
641 
642  if(!ignore_) {
644  dlg.show();
645  res = dlg.get_retval();
646  }
647 
648  if(!check_filename(filename_)) {
649  res = gui2::retval::CANCEL;
650  }
651 
652  return res;
653 }
654 
656  : savegame(gamestate, compress_saves, _("Save Game"))
657 {
658 }
659 
660 std::string ingame_savegame::create_initial_filename(unsigned int turn_number) const
661 {
662  return formatter() << gamestate().classification().label << " " << _("Turn") << " " << turn_number;
663 }
664 
666 {
667  log_scope("write_game");
668 
669  if(!gamestate().get_starting_point().validate_wml()) {
670  throw game::save_game_failed(_("Game state is corrupted"));
671  }
672 
674 
675  gamestate().write_carryover(out);
676  out.write_child("snapshot", gamestate().get_starting_point());
677  out.write_child("replay_start", gamestate().replay_start());
678  out.open_child("replay");
679  gamestate().get_replay().write(out);
680  out.close_child("replay");
681 }
682 
683 // changes done during 1.11.0-dev
685 {
686  if(!cfg.has_child("snapshot")) {
687  return;
688  }
689 
690  const config& snapshot = cfg.child("snapshot");
691  const config& replay_start = cfg.child("replay_start");
692  const config& replay = cfg.child("replay");
693 
694  if(!cfg.has_child("carryover_sides") && !cfg.has_child("carryover_sides_start")) {
696  // copy rng and menu items from toplevel to new carryover_sides
697  carryover["random_seed"] = cfg["random_seed"];
698  carryover["random_calls"] = cfg["random_calls"];
699 
700  for(const config& menu_item : cfg.child_range("menu_item")) {
701  carryover.add_child("menu_item", menu_item);
702  }
703 
704  carryover["difficulty"] = cfg["difficulty"];
705  carryover["random_mode"] = cfg["random_mode"];
706  // the scenario to be played is always stored as next_scenario in carryover_sides_start
707  carryover["next_scenario"] = cfg["scenario"];
708 
709  config carryover_start = carryover;
710 
711  // copy sides from either snapshot or replay_start to new carryover_sides
712  if(!snapshot.empty()) {
713  for(const config& side : snapshot.child_range("side")) {
714  carryover.add_child("side", side);
715  }
716  // for compatibility with old savegames that use player instead of side
717  for(const config& side : snapshot.child_range("player")) {
718  carryover.add_child("side", side);
719  }
720  // save the sides from replay_start in carryover_sides_start
721  for(const config& side : replay_start.child_range("side")) {
722  carryover_start.add_child("side", side);
723  }
724  // for compatibility with old savegames that use player instead of side
725  for(const config& side : replay_start.child_range("player")) {
726  carryover_start.add_child("side", side);
727  }
728  } else if(!replay_start.empty()) {
729  for(const config& side : replay_start.child_range("side")) {
730  carryover.add_child("side", side);
731  carryover_start.add_child("side", side);
732  }
733  // for compatibility with old savegames that use player instead of side
734  for(const config& side : replay_start.child_range("player")) {
735  carryover.add_child("side", side);
736  carryover_start.add_child("side", side);
737  }
738  }
739 
740  // get variables according to old hierarchy and copy them to new carryover_sides
741  if(!snapshot.empty()) {
742  if(const config& variables_from_snapshot = snapshot.child("variables")) {
743  carryover.add_child("variables", variables_from_snapshot);
744  carryover_start.add_child("variables", replay_start.child_or_empty("variables"));
745  } else if(const config& variables_from_cfg = cfg.child("variables")) {
746  carryover.add_child("variables", variables_from_cfg);
747  carryover_start.add_child("variables", variables_from_cfg);
748  }
749  } else if(!replay_start.empty()) {
750  if(const config& variables = replay_start.child("variables")) {
751  carryover.add_child("variables", variables);
752  carryover_start.add_child("variables", variables);
753  }
754  } else {
755  carryover.add_child("variables", cfg.child("variables"));
756  carryover_start.add_child("variables", cfg.child("variables"));
757  }
758 
759  cfg.add_child("carryover_sides", carryover);
760  cfg.add_child("carryover_sides_start", carryover_start);
761  }
762 
763  // if replay and snapshot are empty we've got a start of scenario save and don't want replay_start either
764  if(replay.empty() && snapshot.empty()) {
765  LOG_RG << "removing replay_start \n";
766  cfg.clear_children("replay_start");
767  }
768 
769  // remove empty replay or snapshot so type of save can be detected more easily
770  if(replay.empty()) {
771  LOG_RG << "removing replay \n";
772  cfg.clear_children("replay");
773  }
774 
775  if(snapshot.empty()) {
776  LOG_RG << "removing snapshot \n";
777  cfg.clear_children("snapshot");
778  }
779 }
780 // changes done during 1.13.0-dev
782 {
783  if(config& carryover_sides_start = cfg.child("carryover_sides_start")) {
784  if(!carryover_sides_start.has_attribute("next_underlying_unit_id")) {
785  carryover_sides_start["next_underlying_unit_id"] = cfg["next_underlying_unit_id"];
786  }
787  }
788 
789  if(cfg.child_or_empty("snapshot").empty()) {
790  cfg.clear_children("snapshot");
791  }
792 
793  if(cfg.child_or_empty("replay_start").empty()) {
794  cfg.clear_children("replay_start");
795  }
796 
797  if(config& snapshot = cfg.child("snapshot")) {
798  // make [end_level] -> [end_level_data] since its alo called [end_level_data] in the carryover.
799  if(config& end_level = cfg.child("end_level")) {
800  snapshot.add_child("end_level_data", end_level);
801  snapshot.clear_children("end_level");
802  }
803  // if we have a snapshot then we already applied carryover so there is no reason to keep this data.
804  if(cfg.has_child("carryover_sides_start")) {
805  cfg.clear_children("carryover_sides_start");
806  }
807  }
808 
809  if(!cfg.has_child("snapshot") && !cfg.has_child("replay_start")) {
810  cfg.clear_children("carryover_sides");
811  }
812 
813  // This code is needed because for example otherwise it won't find the (empty) era
814  if(!cfg.has_child("multiplayer")) {
815  cfg.add_child("multiplayer",
816  config{
817  "mp_era",
818  "era_blank",
819  "mp_use_map_settings",
820  true,
821  });
822  }
823 }
824 
825 // changes done during 1.13.0+dev
827 {
828  if(config& multiplayer = cfg.child("multiplayer")) {
829  if(multiplayer["mp_era"] == "era_blank") {
830  multiplayer["mp_era"] = "era_default";
831  }
832  }
833 
834  // This currently only fixes start-of-scenario saves.
835  if(config& carryover_sides_start = cfg.child("carryover_sides_start")) {
836  for(config& side : carryover_sides_start.child_range("side")) {
837  for(config& unit : side.child_range("unit")) {
838  if(config& modifications = unit.child("modifications")) {
839  for(config& advancement : modifications.child_range("advance")) {
840  modifications.add_child("advancement", advancement);
841  }
842  modifications.clear_children("advance");
843  }
844  }
845  }
846  }
847 
848  for(config& snapshot : cfg.child_range("snapshot")) {
849  if(snapshot.has_attribute("used_items")) {
850  config used_items;
851  for(const std::string& item : utils::split(snapshot["used_items"])) {
852  used_items[item] = true;
853  }
854 
855  snapshot.remove_attribute("used_items");
856  snapshot.add_child("used_items", used_items);
857  }
858  }
859 }
860 
861 // changes done during 1.15.3+dev
863 {
864  if(cfg["era_id"].empty()) {
865  cfg["era_id"] = cfg.child_or_empty("multiplayer")["mp_era"];
866  }
867 
868  if(cfg["active_mods"].empty()) {
869  cfg["active_mods"] = cfg.child_or_empty("multiplayer")["active_mods"];
870  }
871 }
872 
874 {
875  version_info loaded_version(cfg["version"]);
876  if(loaded_version < version_info("1.12.0")) {
878  }
879 
880  // '<= version_info("1.13.0")' doesn't work
881  // because version_info cannot handle 1.13.0-dev versions correctly.
882  if(loaded_version < version_info("1.13.1")) {
884  }
885 
886  if(loaded_version <= version_info("1.13.1")) {
888  }
889 
890  if(loaded_version < version_info("1.15.4")) {
892  }
893 
894  LOG_RG << "cfg after conversion " << cfg << "\n";
895 }
896 
897 } // namespace savegame
oos_savegame(saved_game &gamestate, bool &ignore)
Definition: savegame.cpp:632
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:826
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:781
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:576
void write_game(config_writer &out) override
Writing the savegame config to a file.
Definition: savegame.cpp:570
virtual std::string create_initial_filename(unsigned int turn_number) const override
Create a filename for automatic saves.
Definition: savegame.cpp:620
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:210
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:655
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:603
static void convert_old_saves_1_15_3(config &cfg)
Definition: savegame.cpp:862
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:559
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:318
filesystem::scoped_ostream ostream_file(const std::string &fname, std::ios_base::openmode mode, bool create_directory)
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:581
void close_child(const std::string &key)
std::string label
What to show in the filter&#39;s drop-down list.
Definition: manager.cpp:216
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:638
virtual std::string create_initial_filename(unsigned int turn_number) const override
Create a filename for automatic saves.
Definition: savegame.cpp:660
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:665
void write_game(config_writer &out) override
Writing the savegame config to a file.
Definition: savegame.cpp:591
void convert_old_saves(config &cfg)
converts saves from older versions of wesnoth
Definition: savegame.cpp:873
std::unique_ptr< std::ostream > scoped_ostream
Definition: filesystem.hpp:38
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:46
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:246
#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:684
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:609
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:565
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.