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