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