replay.cpp

Go to the documentation of this file.
00001 /* $Id: replay.cpp 26349 2008-05-03 06:30:16Z sapient $ */
00002 /*
00003    Copyright (C) 2003 - 2008 by David White <dave@whitevine.net>
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License version 2
00008    or at your option any later version.
00009    This program is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY.
00011 
00012    See the COPYING file for more details.
00013 */
00014 
00015 //! @file replay.cpp
00016 //! Replay control code.
00017 //!
00018 //! See http://www.wesnoth.org/wiki/ReplayWML for more info.
00019 
00020 #include "global.hpp"
00021 
00022 #include "actions.hpp"
00023 #include "ai_interface.hpp"
00024 #include "dialogs.hpp"
00025 #include "game_display.hpp"
00026 #include "game_preferences.hpp"
00027 #include "filesystem.hpp"
00028 #include "game_config.hpp"
00029 #include "game_events.hpp"
00030 #include "log.hpp"
00031 #include "map_label.hpp"
00032 #include "menu_events.hpp"
00033 #include "pathfind.hpp"
00034 #include "replay.hpp"
00035 #include "show_dialog.hpp"
00036 #include "sound.hpp"
00037 #include "statistics.hpp"
00038 #include "unit_display.hpp"
00039 #include "util.hpp"
00040 #include "wesconfig.h"
00041 #include "serialization/binary_or_text.hpp"
00042 
00043 #include <cassert>
00044 #include <cstdio>
00045 #include <cstdlib>
00046 #include <deque>
00047 #include <iostream>
00048 #include <set>
00049 #include <sstream>
00050 
00051 #define DBG_REPLAY LOG_STREAM(debug, replay)
00052 #define LOG_REPLAY LOG_STREAM(info, replay)
00053 #define WRN_REPLAY LOG_STREAM(warn, replay)
00054 #define ERR_REPLAY LOG_STREAM(err, replay)
00055 
00056 std::string replay::last_replay_error;
00057 
00058 //functions to verify that the unit structure on both machines is identical
00059 
00060 static void verify(const unit_map& units, const config& cfg) {
00061     std::stringstream errbuf;
00062     LOG_REPLAY << "verifying unit structure...\n";
00063 
00064     const size_t nunits = lexical_cast_default<size_t>(cfg["num_units"]);
00065     if(nunits != units.size()) {
00066         errbuf << "SYNC VERIFICATION FAILED: number of units from data source differ: "
00067                << nunits << " according to data source. " << units.size() << " locally\n";
00068 
00069         std::set<gamemap::location> locs;
00070         const config::child_list& items = cfg.get_children("unit");
00071         for(config::child_list::const_iterator i = items.begin(); i != items.end(); ++i) {
00072             const gamemap::location loc(**i, game_events::get_state_of_game());
00073             locs.insert(loc);
00074 
00075             if(units.count(loc) == 0) {
00076                 errbuf << "data source says there is a unit at "
00077                        << loc << " but none found locally\n";
00078             }
00079         }
00080 
00081         for(unit_map::const_iterator j = units.begin(); j != units.end(); ++j) {
00082             if(locs.count(j->first) == 0) {
00083                 errbuf << "local unit at " << j->first
00084                        << " but none in data source\n";
00085             }
00086         }
00087         replay::throw_error(errbuf.str());
00088         errbuf.clear();
00089     }
00090 
00091     const config::child_list& items = cfg.get_children("unit");
00092     for(config::child_list::const_iterator i = items.begin(); i != items.end(); ++i) {
00093         const gamemap::location loc(**i, game_events::get_state_of_game());
00094         const unit_map::const_iterator u = units.find(loc);
00095         if(u == units.end()) {
00096             errbuf << "SYNC VERIFICATION FAILED: data source says there is a '"
00097                    << (**i)["type"] << "' (side " << (**i)["side"] << ") at "
00098                    << loc << " but there is no local record of it\n";
00099             replay::throw_error(errbuf.str());
00100             errbuf.clear();
00101         }
00102 
00103         config cfg;
00104         u->second.write(cfg);
00105 
00106         bool is_ok = true;
00107         static const std::string fields[] = {"type","hitpoints","experience","side",""};
00108         for(const std::string* str = fields; str->empty() == false; ++str) {
00109             if(cfg[*str] != (**i)[*str]) {
00110                 errbuf << "ERROR IN FIELD '" << *str << "' for unit at "
00111                        << loc << " data source: '" << (**i)[*str]
00112                        << "' local: '" << cfg[*str] << "'\n";
00113                 is_ok = false;
00114             }
00115         }
00116 
00117         if(!is_ok) {
00118             errbuf << "(SYNC VERIFICATION FAILED)\n";
00119             replay::throw_error(errbuf.str());
00120             errbuf.clear();
00121         }
00122     }
00123 
00124     LOG_REPLAY << "verification passed\n";
00125 }
00126 
00127 namespace {
00128     const unit_map* unit_map_ref = NULL;
00129 }
00130 
00131 static void verify_units(const config& cfg)
00132     {
00133         if(unit_map_ref != NULL) {
00134             verify(*unit_map_ref,cfg);
00135         }
00136     }
00137 
00138 verification_manager::verification_manager(const unit_map& units)
00139 {
00140     unit_map_ref = &units;
00141 }
00142 
00143 verification_manager::~verification_manager()
00144 {
00145     unit_map_ref = NULL;
00146 }
00147 
00148 // FIXME: this one now has to be assigned with set_random_generator
00149 // from play_level or similar.  We should surely hunt direct
00150 // references to it from this very file and move it out of here.
00151 replay recorder;
00152 
00153 replay::replay() : pos_(0), current_(NULL), skip_(0)
00154 {}
00155 
00156 replay::replay(const config& cfg) : cfg_(cfg), pos_(0), current_(NULL), skip_(0)
00157 {}
00158 
00159 void replay::throw_error(const std::string& msg)
00160 {
00161     ERR_REPLAY << msg;
00162     last_replay_error = msg;
00163     if (!game_config::ignore_replay_errors) throw replay::error(msg);
00164 }
00165 
00166 void replay::set_save_info(const game_state& save)
00167 {
00168     saveInfo_ = save;
00169 }
00170 
00171 
00172 void replay::set_save_info_completion(const std::string &st)
00173 // This function is a kluge to get around the fact that replay objects carry
00174 // around a copy of gamestate rather than a reference to the global gamestate.
00175 // That is probably a design bug that should be fixed.
00176 {
00177     saveInfo_.completion = st;
00178 }
00179 
00180 void replay::set_skip(bool skip)
00181 {
00182     skip_ = skip;
00183 }
00184 
00185 bool replay::is_skipping() const
00186 {
00187     return skip_;
00188 }
00189 
00190 void replay::save_game(const std::string& label, const config& snapshot,
00191                        const config& starting_pos, bool include_replay)
00192 {
00193     log_scope("replay::save_game");
00194     saveInfo_.snapshot = snapshot;
00195     saveInfo_.starting_pos = starting_pos;
00196 
00197     if(include_replay) {
00198         saveInfo_.replay_data = cfg_;
00199     } else {
00200         saveInfo_.replay_data = config();
00201     }
00202 
00203     saveInfo_.label = label;
00204 
00205     std::string filename = label;
00206     if(preferences::compress_saves()) {
00207         filename += ".gz";
00208     }
00209 
00210     scoped_ostream os(open_save_game(filename));
00211     config_writer out(*os, preferences::compress_saves(), PACKAGE);
00212 	::write_game(out, saveInfo_);
00213     finish_save_game(out, saveInfo_, saveInfo_.label);
00214 
00215     saveInfo_.replay_data = config();
00216     saveInfo_.snapshot = config();
00217 }
00218 
00219 void replay::add_unit_checksum(const gamemap::location& loc,config* const cfg)
00220 {
00221     if(! game_config::mp_debug) {
00222         return;
00223     }
00224     assert(unit_map_ref);
00225     config& cc = cfg->add_child("checksum");
00226     loc.write(cc);
00227     unit_map::const_iterator u = unit_map_ref->find(loc);
00228     assert(u != unit_map_ref->end());
00229     cc["value"] = get_checksum(u->second);
00230 }
00231 
00232 void replay::add_start()
00233 {
00234     config* const cmd = add_command(true);
00235     cmd->add_child("start");
00236 }
00237 
00238 void replay::add_recruit(int value, const gamemap::location& loc)
00239 {
00240     config* const cmd = add_command();
00241 
00242     config val;
00243 
00244     char buf[100];
00245     snprintf(buf,sizeof(buf),"%d",value);
00246     val["value"] = buf;
00247 
00248     loc.write(val);
00249 
00250     cmd->add_child("recruit",val);
00251 }
00252 
00253 void replay::add_recall(int value, const gamemap::location& loc)
00254 {
00255     config* const cmd = add_command();
00256 
00257     config val;
00258 
00259     char buf[100];
00260     snprintf(buf,sizeof(buf),"%d",value);
00261     val["value"] = buf;
00262 
00263     loc.write(val);
00264 
00265     cmd->add_child("recall",val);
00266 }
00267 
00268 void replay::add_disband(int value)
00269 {
00270     config* const cmd = add_command();
00271 
00272     config val;
00273 
00274     char buf[100];
00275     snprintf(buf,sizeof(buf),"%d",value);
00276     val["value"] = buf;
00277 
00278     cmd->add_child("disband",val);
00279 }
00280 
00281 void replay::add_countdown_update(int value, int team)
00282 {
00283     config* const cmd = add_command();
00284     config val;
00285 
00286     val["value"] = lexical_cast_default<std::string>(value);
00287     val["team"] = lexical_cast_default<std::string>(team);
00288 
00289     cmd->add_child("countdown_update",val);
00290 }
00291 
00292 
00293 void replay::add_movement(const gamemap::location& a,const gamemap::location& b)
00294 {
00295     add_pos("move",a,b);
00296 }
00297 
00298 void replay::add_attack(const gamemap::location& a, const gamemap::location& b, int att_weapon, int def_weapon)
00299 {
00300     add_pos("attack",a,b);
00301     char buf[100];
00302     snprintf(buf,sizeof(buf),"%d",att_weapon);
00303     current_->child("attack")->values["weapon"] = buf;
00304     snprintf(buf,sizeof(buf),"%d",def_weapon);
00305     current_->child("attack")->values["defender_weapon"] = buf;
00306     add_unit_checksum(a,current_);
00307     add_unit_checksum(b,current_);
00308 }
00309 
00310 void replay::add_pos(const std::string& type,
00311                      const gamemap::location& a, const gamemap::location& b)
00312 {
00313     config* const cmd = add_command();
00314 
00315     config move, src, dst;
00316     a.write(src);
00317     b.write(dst);
00318 
00319     move.add_child("source",src);
00320     move.add_child("destination",dst);
00321     cmd->add_child(type,move);
00322 }
00323 
00324 void replay::add_value(const std::string& type, int value)
00325 {
00326     config* const cmd = add_command();
00327 
00328     config val;
00329 
00330     char buf[100];
00331     snprintf(buf,sizeof(buf),"%d",value);
00332     val["value"] = buf;
00333 
00334     cmd->add_child(type,val);
00335 }
00336 
00337 void replay::choose_option(int index)
00338 {
00339     add_value("choose",index);
00340 }
00341 
00342 void replay::text_input(std::string input)
00343 {
00344     config* const cmd = add_command();
00345 
00346     config val;
00347     val["text"] = input;
00348 
00349     cmd->add_child("input",val);
00350 }
00351 
00352 void replay::set_random_value(const std::string& choice)
00353 {
00354     config* const cmd = add_command();
00355     config val;
00356     val["value"] = choice;
00357     cmd->add_child("random_number",val);
00358 }
00359 
00360 void replay::add_label(const terrain_label* label)
00361 {
00362     assert(label);
00363     config* const cmd = add_command(false);
00364 
00365     (*cmd)["undo"] = "no";
00366 
00367     config val;
00368 
00369     label->write(val);
00370 
00371     cmd->add_child("label",val);
00372 }
00373 
00374 void replay::clear_labels(const std::string& team_name)
00375 {
00376     config* const cmd = add_command(false);
00377 
00378     (*cmd)["undo"] = "no";
00379     config val;
00380     val["team_name"] = team_name;
00381     cmd->add_child("clear_labels",val);
00382 }
00383 
00384 void replay::add_rename(const std::string& name, const gamemap::location& loc)
00385 {
00386     config* const cmd = add_command(false);
00387     (*cmd)["async"] = "yes"; // Not undoable, but depends on moves/recruits that are
00388     config val;
00389     loc.write(val);
00390     val["name"] = name;
00391     cmd->add_child("rename", val);
00392 }
00393 
00394 void replay::end_turn()
00395 {
00396     config* const cmd = add_command();
00397     cmd->add_child("end_turn");
00398 }
00399 
00400 void replay::add_event(const std::string& name, const gamemap::location& loc)
00401 {
00402     config* const cmd = add_command();
00403     config& ev = cmd->add_child("fire_event");
00404     ev["raise"] = name;
00405     if(loc.valid()) {
00406         config& source = ev.add_child("source");
00407         loc.write(source);
00408     }
00409     (*cmd)["undo"] = "no";
00410 }
00411 
00412 void replay::add_checksum_check(const gamemap::location& loc)
00413 {
00414     if(! game_config::mp_debug) {
00415         return;
00416     }
00417     config* const cmd = add_command();
00418     add_unit_checksum(loc,cmd);
00419 }
00420 
00421 void replay::add_advancement(const gamemap::location& loc)
00422 {
00423     config* const cmd = add_command(false);
00424 
00425     config val;
00426     (*cmd)["undo"] = "no";
00427     loc.write(val);
00428     cmd->add_child("advance_unit",val);
00429 }
00430 
00431 void replay::add_chat_message_location()
00432 {
00433     message_locations.push_back(pos_-1);
00434 }
00435 
00436 void replay::speak(const config& cfg)
00437 {
00438     config* const cmd = add_command(false);
00439     if(cmd != NULL) {
00440         cmd->add_child("speak",cfg);
00441         (*cmd)["undo"] = "no";
00442         add_chat_message_location();
00443     }
00444 }
00445 
00446 void replay::add_chat_log_entry(const config* speak, std::stringstream& str, const std::string& team) const
00447 {
00448     if (!speak)
00449     {
00450         return;
00451     }
00452     const config& cfg = *speak;
00453     const std::string& team_name = cfg["team_name"];
00454     if(team_name == "" || team_name == team) {
00455         if(team_name == "") {
00456             str << "<" << cfg["id"] << "> ";
00457         } else {
00458             str << "*" << cfg["id"] << "* ";
00459         }
00460         str << cfg["message"] << "\n";
00461     }
00462 
00463 }
00464 
00465 void replay::remove_command(int index)
00466 {
00467     cfg_.remove_child("command", index);
00468     std::vector<int>::reverse_iterator loc_it;
00469     for (loc_it = message_locations.rbegin(); loc_it != message_locations.rend() && index < *loc_it;++loc_it)
00470     {
00471         --(*loc_it);
00472     }
00473 }
00474 
00475 // cached message log
00476 std::stringstream message_log;
00477 
00478 
00479 std::string replay::build_chat_log(const std::string& team)
00480 {
00481     const config::child_list& cmd = commands();
00482     std::vector<int>::iterator loc_it;
00483     int last_location = 0;
00484     for (loc_it = message_locations.begin(); loc_it != message_locations.end(); ++loc_it)
00485     {
00486         last_location = *loc_it;
00487         const config* speak = cmd[last_location]->child("speak");
00488         add_chat_log_entry(speak,message_log,team);
00489 
00490     }
00491     message_locations.clear();
00492 
00493 #if 0
00494     for(config::child_list::const_iterator i = cmd.begin() + (last_location + 1); i != cmd.end(); ++i) {
00495         ++last_location;
00496         const config* speak = (**i).child("speak");
00497         if(speak != NULL) {
00498             message_locations.push_back(last_location);
00499             add_chat_log_entry(speak,str,team);
00500         }
00501     }
00502 #endif
00503     return message_log.str();
00504 }
00505 
00506 config replay::get_data_range(int cmd_start, int cmd_end, DATA_TYPE data_type)
00507 {
00508     config res;
00509 
00510     const config::child_list& cmd = commands();
00511     while(cmd_start < cmd_end) {
00512         if ((data_type == ALL_DATA || (*cmd[cmd_start])["undo"] == "no")
00513             && (*cmd[cmd_start])["sent"] != "yes")
00514         {
00515             res.add_child("command",*cmd[cmd_start]);
00516 
00517             if(data_type == NON_UNDO_DATA) {
00518                 (*cmd[cmd_start])["sent"] = "yes";
00519             }
00520         }
00521 
00522         ++cmd_start;
00523     }
00524 
00525     return res;
00526 }
00527 
00528 void replay::undo()
00529 {
00530     config::child_itors cmd = cfg_.child_range("command");
00531     std::vector<config::child_iterator> async_cmds;
00532     // Remember commands not yet synced and skip over them.
00533     // We assume that all already sent (sent=yes) data isn't undoable
00534     // even if not marked explicitely with undo=no.
00535     //! @todo Change undo= to default to "no" and explicitely mark all
00536     //! undoable commands with yes.
00537     while(cmd.first != cmd.second && ((**(cmd.second-1))["undo"] == "no"
00538         || (**(cmd.second-1))["async"] == "yes"
00539         || (**(cmd.second-1))["sent"] == "yes"))
00540     {
00541         if ((**(cmd.second-1))["async"] == "yes")
00542             async_cmds.push_back(cmd.second-1);
00543         --cmd.second;
00544     }
00545 
00546     if(cmd.first != cmd.second) {
00547         config* child;
00548         config& cmd_second = (**(cmd.second-1));
00549         if ((child = cmd_second.child("move")) != NULL)
00550         {
00551             // A unit's move is being undone.
00552             // Repair unsynced cmds whose locations depend on that unit's location.
00553             gamemap::location dst(*(child->child("destination")),
00554                 game_events::get_state_of_game());
00555             gamemap::location src(*(child->child("source")),
00556                 game_events::get_state_of_game());
00557             for (std::vector<config::child_iterator>::iterator async_cmd =
00558                  async_cmds.begin(); async_cmd != async_cmds.end(); async_cmd++)
00559             {
00560                 config* async_child;
00561                 if ((async_child = (***async_cmd).child("rename")) != NULL)
00562                 {
00563                     gamemap::location aloc(*async_child, game_events::get_state_of_game());
00564                     if (dst == aloc)
00565                     {
00566                         src.write(*async_child);
00567                     }
00568                 }
00569             }
00570         }
00571         else if ((child = cmd_second.child("recruit")) != NULL
00572             || (child = cmd_second.child("recall")) != NULL)
00573         {
00574             // A unit is being un-recruited or un-recalled.
00575             // Remove unsynced commands that would act on that unit.
00576             gamemap::location src(*child, game_events::get_state_of_game());
00577             for (std::vector<config::child_iterator>::iterator async_cmd =
00578                  async_cmds.begin(); async_cmd != async_cmds.end(); async_cmd++)
00579             {
00580                 config* async_child;
00581                 if ((async_child = (***async_cmd).child("rename")) != NULL)
00582                 {
00583                     gamemap::location aloc(*async_child, game_events::get_state_of_game());
00584                     if (src == aloc)
00585                     {
00586                         remove_command(*async_cmd - cmd.first);
00587                     }
00588                 }
00589             }
00590         }
00591     }
00592 
00593     remove_command(cmd.second - cmd.first - 1);
00594     current_ = NULL;
00595     set_random(NULL);
00596 }
00597 
00598 const config::child_list& replay::commands() const
00599 {
00600     return cfg_.get_children("command");
00601 }
00602 
00603 int replay::ncommands()
00604 {
00605     return commands().size();
00606 }
00607 
00608 config* replay::add_command(bool update_random_context)
00609 {
00610     pos_ = ncommands()+1;
00611     current_ = &cfg_.add_child("command");
00612     if(update_random_context)
00613         set_random(current_);
00614 
00615     return current_;
00616 }
00617 
00618 void replay::start_replay()
00619 {
00620     pos_ = 0;
00621 }
00622 
00623 void replay::revert_action()
00624 {
00625     if (pos_ > 0)
00626         --pos_;
00627 }
00628 
00629 config* replay::get_next_action()
00630 {
00631     if(pos_ >= commands().size())
00632         return NULL;
00633 
00634     LOG_REPLAY << "up to replay action " << pos_ + 1 << "/" << commands().size() << "\n";
00635 
00636     current_ = commands()[pos_];
00637     set_random(current_);
00638     ++pos_;
00639     return current_;
00640 }
00641 
00642 void replay::pre_replay()
00643 {
00644     if ((rng::random() == NULL) && (commands().size() > 0)){
00645         if (at_end())
00646         {
00647             add_command(true);
00648         }
00649         else
00650         {
00651             set_random(commands()[pos_]);
00652         }
00653     }
00654 }
00655 
00656 bool replay::at_end() const
00657 {
00658     return pos_ >= commands().size();
00659 }
00660 
00661 void replay::set_to_end()
00662 {
00663     pos_ = commands().size();
00664     current_ = NULL;
00665     set_random(NULL);
00666 }
00667 
00668 void replay::clear()
00669 {
00670     message_locations.clear();
00671     message_log.str(std::string());
00672     cfg_ = config();
00673     pos_ = 0;
00674     current_ = NULL;
00675     set_random(NULL);
00676     skip_ = 0;
00677 }
00678 
00679 bool replay::empty()
00680 {
00681     return commands().empty();
00682 }
00683 
00684 void replay::add_config(const config& cfg, MARK_SENT mark)
00685 {
00686     for(config::const_child_itors i = cfg.child_range("command"); i.first != i.second; ++i.first) {
00687         config& cfg = cfg_.add_child("command",**i.first);
00688         if (cfg.child("speak"))
00689         {
00690             pos_ = ncommands();
00691             add_chat_message_location();
00692         }
00693         if(mark == MARK_AS_SENT) {
00694             cfg["sent"] = "yes";
00695         }
00696     }
00697 }
00698 
00699 namespace {
00700 
00701 replay* replay_src = NULL;
00702 
00703 struct replay_source_manager
00704 {
00705     replay_source_manager(replay* o) : old_(replay_src)
00706     {
00707         replay_src = o;
00708     }
00709 
00710     ~replay_source_manager()
00711     {
00712         replay_src = old_;
00713     }
00714 
00715 private:
00716     replay* const old_;
00717 };
00718 
00719 }
00720 
00721 replay& get_replay_source()
00722 {
00723     if(replay_src != NULL) {
00724         return *replay_src;
00725     } else {
00726         return recorder;
00727     }
00728 }
00729 
00730 static void check_checksums(game_display& disp,const unit_map& units,const config& cfg)
00731 {
00732     if(! game_config::mp_debug) {
00733         return;
00734     }
00735     for(config::child_list::const_iterator ci = cfg.get_children("checksum").begin(); ci != cfg.get_children("checksum").end(); ++ci) {
00736         gamemap::location loc(**ci, game_events::get_state_of_game());
00737         unit_map::const_iterator u = units.find(loc);
00738         if(u == units.end()) {
00739             std::stringstream message;
00740             message << "non existant unit to checksum at " << loc.x+1 << "," << loc.y+1 << "!";
00741             disp.add_chat_message(time(NULL), "verification", 1, message.str(),
00742                     game_display::MESSAGE_PRIVATE, false);
00743             continue;
00744         }
00745         if(get_checksum(u->second) != (**ci)["value"]) {
00746             std::stringstream message;
00747             message << "checksum mismatch at " << loc.x+1 << "," << loc.y+1 << "!";
00748             disp.add_chat_message(time(NULL), "verification", 1, message.str(),
00749                     game_display::MESSAGE_PRIVATE, false);
00750         }
00751     }
00752 }
00753 
00754 bool do_replay(game_display& disp, const gamemap& map,
00755     unit_map& units, std::vector<team>& teams, int team_num,
00756     const gamestatus& state, game_state& state_of_game, replay* obj)
00757 {
00758     log_scope("do replay");
00759 
00760     const replay_source_manager replaymanager(obj);
00761 
00762 //  replay& replayer = (obj != NULL) ? *obj : recorder;
00763 
00764     if (!get_replay_source().is_skipping()){
00765         disp.recalculate_minimap();
00766     }
00767 
00768     const set_random_generator generator_setter(&get_replay_source());
00769 
00770     update_locker lock_update(disp.video(),get_replay_source().is_skipping());
00771     return do_replay_handle(disp, map, units, teams, team_num, state, state_of_game,
00772                            std::string(""));
00773 }
00774 
00775 bool do_replay_handle(game_display& disp, const gamemap& map,
00776                       unit_map& units, std::vector<team>& teams, int team_num,
00777                       const gamestatus& state, game_state& state_of_game,
00778                       const std::string& do_untill)
00779 {
00780     //a list of units that have promoted from the last attack
00781     std::deque<gamemap::location> advancing_units;
00782 
00783     end_level_exception* delayed_exception = 0;
00784 
00785     team& current_team = teams[team_num-1];
00786 
00787     for(;;) {
00788         config* const cfg = get_replay_source().get_next_action();
00789         config* child;
00790 
00791 
00792         //do we need to recalculate shroud after this action is processed?
00793 
00794         bool fix_shroud = false;
00795         if (cfg)
00796         {
00797             DBG_REPLAY << "Replay data:\n" << *cfg << "\n";
00798         }
00799         else
00800         {
00801             DBG_REPLAY << "Repaly data at end\n";
00802         }
00803 
00804 
00805         //if we are expecting promotions here
00806         if(advancing_units.empty() == false) {
00807             if(cfg == NULL) {
00808                 replay::throw_error("promotion expected, but none found\n");
00809             }
00810 
00811             //if there is a promotion, we process it and go onto the next command
00812             //but if this isn't a promotion, we just keep waiting for the promotion
00813             //command -- it may have been mixed up with other commands such as messages
00814             if((child = cfg->child("choose")) != NULL) {
00815 
00816                 const int val = lexical_cast_default<int>((*child)["value"]);
00817 
00818                 dialogs::animate_unit_advancement(units,advancing_units.front(),disp,val);
00819 
00820                 advancing_units.pop_front();
00821 
00822                 //if there are no more advancing units, then we check for victory,
00823                 //in case the battle that led to advancement caused the end of scenario
00824                 if(advancing_units.empty()) {
00825                     check_victory(units, teams, disp);
00826                 }
00827 
00828                 continue;
00829             }
00830         }
00831 
00832 
00833         //if there is nothing more in the records
00834         if(cfg == NULL) {
00835             //replayer.set_skip(false);
00836             THROW_END_LEVEL_DELETE(delayed_exception);
00837             return false;
00838         }
00839 
00840         // We return if caller wants it for this tag
00841         if (!do_untill.empty()
00842             && cfg->child(do_untill) != NULL)
00843         {
00844             get_replay_source().revert_action();
00845             THROW_END_LEVEL_DELETE(delayed_exception);
00846             return false;
00847         }
00848 
00849         //if there is an empty command tag, create by pre_replay() or a start tag
00850         else if ( (cfg->all_children().size() == 0) || (cfg->child("start") != NULL) ){
00851             //do nothing
00852 
00853         } else if((child = cfg->child("speak")) != NULL) {
00854             const std::string& team_name = (*child)["team_name"];
00855             if (team_name == "" || (!is_observer()
00856                     && teams[disp.viewing_team()].team_name() == team_name)
00857                     || (is_observer() && team_name == "observer"))
00858             {
00859                 const std::string& speaker_name = (*child)["id"];
00860                 const std::string& message = (*child)["message"];
00861                 //if (!preferences::show_lobby_join(speaker_name, message)) return;
00862                 bool is_whisper = (speaker_name.find("whisper: ") == 0);
00863                 get_replay_source().add_chat_message_location();
00864                 if (!get_replay_source().is_skipping() || is_whisper) {
00865                     const int side = lexical_cast_default<int>((*child)["side"],0);
00866                     disp.add_chat_message(time(NULL), speaker_name, side, message,
00867                             (team_name == "" ? game_display::MESSAGE_PUBLIC
00868                             : game_display::MESSAGE_PRIVATE),
00869                             preferences::message_bell());
00870                 }
00871             }
00872         } else if((child = cfg->child("label")) != NULL) {
00873 
00874             terrain_label label(disp.labels(),*child, game_events::get_state_of_game());
00875 
00876             disp.labels().set_label(label.location(),
00877                         label.text(),
00878                         label.team_name(),
00879                         label.colour());
00880 
00881         } else if((child = cfg->child("clear_labels")) != NULL) {
00882 
00883             disp.labels().clear(std::string((*child)["team_name"]));
00884         }
00885 
00886         else if((child = cfg->child("rename")) != NULL) {
00887             const gamemap::location loc(*child, game_events::get_state_of_game());
00888             const std::string& name = (*child)["name"];
00889 
00890             unit_map::iterator u = units.find(loc);
00891             if(u != units.end()) {
00892                 if(u->second.unrenamable()) {
00893                     std::stringstream errbuf;
00894                     errbuf << "renaming unrenamable unit " << u->second.id() << "\n";
00895                     replay::throw_error(errbuf.str());
00896                 }
00897                 u->second.rename(name);
00898             } else {
00899                 // Users can rename units while it's being killed at another machine.
00900                 // This since the player can rename units when it's not his/her turn.
00901                 // There's not a simple way to prevent that so in that case ignore the
00902                 // rename instead of throwing an OOS.
00903                 WRN_REPLAY << "attempt to rename unit at location: "
00904                    << loc << ", where none exists (anymore).\n";
00905             }
00906         }
00907 
00908         //if there is an end turn directive
00909         else if(cfg->child("end_turn") != NULL) {
00910             child = cfg->child("verify");
00911             if(child != NULL) {
00912                 verify_units(*child);
00913             }
00914 
00915             THROW_END_LEVEL_DELETE(delayed_exception);
00916             return true;
00917         }
00918 
00919         else if((child = cfg->child("recruit")) != NULL) {
00920             const std::string& recruit_num = (*child)["value"];
00921             const int val = lexical_cast_default<int>(recruit_num);
00922 
00923             gamemap::location loc(*child, game_events::get_state_of_game());
00924 
00925             const std::set<std::string>& recruits = current_team.recruits();
00926 
00927             if(val < 0 || static_cast<size_t>(val) >= recruits.size()) {
00928                 std::stringstream errbuf;
00929                 errbuf << "recruitment index is illegal: " << val
00930                        << " while this side only has " << recruits.size()
00931                        << " units available for recruitment\n";
00932                 replay::throw_error(errbuf.str());
00933             }
00934 
00935             std::set<std::string>::const_iterator itor = recruits.begin();
00936             std::advance(itor,val);
00937             const std::map<std::string,unit_type>::const_iterator u_type = unit_type_data::types().find(*itor);
00938             if(u_type == unit_type_data::types().end()) {
00939                 std::stringstream errbuf;
00940                 errbuf << "recruiting illegal unit: '" << *itor << "'\n";
00941                 replay::throw_error(errbuf.str());
00942             }
00943 
00944             unit new_unit(&units,&map,&state,&teams,&(u_type->second),team_num,true, false);
00945             const std::string& res = recruit_unit(map,team_num,units,new_unit,loc,false);
00946             if(!res.empty()) {
00947                 std::stringstream errbuf;
00948                 errbuf << "cannot recruit unit: " << res << "\n";
00949                 replay::throw_error(errbuf.str());
00950             }
00951 
00952             if(u_type->second.cost() > current_team.gold()) {
00953                 std::stringstream errbuf;
00954                 errbuf << "unit '" << u_type->second.id() << "' is too expensive to recruit: "
00955                        << u_type->second.cost() << "/" << current_team.gold() << "\n";
00956                 replay::throw_error(errbuf.str());
00957             }
00958             LOG_REPLAY << "recruit: team=" << team_num << " '" << u_type->second.id() << "' at (" << loc
00959                    << ") cost=" << u_type->second.cost() << " from gold=" << current_team.gold() << ' ';
00960 
00961 
00962             statistics::recruit_unit(new_unit);
00963 
00964             current_team.spend_gold(u_type->second.cost());
00965             LOG_REPLAY << "-> " << (current_team.gold()) << "\n";
00966             fix_shroud = !get_replay_source().is_skipping();
00967             check_checksums(disp,units,*cfg);
00968         }
00969 
00970         else if((child = cfg->child("recall")) != NULL) {
00971             player_info* player = state_of_game.get_player(current_team.save_id());
00972             if(player == NULL) {
00973                 replay::throw_error("illegal recall\n");
00974             }
00975 
00976             sort_units(player->available_units);
00977 
00978             const std::string& recall_num = (*child)["value"];
00979             const int val = lexical_cast_default<int>(recall_num);
00980 
00981             gamemap::location loc(*child, game_events::get_state_of_game());
00982 
00983             if(val >= 0 && val < int(player->available_units.size())) {
00984                 statistics::recall_unit(player->available_units[val]);
00985                 player->available_units[val].set_game_context(&units,&map,&state,&teams);
00986                 recruit_unit(map,team_num,units,player->available_units[val],loc,true);
00987                 player->available_units.erase(player->available_units.begin()+val);
00988                 current_team.spend_gold(game_config::recall_cost);
00989             } else {
00990                 replay::throw_error("illegal recall\n");
00991             }
00992             fix_shroud = !get_replay_source().is_skipping();
00993             check_checksums(disp,units,*cfg);
00994         }
00995 
00996         else if((child = cfg->child("disband")) != NULL) {
00997             player_info* const player = state_of_game.get_player(current_team.save_id());
00998             if(player == NULL) {
00999                 replay::throw_error("illegal disband\n");
01000             }
01001 
01002             sort_units(player->available_units);
01003             const std::string& unit_num = (*child)["value"];
01004             const int val = lexical_cast_default<int>(unit_num);
01005 
01006             if(val >= 0 && val < int(player->available_units.size())) {
01007                 player->available_units.erase(player->available_units.begin()+val);
01008             } else {
01009                 replay::throw_error("illegal disband\n");
01010             }
01011         }
01012         else if((child = cfg->child("countdown_update")) != NULL) {
01013             const std::string& num = (*child)["value"];
01014             const int val = lexical_cast_default<int>(num);
01015             const std::string& tnum = (*child)["team"];
01016             const int tval = lexical_cast_default<int>(tnum,-1);
01017             if ( (tval<0)  || (static_cast<size_t>(tval) > teams.size()) ) {
01018                 std::stringstream errbuf;
01019                 errbuf << "Illegal countdown update \n"
01020                     << "Received update for :" << tval << " Current user :"
01021                     << team_num << "\n" << " Updated value :" << val;
01022 
01023                 replay::throw_error(errbuf.str());
01024             } else {
01025                 teams[tval-1].set_countdown_time(val);
01026             }
01027         }
01028         else if((child = cfg->child("move")) != NULL) {
01029 
01030             const config* const destination = child->child("destination");
01031             const config* const source = child->child("source");
01032 
01033             if(destination == NULL || source == NULL) {
01034                 replay::throw_error("no destination/source found in movement\n");
01035             }
01036 
01037             const gamemap::location src(*source, game_events::get_state_of_game());
01038             const gamemap::location dst(*destination, game_events::get_state_of_game());
01039 
01040             unit_map::iterator u = units.find(dst);
01041             if(u != units.end()) {
01042                 std::stringstream errbuf;
01043                 errbuf << "destination already occupied: "
01044                        << dst << '\n';
01045                 replay::throw_error(errbuf.str());
01046             }
01047             u = units.find(src);
01048             if(u == units.end()) {
01049                 std::stringstream errbuf;
01050                 errbuf << "unfound location for source of movement: "
01051                        << src << " -> " << dst << '\n';
01052                 replay::throw_error(errbuf.str());
01053             }
01054 
01055             const bool teleport = u->second.get_ability_bool("teleport",u->first);
01056 
01057             paths paths_list(map,units,src,teams,false,teleport,current_team);
01058 
01059             std::map<gamemap::location,paths::route>::iterator rt = paths_list.routes.find(dst);
01060             if(rt == paths_list.routes.end()) {
01061 
01062                 std::stringstream errbuf;
01063                 for(rt = paths_list.routes.begin(); rt != paths_list.routes.end(); ++rt) {
01064                     errbuf << "can get to: " << rt->first << '\n';
01065                 }
01066 
01067                 errbuf << "src cannot get to dst: " << u->second.movement_left() << ' '
01068                        << paths_list.routes.size() << ' ' << src << " -> " << dst << '\n';
01069                 replay::throw_error(errbuf.str());
01070             }
01071 
01072             rt->second.steps.push_back(dst);
01073 
01074             if(!get_replay_source().is_skipping()) {
01075                 unit_display::move_unit(rt->second.steps,u->second,teams);
01076             }
01077             else{
01078                 //unit location needs to be updated
01079                 u->second.set_goto(*(rt->second.steps.end() - 1));
01080             }
01081             u->second.set_movement(rt->second.move_left);
01082 
01083             std::pair<gamemap::location,unit> *up = units.extract(u->first);
01084             up->first = dst;
01085             units.add(up);
01086             unit::clear_status_caches();
01087             if (up->first == up->second.get_goto())
01088             {
01089                 //if unit has arrived to destination, goto variable is cleaned
01090                 up->second.set_goto(gamemap::location());
01091             }
01092             up->second.set_standing(up->first);
01093             u = units.find(dst);
01094             check_checksums(disp,units,*cfg);
01095             // Get side now, in case game events change the unit.
01096             int u_team = u->second.side()-1;
01097             if(map.is_village(dst)) {
01098                 const int orig_owner = village_owner(dst,teams) + 1;
01099                 if(orig_owner != team_num) {
01100                     u->second.set_movement(0);
01101                     get_village(dst,disp,teams,team_num-1,units);
01102                 }
01103             }
01104 
01105             if(!get_replay_source().is_skipping()) {
01106                 disp.invalidate(dst);
01107                 disp.draw();
01108             }
01109 
01110             game_events::fire("moveto",dst);
01111             //FIXME: what's special about team 1? regroup it with next block
01112             if(team_num != 1 && (teams.front().uses_shroud() || teams.front().uses_fog()) && !teams.front().fogged(dst)) {
01113                 game_events::fire("sighted",dst);
01114             }
01115             for(std::vector<team>::iterator my_team = teams.begin() ; my_team != teams.end() ; my_team++) {
01116                 if((my_team->uses_shroud() || my_team->uses_fog()) && !my_team->fogged(dst)) {
01117                     my_team->see(u_team);
01118                 }
01119             }
01120 
01121             fix_shroud = !get_replay_source().is_skipping();
01122 
01123             // would have to go via mousehandler to make this work:
01124             //disp.unhighlight_reach();
01125         }
01126 
01127         else if((child = cfg->child("attack")) != NULL) {
01128             const config* const destination = child->child("destination");
01129             const config* const source = child->child("source");
01130             check_checksums(disp,units,*cfg);
01131 
01132             if(destination == NULL || source == NULL) {
01133                 replay::throw_error("no destination/source found in attack\n");
01134             }
01135 
01136             //we must get locations by value instead of by references, because the iterators
01137             //may become invalidated later
01138             const gamemap::location src(*source, game_events::get_state_of_game());
01139             const gamemap::location dst(*destination, game_events::get_state_of_game());
01140 
01141             const std::string& weapon = (*child)["weapon"];
01142             const int weapon_num = lexical_cast_default<int>(weapon);
01143 
01144             const std::string& def_weapon = (*child)["defender_weapon"];
01145             int def_weapon_num = -1;
01146             if (def_weapon.empty()) {
01147                 // Let's not gratuitously destroy backwards compat.
01148                 ERR_REPLAY << "Old data, having to guess weapon\n";
01149             } else {
01150                 def_weapon_num = lexical_cast_default<int>(def_weapon);
01151             }
01152 
01153             unit_map::iterator u = units.find(src);
01154             if(u == units.end()) {
01155                 replay::throw_error("unfound location for source of attack\n");
01156             }
01157 
01158             if(size_t(weapon_num) >= u->second.attacks().size()) {
01159                 replay::throw_error("illegal weapon type in attack\n");
01160             }
01161 
01162             unit_map::const_iterator tgt = units.find(dst);
01163 
01164             if(tgt == units.end()) {
01165                 std::stringstream errbuf;
01166                 errbuf << "unfound defender for attack: " << src << " -> " << dst << '\n';
01167                 replay::throw_error(errbuf.str());
01168             }
01169 
01170             if(def_weapon_num >=
01171                     static_cast<int>(tgt->second.attacks().size())) {
01172 
01173                 replay::throw_error("illegal defender weapon type in attack\n");
01174             }
01175 
01176             DBG_REPLAY << "Attacker XP (before attack): " << u->second.experience() << "\n";;
01177 
01178             DELAY_END_LEVEL(delayed_exception, attack(disp, map, teams, src, dst, weapon_num, def_weapon_num, units, state, !get_replay_source().is_skipping()));
01179 
01180             DBG_REPLAY << "Attacker XP (after attack): " << u->second.experience() << "\n";;
01181 
01182             u = units.find(src);
01183             tgt = units.find(dst);
01184 
01185             if(u != units.end() && u->second.advances()) {
01186                 advancing_units.push_back(u->first);
01187             }
01188 
01189             DBG_REPLAY << "advancing_units.size: " << advancing_units.size() << "\n";
01190             if(tgt != units.end() && tgt->second.advances()) {
01191                 advancing_units.push_back(tgt->first);
01192             }
01193 
01194             //check victory now if we don't have any advancements. If we do have advancements,
01195             //we don't check until the advancements are processed.
01196             if(advancing_units.empty()) {
01197                 check_victory(units, teams, disp);
01198             }
01199             fix_shroud = !get_replay_source().is_skipping();
01200         } else if((child = cfg->child("fire_event")) != NULL) {
01201             for(config::child_list::const_iterator v = child->get_children("set_variable").begin(); v != child->get_children("set_variable").end(); ++v) {
01202                 state_of_game.set_variable((**v)["name"],(**v)["value"]);
01203             }
01204             const std::string event = (*child)["raise"];
01205             //exclude these events here, because in a replay proper time of execution can't be
01206             //established and therefore we fire those events inside play_controller::init_side
01207             if ((event != "side turn") && (event != "turn 1") && (event != "new turn") && (event != "turn refresh")){
01208                 const config* const source = child->child("source");
01209                 if(source != NULL) {
01210                     game_events::fire(event, gamemap::location(*source, game_events::get_state_of_game()));
01211                 } else {
01212                     game_events::fire(event);
01213                 }
01214             }
01215 
01216         } else if((child = cfg->child("advance_unit")) != NULL) {
01217             const gamemap::location loc(*child, game_events::get_state_of_game());
01218             advancing_units.push_back(loc);
01219 
01220         } else {
01221             if(! cfg->child("checksum")) {
01222                 replay::throw_error("unrecognized action:\n" + cfg->debug());
01223             } else {
01224                 check_checksums(disp,units,*cfg);
01225             }
01226         }
01227 
01228         //Check if we should refresh the shroud, and redraw the minimap/map tiles.
01229         //This is needed for shared vision to work properly.
01230         if(fix_shroud && clear_shroud(disp,map,units,teams,team_num-1) && !recorder.is_skipping()) {
01231             disp.recalculate_minimap();
01232             disp.invalidate_game_status();
01233             disp.invalidate_all();
01234             disp.draw();
01235         }
01236 
01237         child = cfg->child("verify");
01238         if(child != NULL) {
01239             verify_units(*child);
01240         }
01241     }
01242 
01243     return false; /* Never attained, but silent a gcc warning. --Zas */
01244 }
01245 
01246 replay_network_sender::replay_network_sender(replay& obj) : obj_(obj), upto_(obj_.ncommands())
01247 {
01248 }
01249 
01250 replay_network_sender::~replay_network_sender()
01251 {
01252     commit_and_sync();
01253 }
01254 
01255 void replay_network_sender::sync_non_undoable()
01256 {
01257     if(network::nconnections() > 0) {
01258         config cfg;
01259         const config& data = cfg.add_child("turn",obj_.get_data_range(upto_,obj_.ncommands(),replay::NON_UNDO_DATA));
01260         if(data.empty() == false) {
01261             network::send_data(cfg, 0, true);
01262         }
01263     }
01264 }
01265 
01266 void replay_network_sender::commit_and_sync()
01267 {
01268     if(network::nconnections() > 0) {
01269         config cfg;
01270         const config& data = cfg.add_child("turn",obj_.get_data_range(upto_,obj_.ncommands()));
01271         if(data.empty() == false) {
01272             network::send_data(cfg, 0, true);
01273         }
01274 
01275         upto_ = obj_.ncommands();
01276     }
01277 }

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs