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