The Battle for Wesnoth  1.19.5+dev
replay.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
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 "whiteboard/manager.hpp"
41 #include "wml_exception.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  , sent_upto_(base.size())
186  , message_locations()
187 {}
188 
190 {
192 }
193 /*
194  TODO: there should be different types of OOS messages:
195  1)the normal OOS message
196  2) the 'is guaranteed you'll get an assertion error after this and therefore you cannot continue' OOS message
197  3) the 'do you want to overwrite calculated data with the data stored in replay' OOS error message.
198 
199 */
200 void replay::process_error(const std::string& msg)
201 {
202  ERR_REPLAY << msg;
203 
204  resources::controller->process_oos(msg); // might throw quit_game_exception()
205 }
206 
208 {
209  if(! game_config::mp_debug) {
210  return;
211  }
212  config& cc = cfg.add_child("checksum");
213  loc.write(cc);
215  assert(u.valid());
216  cc["value"] = get_checksum(*u);
217 }
218 
219 
221 {
222  config& cmd = add_command();
224  init_side["side_number"] = resources::controller->current_side();
225  cmd.add_child("init_side", init_side);
226 }
227 
229 {
230  config& cmd = add_command();
231  cmd["sent"] = true;
232  cmd.add_child("start");
233 }
234 
236 {
238  cmd.add_child("surrender")["side_number"] = side_number;
239 }
240 
241 void replay::add_countdown_update(int value, int team)
242 {
243  config& cmd = add_command();
244  config val;
245  val["value"] = value;
246  val["team"] = team;
247  cmd.add_child("countdown_update", std::move(val));
248 }
249 void replay::add_synced_command(const std::string& name, const config& command)
250 {
251  config& cmd = add_command();
252  cmd.add_child(name,command);
253  cmd["from_side"] = resources::controller->current_side();
254  LOG_REPLAY << "add_synced_command: \n" << cmd.debug();
255 }
256 
257 
258 
259 void replay::user_input(const std::string &name, const config &input, int from_side)
260 {
261  config& cmd = add_command();
262  cmd["dependent"] = true;
263  if(from_side == -1)
264  {
265  cmd["from_side"] = "server";
266  }
267  else
268  {
269  cmd["from_side"] = from_side;
270  }
271  cmd.add_child(name, input);
272 }
273 
275 {
276  assert(label);
278  config val;
279 
280  label->write(val);
281 
282  cmd.add_child("label",val);
283 }
284 
285 void replay::clear_labels(const std::string& team_name, bool force)
286 {
288 
289  config val;
290  val["team_name"] = team_name;
291  val["force"] = force;
292  cmd.add_child("clear_labels", std::move(val));
293 }
294 
295 void replay::add_rename(const std::string& name, const map_location& loc)
296 {
297  config& cmd = add_command();
298  cmd["async"] = true; // Not undoable, but depends on moves/recruits that are
299  config val;
300  loc.write(val);
301  val["name"] = name;
302  cmd.add_child("rename", std::move(val));
303 }
304 
305 
306 void replay::end_turn(int next_player_number)
307 {
308  config& cmd = add_command();
309  config& end_turn = cmd.add_child("end_turn");
310 
311  end_turn["next_player_number"] = next_player_number;
312 }
313 
314 
315 void replay::add_log_data(const std::string &key, const std::string &var)
316 {
317  config& ulog = base_->get_upload_log();
318  ulog[key] = var;
319 }
320 
321 void replay::add_log_data(const std::string &category, const std::string &key, const std::string &var)
322 {
323  config& ulog = base_->get_upload_log();
324  config& cat = ulog.child_or_add(category);
325  cat[key] = var;
326 }
327 
328 void replay::add_log_data(const std::string &category, const std::string &key, const config &c)
329 {
330  config& ulog = base_->get_upload_log();
331  config& cat = ulog.child_or_add(category);
332  cat.add_child(key,c);
333 }
334 
336 {
337  return add_chat_message_location(base_->get_pos() - 1);
338 }
339 
341 {
342  assert(base_->get_command_at(pos).has_child("speak"));
343  if(std::find(message_locations.begin(), message_locations.end(), pos) == message_locations.end()) {
344  message_locations.push_back(pos);
345  return true;
346  }
347  else {
348  return false;
349  }
350 }
351 
352 void replay::speak(const config& cfg)
353 {
355  cmd.add_child("speak",cfg);
357 }
358 
359 void replay::add_chat_log_entry(const config &cfg, std::back_insert_iterator<std::vector<chat_msg>> &i) const
360 {
361 
362  if (!prefs::get().parse_should_show_lobby_join(cfg["id"], cfg["message"])) return;
363  if (prefs::get().is_ignored(cfg["id"])) return;
364  *i = chat_msg(cfg);
365 }
366 
368 {
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).mandatory_child("speak");
392  add_chat_log_entry(speak, chat_log_appender);
393 
394  }
395  return message_log;
396 }
397 
399 {
400  config res;
401  for (int cmd = sent_upto_; cmd < ncommands(); ++cmd)
402  {
403  config &c = command(cmd);
404  //prevent creating 'blank' attribute values during checks
405  const config &cc = c;
406  if ((data_type == ALL_DATA || !cc["undo"].to_bool(true)) && !cc["sent"].to_bool(false))
407  {
408  res.add_child("command", c);
409  c["sent"] = true;
410  }
411  }
412  if(data_type == ALL_DATA) {
413  sent_upto_ = ncommands();
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  return retv;
581 }
582 
583 int replay::ncommands() const
584 {
585  return base_->size();
586 }
587 
589 {
590  // If we weren't at the end of the replay we should skip one or more
591  // commands.
592  assert(at_end());
593  config& retv = base_->add_child();
594  set_to_end();
595  return retv;
596 }
597 
599 {
600  const bool was_at_end = at_end();
602  r["undo"] = false;
603  if(was_at_end) {
604  base_->set_pos(base_->get_pos() + 1);
605  }
606  assert(was_at_end == at_end());
607  return r;
608 }
609 
611 {
612  base_->set_pos(0);
613 }
614 
616 {
617 
618  if (base_->get_pos() > 0)
619  base_->set_pos(base_->get_pos() - 1);
620 }
621 
623 {
624  if (at_end())
625  return nullptr;
626 
627  LOG_REPLAY << "up to replay action " << base_->get_pos() + 1 << '/' << ncommands();
628 
629  config* retv = &command(base_->get_pos());
630  base_->set_pos(base_->get_pos() + 1);
631  return retv;
632 }
633 
635 {
636  if (at_end())
637  return nullptr;
638 
639  LOG_REPLAY << "up to replay action " << base_->get_pos() + 1 << '/' << ncommands();
640 
641  config* retv = &command(base_->get_pos());
642  return retv;
643 }
644 
645 
646 bool replay::at_end() const
647 {
648  assert(base_->get_pos() <= ncommands());
649  return base_->get_pos() == ncommands();
650 }
651 
653 {
654  base_->set_to_end();
655 }
656 
657 bool replay::empty() const
658 {
659  return ncommands() == 0;
660 }
661 
663 {
664  for (const config &cmd : cfg.child_range("command"))
665  {
666  config &cmd_cfg = base_->insert_command(base_->size());
667  cmd_cfg = cmd;
668  if(mark == MARK_AS_SENT) {
669  cmd_cfg["sent"] = true;
670  }
671  if(cmd_cfg.has_child("speak")) {
672  cmd_cfg["undo"] = false;
673  }
674  }
675 }
677 {
678  //this method would confuse the value of 'pos' otherwise
679  VALIDATE(base_->get_pos() == 0, _("The file you have tried to load is corrupt"));
680  //since pos is 0, at_end() is equivalent to empty()
681  if(at_end() || !base_->get_command_at(0).has_child("start"))
682  {
683  base_->insert_command(0) = config {"start", config(), "sent", true};
684  return true;
685  }
686  else
687  {
688  return false;
689  }
690 }
691 
693 {
694  if(command.all_children_count() != 1) {
696  }
697  auto [key, _] = command.all_children_view().front();
698  if(key == "speak" || key == "label" || key == "surrender" || key == "clear_labels" || key == "rename" || key == "countdown_update") {
700  }
701  if(command["dependent"].to_bool(false)) {
703  }
705 }
706 
707 REPLAY_RETURN do_replay(bool one_move)
708 {
709  log_scope("do replay");
710 
711  if (!resources::controller->is_skipping_replay()) {
713  }
714 
715  return do_replay_handle(one_move);
716 }
717 /**
718  @returns:
719  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)
720  else if we found an [end_turn] we return REPLAY_FOUND_END_TURN
721  else if we found a player action and one_move=true we return REPLAY_FOUND_END_MOVE
722  else (<=> we reached the end of the replay) we return REPLAY_RETURN_AT_END
723 */
725 {
726 
727  //team &current_team = resources::gameboard->get_team(side_num);
728 
729  const int side_num = resources::controller->current_side();
730  while(true)
731  {
733  const bool is_synced = synced_context::is_synced();
734  const bool is_unsynced = synced_context::get_synced_state() == synced_context::UNSYNCED;
735 
736  DBG_REPLAY << "in do replay with is_synced=" << is_synced << "is_unsynced=" << is_unsynced;
737 
738  if (cfg != nullptr)
739  {
740  DBG_REPLAY << "Replay data:\n" << *cfg;
741  }
742  else
743  {
744  DBG_REPLAY << "Replay data at end";
745  return REPLAY_RETURN_AT_END;
746  }
747 
748 
749  const auto ch_itors = cfg->all_children_view();
750  //if there is an empty command tag or a start tag
751  if (ch_itors.empty() || cfg->has_child("start"))
752  {
753  //this shouldn't happen anymore because replaycontroller now moves over the [start] with get_next_action
754  //also we removed the the "add empty replay entry at scenario reload" behavior.
755  ERR_REPLAY << "found "<< cfg->debug() <<" in replay";
756  //do nothing
757  }
758  else if (auto speak = cfg->optional_child("speak"))
759  {
760  const std::string &team_name = speak["to_sides"];
761  const std::string &speaker_name = speak["id"];
762  const std::string &message = speak["message"];
763 
764  bool is_whisper = (speaker_name.find("whisper: ") == 0);
765  if(resources::recorder->add_chat_message_location()) {
766  DBG_REPLAY << "tried to add a chat message twice.";
767  if (!resources::controller->is_skipping_replay() || is_whisper) {
768  int side = speak["side"].to_int();
769  game_display::get_singleton()->get_chat_manager().add_chat_message(get_time(*speak), speaker_name, side, message,
770  (team_name.empty() ? events::chat_handler::MESSAGE_PUBLIC
773  }
774  }
775  }
776  else if (cfg->has_child("surrender"))
777  {
778  //prevent sending of a synced command for surrender
779  }
780  else if (auto label_config = cfg->optional_child("label"))
781  {
782  terrain_label label(display::get_singleton()->labels(), *label_config);
783 
785  label.text(),
786  label.creator(),
787  label.team_name(),
788  label.color());
789  }
790  else if (auto clear_labels = cfg->optional_child("clear_labels"))
791  {
792  display::get_singleton()->labels().clear(std::string(clear_labels["team_name"]), clear_labels["force"].to_bool());
793  }
794  else if (auto rename = cfg->optional_child("rename"))
795  {
796  const map_location loc(*rename);
797  const std::string &name = rename["name"];
798 
800  if (u.valid() && !u->unrenamable()) {
801  u->rename(name);
802  } else {
803  // Users can rename units while it's being killed or at another machine.
804  // This since the player can rename units when it's not his/her turn.
805  // There's not a simple way to prevent that so in that case ignore the
806  // rename instead of throwing an OOS.
807  // The same way it is possible that an unrenamable unit moves to a
808  // hex where previously a renamable unit was.
809  WRN_REPLAY << "attempt to rename unit at location: "
810  << loc << (u.valid() ? ", which is unrenamable" : ", where none exists (anymore)");
811  }
812  }
813 
814  else if (cfg->has_child("init_side"))
815  {
816 
817  if(!is_unsynced)
818  {
819  replay::process_error("found side initialization in replay expecting a user choice\n" );
821  return REPLAY_FOUND_DEPENDENT;
822  }
823  else
824  {
826  if (one_move) {
827  return REPLAY_FOUND_INIT_TURN;
828  }
829  }
830  }
831 
832  //if there is an end turn directive
833  else if (auto end_turn = cfg->optional_child("end_turn"))
834  {
835  if(!is_unsynced)
836  {
837  replay::process_error("found turn end in replay while expecting a user choice\n" );
839  return REPLAY_FOUND_DEPENDENT;
840  }
841  else
842  {
843  if (auto cfg_verify = cfg->optional_child("verify")) {
844  verify(resources::gameboard->units(), *cfg_verify);
845  }
846  if(int npn = end_turn["next_player_number"].to_int(0); npn > 0) {
848  }
850  return REPLAY_FOUND_END_TURN;
851  }
852  }
853  else if (auto countdown_update = cfg->optional_child("countdown_update"))
854  {
855  auto val = chrono::parse_duration<std::chrono::milliseconds>(countdown_update["value"]);
856  int tval = countdown_update["team"].to_int();
857  if (tval <= 0 || tval > static_cast<int>(resources::gameboard->teams().size())) {
858  std::stringstream errbuf;
859  errbuf << "Illegal countdown update \n"
860  << "Received update for :" << tval << " Current user :"
861  << side_num << "\n" << " Updated value :" << val.count();
862 
863  replay::process_error(errbuf.str());
864  } else {
866  }
867  }
868  else if ((*cfg)["dependent"].to_bool(false))
869  {
870  if(is_unsynced)
871  {
872  replay::process_error("found dependent command in replay while is_synced=false\n" );
873  //ignore this command
874  continue;
875  }
876  //this means user choice.
877  // it never makes sense to try to execute a user choice.
878  // but we are called from
879  // the only other option for "dependent" command is checksum which is already checked.
880  assert(cfg->all_children_count() == 1);
881  auto [child_name, _] = cfg->all_children_view().front();
882  DBG_REPLAY << "got an dependent action name = " << child_name;
884  return REPLAY_FOUND_DEPENDENT;
885  }
886  else
887  {
888  //we checked for empty commands at the beginning.
889  const auto [commandname, data] = cfg->all_children_view().front();
890 
891  if(!is_unsynced)
892  {
893  replay::process_error("found [" + commandname + "] command in replay expecting a user choice\n" );
895  return REPLAY_FOUND_DEPENDENT;
896  }
897  else
898  {
899  LOG_REPLAY << "found commandname " << commandname << "in replay";
900 
901  if((*cfg)["from_side"].to_int(0) != resources::controller->current_side()) {
902  ERR_REPLAY << "received a synced [command] from side " << (*cfg)["from_side"].to_int(0) << ". Expacted was a [command] from side " << resources::controller->current_side();
903  }
904  else if((*cfg)["side_invalid"].to_bool(false)) {
905  ERR_REPLAY << "received a synced [command] from side " << (*cfg)["from_side"].to_int(0) << ". Sent from wrong client.";
906  }
907  /*
908  we need to use the undo stack during replays in order to make delayed shroud updated work.
909  */
910  auto spectator = action_spectator([](const std::string& message) { replay::process_error(message); });
911  synced_context::run(commandname, data, spectator);
912  if(resources::controller->is_regular_game_end()) {
913  return REPLAY_FOUND_END_LEVEL;
914  }
915  if (one_move) {
916  return REPLAY_FOUND_END_MOVE;
917  }
918  }
919  }
920 
921  if (auto child = cfg->optional_child("verify")) {
922  verify(resources::gameboard->units(), *child);
923  }
924  }
925 }
virtual ~chat_msg()
Definition: replay.cpp:179
std::time_t time_
Definition: replay.hpp:43
std::string color_
Definition: replay.hpp:40
std::string nick_
Definition: replay.hpp:41
chat_msg(const config &cfg)
Definition: replay.cpp:148
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
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:366
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:810
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:316
child_itors child_range(config_key_type key)
Definition: config.cpp:272
config & child_or_add(config_key_type key)
Returns a reference to the first child with the given key.
Definition: config.cpp:405
std::size_t all_children_count() const
Definition: config.cpp:306
std::string debug() const
Definition: config.cpp:1240
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:384
config & add_child(config_key_type key)
Definition: config.cpp:440
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)
void recalculate_minimap()
Schedule the minimap for recalculation.
Definition: display.cpp:1576
map_labels & labels()
Definition: display.cpp:2552
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:111
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:147
void clear(const std::string &, bool force)
Definition: label.cpp:211
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:583
config * peek_next_action()
Definition: replay.cpp:634
void add_start()
Definition: replay.cpp:228
void add_rename(const std::string &name, const map_location &loc)
Definition: replay.cpp:295
config & add_nonundoable_command()
adds a new command to the command list at the current position.
Definition: replay.cpp:598
bool add_start_if_not_there_yet()
Definition: replay.cpp:676
void undo_cut(config &dst)
Definition: replay.cpp:503
void set_to_end()
Definition: replay.cpp:652
config & get_last_real_command()
Definition: replay.cpp:439
void add_config(const config &cfg, MARK_SENT mark=MARK_AS_UNSENT)
Definition: replay.cpp:662
void add_label(const terrain_label *)
Definition: replay.cpp:274
void add_synced_command(const std::string &name, const config &command)
Definition: replay.cpp:249
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:306
void add_surrender(int side_number)
Definition: replay.cpp:235
void speak(const config &cfg)
Definition: replay.cpp:352
void clear_labels(const std::string &, bool)
Definition: replay.cpp:285
const std::vector< chat_msg > & build_chat_log() const
Definition: replay.cpp:381
config & command(int) const
Definition: replay.cpp:577
replay(replay_recorder_base &base)
Definition: replay.cpp:183
void remove_command(int)
Definition: replay.cpp:367
void user_input(const std::string &name, const config &input, int from_side)
adds a user_input to the replay
Definition: replay.cpp:259
void redo(const config &dst, bool set_to_end=false)
Definition: replay.cpp:418
bool at_end() const
Definition: replay.cpp:646
void add_unit_checksum(const map_location &loc, config &cfg)
Definition: replay.cpp:207
void revert_action()
Definition: replay.cpp:615
void undo()
Definition: replay.cpp:571
std::vector< int > message_locations
Definition: replay.hpp:163
static void process_error(const std::string &msg)
Definition: replay.cpp:200
void init_side()
Definition: replay.cpp:220
void start_replay()
Definition: replay.cpp:610
void delete_upcoming_commands()
Definition: replay.cpp:189
config get_unsent_commands(DATA_TYPE data_type)
Definition: replay.cpp:398
void add_countdown_update(int value, int team)
Definition: replay.cpp:241
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:335
bool empty() const
Definition: replay.cpp:657
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:359
void add_log_data(const std::string &key, const std::string &var)
Definition: replay.cpp:315
config & add_command()
Adds a new empty command to the command list at the end.
Definition: replay.cpp:588
config * get_next_action()
Definition: replay.cpp:622
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:75
static std::string get_side_highlight_pango(int side)
Definition: team.cpp:1031
void set_countdown_time(const std::chrono::milliseconds &amount) const
Definition: team.hpp:198
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
std::size_t i
Definition: function.cpp:1028
static auto & dummy
static std::string _(const char *str)
Definition: gettext.hpp:93
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:200
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:447
Standard logging facilities (interface).
#define log_scope(description)
Definition: log.hpp:278
int side_number
Definition: game_info.hpp:40
game_board * gameboard
Definition: resources.cpp:20
replay * recorder
Definition: resources.cpp:28
play_controller * controller
Definition: resources.cpp:21
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:70
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
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:178
REPLAY_ACTION_TYPE get_replay_action_type(const config &command)
Definition: replay.cpp:692
static bool fix_rename_command(const config &c, config &async_child)
fixes a rename command when undoing a earlier command.
Definition: replay.cpp:459
#define WRN_REPLAY
Definition: replay.cpp:50
REPLAY_RETURN do_replay_handle(bool one_move)
Definition: replay.cpp:724
#define DBG_REPLAY
Definition: replay.cpp:48
static lg::log_domain log_replay("replay")
REPLAY_RETURN do_replay(bool one_move)
Definition: replay.cpp:707
#define LOG_REPLAY
Definition: replay.cpp:49
static lg::log_domain log_random("random")
static void verify(const unit_map &units, const config &cfg)
Definition: replay.cpp:62
static std::time_t get_time(const config &speak)
Definition: replay.cpp:132
static std::vector< chat_msg > message_log
Definition: replay.cpp:378
#define ERR_REPLAY
Definition: replay.cpp:51
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:45
void write(config &cfg) const
Definition: location.cpp:225
bool empty() const
False if both w and h are > 0, true otherwise.
Definition: rect.cpp:48
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:2829
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.