The Battle for Wesnoth  1.15.1+dev
replay.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 /**
16  * @file
17  * Replay control code.
18  *
19  * See https://www.wesnoth.org/wiki/ReplayWML for more info.
20  */
21 
22 #include "replay.hpp"
23 
24 #include "actions/undo.hpp"
25 #include "display_chat_manager.hpp"
26 #include "floating_label.hpp"
27 #include "game_display.hpp"
28 #include "preferences/game.hpp"
29 #include "game_data.hpp"
30 #include "lexical_cast.hpp"
31 #include "log.hpp"
32 #include "map/label.hpp"
33 #include "map/location.hpp"
34 #include "play_controller.hpp"
35 #include "synced_context.hpp"
36 #include "resources.hpp"
37 #include "statistics.hpp"
38 #include "units/unit.hpp"
39 #include "whiteboard/manager.hpp"
40 #include "replay_recorder_base.hpp"
41 
42 #include <set>
43 #include <map>
44 
45 static lg::log_domain log_replay("replay");
46 #define DBG_REPLAY LOG_STREAM(debug, log_replay)
47 #define LOG_REPLAY LOG_STREAM(info, log_replay)
48 #define WRN_REPLAY LOG_STREAM(warn, log_replay)
49 #define ERR_REPLAY LOG_STREAM(err, log_replay)
50 
51 static lg::log_domain log_random("random");
52 #define DBG_RND LOG_STREAM(debug, log_random)
53 #define LOG_RND LOG_STREAM(info, log_random)
54 #define WRN_RND LOG_STREAM(warn, log_random)
55 #define ERR_RND LOG_STREAM(err, log_random)
56 
57 
58 //functions to verify that the unit structure on both machines is identical
59 
60 static void verify(const unit_map& units, const config& cfg) {
61  std::stringstream errbuf;
62  LOG_REPLAY << "verifying unit structure...\n";
63 
64  const std::size_t nunits = cfg["num_units"].to_size_t();
65  if(nunits != units.size()) {
66  errbuf << "SYNC VERIFICATION FAILED: number of units from data source differ: "
67  << nunits << " according to data source. " << units.size() << " locally\n";
68 
69  std::set<map_location> locs;
70  for (const config &u : cfg.child_range("unit"))
71  {
72  const map_location loc(u);
73  locs.insert(loc);
74 
75  if(units.count(loc) == 0) {
76  errbuf << "data source says there is a unit at "
77  << loc << " but none found locally\n";
78  }
79  }
80 
81  for(unit_map::const_iterator j = units.begin(); j != units.end(); ++j) {
82  if (locs.count(j->get_location()) == 0) {
83  errbuf << "local unit at " << j->get_location()
84  << " but none in data source\n";
85  }
86  }
87  replay::process_error(errbuf.str());
88  errbuf.clear();
89  }
90 
91  for (const config &un : cfg.child_range("unit"))
92  {
93  const map_location loc(un);
94  const unit_map::const_iterator u = units.find(loc);
95  if(u == units.end()) {
96  errbuf << "SYNC VERIFICATION FAILED: data source says there is a '"
97  << un["type"] << "' (side " << un["side"] << ") at "
98  << loc << " but there is no local record of it\n";
99  replay::process_error(errbuf.str());
100  errbuf.clear();
101  }
102 
103  config u_cfg;
104  u->write(u_cfg);
105 
106  bool is_ok = true;
107  static const std::string fields[] {"type","hitpoints","experience","side",""};
108  for(const std::string* str = fields; str->empty() == false; ++str) {
109  if (u_cfg[*str] != un[*str]) {
110  errbuf << "ERROR IN FIELD '" << *str << "' for unit at "
111  << loc << " data source: '" << un[*str]
112  << "' local: '" << u_cfg[*str] << "'\n";
113  is_ok = false;
114  }
115  }
116 
117  if(!is_ok) {
118  errbuf << "(SYNC VERIFICATION FAILED)\n";
119  replay::process_error(errbuf.str());
120  errbuf.clear();
121  }
122  }
123 
124  LOG_REPLAY << "verification passed\n";
125 }
126 
127 static std::time_t get_time(const config &speak)
128 {
129  std::time_t time;
130  if (!speak["time"].empty())
131  {
132  std::stringstream ss(speak["time"].str());
133  ss >> time;
134  }
135  else
136  {
137  //fallback in case sender uses wesnoth that doesn't send timestamps
138  time = std::time(nullptr);
139  }
140  return time;
141 }
142 
144  : color_()
145  , nick_()
146  , text_(cfg["message"].str())
147 {
148  if(cfg["team_name"].empty() && cfg["to_sides"].empty())
149  {
150  nick_ = cfg["id"].str();
151  } else {
152  nick_ = "*"+cfg["id"].str()+"*";
153  }
154  int side = cfg["side"].to_int(0);
155  LOG_REPLAY << "side in message: " << side << std::endl;
156  if (side==0) {
157  color_ = "white";//observers
158  } else {
160  }
161  time_ = get_time(cfg);
162  /*
163  } else if (side==1) {
164  color_ = "red";
165  } else if (side==2) {
166  color_ = "blue";
167  } else if (side==3) {
168  color_ = "green";
169  } else if (side==4) {
170  color_ = "purple";
171  }*/
172 }
173 
175 {
176 }
177 
179  : base_(&base)
180  , message_locations()
181 {}
182 
184 {
186 }
187 /*
188  TODO: there should be different types of OOS messages:
189  1)the normal OOS message
190  2) the 'is guaranteed you'll get an assertion error after this and therefore you cannot continue' OOS message
191  3) the 'do you want to overwrite calculated data with the data stored in replay' OOS error message.
192 
193 */
194 void replay::process_error(const std::string& msg)
195 {
196  ERR_REPLAY << msg << std::flush;
197 
198  resources::controller->process_oos(msg); // might throw quit_game_exception()
199 }
200 
202 {
203  if(! game_config::mp_debug) {
204  return;
205  }
206  config& cc = cfg.add_child("checksum");
207  loc.write(cc);
209  assert(u.valid());
210  cc["value"] = get_checksum(*u);
211 }
212 
213 
215 {
216  config& cmd = add_command();
218  init_side["side_number"] = resources::controller->current_side();
219  cmd.add_child("init_side", init_side);
220 }
221 
223 {
224  config& cmd = add_command();
225  cmd["sent"] = true;
226  cmd.add_child("start");
227 }
228 
230 {
232  cmd.add_child("surrender")["side_number"] = side_number;
233 }
234 
235 void replay::add_countdown_update(int value, int team)
236 {
237  config& cmd = add_command();
238  config val;
239  val["value"] = value;
240  val["team"] = team;
241  cmd.add_child("countdown_update", std::move(val));
242 }
243 void replay::add_synced_command(const std::string& name, const config& command)
244 {
245  config& cmd = add_command();
246  cmd.add_child(name,command);
247  cmd["from_side"] = resources::controller->current_side();
248  LOG_REPLAY << "add_synced_command: \n" << cmd.debug() << "\n";
249 }
250 
251 
252 
253 void replay::user_input(const std::string &name, const config &input, int from_side)
254 {
255  config& cmd = add_command();
256  cmd["dependent"] = true;
257  if(from_side == -1)
258  {
259  cmd["from_side"] = "server";
260  }
261  else
262  {
263  cmd["from_side"] = from_side;
264  }
265  cmd.add_child(name, input);
266 }
267 
269 {
270  assert(label);
272  config val;
273 
274  label->write(val);
275 
276  cmd.add_child("label",val);
277 }
278 
279 void replay::clear_labels(const std::string& team_name, bool force)
280 {
282 
283  config val;
284  val["team_name"] = team_name;
285  val["force"] = force;
286  cmd.add_child("clear_labels", std::move(val));
287 }
288 
289 void replay::add_rename(const std::string& name, const map_location& loc)
290 {
291  config& cmd = add_command();
292  cmd["async"] = true; // Not undoable, but depends on moves/recruits that are
293  config val;
294  loc.write(val);
295  val["name"] = name;
296  cmd.add_child("rename", std::move(val));
297 }
298 
299 
300 void replay::end_turn(int next_player_number)
301 {
302  config& cmd = add_command();
303  config& end_turn = cmd.add_child("end_turn");
304 
305  end_turn["next_player_number"] = next_player_number;
306 }
307 
308 
309 void replay::add_log_data(const std::string &key, const std::string &var)
310 {
311  config& ulog = base_->get_upload_log();
312  ulog[key] = var;
313 }
314 
315 void replay::add_log_data(const std::string &category, const std::string &key, const std::string &var)
316 {
317  config& ulog = base_->get_upload_log();
318  config& cat = ulog.child_or_add(category);
319  cat[key] = var;
320 }
321 
322 void replay::add_log_data(const std::string &category, const std::string &key, const config &c)
323 {
324  config& ulog = base_->get_upload_log();
325  config& cat = ulog.child_or_add(category);
326  cat.add_child(key,c);
327 }
328 
330 {
331  return add_chat_message_location(base_->get_pos() - 1);
332 }
333 
335 {
336  assert(base_->get_command_at(pos).has_child("speak"));
337  if(std::find(message_locations.begin(), message_locations.end(), pos) == message_locations.end()) {
338  message_locations.push_back(pos);
339  return true;
340  }
341  else {
342  return false;
343  }
344 }
345 
346 void replay::speak(const config& cfg)
347 {
349  cmd.add_child("speak",cfg);
351 }
352 
353 void replay::add_chat_log_entry(const config &cfg, std::back_insert_iterator<std::vector<chat_msg>> &i) const
354 {
355  if (!cfg) return;
356 
357  if (!preferences::parse_should_show_lobby_join(cfg["id"], cfg["message"])) return;
358  if (preferences::is_ignored(cfg["id"])) return;
359  *i = chat_msg(cfg);
360 }
361 
363 {
364  base_->remove_command(index);
365  std::vector<int>::reverse_iterator loc_it;
366  for (loc_it = message_locations.rbegin(); loc_it != message_locations.rend() && index < *loc_it;++loc_it)
367  {
368  --(*loc_it);
369  }
370 }
371 
372 // cached message log
373 static std::vector< chat_msg > message_log;
374 
375 
376 const std::vector<chat_msg>& replay::build_chat_log() const
377 {
378  message_log.clear();
379  std::vector<int>::const_iterator loc_it;
380  int last_location = 0;
381  std::back_insert_iterator<std::vector < chat_msg >> chat_log_appender( back_inserter(message_log));
382  for (loc_it = message_locations.begin(); loc_it != message_locations.end(); ++loc_it)
383  {
384  last_location = *loc_it;
385 
386  const config &speak = command(last_location).child("speak");
387  assert(speak);
388  add_chat_log_entry(speak, chat_log_appender);
389 
390  }
391  return message_log;
392 }
393 
394 config replay::get_data_range(int cmd_start, int cmd_end, DATA_TYPE data_type) const
395 {
396  config res;
397 
398  for (int cmd = cmd_start; cmd < cmd_end; ++cmd)
399  {
400  config &c = command(cmd);
401  //prevent creating 'blank' attribute values during checks
402  const config &cc = c;
403  if ((data_type == ALL_DATA || !cc["undo"].to_bool(true)) && !cc["sent"].to_bool(false))
404  {
405  res.add_child("command", c);
406  if (data_type == NON_UNDO_DATA) c["sent"] = true;
407  }
408  }
409 
410  return res;
411 }
412 
413 void replay::redo(const config& cfg, bool set_to_end)
414 {
415  assert(base_->get_pos() == ncommands());
416  int old_pos = base_->get_pos();
417  for (const config &cmd : cfg.child_range("command"))
418  {
419  base_->add_child() = cmd;
420  }
421  if(set_to_end) {
422  //The engine does not execute related wml events so mark ad dpendent actions as handled
423  base_->set_to_end();
424  }
425  else {
426  //The engine does execute related wml events so it needs to reprocess depndent choices
427  base_->set_pos(old_pos + 1);
428  }
429 
430 }
431 
432 
433 
435 {
436  for (int cmd_num = base_->get_pos() - 1; cmd_num >= 0; --cmd_num)
437  {
438  config &c = command(cmd_num);
439  const config &cc = c;
440  if (cc["dependent"].to_bool(false) || !cc["undo"].to_bool(true) || cc["async"].to_bool(false))
441  {
442  continue;
443  }
444  return c;
445  }
446  ERR_REPLAY << "replay::get_last_real_command called with no existent command." << std::endl;
447  assert(false && "replay::get_last_real_command called with no existent command.");
448  throw "replay::get_last_real_command called with no existent command.";
449 }
450 /// fixes a rename command when undoing a earlier command.
451 /// @return: true if the command should be removed.
452 static bool fix_rename_command(const config& c, config& async_child)
453 {
454  if (const config &child = c.child("move"))
455  {
456  // A unit's move is being undone.
457  // Repair unsynced cmds whose locations depend on that unit's location.
458  std::vector<map_location> steps;
459 
460  try {
461  read_locations(child,steps);
462  } catch(const bad_lexical_cast &) {
463  WRN_REPLAY << "Warning: Path data contained something which could not be parsed to a sequence of locations:" << "\n config = " << child.debug() << std::endl;
464  }
465 
466  if (steps.empty()) {
467  ERR_REPLAY << "trying to undo a move using an empty path";
468  }
469  else {
470  const map_location &src = steps.front();
471  const map_location &dst = steps.back();
472  map_location aloc(async_child);
473  if (dst == aloc) src.write(async_child);
474  }
475  }
476  else
477  {
478  const config *chld = &c.child("recruit");
479  if (!*chld) chld = &c.child("recall");
480  if (*chld) {
481  // A unit is being un-recruited or un-recalled.
482  // Remove unsynced commands that would act on that unit.
483  map_location src(*chld);
484  map_location aloc(async_child);
485  if (src == aloc) {
486  return true;
487  }
488  }
489  }
490  return false;
491 }
492 
494 {
495  assert(dst.empty());
496  //assert that we are not undoing a command which we didn't execute yet.
497  assert(at_end());
498 
499  //calculate the index of the last synced user action (which we want to undo).
500  int cmd_index = ncommands() - 1;
501  for (; cmd_index >= 0; --cmd_index)
502  {
503  //"undo"=no means speak/label/remove_label, especially attack, recruits etc. have "undo"=yes
504  //"async"=yes means rename_unit
505  //"dependent"=true means user input
506  const config &c = command(cmd_index);
507 
508  if(c["undo"].to_bool(true) && !c["async"].to_bool(false) && !c["dependent"].to_bool(false))
509  {
510  if(c["sent"].to_bool(false))
511  {
512  ERR_REPLAY << "trying to undo a command that was already sent.\n";
513  return;
514  }
515  else
516  {
517  break;
518  }
519  }
520  }
521 
522  if (cmd_index < 0)
523  {
524  ERR_REPLAY << "trying to undo a command but no command was found.\n";
525  return;
526  }
527  //Fix the [command]s after the undone action. This includes dependent commands for that user actions and async user action.
528  for(int i = ncommands() - 1; i >= cmd_index; --i)
529  {
530  config &c = command(i);
531  const config &cc = c;
532  if(!cc["undo"].to_bool(true))
533  {
534  //Leave these commands on the replay.
535  }
536  else if(cc["async"].to_bool(false))
537  {
538  if(config& rename = c.child("rename"))
539  {
540  if(fix_rename_command(command(cmd_index), rename))
541  {
542  //remove the command from the replay if fix_rename_command requested it.
543  remove_command(i);
544  }
545  }
546  }
547  else if(cc["dependent"].to_bool(false) || i == cmd_index)
548  {
549  //we loop backwars so we must insert new insert at beginning to preserve order.
550  dst.add_child_at("command", config(), 0).swap(c);
551  remove_command(i);
552  }
553  else
554  {
555  ERR_REPLAY << "Couldn't handle command:\n" << cc << "\nwhen undoing.\n";
556  }
557  }
558  set_to_end();
559 }
560 
562 {
563  config dummy;
564  undo_cut(dummy);
565 }
566 
568 {
569  config & retv = base_->get_command_at(n);
570  assert(retv);
571  return retv;
572 }
573 
574 int replay::ncommands() const
575 {
576  return base_->size();
577 }
578 
580 {
581  // If we weren't at the end of the replay we should skip one or more
582  // commands.
583  assert(at_end());
584  config& retv = base_->add_child();
585  set_to_end();
586  return retv;
587 }
588 
590 {
591  const bool was_at_end = at_end();
593  r["undo"] = false;
594  if(was_at_end) {
595  base_->set_pos(base_->get_pos() + 1);
596  }
597  assert(was_at_end == at_end());
598  return r;
599 }
600 
602 {
603  base_->set_pos(0);
604 }
605 
607 {
608 
609  if (base_->get_pos() > 0)
610  base_->set_pos(base_->get_pos() - 1);
611 }
612 
614 {
615  if (at_end())
616  return nullptr;
617 
618  LOG_REPLAY << "up to replay action " << base_->get_pos() + 1 << '/' << ncommands() << '\n';
619 
620  config* retv = &command(base_->get_pos());
621  base_->set_pos(base_->get_pos() + 1);
622  return retv;
623 }
624 
625 
626 bool replay::at_end() const
627 {
628  assert(base_->get_pos() <= ncommands());
629  return base_->get_pos() == ncommands();
630 }
631 
633 {
634  base_->set_to_end();
635 }
636 
637 bool replay::empty() const
638 {
639  return ncommands() == 0;
640 }
641 
643 {
644  for (const config &cmd : cfg.child_range("command"))
645  {
646  config &cmd_cfg = base_->insert_command(base_->size());
647  cmd_cfg = cmd;
648  if(mark == MARK_AS_SENT) {
649  cmd_cfg["sent"] = true;
650  }
651  if(cmd_cfg.has_child("speak")) {
652  cmd_cfg["undo"] = false;
653  }
654  }
655 }
657 {
658  //this method would confuse the value of 'pos' otherwise
659  assert(base_->get_pos() == 0);
660  //since pos is 0, at_end() is equivalent to empty()
661  if(at_end() || !base_->get_command_at(0).has_child("start"))
662  {
663  base_->insert_command(0) = config {"start", config(), "sent", true};
664  return true;
665  }
666  else
667  {
668  return false;
669  }
670 }
671 
672 static void show_oos_error_error_function(const std::string& message, bool /*heavy*/)
673 {
674  replay::process_error(message);
675 }
676 
677 REPLAY_RETURN do_replay(bool one_move)
678 {
679  log_scope("do replay");
680 
681  if (!resources::controller->is_skipping_replay()) {
683  }
684 
685  update_locker lock_update(CVideo::get_singleton(), resources::controller->is_skipping_replay());
686  return do_replay_handle(one_move);
687 }
688 /**
689  @returns:
690  if we expect a user choice and found something that prevents us from moving on we return REPLAY_FOUND_DEPENDENT (even if it is not a dependent command)
691  else if we found an [end_turn] we return REPLAY_FOUND_END_TURN
692  else if we found a player action and one_move=true we return REPLAY_FOUND_END_MOVE
693  else (<=> we reached the end of the replay) we return REPLAY_RETURN_AT_END
694 */
696 {
697 
698  //team &current_team = resources::gameboard->get_team(side_num);
699 
700  const int side_num = resources::controller->current_side();
701  while(true)
702  {
704  const bool is_synced = synced_context::is_synced();
705  const bool is_unsynced = synced_context::get_synced_state() == synced_context::UNSYNCED;
706 
707  DBG_REPLAY << "in do replay with is_synced=" << is_synced << "is_unsynced=" << is_unsynced << "\n";
708 
709  if (cfg != nullptr)
710  {
711  DBG_REPLAY << "Replay data:\n" << *cfg << "\n";
712  }
713  else
714  {
715  DBG_REPLAY << "Replay data at end\n";
716  return REPLAY_RETURN_AT_END;
717  }
718 
719 
721  //if there is an empty command tag or a start tag
722  if (ch_itors.empty() || cfg->has_child("start"))
723  {
724  //this shouldn't happen anymore because replaycontroller now moves over the [start] with get_next_action
725  //also we removed the the "add empty replay entry at scenario reload" behavior.
726  ERR_REPLAY << "found "<< cfg->debug() <<" in replay" << std::endl;
727  //do nothing
728  }
729  else if (const config &speak = cfg->child("speak"))
730  {
731  const std::string &team_name = speak["to_sides"];
732  const std::string &speaker_name = speak["id"];
733  const std::string &message = speak["message"];
734  //if (!preferences::parse_should_show_lobby_join(speaker_name, message)) return;
735  bool is_whisper = (speaker_name.find("whisper: ") == 0);
737  DBG_REPLAY << "tried to add a chat message twice.\n";
738  if (!resources::controller->is_skipping_replay() || is_whisper) {
739  int side = speak["side"];
741  (team_name.empty() ? events::chat_handler::MESSAGE_PUBLIC
744  }
745  }
746  }
747  else if (cfg->child("surrender"))
748  {
749  //prevent sending of a synced command for surrender
750  }
751  else if (const config &label_config = cfg->child("label"))
752  {
753  terrain_label label(display::get_singleton()->labels(), label_config);
754 
756  label.text(),
757  label.creator(),
758  label.team_name(),
759  label.color());
760  }
761  else if (const config &clear_labels = cfg->child("clear_labels"))
762  {
763  display::get_singleton()->labels().clear(std::string(clear_labels["team_name"]), clear_labels["force"].to_bool());
764  }
765  else if (const config &rename = cfg->child("rename"))
766  {
767  const map_location loc(rename);
768  const std::string &name = rename["name"];
769 
771  if (u.valid() && !u->unrenamable()) {
772  u->rename(name);
773  } else {
774  // Users can rename units while it's being killed or at another machine.
775  // This since the player can rename units when it's not his/her turn.
776  // There's not a simple way to prevent that so in that case ignore the
777  // rename instead of throwing an OOS.
778  // The same way it is possible that an unrenamable unit moves to a
779  // hex where previously a renamable unit was.
780  WRN_REPLAY << "attempt to rename unit at location: "
781  << loc << (u.valid() ? ", which is unrenamable" : ", where none exists (anymore)") << "\n";
782  }
783  }
784 
785  else if (cfg->child("init_side"))
786  {
787 
788  if(!is_unsynced)
789  {
790  replay::process_error("found side initialization in replay expecting a user choice\n" );
792  return REPLAY_FOUND_DEPENDENT;
793  }
794  else
795  {
797  if (one_move) {
798  return REPLAY_FOUND_INIT_TURN;
799  }
800  }
801  }
802 
803  //if there is an end turn directive
804  else if (const config& end_turn = cfg->child("end_turn"))
805  {
806  if(!is_unsynced)
807  {
808  replay::process_error("found turn end in replay while expecting a user choice\n" );
810  return REPLAY_FOUND_DEPENDENT;
811  }
812  else
813  {
814  if (const config &cfg_verify = cfg->child("verify")) {
815  verify(resources::gameboard->units(), cfg_verify);
816  }
817  resources::controller->gamestate().next_player_number_ = end_turn["next_player_number"];
818  return REPLAY_FOUND_END_TURN;
819  }
820  }
821  else if (const config &countdown_update = cfg->child("countdown_update"))
822  {
823  int val = countdown_update["value"];
824  int tval = countdown_update["team"];
825  if (tval <= 0 || tval > static_cast<int>(resources::gameboard->teams().size())) {
826  std::stringstream errbuf;
827  errbuf << "Illegal countdown update \n"
828  << "Received update for :" << tval << " Current user :"
829  << side_num << "\n" << " Updated value :" << val;
830 
831  replay::process_error(errbuf.str());
832  } else {
834  }
835  }
836  else if ((*cfg)["dependent"].to_bool(false))
837  {
838  if(is_unsynced)
839  {
840  replay::process_error("found dependent command in replay while is_synced=false\n" );
841  //ignore this command
842  continue;
843  }
844  //this means user choice.
845  // it never makes sense to try to execute a user choice.
846  // but we are called from
847  // the only other option for "dependent" command is checksum which is already checked.
848  assert(cfg->all_children_count() == 1);
849  std::string child_name = cfg->all_children_range().front().key;
850  DBG_REPLAY << "got an dependent action name = " << child_name <<"\n";
852  return REPLAY_FOUND_DEPENDENT;
853  }
854  else
855  {
856  //we checked for empty commands at the beginning.
857  const std::string & commandname = cfg->ordered_begin()->key;
858  config data = cfg->ordered_begin()->cfg;
859 
860  if(!is_unsynced)
861  {
862  replay::process_error("found [" + commandname + "] command in replay expecting a user choice\n" );
864  return REPLAY_FOUND_DEPENDENT;
865  }
866  else
867  {
868  LOG_REPLAY << "found commandname " << commandname << "in replay";
869 
870  if((*cfg)["from_side"].to_int(0) != resources::controller->current_side()) {
871  ERR_REPLAY << "received a synced [command] from side " << (*cfg)["from_side"].to_int(0) << ". Expacted was a [command] from side " << resources::controller->current_side() << "\n";
872  }
873  else if((*cfg)["side_invalid"].to_bool(false)) {
874  ERR_REPLAY << "received a synced [command] from side " << (*cfg)["from_side"].to_int(0) << ". Sent from wrong client.\n";
875  }
876  /*
877  we need to use the undo stack during replays in order to make delayed shroud updated work.
878  */
879  synced_context::run(commandname, data, true, !resources::controller->is_skipping_replay(), show_oos_error_error_function);
880  if(resources::controller->is_regular_game_end()) {
881  return REPLAY_FOUND_END_LEVEL;
882  }
883  if (one_move) {
884  return REPLAY_FOUND_END_MOVE;
885  }
886  }
887  }
888 
889  if (const config &child = cfg->child("verify")) {
890  verify(resources::gameboard->units(), child);
891  }
892  }
893 }
894 
896 {
897 }
898 
900 {
901  try {
902  commit_and_sync();
903  } catch (...) {}
904 }
905 
907 {
908  if(resources::controller->is_networked_mp()) {
909  resources::whiteboard->send_network_data();
910 
911  config cfg;
913  if(data.empty() == false) {
915  }
916  }
917 }
918 
920 {
921  if(resources::controller->is_networked_mp()) {
922  resources::whiteboard->send_network_data();
923 
924  config cfg;
925  const config& data = cfg.add_child("turn",obj_.get_data_range(upto_,obj_.ncommands()));
926 
927  if(data.empty() == false) {
929  }
930 
931  upto_ = obj_.ncommands();
932  }
933 }
void end_turn(int next_player_number)
Definition: replay.cpp:300
play_controller * controller
Definition: resources.cpp:21
void delete_upcoming_commands()
Definition: replay.cpp:183
std::time_t time_
Definition: replay.hpp:46
const map_location & location() const
Definition: label.hpp:176
void read_locations(const config &cfg, std::vector< map_location > &locs)
Parse x,y keys of a config into a vector of locations.
Definition: location.cpp:485
DATA_TYPE
Definition: replay.hpp:96
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:420
replay(replay_recorder_base &base)
Definition: replay.cpp:178
const color_t & color() const
Definition: label.hpp:181
unit_iterator end()
Definition: map.hpp:415
void set_countdown_time(const int amount) const
Definition: team.hpp:211
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:88
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:921
replay_network_sender(replay &obj)
Definition: replay.cpp:895
void add_countdown_update(int value, int team)
Definition: replay.cpp:235
virtual const unit_map & units() const override
Definition: game_board.hpp:114
int dummy
Definition: lstrlib.cpp:1125
void speak(const config &cfg)
Definition: replay.cpp:346
bool add_chat_message_location()
adds a chat message if it wasn&#39;t added yet.
Definition: replay.cpp:329
void write(config &res) const
Definition: label.cpp:431
boost::iterator_range< const_all_children_iterator > const_all_children_itors
Definition: config.hpp:668
void add_log_data(const std::string &key, const std::string &var)
Definition: replay.cpp:309
static bool run(const std::string &commandname, const config &data, bool use_undo=true, bool show=true, synced_command::error_handler_function error_handler=default_error_function)
Sets the context to &#39;synced&#39;, initialises random context, and calls the given function.
const std::vector< chat_msg > & build_chat_log() const
Definition: replay.cpp:376
New lexcical_cast header.
virtual ~chat_msg()
Definition: replay.cpp:174
config & command(int) const
Definition: replay.cpp:567
void add_start()
Definition: replay.cpp:222
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:412
int next_player_number_
Definition: game_state.hpp:58
const t_string & text() const
Definition: label.hpp:136
bool at_end() const
Definition: replay.cpp:626
child_itors child_range(config_key_type key)
Definition: config.cpp:362
config & insert_command(int index)
void sync_non_undoable()
Definition: replay.cpp:906
unit_iterator begin()
Definition: map.hpp:405
bool message_bell()
Definition: general.cpp:653
virtual void process_oos(const std::string &msg) const
Asks the user whether to continue on an OOS error.
const std::string & team_name() const
Definition: label.hpp:151
#define LOG_REPLAY
Definition: replay.cpp:47
static CVideo & get_singleton()
Definition: video.hpp:43
#define WRN_REPLAY
Definition: replay.cpp:48
Replay control code.
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
config & child_or_add(config_key_type key)
Definition: config.cpp:466
int creator() const
Definition: label.hpp:146
REPLAY_RETURN do_replay(bool one_move)
Definition: replay.cpp:677
config & get_last_real_command()
Definition: replay.cpp:434
#define ERR_REPLAY
Definition: replay.cpp:49
replay_recorder_base * base_
Definition: replay.hpp:149
void add_synced_command(const std::string &name, const config &command)
Definition: replay.cpp:243
static synced_state get_synced_state()
int ncommands() const
Definition: replay.cpp:574
MARK_SENT
Definition: replay.hpp:120
void add_chat_message(const std::time_t &time, const std::string &speaker, int side, const std::string &msg, events::chat_handler::MESSAGE_TYPE type, bool bell)
static std::string get_side_highlight_pango(int side)
Definition: team.cpp:999
REPLAY_RETURN
Definition: replay.hpp:153
static const char * name(const std::vector< SDL_Joystick *> &joysticks, const std::size_t index)
Definition: joystick.cpp:48
static void show_oos_error_error_function(const std::string &message, bool)
Definition: replay.cpp:672
static void verify(const unit_map &units, const config &cfg)
Definition: replay.cpp:60
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:44
void init_side()
Definition: replay.cpp:214
void swap(config &cfg)
Definition: config.cpp:1377
team & get_team(int i)
Definition: game_board.hpp:104
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:86
void redo(const config &dst, bool set_to_end=false)
Definition: replay.cpp:413
bool add_start_if_not_there_yet()
Definition: replay.cpp:656
static std::time_t get_time(const config &speak)
Definition: replay.cpp:127
const terrain_label * set_label(const map_location &loc, const t_string &text, const int creator=-1, const std::string &team="", const color_t color=font::NORMAL_COLOR, const bool visible_in_fog=true, const bool visible_in_shroud=false, const bool immutable=false, const std::string &category="", const t_string &tooltip="")
Definition: label.cpp:146
void remove_command(int index)
void clear(const std::string &, bool force)
Definition: label.cpp:210
void recalculate_minimap()
Schedule the minimap for recalculation.
Definition: display.hpp:616
void add_surrender(int side_number)
Definition: replay.cpp:229
config & add_child_at(config_key_type key, const config &val, unsigned index)
Definition: config.cpp:508
unsigned all_children_count() const
Definition: config.cpp:402
game_board * gameboard
Definition: resources.cpp:20
void start_replay()
Definition: replay.cpp:601
#define DBG_REPLAY
Definition: replay.cpp:46
void add_rename(const std::string &name, const map_location &loc)
Definition: replay.cpp:289
void set_to_end()
Definition: replay.cpp:632
replay * recorder
Definition: resources.cpp:28
std::size_t count(const map_location &loc) const
Definition: map.hpp:400
std::size_t size() const
Definition: map.hpp:425
An object which will lock the display for the duration of its lifetime.
Definition: video.hpp:279
bool is_ignored(const std::string &nick)
Definition: game.cpp:319
static std::vector< chat_msg > message_log
Definition: replay.cpp:373
void add_unit_checksum(const map_location &loc, config &cfg)
Definition: replay.cpp:201
config & get_command_at(int pos)
void do_init_side()
Called by replay handler or init_side() to do actual work for turn change.
Encapsulates the map of the game.
Definition: location.hpp:42
unit_iterator find(std::size_t id)
Definition: map.cpp:311
std::shared_ptr< wb::manager > whiteboard
Definition: resources.cpp:33
static void process_error(const std::string &msg)
Definition: replay.cpp:194
static std::string mark
Definition: tstring.cpp:73
std::size_t i
Definition: function.cpp:933
std::string color_
Definition: replay.hpp:43
std::string get_checksum(const unit &u)
Gets a checksum for a unit.
Definition: unit.cpp:2635
bool empty() const
Definition: replay.cpp:637
#define log_scope(description)
Definition: log.hpp:186
void remove_command(int)
Definition: replay.cpp:362
static std::string flush(std::ostringstream &s)
Definition: reports.cpp:88
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:71
config & add_child(config_key_type key)
Definition: config.cpp:476
const_all_children_iterator ordered_begin() const
Definition: config.cpp:901
display_chat_manager & get_chat_manager()
To store label data Class implements logic for rendering.
Definition: label.hpp:107
config & add_nonundoable_command()
adds a new command to the command list at the current position.
Definition: replay.cpp:589
game_state & gamestate()
static lg::log_domain log_replay("replay")
bool find(E event, F functor)
Tests whether an event handler is available.
void add_config(const config &cfg, MARK_SENT mark=MARK_AS_UNSENT)
Definition: replay.cpp:642
std::vector< int > message_locations
Definition: replay.hpp:150
Various functions that implement the undoing (and redoing) of in-game commands.
void undo_cut(config &dst)
Definition: replay.cpp:493
Standard logging facilities (interface).
int current_side() const
Returns the number of the side whose turn it is.
std::string nick_
Definition: replay.hpp:44
Container associating units to locations.
Definition: map.hpp:99
config * get_next_action()
Definition: replay.cpp:613
void revert_action()
Definition: replay.cpp:606
bool parse_should_show_lobby_join(const std::string &sender, const std::string &message)
Definition: game.cpp:345
config & add_command()
Adds a new empty command to the command list at the end.
Definition: replay.cpp:579
map_labels & labels()
Definition: display.cpp:2494
int side_number
Definition: game_info.hpp:39
static lg::log_domain log_random("random")
void undo()
Definition: replay.cpp:561
void user_input(const std::string &, const config &, int from_side)
adds a user_input to the replay
Definition: replay.cpp:253
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:92
static bool fix_rename_command(const config &c, config &async_child)
fixes a rename command when undoing a earlier command.
Definition: replay.cpp:452
REPLAY_RETURN do_replay_handle(bool one_move)
Definition: replay.cpp:695
bool valid() const
Definition: map.hpp:276
mock_char c
static map_location::DIRECTION n
Thrown when a lexical_cast fails.
void clear_labels(const std::string &, bool)
Definition: replay.cpp:279
static bool is_synced()
bool empty() const
Definition: config.cpp:884
virtual void send_to_wesnothd(const config &, const std::string &="unknown") const
std::string debug() const
Definition: config.cpp:1277
chat_msg(const config &cfg)
Definition: replay.cpp:143
void add_chat_log_entry(const config &speak, std::back_insert_iterator< std::vector< chat_msg >> &i) const
Definition: replay.cpp:353
static game_display * get_singleton()
void add_label(const terrain_label *)
Definition: replay.cpp:268
config get_data_range(int cmd_start, int cmd_end, DATA_TYPE data_type=ALL_DATA) const
Definition: replay.cpp:394
void write(config &cfg) const
Definition: location.cpp:210