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