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