The Battle for Wesnoth  1.19.18+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 /** Confirms attempts to load saves from previous game versions. */
102 bool check_version_compatibility(const version_info& save_version)
103 {
104  if(save_version == game_config::wesnoth_version) {
105  return true;
106  }
107 
109 
110  // Even minor version numbers indicate stable releases which are
111  // compatible with each other.
112  if(wesnoth_version.minor_version() % 2 == 0 && wesnoth_version.major_version() == save_version.major_version()
113  && wesnoth_version.minor_version() == save_version.minor_version()) {
114  return true;
115  }
116 
117  // Do not load if too old. If either the savegame or the current
118  // game has the version 'test', load. This 'test' version is never
119  // supposed to occur, except when Soliton is testing MP servers.
120  if(save_version < game_config::min_savegame_version && save_version != game_config::test_version
122  const std::string message
123  = _("This save is from an old, unsupported version ($version_number|) and cannot be loaded.");
124  utils::string_map symbols;
125  symbols["version_number"] = save_version.str();
127  return false;
128  }
129 
130  if(prefs::get().confirm_load_save_from_different_version()) {
131  const std::string message
132  = _("This save is from a different version of the game ($version_number|), and might not work with this "
133  "version.\n"
134  "\n"
135  "<b>Warning:</b> saves in the middle of campaigns are especially likely to fail, and you should either "
136  "use the old version or restart the campaign. Even when a saved game seems to load successfully, "
137  "subtler aspects like gameplay balance and story progression could be impacted. The difficulty, the "
138  "challenge, the <i>fun</i> may be missing.\n"
139  "\n"
140  "For example, the campaign may have been rebalanced with fewer enemies in the early scenarios, but "
141  "expecting your recall list to have correspondingly less experience in the late scenarios.\n"
142  "\n"
143  "Do you wish to continue?");
144  utils::string_map symbols;
145  symbols["version_number"] = save_version.str();
146  const int res = gui2::show_message(_("Load Game"), utils::interpolate_variables_into_string(message, &symbols),
148  return res == gui2::retval::OK;
149  }
150 
151  return true;
152 }
153 
154 /** Confirms attempts to load saves from previous game versions. */
155 bool check_version_compatibility(const config& cfg)
156 {
157  return check_version_compatibility(cfg["version"].str());
158 }
159 
160 } // namespace
161 
163 {
164  try {
166  } catch(const game::load_game_failed& e) {
167  gui2::show_error_message(_("The file you have tried to load is corrupt") + "\n\n" + e.what());
168  throw;
169  }
170 
172 }
173 
175 {
176  if(video::headless()) {
177  return;
178  }
179 
180  if(auto load_data = load_interactive()) {
181  throw load_game_exception(std::move(load_data).value());
182  }
183 }
184 
185 utils::optional<load_game_metadata> load_interactive()
186 {
187  // FIXME: game_load dialog should initialize its own manager pointer
189 
191  return utils::nullopt;
192  }
193 
194  load_data.show_replay |= is_replay_save(load_data.summary);
195 
196  try {
197  load_data.read_file();
198  } catch(const game::load_game_failed&) {
199  return utils::nullopt;
200  }
201 
202  if(load_data.select_difficulty) {
203  if(!show_difficulty_dialog(load_data)) {
204  return utils::nullopt;
205  }
206  }
207 
208  if(!check_version_compatibility(load_data.load_config)) {
209  return utils::nullopt;
210  }
211 
212  return load_data;
213 }
214 
215 // TODO: reduce code duplication with load_interactive
216 utils::optional<load_game_metadata> load_interactive_for_multiplayer()
217 {
218  // FIXME: game_load dialog should initialize its own manager pointer
220 
222  return utils::nullopt;
223  }
224 
225  load_data.show_replay |= is_replay_save(load_data.summary);
226 
227  // read_save_file needs to be called before we can verify the classification so the data has
228  // been populated. Since we do that, we report any errors in that process first.
229  try {
231  log_scope("load_game");
232 
233  load_data.read_file();
234  } catch(const game::load_game_failed&) {
235  return utils::nullopt;
236  }
237 
238  if(is_replay_save(load_data.summary)) {
239  gui2::show_transient_message(_("Load Game"), _("Replays are not supported in multiplayer mode."));
240  return utils::nullopt;
241  }
242 
243  const auto metadata = game_classification{load_data.load_config};
244 
245  if(!metadata.is_multiplayer()) {
246  gui2::show_transient_error_message(_("This is not a multiplayer save."));
247  return utils::nullopt;
248  }
249 
250  if(!check_version_compatibility(metadata.version)) {
251  return utils::nullopt;
252  }
253 
254  return load_data;
255 }
256 
257 void set_gamestate(saved_game& gamestate, load_game_metadata& load_data)
258 {
259  gamestate.set_data(std::exchange(load_data.load_config, {}));
260  gamestate.unify_controllers();
261 }
262 
263 savegame::savegame(saved_game& gamestate, const compression::format compress_saves, const std::string& title)
264  : filename_()
265  , title_(title)
266  , save_index_manager_(save_index_class::default_saves_dir())
267  , gamestate_(gamestate)
268  , error_message_(_("The game could not be saved: "))
269  , show_confirmation_(false)
270  , compress_saves_(compress_saves)
271 {
272 }
273 
274 bool savegame::save_game_automatic(bool ask_for_overwrite, const std::string& filename)
275 {
276  if(filename.empty()) {
277  filename_ = create_filename();
278  } else {
280  }
281 
282  if(ask_for_overwrite) {
283  if(!check_overwrite()) {
284  return save_game_interactive("", savegame::OK_CANCEL);
285  }
286  }
287 
288  return save_game();
289 }
290 
291 bool savegame::save_game_interactive(const std::string& message, DIALOG_TYPE dialog_type)
292 {
293  show_confirmation_ = true;
294  filename_ = create_filename();
295 
296  const int res = show_save_dialog(message, dialog_type);
297 
298  if(res == 2) {
299  throw_quit_game_exception(); // Quit game
300  }
301 
302  if(res == gui2::retval::OK && check_overwrite()) {
303  return save_game();
304  }
305 
306  return false;
307 }
308 
309 int savegame::show_save_dialog(const std::string& message, DIALOG_TYPE dialog_type)
310 {
311  int res = 0;
312 
313  if(dialog_type == OK_CANCEL) {
314  gui2::dialogs::game_save dlg(filename_, title_);
315  dlg.show();
316  res = dlg.get_retval();
317  } else if(dialog_type == YES_NO) {
318  gui2::dialogs::game_save_message dlg(filename_, title_, message);
319  dlg.show();
320  res = dlg.get_retval();
321  }
322 
323  if(!check_filename(filename_)) {
324  res = gui2::retval::CANCEL;
325  }
326 
327  return res;
328 }
329 
330 bool savegame::check_overwrite()
331 {
332  if(!save_game_exists(filename_, compress_saves_)) {
333  return true;
334  }
335 
336  std::ostringstream message;
337  message << _("Save already exists. Do you want to overwrite it?") << "\n" << _("Name: ") << filename_;
338  const int res = gui2::show_message(_("Overwrite?"), message.str(), gui2::dialogs::message::yes_no_buttons);
339  return res == gui2::retval::OK;
340 }
341 
342 bool savegame::check_filename(const std::string& filename)
343 {
345  gui2::show_error_message(_("Save names should not end with ‘.gz’ or ‘.bz2’. Please remove the extension."));
346  return false;
348  // This message is not all-inclusive. This is on purpose. Few people
349  // need to know about DOS device names or the 255 character limit.
350  gui2::show_error_message(_("Save names may not end with a dot, or contain two dots or any of the following characters:\n \" * / : < > ? \\ | ~"));
351  return false;
352  }
353 
354  return true;
355 }
356 
357 std::string savegame::create_filename(unsigned int turn_number) const
358 {
359  return create_initial_filename(turn_number);
360 }
361 
362 void savegame::before_save()
363 {
364 }
365 
366 bool savegame::save_game(const std::string& filename)
367 {
368  try {
369  utils::optional<const utils::ms_optimer> timer([this](const auto& timer) {
370  LOG_SAVE << "Milliseconds to save " << filename_ << ": " << timer;
371  });
372 
373  if(filename_.empty()) {
375  }
376 
377  before_save();
378 
379  write_game_to_disk(filename_);
380  if(resources::persist != nullptr) {
383  }
384 
385  // Create an entry in the save_index. Doing this here ensures all leader image paths
386  // sre expanded in a context-independent fashion and can appear in the Load Game dialog
387  // even if a campaign-specific sprite is used. This is because the image's full path is
388  // only available if the binary-path context its a part of is loaded. Without this, if
389  // a player saves a game and exits the game or reloads the cache, the leader image will
390  // only be available within that specific binary context (when playing another game from
391  // the came campaign, for example).
392  save_index_manager_->rebuild(filename_);
393 
394  // Log time before showing the confirmation
395  timer.reset();
396 
397  if(show_confirmation_) {
398  gui2::show_transient_message(_("Saved"), _("The game has been saved."));
399  }
400 
401  return true;
402  } catch(const game::save_game_failed& e) {
403  ERR_SAVE << error_message_ << e.message;
404  gui2::show_error_message(error_message_ + e.message);
405 
406  // do not bother retrying, since the user can just try to save the game again
407  // maybe show a yes-no dialog for "disable autosaves now"?
408  return false;
409  };
410 }
411 
412 void savegame::write_game_to_disk(const std::string& filename)
413 {
414  LOG_SAVE << "savegame::save_game";
415 
417  filename_ += compression::format_extension(compress_saves_);
418 
419  std::stringstream ss;
420  {
421  config_writer out(ss, compress_saves_);
422  write_game(out);
423  finish_save_game(out);
424  }
425 
426  filesystem::scoped_ostream os(open_save_game(filename_));
427  (*os) << ss.str();
428 
429  if(!os->good()) {
430  throw game::save_game_failed(_("Could not write to file"));
431  }
432 }
433 
434 void savegame::write_game(config_writer& out)
435 {
436  log_scope("write_game");
437 
438  out.write_key_val("version", game_config::wesnoth_version.str());
439 
440  gamestate_.write_general_info(out);
441 }
442 
443 void savegame::finish_save_game(const config_writer& out)
444 {
445  try {
446  if(!out.good()) {
447  throw game::save_game_failed(_("Could not write to file"));
448  }
449  } catch(const filesystem::io_exception& e) {
450  throw game::save_game_failed(e.what());
451  }
452 }
453 
454 // Throws game::save_game_failed
455 filesystem::scoped_ostream savegame::open_save_game(const std::string& label)
456 {
457  try {
458  return filesystem::ostream_file(save_index_manager_->dir() + "/" + label);
459  } catch(const filesystem::io_exception& e) {
460  throw game::save_game_failed(e.what());
461  }
462 }
463 
465  : savegame(gamestate, compress_saves)
466 {
468 }
469 
470 std::string scenariostart_savegame::create_initial_filename(unsigned int) const
471 {
472  return gamestate().classification().label;
473 }
474 
476 {
478  gamestate().write_carryover(out);
479 }
480 
482  : savegame(gamestate, compress_saves, _("Save Replay"))
483 {
484 }
485 
486 std::string replay_savegame::create_initial_filename(unsigned int) const
487 {
488  auto time = chrono::format_local_timestamp(std::chrono::system_clock::now(), "%Y%m%d-%H%M%S");
489  // TRANSLATORS: This string is used as part of a filename, as in, "HttT-The Elves Besieged replay.gz"
490  return formatter() << gamestate().classification().label << " " << _("replay") << " " << time;
491 }
492 
494 {
496 
497  gamestate().write_carryover(out);
498  out.write_child("replay_start", gamestate().replay_start());
499 
500  out.open_child("replay");
501  gamestate().get_replay().write(out);
502  out.close_child("replay");
503 }
504 
506  : ingame_savegame(gamestate, compress_saves)
507 {
508  set_error_message(_("Could not auto save the game. Please save the game manually."));
509 }
510 
511 void autosave_savegame::autosave(const bool disable_autosave, const int autosave_max, const int infinite_autosaves)
512 {
513  if(disable_autosave)
514  return;
515 
517 
518  auto manager = save_index_class::default_saves_dir();
519  manager->delete_old_auto_saves(autosave_max, infinite_autosaves);
520 }
521 
522 std::string autosave_savegame::create_initial_filename(unsigned int turn_number) const
523 {
524  std::string filename;
525  if(gamestate().classification().label.empty()) {
526  filename = _("Auto-Save");
527  } else {
528  filename = gamestate().classification().label + "-" + _("Auto-Save") + std::to_string(turn_number);
529  }
530 
531  return filename;
532 }
533 
534 oos_savegame::oos_savegame(saved_game& gamestate, bool& ignore)
535  : ingame_savegame(gamestate, prefs::get().save_compression_format())
536  , ignore_(ignore)
537 {
538 }
539 
540 int oos_savegame::show_save_dialog(const std::string& message, DIALOG_TYPE /*dialog_type*/)
541 {
542  int res = 0;
543 
544  if(!ignore_) {
546  dlg.show();
547  res = dlg.get_retval();
548  }
549 
550  if(!check_filename(filename_)) {
551  res = gui2::retval::CANCEL;
552  }
553 
554  return res;
555 }
556 
558  : savegame(gamestate, compress_saves, _("Save Game"))
559 {
560 }
561 
562 std::string ingame_savegame::create_initial_filename(unsigned int turn_number) const
563 {
564  return formatter() << gamestate().classification().label << " " << _("Turn") << " " << turn_number;
565 }
566 
568 {
569  log_scope("write_game");
570 
571  if(!gamestate().get_starting_point().validate_wml()) {
572  throw game::save_game_failed(_("Game state is corrupted"));
573  }
574 
576 
577  gamestate().write_carryover(out);
578  out.write_child("snapshot", gamestate().get_starting_point());
579  out.write_child("replay_start", gamestate().replay_start());
580  out.open_child("replay");
581  gamestate().get_replay().write(out);
582  out.close_child("replay");
583 }
584 
585 // changes done during 1.11.0-dev
587 {
588  if(!cfg.has_child("snapshot")) {
589  return;
590  }
591 
592  const config& snapshot = cfg.mandatory_child("snapshot");
593  const config& replay_start = cfg.mandatory_child("replay_start");
594  const config& replay = cfg.mandatory_child("replay");
595 
596  if(!cfg.has_child("carryover_sides") && !cfg.has_child("carryover_sides_start")) {
598  // copy rng and menu items from toplevel to new carryover_sides
599  carryover["random_seed"] = cfg["random_seed"];
600  carryover["random_calls"] = cfg["random_calls"];
601 
602  for(const config& menu_item : cfg.child_range("menu_item")) {
603  carryover.add_child("menu_item", menu_item);
604  }
605 
606  carryover["difficulty"] = cfg["difficulty"];
607  carryover["random_mode"] = cfg["random_mode"];
608  // the scenario to be played is always stored as next_scenario in carryover_sides_start
609  carryover["next_scenario"] = cfg["scenario"];
610 
611  config carryover_start = carryover;
612 
613  // copy sides from either snapshot or replay_start to new carryover_sides
614  if(!snapshot.empty()) {
615  for(const config& side : snapshot.child_range("side")) {
616  carryover.add_child("side", side);
617  }
618  // for compatibility with old savegames that use player instead of side
619  for(const config& side : snapshot.child_range("player")) {
620  carryover.add_child("side", side);
621  }
622  // save the sides from replay_start in carryover_sides_start
623  for(const config& side : replay_start.child_range("side")) {
624  carryover_start.add_child("side", side);
625  }
626  // for compatibility with old savegames that use player instead of side
627  for(const config& side : replay_start.child_range("player")) {
628  carryover_start.add_child("side", side);
629  }
630  } else if(!replay_start.empty()) {
631  for(const config& side : replay_start.child_range("side")) {
632  carryover.add_child("side", side);
633  carryover_start.add_child("side", side);
634  }
635  // for compatibility with old savegames that use player instead of side
636  for(const config& side : replay_start.child_range("player")) {
637  carryover.add_child("side", side);
638  carryover_start.add_child("side", side);
639  }
640  }
641 
642  // get variables according to old hierarchy and copy them to new carryover_sides
643  if(!snapshot.empty()) {
644  if(auto variables_from_snapshot = snapshot.optional_child("variables")) {
645  carryover.add_child("variables", *variables_from_snapshot);
646  carryover_start.add_child("variables", replay_start.child_or_empty("variables"));
647  } else if(auto variables_from_cfg = cfg.optional_child("variables")) {
648  carryover.add_child("variables", *variables_from_cfg);
649  carryover_start.add_child("variables", *variables_from_cfg);
650  }
651  } else if(!replay_start.empty()) {
652  if(auto variables = replay_start.optional_child("variables")) {
653  carryover.add_child("variables", *variables);
654  carryover_start.add_child("variables", *variables);
655  }
656  } else {
657  carryover.add_child("variables", cfg.mandatory_child("variables"));
658  carryover_start.add_child("variables", cfg.mandatory_child("variables"));
659  }
660 
661  cfg.add_child("carryover_sides", carryover);
662  cfg.add_child("carryover_sides_start", carryover_start);
663  }
664 
665  // if replay and snapshot are empty we've got a start of scenario save and don't want replay_start either
666  if(replay.empty() && snapshot.empty()) {
667  LOG_RG << "removing replay_start";
668  cfg.clear_children("replay_start");
669  }
670 
671  // remove empty replay or snapshot so type of save can be detected more easily
672  if(replay.empty()) {
673  LOG_RG << "removing replay";
674  cfg.clear_children("replay");
675  }
676 
677  if(snapshot.empty()) {
678  LOG_RG << "removing snapshot";
679  cfg.clear_children("snapshot");
680  }
681 }
682 // changes done during 1.13.0-dev
684 {
685  if(auto carryover_sides_start = cfg.optional_child("carryover_sides_start")) {
686  if(!carryover_sides_start->has_attribute("next_underlying_unit_id")) {
687  carryover_sides_start["next_underlying_unit_id"] = cfg["next_underlying_unit_id"];
688  }
689  }
690 
691  if(cfg.child_or_empty("snapshot").empty()) {
692  cfg.clear_children("snapshot");
693  }
694 
695  if(cfg.child_or_empty("replay_start").empty()) {
696  cfg.clear_children("replay_start");
697  }
698 
699  if(auto snapshot = cfg.optional_child("snapshot")) {
700  // make [end_level] -> [end_level_data] since its alo called [end_level_data] in the carryover.
701  if(auto end_level = cfg.optional_child("end_level")) {
702  snapshot->add_child("end_level_data", *end_level);
703  snapshot->clear_children("end_level");
704  }
705  // if we have a snapshot then we already applied carryover so there is no reason to keep this data.
706  if(cfg.has_child("carryover_sides_start")) {
707  cfg.clear_children("carryover_sides_start");
708  }
709  }
710 
711  if(!cfg.has_child("snapshot") && !cfg.has_child("replay_start")) {
712  cfg.clear_children("carryover_sides");
713  }
714 
715  // This code is needed because for example otherwise it won't find the (empty) era
716  if(!cfg.has_child("multiplayer")) {
717  cfg.add_child("multiplayer",
718  config{
719  "mp_era",
720  "era_blank",
721  "mp_use_map_settings",
722  true,
723  });
724  }
725 }
726 
727 // changes done during 1.13.0+dev
729 {
730  if(auto multiplayer = cfg.optional_child("multiplayer")) {
731  if(multiplayer["mp_era"] == "era_blank") {
732  multiplayer["mp_era"] = "era_default";
733  }
734  }
735 
736  // This currently only fixes start-of-scenario saves.
737  if(auto carryover_sides_start = cfg.optional_child("carryover_sides_start")) {
738  for(config& side : carryover_sides_start->child_range("side")) {
739  for(config& unit : side.child_range("unit")) {
740  if(auto modifications = unit.optional_child("modifications")) {
741  for(config& advancement : modifications->child_range("advance")) {
742  modifications->add_child("advancement", advancement);
743  }
744  modifications->clear_children("advance");
745  }
746  }
747  }
748  }
749 
750  for(config& snapshot : cfg.child_range("snapshot")) {
751  if(snapshot.has_attribute("used_items")) {
752  config used_items;
753  for(const std::string& item : utils::split(snapshot["used_items"])) {
754  used_items[item] = true;
755  }
756 
757  snapshot.remove_attribute("used_items");
758  snapshot.add_child("used_items", used_items);
759  }
760  }
761 }
762 
763 // changes done during 1.15.3+dev
765 {
766  if(cfg["era_id"].empty()) {
767  cfg["era_id"] = cfg.child_or_empty("multiplayer")["mp_era"];
768  }
769 
770  if(cfg["active_mods"].empty()) {
771  cfg["active_mods"] = cfg.child_or_empty("multiplayer")["active_mods"];
772  }
773 }
774 
776 {
777  version_info loaded_version(cfg["version"]);
778  if(loaded_version < version_info("1.12.0")) {
780  }
781 
782  // '<= version_info("1.13.0")' doesn't work
783  // because version_info cannot handle 1.13.0-dev versions correctly.
784  if(loaded_version < version_info("1.13.1")) {
786  }
787 
788  if(loaded_version <= version_info("1.13.1")) {
790  }
791 
792  if(loaded_version < version_info("1.15.4")) {
794  }
795 
796  LOG_RG << "cfg after conversion " << cfg;
797 }
798 
799 } // 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:157
void remove_attribute(std::string_view key)
Definition: config.cpp:162
config & add_child(std::string_view key)
Definition: config.cpp:436
optional_config_impl< config > optional_child(std::string_view key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:380
child_itors child_range(std::string_view key)
Definition: config.cpp:268
void clear_children(T... keys)
Definition: config.hpp:601
bool has_child(std::string_view key) const
Determine whether a config has a child or not.
Definition: config.cpp:312
const config & child_or_empty(std::string_view key) const
Returns the first child with the given key, or an empty config if there is none.
Definition: config.cpp:390
bool empty() const
Definition: config.cpp:823
config & mandatory_child(std::string_view key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition: config.cpp:362
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:511
autosave_savegame(saved_game &gamestate, const compression::format compress_saves)
Definition: savegame.cpp:505
virtual std::string create_initial_filename(unsigned int turn_number) const override
Create a filename for automatic saves.
Definition: savegame.cpp:522
Class for "normal" midgame saves.
Definition: savegame.hpp:230
ingame_savegame(saved_game &gamestate, const compression::format compress_saves)
Definition: savegame.cpp:557
void write_game(config_writer &out) override
Writing the savegame config to a file.
Definition: savegame.cpp:567
virtual std::string create_initial_filename(unsigned int turn_number) const override
Create a filename for automatic saves.
Definition: savegame.cpp:562
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:534
virtual int show_save_dialog(const std::string &message, DIALOG_TYPE dialog_type) override
Display the save game dialog.
Definition: savegame.cpp:540
void write_game(config_writer &out) override
Writing the savegame config to a file.
Definition: savegame.cpp:493
virtual std::string create_initial_filename(unsigned int turn_number) const override
Create a filename for automatic saves.
Definition: savegame.cpp:486
replay_savegame(saved_game &gamestate, const compression::format compress_saves)
Definition: savegame.cpp:481
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:202
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:342
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:274
virtual void write_game(config_writer &out)
Writing the savegame config to a file.
Definition: savegame.cpp:434
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:464
void write_game(config_writer &out) override
Writing the savegame config to a file.
Definition: savegame.cpp:475
virtual std::string create_initial_filename(unsigned int turn_number) const override
Create a filename for automatic saves.
Definition: savegame.cpp:470
This class represents a single unit of a specific type.
Definition: unit.hpp:39
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:300
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:683
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:775
utils::optional< load_game_metadata > load_interactive_for_multiplayer()
Definition: savegame.cpp:216
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:174
void set_gamestate(saved_game &gamestate, load_game_metadata &load_data)
Definition: savegame.cpp:257
static void convert_old_saves_1_11_0(config &cfg)
Definition: savegame.cpp:586
static void convert_old_saves_1_13_1(config &cfg)
Definition: savegame.cpp:728
utils::optional< load_game_metadata > load_interactive()
Definition: savegame.cpp:185
static void convert_old_saves_1_15_3(config &cfg)
Definition: savegame.cpp:764
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:162
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