00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017 #include <boost/iostreams/filter/gzip.hpp>
00018
00019 #include "savegame.hpp"
00020
00021 #include "dialogs.hpp"
00022 #include "foreach.hpp"
00023 #include "formula_string_utils.hpp"
00024 #include "game_display.hpp"
00025 #include "game_end_exceptions.hpp"
00026 #include "game_preferences.hpp"
00027 #include "gettext.hpp"
00028 #include "gui/dialogs/game_load.hpp"
00029 #include "gui/dialogs/game_save.hpp"
00030 #include "gui/dialogs/message.hpp"
00031 #include "gui/dialogs/campaign_difficulty.hpp"
00032 #include "gui/widgets/settings.hpp"
00033 #include "gui/widgets/window.hpp"
00034 #include "log.hpp"
00035 #include "map.hpp"
00036 #include "map_label.hpp"
00037 #include "persist_manager.hpp"
00038 #include "replay.hpp"
00039 #include "resources.hpp"
00040 #include "serialization/binary_or_text.hpp"
00041 #include "serialization/parser.hpp"
00042 #include "statistics.hpp"
00043
00044 #include "unit_id.hpp"
00045 #include "version.hpp"
00046
00047 static lg::log_domain log_engine("engine");
00048 #define LOG_SAVE LOG_STREAM(info, log_engine)
00049 #define ERR_SAVE LOG_STREAM(err, log_engine)
00050
00051 #ifdef _WIN32
00052 #ifdef INADDR_ANY
00053 #undef INADDR_ANY
00054 #endif
00055 #ifdef INADDR_BROADCAST
00056 #undef INADDR_BROADCAST
00057 #endif
00058 #ifdef INADDR_NONE
00059 #undef INADDR_NONE
00060 #endif
00061
00062 #include <windows.h>
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073 void conv_ansi_utf8(std::string &name, bool a2u) {
00074 int wlen = MultiByteToWideChar(a2u ? CP_ACP : CP_UTF8, 0,
00075 name.c_str(), -1, NULL, 0);
00076 if (wlen == 0) return;
00077 WCHAR *wc = new WCHAR[wlen];
00078 if (wc == NULL) return;
00079 if (MultiByteToWideChar(a2u ? CP_ACP : CP_UTF8, 0, name.c_str(), -1,
00080 wc, wlen) == 0) {
00081 delete [] wc;
00082 return;
00083 }
00084 int alen = WideCharToMultiByte(!a2u ? CP_ACP : CP_UTF8, 0, wc, wlen,
00085 NULL, 0, NULL, NULL);
00086 if (alen == 0) {
00087 delete [] wc;
00088 return;
00089 }
00090 CHAR *ac = new CHAR[alen];
00091 if (ac == NULL) {
00092 delete [] wc;
00093 return;
00094 }
00095 WideCharToMultiByte(!a2u ? CP_ACP : CP_UTF8, 0, wc, wlen,
00096 ac, alen, NULL, NULL);
00097 delete [] wc;
00098 if (ac == NULL) {
00099 return;
00100 }
00101 name = ac;
00102 delete [] ac;
00103
00104 return;
00105 }
00106
00107 void replace_underbar2space(std::string &name) {
00108 LOG_SAVE << "conv(A2U)-from:[" << name << "]" << std::endl;
00109 conv_ansi_utf8(name, true);
00110 LOG_SAVE << "conv(A2U)-to:[" << name << "]" << std::endl;
00111 LOG_SAVE << "replace_underbar2space-from:[" << name << "]" << std::endl;
00112 std::replace(name.begin(), name.end(), '_', ' ');
00113 LOG_SAVE << "replace_underbar2space-to:[" << name << "]" << std::endl;
00114 }
00115
00116 void replace_space2underbar(std::string &name) {
00117 LOG_SAVE << "conv(U2A)-from:[" << name << "]" << std::endl;
00118 conv_ansi_utf8(name, false);
00119 LOG_SAVE << "conv(U2A)-to:[" << name << "]" << std::endl;
00120 LOG_SAVE << "replace_underbar2space-from:[" << name << "]" << std::endl;
00121 std::replace(name.begin(), name.end(), ' ', '_');
00122 LOG_SAVE << "replace_underbar2space-to:[" << name << "]" << std::endl;
00123 }
00124 #else
00125 void replace_underbar2space(std::string &name) {
00126 std::replace(name.begin(),name.end(),'_',' ');
00127 }
00128 void replace_space2underbar(std::string &name) {
00129 std::replace(name.begin(),name.end(),' ','_');
00130 }
00131 #endif
00132
00133 namespace savegame {
00134
00135 class save_index_class
00136 {
00137 public:
00138 void rebuild(const std::string& name) {
00139 std::string filename = name;
00140 replace_space2underbar(filename);
00141 time_t modified = file_create_time(get_saves_dir() + "/" + filename);
00142 rebuild(name, modified);
00143 }
00144 void rebuild(const std::string& name, const time_t& modified) {
00145 log_scope("load_summary_from_file");
00146 config& summary = data(name);
00147 try {
00148 config full;
00149 std::string dummy;
00150 read_save_file(name, full, &dummy);
00151 ::extract_summary_from_config(full, summary);
00152 } catch(game::load_game_failed&) {
00153 summary["corrupt"] = true;
00154 }
00155 summary["mod_time"] = str_cast(static_cast<int>(modified));
00156 write_save_index();
00157 }
00158 void remove(const std::string& name) {
00159 config& root = data();
00160 root.remove_attribute(name);
00161 write_save_index();
00162 }
00163 void set_modified(const std::string& name, const time_t& modified) {
00164 modified_[name] = modified;
00165 }
00166 config& get(const std::string& name) {
00167 config& result = data(name);
00168 time_t m = modified_[name];
00169 config::attribute_value& mod_time = result["mod_time"];
00170 if (mod_time.empty() || static_cast<time_t>(mod_time.to_int()) != m) {
00171 rebuild(name, m);
00172 }
00173 return result;
00174 }
00175 public:
00176 void write_save_index() {
00177 log_scope("write_save_index()");
00178 try {
00179 scoped_ostream stream = ostream_file(get_save_index_file());
00180 if (preferences::compress_saves()) {
00181 write_gz(*stream, data());
00182 } else {
00183 write(*stream, data());
00184 }
00185 } catch(io_exception& e) {
00186 ERR_SAVE << "error writing to save index file: '" << e.what() << "'\n";
00187 }
00188 }
00189
00190 public:
00191 save_index_class()
00192 : loaded_(false)
00193 , data_()
00194 , modified_()
00195 {
00196 }
00197 private:
00198 config& data(const std::string& name) {
00199 std::string save = name;
00200
00201
00202
00203
00204
00205 if(save.length() < 3 || save.substr(save.length() - 3) != ".gz") {
00206 save += ".gz";
00207 }
00208
00209 config& cfg = data();
00210 if (config& sv = cfg.find_child("save", "save", save)) {
00211 return sv;
00212 }
00213
00214 config& res = cfg.add_child("save");
00215 res["save"] = save;
00216 return res;
00217 }
00218 config& data() {
00219 if(loaded_ == false) {
00220 try {
00221 scoped_istream stream = istream_file(get_save_index_file());
00222 try {
00223 read_gz(data_, *stream);
00224 } catch (boost::iostreams::gzip_error&) {
00225 stream->seekg(0);
00226 read(data_, *stream);
00227 }
00228 } catch(io_exception& e) {
00229 ERR_SAVE << "error reading save index: '" << e.what() << "'\n";
00230 } catch(config::error&) {
00231 ERR_SAVE << "error parsing save index config file\n";
00232 data_.clear();
00233 }
00234 loaded_ = true;
00235 }
00236 return data_;
00237 }
00238 private:
00239 bool loaded_;
00240 config data_;
00241 std::map< std::string, time_t > modified_;
00242 } save_index_manager;
00243
00244 class filename_filter {
00245 public:
00246 filename_filter(const std::string& filter) : filter_(filter) {
00247 }
00248 bool operator()(const std::string& filename) const {
00249 return filename.end() == std::search(filename.begin(), filename.end(),
00250 filter_.begin(), filter_.end());
00251 }
00252 private:
00253 std::string filter_;
00254 };
00255
00256 class create_save_info {
00257 public:
00258 create_save_info(const std::string* d = NULL) : dir(d ? *d : get_saves_dir()) {
00259 }
00260 save_info operator()(const std::string& filename) const {
00261 std::string name = filename;
00262 replace_underbar2space(name);
00263 time_t modified = file_create_time(dir + "/" + filename);
00264 save_index_manager.set_modified(name, modified);
00265 return save_info(name, modified);
00266 }
00267 const std::string dir;
00268 };
00269
00270
00271 std::vector<save_info> get_saves_list(const std::string* dir, const std::string* filter)
00272 {
00273 create_save_info creator(dir);
00274
00275 std::vector<std::string> filenames;
00276 get_files_in_dir(creator.dir,&filenames);
00277
00278 if (filter) {
00279 filenames.erase(std::remove_if(filenames.begin(), filenames.end(),
00280 filename_filter(*filter)),
00281 filenames.end());
00282 }
00283
00284 std::vector<save_info> result;
00285 std::transform(filenames.begin(), filenames.end(),
00286 std::back_inserter(result), creator);
00287 std::sort(result.begin(),result.end(),save_info_less_time());
00288 return result;
00289 }
00290
00291 save_info::save_info(const std::string& n, const time_t& m) : name_(n), modified_(m) {
00292 }
00293
00294 const std::string& save_info::name() const {
00295 return name_;
00296 }
00297
00298 const time_t& save_info::modified() const {
00299 return modified_;
00300 }
00301
00302 const config& save_info::summary() const {
00303 return save_index_manager.get(name());
00304 }
00305
00306 const std::string save_info::format_time_local() const{
00307 char time_buf[256] = {0};
00308 tm* tm_l = localtime(&modified());
00309 if (tm_l) {
00310 const size_t res = strftime(time_buf,sizeof(time_buf),
00311 (preferences::use_twelve_hour_clock_format() ? _("%a %b %d %I:%M %p %Y") : _("%a %b %d %H:%M %Y")),
00312 tm_l);
00313 if(res == 0) {
00314 time_buf[0] = 0;
00315 }
00316 } else {
00317 LOG_SAVE << "localtime() returned null for time " << this->modified() << ", save " << name();
00318 }
00319
00320 return time_buf;
00321 }
00322
00323 const std::string save_info::format_time_summary() const
00324 {
00325 time_t t = modified();
00326 time_t curtime = time(NULL);
00327 const struct tm* timeptr = localtime(&curtime);
00328 if(timeptr == NULL) {
00329 return "";
00330 }
00331
00332 const struct tm current_time = *timeptr;
00333
00334 timeptr = localtime(&t);
00335 if(timeptr == NULL) {
00336 return "";
00337 }
00338
00339 const struct tm save_time = *timeptr;
00340
00341 const char* format_string = _("%b %d %y");
00342
00343 if(current_time.tm_year == save_time.tm_year) {
00344 const int days_apart = current_time.tm_yday - save_time.tm_yday;
00345 if(days_apart == 0) {
00346
00347 if(preferences::use_twelve_hour_clock_format() == false) {
00348 format_string = _("%H:%M");
00349 }
00350 else {
00351 format_string = _("%I:%M %p");
00352 }
00353 } else if(days_apart > 0 && days_apart <= current_time.tm_wday) {
00354
00355 if(preferences::use_twelve_hour_clock_format() == false) {
00356 format_string = _("%A, %H:%M");
00357 }
00358 else {
00359 format_string = _("%A, %I:%M %p");
00360 }
00361 } else {
00362
00363 format_string = _("%b %d");
00364 }
00365 } else {
00366
00367 format_string = _("%b %d %y");
00368 }
00369
00370 char buf[40];
00371 const size_t res = strftime(buf,sizeof(buf),format_string,&save_time);
00372 if(res == 0) {
00373 buf[0] = 0;
00374 }
00375
00376 return buf;
00377 }
00378
00379 bool save_info_less_time::operator() (const save_info& a, const save_info& b) const {
00380 if (a.modified() > b.modified()) {
00381 return true;
00382 } else if (a.modified() < b.modified()) {
00383 return false;
00384
00385
00386
00387
00388
00389 } else if (a.name().find(_(" replay"))==std::string::npos && b.name().find(_(" replay"))!=std::string::npos) {
00390 return true;
00391 } else if (a.name().find(_(" replay"))!=std::string::npos && b.name().find(_(" replay"))==std::string::npos) {
00392 return false;
00393 } else {
00394 return a.name() > b.name();
00395 }
00396 }
00397
00398 void read_save_file(const std::string& name, config& cfg, std::string* error_log)
00399 {
00400 std::string modified_name = name;
00401 replace_space2underbar(modified_name);
00402
00403
00404 scoped_istream file_stream = istream_file(get_saves_dir() + "/" + modified_name);
00405 if (file_stream->fail()) {
00406 file_stream = istream_file(get_saves_dir() + "/" + name);
00407 }
00408 if(file_stream->fail() && !is_gzip_file(modified_name)) {
00409 file_stream = istream_file(get_saves_dir() + "/" + modified_name + ".gz");
00410 if (file_stream->fail()) {
00411 file_stream = istream_file(get_saves_dir() + "/" + name + ".gz");
00412 }
00413 modified_name += ".gz";
00414 }
00415
00416 cfg.clear();
00417 try{
00418
00419
00420
00421
00422 if(is_gzip_file(modified_name)) {
00423 read_gz(cfg, *file_stream);
00424 } else {
00425 read(cfg, *file_stream);
00426 }
00427 } catch (config::error &err)
00428 {
00429 LOG_SAVE << err.message;
00430 if (error_log) *error_log += err.message;
00431 throw game::load_game_failed();
00432 }
00433
00434 if(cfg.empty()) {
00435 LOG_SAVE << "Could not parse file data into config\n";
00436 throw game::load_game_failed();
00437 }
00438 }
00439
00440 bool save_game_exists(const std::string& name, bool compress_saves)
00441 {
00442 std::string fname = name;
00443 replace_space2underbar(fname);
00444
00445 if(compress_saves) {
00446 fname += ".gz";
00447 }
00448
00449 return file_exists(get_saves_dir() + "/" + fname);
00450 }
00451
00452 void clean_saves(const std::string& label)
00453 {
00454 std::vector<save_info> games = get_saves_list();
00455 std::string prefix = label + "-" + _("Auto-Save");
00456 LOG_SAVE << "Cleaning saves with prefix '" << prefix << "'\n";
00457 for (std::vector<save_info>::iterator i = games.begin(); i != games.end(); ++i) {
00458 if (i->name().compare(0, prefix.length(), prefix) == 0) {
00459 LOG_SAVE << "Deleting savegame '" << i->name() << "'\n";
00460 delete_game(i->name());
00461 }
00462 }
00463 }
00464
00465 void remove_old_auto_saves(const int autosavemax, const int infinite_auto_saves)
00466 {
00467 const std::string auto_save = _("Auto-Save");
00468 int countdown = autosavemax;
00469 if (countdown == infinite_auto_saves)
00470 return;
00471
00472 std::vector<save_info> games = get_saves_list(NULL, &auto_save);
00473 for (std::vector<save_info>::iterator i = games.begin(); i != games.end(); ++i) {
00474 if (countdown-- <= 0) {
00475 LOG_SAVE << "Deleting savegame '" << i->name() << "'\n";
00476 delete_game(i->name());
00477 }
00478 }
00479 }
00480
00481 void delete_game(const std::string& name)
00482 {
00483 std::string modified_name = name;
00484 replace_space2underbar(modified_name);
00485
00486 remove((get_saves_dir() + "/" + name).c_str());
00487 remove((get_saves_dir() + "/" + modified_name).c_str());
00488
00489 save_index_manager.remove(name);
00490 }
00491
00492 loadgame::loadgame(display& gui, const config& game_config, game_state& gamestate)
00493 : game_config_(game_config)
00494 , gui_(gui)
00495 , gamestate_(gamestate)
00496 , filename_()
00497 , difficulty_()
00498 , load_config_()
00499 , show_replay_(false)
00500 , cancel_orders_(false)
00501 , select_difficulty_(false)
00502 {}
00503
00504 void loadgame::show_dialog(bool show_replay, bool cancel_orders)
00505 {
00506
00507
00508 if (gui2::new_widgets){
00509 gui2::tgame_load load_dialog(game_config_);
00510 load_dialog.show(gui_.video());
00511
00512 if (load_dialog.get_retval() == gui2::twindow::OK) {
00513 select_difficulty_ = load_dialog.change_difficulty();
00514
00515 filename_ = load_dialog.filename();
00516 show_replay_ = load_dialog.show_replay();
00517 cancel_orders_ = load_dialog.cancel_orders();
00518 }
00519 } else {
00520 bool show_replay_dialog = show_replay;
00521 bool cancel_orders_dialog = cancel_orders;
00522 filename_ = dialogs::load_game_dialog(gui_, game_config_, &select_difficulty_, &show_replay_dialog, &cancel_orders_dialog);
00523 show_replay_ = show_replay_dialog;
00524 cancel_orders_ = cancel_orders_dialog;
00525 }
00526 }
00527
00528 void loadgame::show_difficulty_dialog()
00529 {
00530 create_save_info creator;
00531 save_info info = creator(filename_);
00532 const config& cfg_summary = info.summary();
00533
00534 if ( cfg_summary["corrupt"].to_bool() || (cfg_summary["replay"].to_bool() && !cfg_summary["snapshot"].to_bool(true))
00535 || (!cfg_summary["turn"].empty()) )
00536 return;
00537
00538 const config::const_child_itors &campaigns = game_config_.child_range("campaign");
00539 std::vector<std::string> difficulty_descriptions;
00540 std::vector<std::string> difficulties;
00541 foreach (const config &campaign, campaigns)
00542 {
00543 if (campaign["id"] == cfg_summary["campaign"]) {
00544 difficulty_descriptions = utils::split(campaign["difficulty_descriptions"], ';');
00545 difficulties = utils::split(campaign["difficulties"], ',');
00546
00547 break;
00548 }
00549 }
00550
00551 if (difficulty_descriptions.empty())
00552 return;
00553
00554 int default_difficulty = -1;
00555 for (size_t i = 0; i < difficulties.size(); i++) {
00556 if (difficulties[i] == cfg_summary["difficulty"]) {
00557 default_difficulty = i;
00558 break;
00559 }
00560 }
00561
00562
00563
00564 gui2::tcampaign_difficulty difficulty_dlg(difficulty_descriptions, default_difficulty);
00565 difficulty_dlg.show(gui_.video());
00566
00567 if (difficulty_dlg.get_retval() != gui2::twindow::OK) {
00568 throw load_game_cancelled_exception();
00569 }
00570
00571 difficulty_ = difficulties[difficulty_dlg.selected_index()];
00572 }
00573
00574
00575 void loadgame::load_game()
00576 {
00577 show_dialog(false, false);
00578
00579 if(filename_ != "")
00580 throw game::load_game_exception(filename_, show_replay_, cancel_orders_, select_difficulty_, difficulty_);
00581 }
00582
00583 void loadgame::load_game(
00584 const std::string& filename
00585 , const bool show_replay
00586 , const bool cancel_orders
00587 , const bool select_difficulty
00588 , const std::string& difficulty)
00589 {
00590 filename_ = filename;
00591 difficulty_ = difficulty;
00592 select_difficulty_ = select_difficulty;
00593
00594 if (filename_.empty()){
00595 show_dialog(show_replay, cancel_orders);
00596 }
00597 else{
00598 show_replay_ = show_replay;
00599 cancel_orders_ = cancel_orders;
00600 }
00601
00602 if (filename_.empty())
00603 throw load_game_cancelled_exception();
00604
00605 if (select_difficulty_)
00606 show_difficulty_dialog();
00607
00608 std::string error_log;
00609 read_save_file(filename_, load_config_, &error_log);
00610
00611 if(!error_log.empty()) {
00612 try {
00613 gui2::show_error_message(gui_.video(),
00614 _("Warning: The file you have tried to load is corrupt. Loading anyway.\n") +
00615 error_log);
00616 } catch (utils::invalid_utf8_exception&) {
00617 gui2::show_error_message(gui_.video(),
00618 _("Warning: The file you have tried to load is corrupt. Loading anyway.\n") +
00619 std::string("(UTF-8 ERROR)"));
00620 }
00621 }
00622
00623 if (!difficulty_.empty())
00624 load_config_["difficulty"] = difficulty_;
00625
00626 gamestate_.classification().difficulty = load_config_["difficulty"].str();
00627 gamestate_.classification().campaign_define = load_config_["campaign_define"].str();
00628 gamestate_.classification().campaign_type = load_config_["campaign_type"].str();
00629 gamestate_.classification().campaign_xtra_defines = utils::split(load_config_["campaign_extra_defines"]);
00630 gamestate_.classification().version = load_config_["version"].str();
00631
00632 check_version_compatibility();
00633
00634 }
00635
00636 void loadgame::check_version_compatibility()
00637 {
00638 if (gamestate_.classification().version == game_config::version) {
00639 return;
00640 }
00641
00642 const version_info save_version = gamestate_.classification().version;
00643 const version_info &wesnoth_version = game_config::wesnoth_version;
00644
00645
00646 if (wesnoth_version.minor_version() % 2 == 0 &&
00647 wesnoth_version.major_version() == save_version.major_version() &&
00648 wesnoth_version.minor_version() == save_version.minor_version())
00649 {
00650 return;
00651 }
00652
00653
00654
00655
00656 if (save_version < game_config::min_savegame_version &&
00657 save_version != game_config::test_version &&
00658 wesnoth_version != game_config::test_version)
00659 {
00660 const std::string message = _("This save is from an old, unsupported version ($version_number|) and cannot be loaded.");
00661 utils::string_map symbols;
00662 symbols["version_number"] = save_version.str();
00663 gui2::show_error_message(gui_.video(), utils::interpolate_variables_into_string(message, &symbols));
00664 throw load_game_cancelled_exception();
00665 }
00666
00667 int res = gui2::twindow::OK;
00668 if(preferences::confirm_load_save_from_different_version()) {
00669 const std::string message = _("This save is from a different version of the game ($version_number|). Do you wish to try to load it?");
00670 utils::string_map symbols;
00671 symbols["version_number"] = save_version.str();
00672 res = gui2::show_message(gui_.video(), _("Load Game"), utils::interpolate_variables_into_string(message, &symbols),
00673 gui2::tmessage::yes_no_buttons);
00674 }
00675
00676 if(res == gui2::twindow::CANCEL) {
00677 throw load_game_cancelled_exception();
00678 }
00679 }
00680
00681 void loadgame::set_gamestate()
00682 {
00683 gamestate_ = game_state(load_config_, show_replay_);
00684
00685
00686
00687
00688
00689 int seed = load_config_["random_seed"].to_int(42);
00690 unsigned calls = show_replay_ ? 0 : gamestate_.snapshot["random_calls"].to_int();
00691 gamestate_.rng().seed_random(seed, calls);
00692 }
00693
00694 void loadgame::load_multiplayer_game()
00695 {
00696 show_dialog(false, false);
00697
00698 if (filename_.empty())
00699 throw load_game_cancelled_exception();
00700
00701 std::string error_log;
00702 {
00703 cursor::setter cur(cursor::WAIT);
00704 log_scope("load_game");
00705
00706 read_save_file(filename_, load_config_, &error_log);
00707 copy_era(load_config_);
00708
00709 gamestate_ = game_state(load_config_);
00710 }
00711
00712 if(!error_log.empty()) {
00713 gui2::show_error_message(gui_.video(),
00714 _("The file you have tried to load is corrupt: '") +
00715 error_log);
00716 throw load_game_cancelled_exception();
00717 }
00718
00719 if(gamestate_.classification().campaign_type != "multiplayer") {
00720 gui2::show_message(gui_.video(), "", _("This is not a multiplayer save"));
00721 throw load_game_cancelled_exception();
00722 }
00723
00724 check_version_compatibility();
00725 }
00726
00727 void loadgame::fill_mplevel_config(config& level){
00728 gamestate_.mp_settings().saved_game = true;
00729
00730
00731
00732 const config& start_data = !gamestate_.snapshot.empty() ? gamestate_.snapshot : gamestate_.starting_pos;
00733
00734 level.add_child("map", start_data.child_or_empty("map"));
00735 level["id"] = start_data["id"];
00736 level["name"] = start_data["name"];
00737 level["completion"] = start_data["completion"];
00738 level["next_underlying_unit_id"] = start_data["next_underlying_unit_id"];
00739
00740 level["turn"] = start_data["turn_at"];
00741 level["turn_at"] = start_data["turn_at"];
00742
00743 level.add_child("multiplayer", gamestate_.mp_settings().to_config());
00744
00745
00746 if(gamestate_.snapshot.empty()){
00747
00748
00749 if (config &c = level.child("replay_start")) {
00750 c.merge_with(start_data);
00751 } else {
00752 level.add_child("replay_start") = start_data;
00753 }
00754 level.add_child("snapshot") = config();
00755 } else {
00756 level.add_child("snapshot") = start_data;
00757 level.add_child("replay_start") = gamestate_.starting_pos;
00758 }
00759 level["random_seed"] = start_data["random_seed"];
00760 level["random_calls"] = start_data["random_calls"];
00761
00762
00763
00764 level.add_child("replay") = gamestate_.replay_data;
00765 level.add_child("statistics") = statistics::write_stats();
00766 }
00767
00768 void loadgame::copy_era(config &cfg)
00769 {
00770 const config &replay_start = cfg.child("replay_start");
00771 if (!replay_start) return;
00772
00773 const config &era = replay_start.child("era");
00774 if (!era) return;
00775
00776 config &snapshot = cfg.child("snapshot");
00777 if (!snapshot) return;
00778
00779 snapshot.add_child("era", era);
00780 }
00781
00782 savegame::savegame(game_state& gamestate, const bool compress_saves, const std::string& title)
00783 : gamestate_(gamestate)
00784 , snapshot_()
00785 , filename_()
00786 , title_(title)
00787 , error_message_(_("The game could not be saved: "))
00788 , show_confirmation_(false)
00789 , compress_saves_(compress_saves)
00790 {}
00791
00792 bool savegame::save_game_automatic(CVideo& video, bool ask_for_overwrite, const std::string& filename)
00793 {
00794 bool overwrite = true;
00795
00796 if (filename == "")
00797 create_filename();
00798 else
00799 filename_ = filename;
00800
00801 if (ask_for_overwrite){
00802 overwrite = check_overwrite(video);
00803
00804 if (!overwrite)
00805 return save_game_interactive(video, "", gui::OK_CANCEL);
00806 }
00807
00808 return save_game(&video);
00809 }
00810
00811 bool savegame::save_game_interactive(CVideo& video, const std::string& message,
00812 gui::DIALOG_TYPE dialog_type)
00813 {
00814 show_confirmation_ = true;
00815 create_filename();
00816
00817 int res = gui2::twindow::OK;
00818 bool exit = true;
00819
00820 do{
00821 try{
00822 res = show_save_dialog(video, message, dialog_type);
00823 exit = true;
00824
00825 if (res == gui2::twindow::OK){
00826 exit = check_overwrite(video);
00827 }
00828 }
00829 catch (illegal_filename_exception){
00830 exit = false;
00831 }
00832 }
00833 while (!exit);
00834
00835 if (res == 2)
00836 throw end_level_exception(QUIT);
00837
00838 if (res != gui2::twindow::OK)
00839 return false;
00840
00841 return save_game(&video);
00842 }
00843
00844 int savegame::show_save_dialog(CVideo& video, const std::string& message, const gui::DIALOG_TYPE dialog_type)
00845 {
00846 int res = 0;
00847
00848 std::string filename = filename_;
00849
00850 if (dialog_type == gui::OK_CANCEL){
00851 gui2::tgame_save dlg(filename, title_);
00852 dlg.show(video);
00853 res = dlg.get_retval();
00854 }
00855 else if (dialog_type == gui::YES_NO){
00856 gui2::tgame_save_message dlg(filename, title_, message);
00857 dlg.show(video);
00858 res = dlg.get_retval();
00859 }
00860
00861 check_filename(filename, video);
00862 set_filename(filename);
00863
00864 return res;
00865 }
00866
00867 bool savegame::check_overwrite(CVideo& video)
00868 {
00869 std::string filename = filename_;
00870 if (save_game_exists(filename, compress_saves_)) {
00871 std::stringstream message;
00872 message << _("Save already exists. Do you want to overwrite it?") << "\n" << _("Name: ") << filename;
00873 int retval = gui2::show_message(video, _("Overwrite?"), message.str(), gui2::tmessage::yes_no_buttons);
00874 return retval == gui2::twindow::OK;
00875 } else {
00876 return true;
00877 }
00878 }
00879
00880 void savegame::check_filename(const std::string& filename, CVideo& video)
00881 {
00882 if (is_gzip_file(filename)) {
00883 gui2::show_error_message(video, _("Save names should not end on '.gz'. "
00884 "Please choose a different name."));
00885 throw illegal_filename_exception();
00886 }
00887 }
00888
00889 bool savegame::is_illegal_file_char(char c)
00890 {
00891 return c == '/' || c == '\\' || c == ':'
00892 #ifdef _WIN32
00893 || c == '?' || c == '|' || c == '<' || c == '>' || c == '*' || c == '"'
00894 #endif
00895 ;
00896 }
00897
00898 void savegame::set_filename(std::string filename)
00899 {
00900 filename.erase(std::remove_if(filename.begin(), filename.end(),
00901 is_illegal_file_char), filename.end());
00902 filename_ = filename;
00903 }
00904
00905 void savegame::before_save()
00906 {
00907 gamestate_.replay_data = recorder.get_replay_data();
00908 }
00909
00910 bool savegame::save_game(CVideo* video, const std::string& filename)
00911 {
00912 static std::string parent, grandparent;
00913
00914 try {
00915 Uint32 start, end;
00916 start = SDL_GetTicks();
00917
00918 if (filename_ == "")
00919 filename_ = filename;
00920
00921 before_save();
00922
00923
00924
00925
00926
00927
00928
00929 if (filename_ == parent) {
00930 gamestate_.classification().parent = grandparent;
00931 } else {
00932 gamestate_.classification().parent = parent;
00933 }
00934 LOG_SAVE << "Setting parent of '" << filename_<< "' to " << gamestate_.classification().parent << "\n";
00935
00936 write_game_to_disk(filename_);
00937 if (resources::persist != NULL) {
00938 resources::persist->end_transaction();
00939 resources::persist ->start_transaction();
00940 }
00941
00942 grandparent = parent;
00943 parent = filename_;
00944
00945 end = SDL_GetTicks();
00946 LOG_SAVE << "Milliseconds to save " << filename_ << ": " << end - start << "\n";
00947
00948 if (video != NULL && show_confirmation_)
00949 gui2::show_message(*video, _("Saved"), _("The game has been saved"));
00950 return true;
00951 } catch(game::save_game_failed& e) {
00952 ERR_SAVE << error_message_ << e.message;
00953 if (video != NULL){
00954 gui2::show_error_message(*video, error_message_ + e.message);
00955
00956
00957 }
00958
00959 return false;
00960 };
00961 }
00962
00963 void savegame::write_game_to_disk(const std::string& filename)
00964 {
00965 LOG_SAVE << "savegame::save_game";
00966
00967 filename_ = filename;
00968
00969 if (compress_saves_) {
00970 filename_ += ".gz";
00971 }
00972
00973 std::stringstream ss;
00974 {
00975 config_writer out(ss, compress_saves_);
00976 write_game(out);
00977 finish_save_game(out);
00978 }
00979 scoped_ostream os(open_save_game(filename_));
00980 (*os) << ss.str();
00981
00982 if (!os->good()) {
00983 throw game::save_game_failed(_("Could not write to file"));
00984 }
00985 }
00986
00987 void savegame::write_game(config_writer &out) const
00988 {
00989 log_scope("write_game");
00990
00991 out.write_key_val("version", game_config::version);
00992 out.write_key_val("next_underlying_unit_id", lexical_cast<std::string>(n_unit::id_manager::instance().get_save_id()));
00993 gamestate_.write_config(out, false);
00994 out.write_child("snapshot",snapshot_);
00995 out.open_child("statistics");
00996 statistics::write_stats(out);
00997 out.close_child("statistics");
00998 }
00999
01000 void savegame::finish_save_game(const config_writer &out)
01001 {
01002 try {
01003 if(!out.good()) {
01004 throw game::save_game_failed(_("Could not write to file"));
01005 }
01006 save_index_manager.remove(gamestate_.classification().label);
01007 } catch(io_exception& e) {
01008 throw game::save_game_failed(e.what());
01009 }
01010 }
01011
01012
01013 scoped_ostream savegame::open_save_game(const std::string &label)
01014 {
01015 std::string name = label;
01016 replace_space2underbar(name);
01017
01018 try {
01019 return scoped_ostream(ostream_file(get_saves_dir() + "/" + name));
01020 } catch(io_exception& e) {
01021 throw game::save_game_failed(e.what());
01022 }
01023 }
01024
01025 scenariostart_savegame::scenariostart_savegame(game_state &gamestate, const bool compress_saves)
01026 : savegame(gamestate, compress_saves)
01027 {
01028 set_filename(gamestate.classification().label);
01029 }
01030
01031 void scenariostart_savegame::before_save()
01032 {
01033
01034
01035
01036
01037 if (gamestate().starting_pos["id"].empty()) {
01038 foreach(const config &snapshot_side, gamestate().snapshot.child_range("side")) {
01039
01040 gamestate().starting_pos.add_child("side", snapshot_side);
01041 }
01042 }
01043 }
01044
01045 replay_savegame::replay_savegame(game_state &gamestate, const bool compress_saves)
01046 : savegame(gamestate, compress_saves, _("Save Replay"))
01047 {}
01048
01049 void replay_savegame::create_filename()
01050 {
01051 std::stringstream stream;
01052
01053 const std::string ellipsed_name = font::make_text_ellipsis(gamestate().classification().label,
01054 font::SIZE_NORMAL, 200);
01055 stream << ellipsed_name << " " << _("replay");
01056
01057 set_filename(stream.str());
01058 }
01059
01060 autosave_savegame::autosave_savegame(game_state &gamestate,
01061 game_display& gui, const config& snapshot_cfg, const bool compress_saves)
01062 : game_savegame(gamestate, gui, snapshot_cfg, compress_saves)
01063 {
01064 set_error_message(_("Could not auto save the game. Please save the game manually."));
01065 }
01066
01067 void autosave_savegame::autosave(const bool disable_autosave, const int autosave_max, const int infinite_autosaves)
01068 {
01069 if(disable_autosave)
01070 return;
01071
01072 save_game_automatic(gui_.video());
01073
01074 remove_old_auto_saves(autosave_max, infinite_autosaves);
01075 }
01076
01077 void autosave_savegame::create_filename()
01078 {
01079 std::string filename;
01080 if (gamestate().classification().label.empty())
01081 filename = _("Auto-Save");
01082 else
01083 filename = gamestate().classification().label + "-" + _("Auto-Save") + snapshot()["turn_at"];
01084
01085 set_filename(filename);
01086 }
01087
01088 oos_savegame::oos_savegame(const config& snapshot_cfg)
01089 : game_savegame(*resources::state_of_game, *resources::screen, snapshot_cfg, preferences::compress_saves())
01090 {}
01091
01092 int oos_savegame::show_save_dialog(CVideo& video, const std::string& message, const gui::DIALOG_TYPE )
01093 {
01094 static bool ignore_all = false;
01095 int res = 0;
01096
01097 std::string filename = this->filename();
01098
01099 if (!ignore_all){
01100 gui2::tgame_save_oos dlg(ignore_all, filename, title(), message);
01101 dlg.show(video);
01102 res = dlg.get_retval();
01103 }
01104
01105 check_filename(filename, video);
01106 set_filename(filename);
01107
01108 return res;
01109 }
01110
01111 game_savegame::game_savegame(game_state &gamestate,
01112 game_display& gui, const config& snapshot_cfg, const bool compress_saves)
01113 : savegame(gamestate, compress_saves, _("Save Game")),
01114 gui_(gui)
01115 {
01116 snapshot().merge_with(snapshot_cfg);
01117 }
01118
01119 void game_savegame::create_filename()
01120 {
01121 std::stringstream stream;
01122
01123 const std::string ellipsed_name = font::make_text_ellipsis(gamestate().classification().label,
01124 font::SIZE_NORMAL, 200);
01125 stream << ellipsed_name << " " << _("Turn") << " " << snapshot()["turn_at"];
01126 set_filename(stream.str());
01127 }
01128
01129 void game_savegame::before_save()
01130 {
01131 savegame::before_save();
01132 write_game_snapshot();
01133 }
01134
01135 void game_savegame::write_game_snapshot()
01136 {
01137 snapshot()["snapshot"] = true;
01138 snapshot()["playing_team"] = str_cast(gui_.playing_team());
01139
01140 write_events(snapshot());
01141
01142 write_music_play_list(snapshot());
01143
01144 gamestate().write_snapshot(snapshot());
01145
01146 gui_.labels().write(snapshot());
01147 }
01148
01149 }
01150