The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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"
23#include "game_config_view.hpp"
25#include "game_errors.hpp"
26#include "game_version.hpp"
27#include "gettext.hpp"
34#include "log.hpp"
35#include "persist_manager.hpp"
37#include "resources.hpp"
38#include "save_index.hpp"
39#include "saved_game.hpp"
43#include "utils/optimer.hpp"
44#include "video.hpp" // only for faked
45
46#include <iomanip>
47
48static 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
52static lg::log_domain log_enginerefac("enginerefac");
53#define LOG_RG LOG_STREAM(info, log_enginerefac)
54
55namespace savegame
56{
57bool save_game_exists(std::string name, compression::format compressed)
58{
59 name += compression::format_extension(compressed);
61 return filesystem::file_exists(manager->dir() + "/" + name);
62}
63
64void clean_saves(const std::string& label)
65{
66 const std::string prefix = label + "-" + _("Auto-Save");
67 LOG_SAVE << "Cleaning saves with prefix '" << prefix << "'";
68
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
78namespace
79{
80bool 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. */
102bool 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
121 && wesnoth_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. */
155bool 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
185utils::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
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
216utils::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
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
258{
259 gamestate.set_data(std::exchange(load_data.load_config, {}));
260 gamestate.unify_controllers();
261}
262
263savegame::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
274bool 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
291bool 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
309int savegame::show_save_dialog(const std::string& message, DIALOG_TYPE dialog_type)
310{
311 int res = 0;
312
313 if(dialog_type == OK_CANCEL) {
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_)) {
325 }
326
327 return res;
328}
329
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
342bool 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
357std::string savegame::create_filename(unsigned int turn_number) const
358{
359 return create_initial_filename(turn_number);
360}
361
363{
364}
365
366bool 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
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
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
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
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
471{
472 return gamestate().classification().label;
473}
474
480
482 : savegame(gamestate, compress_saves, _("Save Replay"))
483{
484}
485
486std::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
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
511void autosave_savegame::autosave(const bool disable_autosave, const int autosave_max, const int infinite_autosaves)
512{
513 if(disable_autosave)
514 return;
515
517
519 manager->delete_old_auto_saves(autosave_max, infinite_autosaves);
520}
521
522std::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
534oos_savegame::oos_savegame(saved_game& gamestate, bool& ignore)
535 : ingame_savegame(gamestate, prefs::get().save_compression_format())
536 , ignore_(ignore)
537{
538}
539
540int 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
552 }
553
554 return res;
555}
556
558 : savegame(gamestate, compress_saves, _("Save Game"))
559{
560}
561
562std::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
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_
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
void set_data(config &&cfg)
replay_recorder_base & get_replay()
void unify_controllers()
game_classification & classification()
void write_carryover(config_writer &out) const
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()
The base class for all savegame stuff.
Definition savegame.hpp:131
virtual int show_save_dialog(const std::string &message, DIALOG_TYPE dialog_type)
Display the save game dialog.
Definition savegame.cpp:309
bool check_filename(const std::string &filename)
Check, if the filename contains illegal constructs like ".gz".
Definition savegame.cpp:342
bool save_game(const std::string &filename="")
Save a game without any further user interaction.
Definition savegame.cpp:366
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
bool check_overwrite()
Ask the user if an existing file should be overwritten.
Definition savegame.cpp:330
const saved_game & gamestate() const
Definition savegame.hpp:179
void finish_save_game(const config_writer &out)
Update the save_index.
Definition savegame.cpp:443
bool save_game_interactive(const std::string &message, DIALOG_TYPE dialog_type)
Save a game interactively through the savegame dialog.
Definition savegame.cpp:291
const std::string & filename() const
Definition savegame.hpp:154
filesystem::scoped_ostream open_save_game(const std::string &label)
Throws game::save_game_failed.
Definition savegame.cpp:455
void set_error_message(const std::string &error_message)
Customize the standard error message.
Definition savegame.hpp:176
void before_save()
If there needs to be some data fiddling before saving the game, this is the place to go.
Definition savegame.cpp:362
void write_game_to_disk(const std::string &filename)
The actual method for saving the game to disk.
Definition savegame.cpp:412
const std::string & title() const
Definition savegame.hpp:178
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
#define LOG_RG
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:100
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:72
std::string format_extension(format compression_format)
@ WAIT
Definition cursor.hpp:28
bool file_exists(const std::string &name)
Returns true if a file or directory with such name already exists.
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)
filesystem::scoped_ostream ostream_file(const std::string &fname, std::ios_base::openmode mode=std::ios_base::binary, bool create_directory=true)
std::unique_ptr< std::ostream > scoped_ostream
Game configuration data as global variables.
const version_info min_savegame_version(MIN_SAVEGAME_VERSION)
const version_info test_version("test")
const version_info wesnoth_version(VERSION)
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
config read_save_file(const std::string &dir, const std::string &name)
Read the complete config information out of a savefile.
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:146
#define LOG_SAVE
#define ERR_SAVE
static lg::log_domain log_engine("engine")
static lg::log_domain log_enginerefac("enginerefac")
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
std::string filename
Filename.
An exception object used when an IO error occurs.
Error used when game loading fails.
Error used when game saving fails.
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
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
#define e