replay.cpp

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