savegame.cpp

Go to the documentation of this file.
00001 /* $Id: savegame.cpp 54210 2012-05-18 20:11:11Z shadowmaster $ */
00002 /*
00003    Copyright (C) 2003 - 2012 by Jörg Hinrichs, refactored from various
00004    places formerly created by David White <dave@whitevine.net>
00005    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00006 
00007    This program is free software; you can redistribute it and/or modify
00008    it under the terms of the GNU General Public License as published by
00009    the Free Software Foundation; either version 2 of the License, or
00010    (at your option) any later version.
00011    This program is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY.
00013 
00014    See the COPYING file for more details.
00015 */
00016 
00017 #include <boost/iostreams/filter/gzip.hpp>
00018 
00019 #include "savegame.hpp"
00020 
00021 #include "dialogs.hpp" //FIXME: get rid of this as soon as the two remaining dialogs are moved to gui2
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 //#include "unit.hpp"
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      * conv_ansi_utf8()
00066      *   - Convert a string between ANSI encoding (for Windows filename) and UTF-8
00067      *  string &name
00068      *     - filename to be converted
00069      *  bool a2u
00070      *     - if true, convert the string from ANSI to UTF-8.
00071      *     - if false, reverse. (convert it from UTF-8 to ANSI)
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 /* ! _WIN32 */
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 /* _WIN32 */
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          * All saves are .gz files now so make sure we use that name when opening
00202          * a file. If not some parts of the code use the name with and some parts
00203          * without the .gz suffix.
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 /** Get a list of available saves. */
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             // save is from today
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             // save is from this week
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             // save is from current year
00363             format_string = _("%b %d");
00364         }
00365     } else {
00366         // save is from a different year
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         // Special funky case; for files created in the same second,
00385         // a replay file sorts less than a non-replay file.  Prevents
00386         // a timing-dependent bug where it may look like, at the end
00387         // of a scenario, the replay and the autosave for the next
00388         // scenario are displayed in the wrong order.
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     // Try reading the file both with and without underscores, if needed append .gz as well
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          * Test the modified name, since it might use a .gz
00420          * file even when not requested.
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     //FIXME: Integrate the load_game dialog into this class
00507     //something to watch for the curious, but not yet ready to go
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     //default_difficulty = 2;
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     // Even minor version numbers indicate stable releases which are
00645     // compatible with each other.
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     // Do not load if too old. If either the savegame or the current
00654     // game has the version 'test', load. This 'test' version is never
00655     // supposed to occur, except when Soliton is testing MP servers.
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     // Get the status of the random in the snapshot.
00686     // For a replay we need to restore the start only, the replaying gets at
00687     // proper location.
00688     // For normal loading also restore the call count.
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     // If we have a start of scenario MP campaign scenario the snapshot
00731     // is empty the starting position contains the wanted info.
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     // Probably not needed.
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     //Start-of-scenario save
00746     if(gamestate_.snapshot.empty()){
00747         //For a start-of-scenario-save, write the data to the starting_pos and not the snapshot, since
00748         //there should only be snapshots for midgame reloads
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     // Adds the replay data, and the replay start, to the level,
00763     // so clients can receive it.
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) //Quit game
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         // The magic moment that does save threading; after
00924         // each save, the filename of the save file becomes
00925         // the parent for the next. *Unless* the parent file
00926         // has the same name as the savefile, in which case we
00927         // use the grandparent name. When user loads a savegame,
00928         // we load its correct parent link along with it.
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             //do not bother retrying, since the user can just try to save the game again
00956             //maybe show a yes-no dialog for "disable autosaves now"?
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 // Throws game::save_game_failed
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     //Add the player section to the starting position so we can get the correct recall list
01034     //when loading the replay later on
01035     // if there is no scenario information in the starting pos, add the (persistent) sides from the snapshot
01036     // else do nothing, as persistence information was already added at the end of the previous scenario
01037     if (gamestate().starting_pos["id"].empty()) {
01038         foreach(const config &snapshot_side, gamestate().snapshot.child_range("side")) {
01039             //add all side tags (assuming they only contain carryover information)
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 /*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 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

Generated by doxygen 1.7.1 on Fri May 25 2012 01:03:08 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs