diff --git a/src/ai.cpp b/src/ai.cpp index 05925183ed6..69fc2268f10 100644 --- a/src/ai.cpp +++ b/src/ai.cpp @@ -305,6 +305,7 @@ bool ai_interface::recruit(const std::string& unit_name, location loc) " gold=" << data.gold << ((data.net_income < 0) ? "" : "+") << data.net_income << "\n"; + recorder.add_checksum_check(loc); return true; } else { const team_data data = calculate_team_data(current_team(),info_.team_num,info_.units); diff --git a/src/game.cpp b/src/game.cpp index eab8c9f7aca..4693814b5fa 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -203,6 +203,7 @@ game_controller::game_controller(int argc, char** argv) test_mode_ = true; } else if(val == "--debug" || val == "-d") { game_config::debug = true; + game_config::mp_debug = true; } else if (val.substr(0, 6) == "--log-") { } else if(val == "--nosound") { preferences::set_sound(false); diff --git a/src/game_config.cpp b/src/game_config.cpp index b384c6abc68..878b8318d22 100644 --- a/src/game_config.cpp +++ b/src/game_config.cpp @@ -30,7 +30,7 @@ namespace game_config int kill_experience = 8; int leadership_bonus = 25; const std::string version = VERSION; - bool debug = false, editor = false, ignore_replay_errors = false; + bool debug = false, editor = false, ignore_replay_errors = false, mp_debug = false; std::string game_icon = "wesnoth-icon.png", game_title, game_logo, title_music, anonymous_music, victory_music, defeat_music; diff --git a/src/game_config.hpp b/src/game_config.hpp index 984531b4c04..3ede0086982 100644 --- a/src/game_config.hpp +++ b/src/game_config.hpp @@ -33,7 +33,7 @@ namespace game_config extern int leadership_bonus; extern const std::string version; - extern bool debug, editor, ignore_replay_errors; + extern bool debug, editor, ignore_replay_errors, mp_debug; extern std::string path; diff --git a/src/menu_events.cpp b/src/menu_events.cpp index 59b52296b70..a7859308f7a 100644 --- a/src/menu_events.cpp +++ b/src/menu_events.cpp @@ -644,6 +644,7 @@ namespace events{ gui_->recalculate_minimap(); gui_->invalidate_game_status(); gui_->invalidate_all(); + recorder.add_checksum_check(loc); } else { recorder.undo(); gui::show_dialog(*gui_,NULL,"",msg,gui::OK_ONLY); @@ -753,6 +754,7 @@ namespace events{ recall_list.erase(recall_list.begin()+res); gui_->invalidate_game_status(); gui_->invalidate_all(); + recorder.add_checksum_check(loc); } } } @@ -895,6 +897,7 @@ namespace events{ gui_->invalidate(action.recall_loc); gui_->draw(); + recorder.add_checksum_check(action.recall_loc); } else { recorder.undo(); gui::show_dialog(*gui_,NULL,"",msg,gui::OK_ONLY); @@ -936,6 +939,7 @@ namespace events{ gui_->draw(); //gui_.invalidate_game_status(); //gui_.invalidate_all(); + recorder.add_checksum_check(loc); } else { recorder.undo(); gui::show_dialog(*gui_,NULL,"",msg,gui::OK_ONLY); diff --git a/src/replay.cpp b/src/replay.cpp index 0ec17b472c4..885496c7613 100644 --- a/src/replay.cpp +++ b/src/replay.cpp @@ -214,6 +214,21 @@ void replay::save_game(const std::string& label, const config& snapshot, saveInfo_.snapshot = config(); } +void replay::add_unit_checksum(const gamemap::location& loc,config* const cfg) +{ + if(! game_config::mp_debug) { + return; + } + wassert(unit_map_ref); + config& cc = cfg->add_child("checksum"); + loc.write(cc); + unit_map::const_iterator u = unit_map_ref->find(loc); + wassert(u != unit_map_ref->end()); + std::string chk_value; + u->second.write_checksum(chk_value); + cc["value"] = chk_value; +} + void replay::add_start() { config* const cmd = add_command(true); @@ -286,6 +301,8 @@ void replay::add_attack(const gamemap::location& a, const gamemap::location& b, char buf[100]; snprintf(buf,sizeof(buf),"%d",weapon); current_->child("attack")->values["weapon"] = buf; + add_unit_checksum(a,current_); + add_unit_checksum(b,current_); } void replay::add_pos(const std::string& type, @@ -366,6 +383,14 @@ void replay::add_event(const std::string& name) ev["raise"] = name; (*cmd)["undo"] = "no"; } +void replay::add_checksum_check(const gamemap::location& loc) +{ + if(! game_config::mp_debug) { + return; + } + config* const cmd = add_command(); + add_unit_checksum(loc,cmd); +} void replay::speak(const config& cfg) { config* const cmd = add_command(false); @@ -560,6 +585,32 @@ replay& get_replay_source() } } +namespace { +void check_checksums(display& disp,const unit_map& units,const config& cfg) +{ + if(! game_config::mp_debug) { + return; + } + for(config::child_list::const_iterator ci = cfg.get_children("checksum").begin(); ci != cfg.get_children("checksum").end(); ++ci) { + gamemap::location loc(**ci); + unit_map::const_iterator u = units.find(loc); + if(u == units.end()) { + std::stringstream message; + message << "non existant unit to checksum at " << loc.x+1 << "," << loc.y+1 << "!"; + disp.add_chat_message("verification",1,message.str(),display::MESSAGE_PRIVATE); + continue; + } + std::string check; + u->second.write_checksum(check); + if(check != (**ci)["value"]) { + std::stringstream message; + message << "checksum mismatch at " << loc.x+1 << "," << loc.y+1 << "!"; + disp.add_chat_message("verification",1,message.str(),display::MESSAGE_PRIVATE); + } + } +} +} + bool do_replay(display& disp, const gamemap& map, const game_data& gameinfo, unit_map& units, std::vector& teams, int team_num, const gamestatus& state, @@ -618,7 +669,8 @@ bool do_replay(display& disp, const gamemap& map, const game_data& gameinfo, continue; } } - + + //if there is nothing more in the records if(cfg == NULL) { //replayer.set_skip(false); @@ -728,6 +780,7 @@ bool do_replay(display& disp, const gamemap& map, const game_data& gameinfo, current_team.spend_gold(u_type->second.cost()); LOG_NW << "-> " << (current_team.gold()) << "\n"; fix_shroud = !replayer.is_skipping(); + check_checksums(disp,units,*cfg); } else if((child = cfg->child("recall")) != NULL) { @@ -755,6 +808,7 @@ bool do_replay(display& disp, const gamemap& map, const game_data& gameinfo, if (!game_config::ignore_replay_errors) throw replay::error(); } fix_shroud = !replayer.is_skipping(); + check_checksums(disp,units,*cfg); } else if((child = cfg->child("disband")) != NULL) { @@ -852,6 +906,7 @@ bool do_replay(display& disp, const gamemap& map, const game_data& gameinfo, up->first = dst; units.add(up); u = units.find(dst); + check_checksums(disp,units,*cfg); if(map.is_village(dst)) { const int orig_owner = village_owner(dst,teams) + 1; if(orig_owner != team_num) { @@ -883,6 +938,7 @@ bool do_replay(display& disp, const gamemap& map, const game_data& gameinfo, else if((child = cfg->child("attack")) != NULL) { const config* const destination = child->child("destination"); const config* const source = child->child("source"); + check_checksums(disp,units,*cfg); if(destination == NULL || source == NULL) { ERR_NW << "no destination/source found in attack\n"; @@ -938,8 +994,12 @@ bool do_replay(display& disp, const gamemap& map, const game_data& gameinfo, } game_events::fire((*child)["raise"]); } else { - ERR_NW << "unrecognized action\n"; - if (!game_config::ignore_replay_errors) throw replay::error(); + if(! cfg->child("checksum")) { + ERR_NW << "unrecognized action\n"; + if (!game_config::ignore_replay_errors) throw replay::error(); + } else { + check_checksums(disp,units,*cfg); + } } //Check if we should refresh the shroud, and redraw the minimap/map tiles. diff --git a/src/replay.hpp b/src/replay.hpp index 0c301dc639e..1ae8fa7c036 100644 --- a/src/replay.hpp +++ b/src/replay.hpp @@ -58,6 +58,8 @@ public: void add_rename(const std::string& name, const gamemap::location& loc); void end_turn(); void add_event(const std::string& name); + void add_unit_checksum(const gamemap::location& loc,config* const cfg); + void add_checksum_check(const gamemap::location& loc); void speak(const config& cfg); std::string build_chat_log(const std::string& team) const; diff --git a/src/unit.cpp b/src/unit.cpp index 92808e7d36d..ddb7c1c019e 100644 --- a/src/unit.cpp +++ b/src/unit.cpp @@ -203,7 +203,13 @@ void unit::set_game_context(const game_data* gamedata, unit_map* unitmap, const gamestatus_ = game_status; teams_ = teams; } - +void unit::write_checksum(std::string& str) const +{ + config unit_config; + write(unit_config); + unit_config["controller"] = ""; + str = unit_config.hash(); +} std::string unit::generate_description() const { @@ -932,7 +938,6 @@ void unit::read(const config& cfg) bool type_set = false; id_ = ""; - traits_description_ = cfg["traits_description"]; if(cfg["type"] != "" && cfg["type"] != cfg["id"]) { wassert(gamedata_ != NULL); std::map::const_iterator i = gamedata_->unit_types.find(cfg["type"]); @@ -1159,8 +1164,6 @@ void unit::write(config& cfg) const cfg["user_description"] = custom_unit_description_; cfg["description"] = underlying_description_; - cfg["traits_description"] = traits_description_; - if(can_recruit()) cfg["canrecruit"] = "1"; diff --git a/src/unit.hpp b/src/unit.hpp index 5aeeefcd043..f642733547e 100644 --- a/src/unit.hpp +++ b/src/unit.hpp @@ -59,6 +59,7 @@ class unit virtual ~unit(); void set_game_context(const game_data* gamedata, unit_map* unitmap, const gamemap* map, const gamestatus* game_status, const std::vector* teams); + void write_checksum(std::string& str) const; // Advances this unit to another type void advance_to(const unit_type* t); diff --git a/src/unit_types.cpp b/src/unit_types.cpp index 43740412b62..75016c73ac2 100644 --- a/src/unit_types.cpp +++ b/src/unit_types.cpp @@ -954,11 +954,11 @@ const std::string& unit_type::image_profile() const return val; } -const std::string& unit_type::unit_description() const +const t_string& unit_type::unit_description() const { - static const std::string default_val("No description available"); + static const t_string default_val("No description available"); - const std::string& desc = cfg_["unit_description"]; + const t_string& desc = cfg_["unit_description"]; if(desc.empty()) return default_val; else diff --git a/src/unit_types.hpp b/src/unit_types.hpp index bcfea1ce4d5..386b8484b4f 100644 --- a/src/unit_types.hpp +++ b/src/unit_types.hpp @@ -188,7 +188,7 @@ public: const std::string& image() const; const std::string& image_profile() const; - const std::string& unit_description() const; + const t_string& unit_description() const; const std::vector& flag_rgb() const;