The Battle for Wesnoth  1.19.14+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_()
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) {
158  color_ = "white";//observers
159  } else {
161  }
162 }
163 
165 {
166 }
167 
169  : base_(&base)
170  , sent_upto_(base.size())
171  , message_locations()
172 {}
173 
175 {
177 }
178 /*
179  TODO: there should be different types of OOS messages:
180  1)the normal OOS message
181  2) the 'is guaranteed you'll get an assertion error after this and therefore you cannot continue' OOS message
182  3) the 'do you want to overwrite calculated data with the data stored in replay' OOS error message.
183 
184 */
185 void replay::process_error(const std::string& msg)
186 {
187  ERR_REPLAY << msg;
188 
189  resources::controller->process_oos(msg); // might throw quit_game_exception()
190 }
191 
193 {
194  if(! game_config::mp_debug) {
195  return;
196  }
197  config& cc = cfg.add_child("checksum");
198  loc.write(cc);
200  assert(u.valid());
201  cc["value"] = get_checksum(*u);
202 }
203 
204 
206 {
207  config& cmd = add_command();
209  init_side["side_number"] = resources::controller->current_side();
210  cmd.add_child("init_side", init_side);
211 }
212 
214 {
215  config& cmd = add_command();
216  cmd["sent"] = true;
217  cmd.add_child("start");
218 }
219 
221 {
223  cmd.add_child("surrender")["side_number"] = side_number;
224 }
225 
226 void replay::add_countdown_update(int value, int team)
227 {
228  config& cmd = add_command();
229  config val;
230  val["value"] = value;
231  val["team"] = team;
232  cmd.add_child("countdown_update", std::move(val));
233 }
234 void replay::add_synced_command(const std::string& name, const config& command)
235 {
236  config& cmd = add_command();
237  cmd.add_child(name,command);
238  cmd["from_side"] = resources::controller->current_side();
239  LOG_REPLAY << "add_synced_command: \n" << cmd.debug();
240 }
241 
242 
243 
244 void replay::user_input(const std::string &name, const config &input, int from_side)
245 {
246  config& cmd = add_command();
247  cmd["dependent"] = true;
248  if(from_side == -1)
249  {
250  cmd["from_side"] = "server";
251  }
252  else
253  {
254  cmd["from_side"] = from_side;
255  }
256  cmd.add_child(name, input);
257 }
258 
260 {
261  assert(label);
263  config val;
264 
265  label->write(val);
266 
267  cmd.add_child("label",val);
268 }
269 
270 void replay::clear_labels(const std::string& team_name, bool force)
271 {
273 
274  config val;
275  val["team_name"] = team_name;
276  val["force"] = force;
277  cmd.add_child("clear_labels", std::move(val));
278 }
279 
280 void replay::add_rename(const std::string& name, const map_location& loc)
281 {
282  config& cmd = add_command();
283  cmd["async"] = true; // Not undoable, but depends on moves/recruits that are
284  config val;
285  loc.write(val);
286  val["name"] = name;
287  cmd.add_child("rename", std::move(val));
288 }
289 
290 
291 void replay::end_turn(int next_player_number)
292 {
293  config& cmd = add_command();
294  config& end_turn = cmd.add_child("end_turn");
295 
296  end_turn["next_player_number"] = next_player_number;
297 }
298 
299 
300 void replay::add_log_data(const std::string &key, const std::string &var)
301 {
302  config& ulog = base_->get_upload_log();
303  ulog[key] = var;
304 }
305 
306 void replay::add_log_data(const std::string &category, const std::string &key, const std::string &var)
307 {
308  config& ulog = base_->get_upload_log();
309  config& cat = ulog.child_or_add(category);
310  cat[key] = var;
311 }
312 
313 void replay::add_log_data(const std::string &category, const std::string &key, const config &c)
314 {
315  config& ulog = base_->get_upload_log();
316  config& cat = ulog.child_or_add(category);
317  cat.add_child(key,c);
318 }
319 
321 {
322  return add_chat_message_location(base_->get_pos() - 1);
323 }
324 
326 {
327  assert(base_->get_command_at(pos).has_child("speak"));
329  message_locations.push_back(pos);
330  return true;
331  }
332  else {
333  return false;
334  }
335 }
336 
338 {
340  cmd.add_child("speak",cfg);
342 }
343 
344 void replay::add_chat_log_entry(const config &cfg, std::back_insert_iterator<std::vector<chat_msg>> &i) const
345 {
346 
347  if (!prefs::get().parse_should_show_lobby_join(cfg["id"], cfg["message"])) return;
348  if (prefs::get().is_ignored(cfg["id"])) return;
349  *i = chat_msg(cfg);
350 }
351 
353 {
355  std::vector<int>::reverse_iterator loc_it;
356  for (loc_it = message_locations.rbegin(); loc_it != message_locations.rend() && index < *loc_it;++loc_it)
357  {
358  --(*loc_it);
359  }
360 }
361 
362 // cached message log
363 static std::vector< chat_msg > message_log;
364 
365 
366 const std::vector<chat_msg>& replay::build_chat_log() const
367 {
368  message_log.clear();
369  std::vector<int>::const_iterator loc_it;
370  int last_location = 0;
371  std::back_insert_iterator<std::vector < chat_msg >> chat_log_appender( back_inserter(message_log));
372  for (loc_it = message_locations.begin(); loc_it != message_locations.end(); ++loc_it)
373  {
374  last_location = *loc_it;
375 
376  const config &speak = command(last_location).mandatory_child("speak");
377  add_chat_log_entry(speak, chat_log_appender);
378 
379  }
380  return message_log;
381 }
382 
384 {
385  config res;
386  for (int cmd = sent_upto_; cmd < ncommands(); ++cmd)
387  {
388  config &c = command(cmd);
389  //prevent creating 'blank' attribute values during checks
390  const config &cc = c;
391  if ((data_type == ALL_DATA || !cc["undo"].to_bool(true)) && !cc["sent"].to_bool(false))
392  {
393  res.add_child("command", c);
394  c["sent"] = true;
395  }
396  }
397  if(data_type == ALL_DATA) {
398  sent_upto_ = ncommands();
399  }
400  return res;
401 }
402 
403 void replay::redo(const config& cfg, bool set_to_end)
404 {
405  assert(base_->get_pos() == ncommands());
406  int old_pos = base_->get_pos();
407  for (const config &cmd : cfg.child_range("command"))
408  {
409  base_->add_child() = cmd;
410  }
411  if(set_to_end) {
412  //The engine does not execute related wml events so mark ad dpendent actions as handled
413  base_->set_to_end();
414  }
415  else {
416  //The engine does execute related wml events so it needs to reprocess depndent choices
417  base_->set_pos(old_pos + 1);
418  }
419 
420 }
421 
422 
423 
425 {
426  for (int cmd_num = base_->get_pos() - 1; cmd_num >= 0; --cmd_num)
427  {
428  config &c = command(cmd_num);
429  const config &cc = c;
430  if (cc["dependent"].to_bool(false) || !cc["undo"].to_bool(true) || cc["async"].to_bool(false))
431  {
432  continue;
433  }
434  return c;
435  }
436  ERR_REPLAY << "replay::get_last_real_command called with no existent command.";
437  assert(false && "replay::get_last_real_command called with no existent command.");
438  throw "replay::get_last_real_command called with no existent command.";
439 }
440 /**
441  * fixes a rename command when undoing a earlier command.
442  * @return: true if the command should be removed.
443  */
444 static bool fix_rename_command(const config& c, config& async_child)
445 {
446  if (const auto child = c.optional_child("move"))
447  {
448  // A unit's move is being undone.
449  // Repair unsynced cmds whose locations depend on that unit's location.
450  std::vector<map_location> steps;
451 
452  try {
453  read_locations(child.value(), steps);
454  } catch(const bad_lexical_cast &) {
455  WRN_REPLAY << "Warning: Path data contained something which could not be parsed to a sequence of locations:" << "\n config = " << child->debug();
456  }
457 
458  if (steps.empty()) {
459  ERR_REPLAY << "trying to undo a move using an empty path";
460  }
461  else {
462  const map_location &src = steps.front();
463  const map_location &dst = steps.back();
464  map_location aloc(async_child);
465  if (dst == aloc) src.write(async_child);
466  }
467  }
468  else
469  {
470  auto loc = c.optional_child("recruit");
471  if(!loc) {
472  loc = c.optional_child("recall");
473  }
474 
475  if(loc) {
476  // A unit is being un-recruited or un-recalled.
477  // Remove unsynced commands that would act on that unit.
478  map_location src(loc.value());
479  map_location aloc(async_child);
480  if (src == aloc) {
481  return true;
482  }
483  }
484  }
485  return false;
486 }
487 
489 {
490  assert(dst.empty());
491  //assert that we are not undoing a command which we didn't execute yet.
492  assert(at_end());
493 
494  //calculate the index of the last synced user action (which we want to undo).
495  int cmd_index = ncommands() - 1;
496  for (; cmd_index >= 0; --cmd_index)
497  {
498  //"undo"=no means speak/label/remove_label, especially attack, recruits etc. have "undo"=yes
499  //"async"=yes means rename_unit
500  //"dependent"=true means user input
501  const config &c = command(cmd_index);
502 
503  if(c["undo"].to_bool(true) && !c["async"].to_bool(false) && !c["dependent"].to_bool(false))
504  {
505  if(c["sent"].to_bool(false))
506  {
507  ERR_REPLAY << "trying to undo a command that was already sent.";
508  return;
509  }
510  else
511  {
512  break;
513  }
514  }
515  }
516 
517  if (cmd_index < 0)
518  {
519  ERR_REPLAY << "trying to undo a command but no command was found.";
520  return;
521  }
522  //Fix the [command]s after the undone action. This includes dependent commands for that user actions and async user action.
523  for(int i = ncommands() - 1; i >= cmd_index; --i)
524  {
525  config &c = command(i);
526  const config &cc = c;
527  if(!cc["undo"].to_bool(true))
528  {
529  //Leave these commands on the replay.
530  }
531  else if(cc["async"].to_bool(false))
532  {
533  if(auto rename = c.optional_child("rename"))
534  {
535  if(fix_rename_command(command(cmd_index), rename.value()))
536  {
537  //remove the command from the replay if fix_rename_command requested it.
538  remove_command(i);
539  }
540  }
541  }
542  else if(cc["dependent"].to_bool(false) || i == cmd_index)
543  {
544  //we loop backwars so we must insert new insert at beginning to preserve order.
545  dst.add_child_at("command", config(), 0).swap(c);
546  remove_command(i);
547  }
548  else
549  {
550  ERR_REPLAY << "Couldn't handle command:\n" << cc << "\nwhen undoing.";
551  }
552  }
553  set_to_end();
554 }
555 
557 {
558  config dummy;
559  undo_cut(dummy);
560 }
561 
563 {
564  config & retv = base_->get_command_at(n);
565  return retv;
566 }
567 
568 int replay::ncommands() const
569 {
570  return base_->size();
571 }
572 
574 {
575  // If we weren't at the end of the replay we should skip one or more
576  // commands.
577  assert(at_end());
578  config& retv = base_->add_child();
579  set_to_end();
580  return retv;
581 }
582 
584 {
585  const bool was_at_end = at_end();
587  r["undo"] = false;
588  if(was_at_end) {
589  base_->set_pos(base_->get_pos() + 1);
590  }
591  assert(was_at_end == at_end());
592  return r;
593 }
594 
596 {
597  base_->set_pos(0);
598 }
599 
601 {
602 
603  if (base_->get_pos() > 0)
604  base_->set_pos(base_->get_pos() - 1);
605 }
606 
608 {
609  if (at_end())
610  return nullptr;
611 
612  LOG_REPLAY << "up to replay action " << base_->get_pos() + 1 << '/' << ncommands();
613 
614  config* retv = &command(base_->get_pos());
615  base_->set_pos(base_->get_pos() + 1);
616  return retv;
617 }
618 
620 {
621  if (at_end())
622  return nullptr;
623 
624  LOG_REPLAY << "up to replay action " << base_->get_pos() + 1 << '/' << ncommands();
625 
626  config* retv = &command(base_->get_pos());
627  return retv;
628 }
629 
630 
631 bool replay::at_end() const
632 {
633  assert(base_->get_pos() <= ncommands());
634  return base_->get_pos() == ncommands();
635 }
636 
638 {
639  base_->set_to_end();
640 }
641 
642 bool replay::empty() const
643 {
644  return ncommands() == 0;
645 }
646 
648 {
649  for (const config &cmd : cfg.child_range("command"))
650  {
651  config &cmd_cfg = base_->insert_command(base_->size());
652  cmd_cfg = cmd;
653  if(mark == MARK_AS_SENT) {
654  cmd_cfg["sent"] = true;
655  }
656  if(cmd_cfg.has_child("speak")) {
657  cmd_cfg["undo"] = false;
658  }
659  }
660 }
662 {
663  //this method would confuse the value of 'pos' otherwise
664  VALIDATE(base_->get_pos() == 0, _("The file you have tried to load is corrupt"));
665  //since pos is 0, at_end() is equivalent to empty()
666  if(at_end() || !base_->get_command_at(0).has_child("start"))
667  {
668  base_->insert_command(0) = config {"start", config(), "sent", true};
669  return true;
670  }
671  else
672  {
673  return false;
674  }
675 }
676 
678 {
679  if(command.all_children_count() != 1) {
681  }
682  auto [key, _] = command.all_children_view().front();
683  if(key == "speak" || key == "label" || key == "surrender" || key == "clear_labels" || key == "rename" || key == "countdown_update") {
685  }
686  if(command["dependent"].to_bool(false)) {
688  }
690 }
691 
692 REPLAY_RETURN do_replay(bool one_move)
693 {
694  log_scope("do replay");
695 
696  if (!resources::controller->is_skipping_replay()) {
698  }
699 
700  return do_replay_handle(one_move);
701 }
702 /**
703  @returns:
704  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)
705  else if we found an [end_turn] we return REPLAY_FOUND_END_TURN
706  else if we found a player action and one_move=true we return REPLAY_FOUND_END_MOVE
707  else (<=> we reached the end of the replay) we return REPLAY_RETURN_AT_END
708 */
710 {
711 
712  //team &current_team = resources::gameboard->get_team(side_num);
713 
714  const int side_num = resources::controller->current_side();
715  while(true)
716  {
718  const bool is_synced = synced_context::is_synced();
719  const bool is_unsynced = synced_context::get_synced_state() == synced_context::UNSYNCED;
720 
721  DBG_REPLAY << "in do replay with is_synced=" << is_synced << "is_unsynced=" << is_unsynced;
722 
723  if (cfg != nullptr)
724  {
725  DBG_REPLAY << "Replay data:\n" << *cfg;
726  }
727  else
728  {
729  DBG_REPLAY << "Replay data at end";
730  return REPLAY_RETURN_AT_END;
731  }
732 
733 
734  const auto ch_itors = cfg->all_children_view();
735  //if there is an empty command tag or a start tag
736  if (ch_itors.empty() || cfg->has_child("start"))
737  {
738  //this shouldn't happen anymore because replaycontroller now moves over the [start] with get_next_action
739  //also we removed the the "add empty replay entry at scenario reload" behavior.
740  ERR_REPLAY << "found "<< cfg->debug() <<" in replay";
741  //do nothing
742  }
743  else if (auto speak = cfg->optional_child("speak"))
744  {
745  const std::string &team_name = speak["to_sides"];
746  const std::string &speaker_name = speak["id"];
747  const std::string &message = speak["message"];
748 
749  bool is_whisper = (speaker_name.find("whisper: ") == 0);
750  if(resources::recorder->add_chat_message_location()) {
751  DBG_REPLAY << "tried to add a chat message twice.";
752  if (!resources::controller->is_skipping_replay() || is_whisper) {
753  int side = speak["side"].to_int();
754  game_display::get_singleton()->get_chat_manager().add_chat_message(get_time(*speak), speaker_name, side, message,
755  (team_name.empty() ? events::chat_handler::MESSAGE_PUBLIC
758  }
759  }
760  }
761  else if (cfg->has_child("surrender"))
762  {
763  //prevent sending of a synced command for surrender
764  }
765  else if (auto label_config = cfg->optional_child("label"))
766  {
767  terrain_label label(display::get_singleton()->labels(), *label_config);
768 
770  label.text(),
771  label.creator(),
772  label.team_name(),
773  label.color());
774  }
775  else if (auto clear_labels = cfg->optional_child("clear_labels"))
776  {
777  display::get_singleton()->labels().clear(std::string(clear_labels["team_name"]), clear_labels["force"].to_bool());
778  }
779  else if (auto rename = cfg->optional_child("rename"))
780  {
781  const map_location loc(*rename);
782  const std::string &name = rename["name"];
783 
785  if (u.valid() && !u->unrenamable()) {
786  u->rename(name);
787  } else {
788  // Users can rename units while it's being killed or at another machine.
789  // This since the player can rename units when it's not his/her turn.
790  // There's not a simple way to prevent that so in that case ignore the
791  // rename instead of throwing an OOS.
792  // The same way it is possible that an unrenamable unit moves to a
793  // hex where previously a renamable unit was.
794  WRN_REPLAY << "attempt to rename unit at location: "
795  << loc << (u.valid() ? ", which is unrenamable" : ", where none exists (anymore)");
796  }
797  }
798 
799  else if (cfg->has_child("init_side"))
800  {
801 
802  if(!is_unsynced)
803  {
804  replay::process_error("found side initialization in replay expecting a user choice\n" );
806  return REPLAY_FOUND_DEPENDENT;
807  }
808  else
809  {
811  if (one_move) {
812  return REPLAY_FOUND_INIT_TURN;
813  }
814  }
815  }
816 
817  //if there is an end turn directive
818  else if (auto end_turn = cfg->optional_child("end_turn"))
819  {
820  if(!is_unsynced)
821  {
822  replay::process_error("found turn end in replay while expecting a user choice\n" );
824  return REPLAY_FOUND_DEPENDENT;
825  }
826  else
827  {
828  if (auto cfg_verify = cfg->optional_child("verify")) {
829  verify(resources::gameboard->units(), *cfg_verify);
830  }
831  if(int npn = end_turn["next_player_number"].to_int(0); npn > 0) {
833  }
835  return REPLAY_FOUND_END_TURN;
836  }
837  }
838  else if (auto countdown_update = cfg->optional_child("countdown_update"))
839  {
840  auto val = chrono::parse_duration<std::chrono::milliseconds>(countdown_update["value"]);
841  int tval = countdown_update["team"].to_int();
842  if (tval <= 0 || tval > static_cast<int>(resources::gameboard->teams().size())) {
843  std::stringstream errbuf;
844  errbuf << "Illegal countdown update \n"
845  << "Received update for :" << tval << " Current user :"
846  << side_num << "\n" << " Updated value :" << val.count();
847 
848  replay::process_error(errbuf.str());
849  } else {
851  }
852  }
853  else if ((*cfg)["dependent"].to_bool(false))
854  {
855  if(is_unsynced)
856  {
857  replay::process_error("found dependent command in replay while is_synced=false\n" );
858  //ignore this command
859  continue;
860  }
861  //this means user choice.
862  // it never makes sense to try to execute a user choice.
863  // but we are called from
864  // the only other option for "dependent" command is checksum which is already checked.
865  assert(cfg->all_children_count() == 1);
866  auto [child_name, _] = cfg->all_children_view().front();
867  DBG_REPLAY << "got an dependent action name = " << child_name;
869  return REPLAY_FOUND_DEPENDENT;
870  }
871  else
872  {
873  //we checked for empty commands at the beginning.
874  const auto [commandname, data] = cfg->all_children_view().front();
875 
876  if(!is_unsynced)
877  {
878  replay::process_error("found [" + commandname + "] command in replay expecting a user choice\n" );
880  return REPLAY_FOUND_DEPENDENT;
881  }
882  else
883  {
884  LOG_REPLAY << "found commandname " << commandname << "in replay";
885 
886  if((*cfg)["from_side"].to_int(0) != resources::controller->current_side()) {
887  ERR_REPLAY << "received a synced [command] from side " << (*cfg)["from_side"].to_int(0) << ". Expacted was a [command] from side " << resources::controller->current_side();
888  }
889  else if((*cfg)["side_invalid"].to_bool(false)) {
890  ERR_REPLAY << "received a synced [command] from side " << (*cfg)["from_side"].to_int(0) << ". Sent from wrong client.";
891  }
892  /*
893  we need to use the undo stack during replays in order to make delayed shroud updated work.
894  */
895  auto spectator = action_spectator([](const std::string& message) { replay::process_error(message); });
896  synced_context::run(commandname, data, spectator);
897  if(resources::controller->is_regular_game_end()) {
898  return REPLAY_FOUND_END_LEVEL;
899  }
900  if (one_move) {
901  return REPLAY_FOUND_END_MOVE;
902  }
903  }
904  }
905 
906  if (auto child = cfg->optional_child("verify")) {
907  verify(resources::gameboard->units(), *child);
908  }
909  }
910 }
map_location loc
Definition: move.cpp:172
static auto & dummy
virtual ~chat_msg()
Definition: replay.cpp:164
std::string color_
Definition: replay.hpp:40
std::string nick_
Definition: replay.hpp:41
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:158
config & mandatory_child(config_key_type key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition: config.cpp:362
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:796
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:312
child_itors child_range(config_key_type key)
Definition: config.cpp:268
config & child_or_add(config_key_type key)
Returns a reference to the first child with the given key.
Definition: config.cpp:401
std::size_t all_children_count() const
Definition: config.cpp:302
std::string debug() const
Definition: config.cpp:1230
optional_config_impl< config > optional_child(config_key_type 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 & add_child(config_key_type key)
Definition: config.cpp:436
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:1455
map_labels & labels()
Definition: display.cpp:2425
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:568
config * peek_next_action()
Definition: replay.cpp:619
void add_start()
Definition: replay.cpp:213
void add_rename(const std::string &name, const map_location &loc)
Definition: replay.cpp:280
config & add_nonundoable_command()
adds a new command to the command list at the current position.
Definition: replay.cpp:583
bool add_start_if_not_there_yet()
Definition: replay.cpp:661
void undo_cut(config &dst)
Definition: replay.cpp:488
void set_to_end()
Definition: replay.cpp:637
config & get_last_real_command()
Definition: replay.cpp:424
void add_config(const config &cfg, MARK_SENT mark=MARK_AS_UNSENT)
Definition: replay.cpp:647
void add_label(const terrain_label *)
Definition: replay.cpp:259
void add_synced_command(const std::string &name, const config &command)
Definition: replay.cpp:234
MARK_SENT
Definition: replay.hpp:132
@ MARK_AS_SENT
Definition: replay.hpp:132
DATA_TYPE
Definition: replay.hpp:107
@ ALL_DATA
Definition: replay.hpp:107
void end_turn(int next_player_number)
Definition: replay.cpp:291
void add_surrender(int side_number)
Definition: replay.cpp:220
void speak(const config &cfg)
Definition: replay.cpp:337
void clear_labels(const std::string &, bool)
Definition: replay.cpp:270
const std::vector< chat_msg > & build_chat_log() const
Definition: replay.cpp:366
config & command(int) const
Definition: replay.cpp:562
replay(replay_recorder_base &base)
Definition: replay.cpp:168
void remove_command(int)
Definition: replay.cpp:352
void user_input(const std::string &name, const config &input, int from_side)
adds a user_input to the replay
Definition: replay.cpp:244
void redo(const config &dst, bool set_to_end=false)
Definition: replay.cpp:403
bool at_end() const
Definition: replay.cpp:631
void add_unit_checksum(const map_location &loc, config &cfg)
Definition: replay.cpp:192
void revert_action()
Definition: replay.cpp:600
void undo()
Definition: replay.cpp:556
std::vector< int > message_locations
Definition: replay.hpp:163
static void process_error(const std::string &msg)
Definition: replay.cpp:185
void init_side()
Definition: replay.cpp:205
void start_replay()
Definition: replay.cpp:595
void delete_upcoming_commands()
Definition: replay.cpp:174
config get_unsent_commands(DATA_TYPE data_type)
Definition: replay.cpp:383
void add_countdown_update(int value, int team)
Definition: replay.cpp:226
replay_recorder_base * base_
Definition: replay.hpp:161
bool add_chat_message_location()
adds a chat message if it wasn't added yet.
Definition: replay.cpp:320
bool empty() const
Definition: replay.cpp:642
int sent_upto_
Definition: replay.hpp:162
void add_chat_log_entry(const config &speak, std::back_insert_iterator< std::vector< chat_msg >> &i) const
Definition: replay.cpp:344
void add_log_data(const std::string &key, const std::string &var)
Definition: replay.cpp:300
config & add_command()
Adds a new empty command to the command list at the end.
Definition: replay.cpp:573
config * get_next_action()
Definition: replay.cpp:607
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
static std::string get_side_highlight_pango(int side)
Definition: team.cpp:1016
void set_countdown_time(const std::chrono::milliseconds &amount) const
Definition: team.hpp:234
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:1032
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.
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:480
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:85
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:677
static bool fix_rename_command(const config &c, config &async_child)
fixes a rename command when undoing a earlier command.
Definition: replay.cpp:444
#define WRN_REPLAY
Definition: replay.cpp:51
REPLAY_RETURN do_replay_handle(bool one_move)
Definition: replay.cpp:709
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:692
#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:363
#define ERR_REPLAY
Definition: replay.cpp:52
Replay control code.
REPLAY_RETURN
Definition: replay.hpp:167
@ REPLAY_FOUND_DEPENDENT
Definition: replay.hpp:169
@ REPLAY_FOUND_INIT_TURN
Definition: replay.hpp:171
@ REPLAY_FOUND_END_LEVEL
Definition: replay.hpp:173
@ REPLAY_RETURN_AT_END
Definition: replay.hpp:168
@ REPLAY_FOUND_END_MOVE
Definition: replay.hpp:172
@ REPLAY_FOUND_END_TURN
Definition: replay.hpp:170
REPLAY_ACTION_TYPE
Definition: replay.hpp:47
rect dst
Location on the final composed sheet.
rect src
Non-transparent portion of the surface to compose.
Thrown when a lexical_cast fails.
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:2868
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.