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