The Battle for Wesnoth  1.19.7+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::chrono::system_clock::time_point get_time(const config& speak)
133 {
134  if(!speak["time"].empty()) {
135  return chrono::parse_timestamp(speak["time"]);
136  } else {
137  // fallback in case sender uses wesnoth that doesn't send timestamps
138  return std::chrono::system_clock::now();
139  }
140 }
141 
143  : color_()
144  , nick_()
145  , text_(cfg["message"].str())
146  , time_(get_time(cfg))
147 {
148  if(cfg["team_name"].empty() && cfg["to_sides"].empty())
149  {
150  nick_ = cfg["id"].str();
151  } else {
152  nick_ = "*"+cfg["id"].str()+"*";
153  }
154  int side = cfg["side"].to_int(0);
155  LOG_REPLAY << "side in message: " << side;
156  if (side==0) {
157  color_ = "white";//observers
158  } else {
160  }
161 }
162 
164 {
165 }
166 
168  : base_(&base)
169  , sent_upto_(base.size())
170  , message_locations()
171 {}
172 
174 {
176 }
177 /*
178  TODO: there should be different types of OOS messages:
179  1)the normal OOS message
180  2) the 'is guaranteed you'll get an assertion error after this and therefore you cannot continue' OOS message
181  3) the 'do you want to overwrite calculated data with the data stored in replay' OOS error message.
182 
183 */
184 void replay::process_error(const std::string& msg)
185 {
186  ERR_REPLAY << msg;
187 
188  resources::controller->process_oos(msg); // might throw quit_game_exception()
189 }
190 
192 {
193  if(! game_config::mp_debug) {
194  return;
195  }
196  config& cc = cfg.add_child("checksum");
197  loc.write(cc);
199  assert(u.valid());
200  cc["value"] = get_checksum(*u);
201 }
202 
203 
205 {
206  config& cmd = add_command();
208  init_side["side_number"] = resources::controller->current_side();
209  cmd.add_child("init_side", init_side);
210 }
211 
213 {
214  config& cmd = add_command();
215  cmd["sent"] = true;
216  cmd.add_child("start");
217 }
218 
220 {
222  cmd.add_child("surrender")["side_number"] = side_number;
223 }
224 
225 void replay::add_countdown_update(int value, int team)
226 {
227  config& cmd = add_command();
228  config val;
229  val["value"] = value;
230  val["team"] = team;
231  cmd.add_child("countdown_update", std::move(val));
232 }
233 void replay::add_synced_command(const std::string& name, const config& command)
234 {
235  config& cmd = add_command();
236  cmd.add_child(name,command);
237  cmd["from_side"] = resources::controller->current_side();
238  LOG_REPLAY << "add_synced_command: \n" << cmd.debug();
239 }
240 
241 
242 
243 void replay::user_input(const std::string &name, const config &input, int from_side)
244 {
245  config& cmd = add_command();
246  cmd["dependent"] = true;
247  if(from_side == -1)
248  {
249  cmd["from_side"] = "server";
250  }
251  else
252  {
253  cmd["from_side"] = from_side;
254  }
255  cmd.add_child(name, input);
256 }
257 
259 {
260  assert(label);
262  config val;
263 
264  label->write(val);
265 
266  cmd.add_child("label",val);
267 }
268 
269 void replay::clear_labels(const std::string& team_name, bool force)
270 {
272 
273  config val;
274  val["team_name"] = team_name;
275  val["force"] = force;
276  cmd.add_child("clear_labels", std::move(val));
277 }
278 
279 void replay::add_rename(const std::string& name, const map_location& loc)
280 {
281  config& cmd = add_command();
282  cmd["async"] = true; // Not undoable, but depends on moves/recruits that are
283  config val;
284  loc.write(val);
285  val["name"] = name;
286  cmd.add_child("rename", std::move(val));
287 }
288 
289 
290 void replay::end_turn(int next_player_number)
291 {
292  config& cmd = add_command();
293  config& end_turn = cmd.add_child("end_turn");
294 
295  end_turn["next_player_number"] = next_player_number;
296 }
297 
298 
299 void replay::add_log_data(const std::string &key, const std::string &var)
300 {
301  config& ulog = base_->get_upload_log();
302  ulog[key] = var;
303 }
304 
305 void replay::add_log_data(const std::string &category, const std::string &key, const std::string &var)
306 {
307  config& ulog = base_->get_upload_log();
308  config& cat = ulog.child_or_add(category);
309  cat[key] = var;
310 }
311 
312 void replay::add_log_data(const std::string &category, const std::string &key, const config &c)
313 {
314  config& ulog = base_->get_upload_log();
315  config& cat = ulog.child_or_add(category);
316  cat.add_child(key,c);
317 }
318 
320 {
321  return add_chat_message_location(base_->get_pos() - 1);
322 }
323 
325 {
326  assert(base_->get_command_at(pos).has_child("speak"));
327  if(std::find(message_locations.begin(), message_locations.end(), pos) == message_locations.end()) {
328  message_locations.push_back(pos);
329  return true;
330  }
331  else {
332  return false;
333  }
334 }
335 
336 void replay::speak(const config& cfg)
337 {
339  cmd.add_child("speak",cfg);
341 }
342 
343 void replay::add_chat_log_entry(const config &cfg, std::back_insert_iterator<std::vector<chat_msg>> &i) const
344 {
345 
346  if (!prefs::get().parse_should_show_lobby_join(cfg["id"], cfg["message"])) return;
347  if (prefs::get().is_ignored(cfg["id"])) return;
348  *i = chat_msg(cfg);
349 }
350 
352 {
354  std::vector<int>::reverse_iterator loc_it;
355  for (loc_it = message_locations.rbegin(); loc_it != message_locations.rend() && index < *loc_it;++loc_it)
356  {
357  --(*loc_it);
358  }
359 }
360 
361 // cached message log
362 static std::vector< chat_msg > message_log;
363 
364 
365 const std::vector<chat_msg>& replay::build_chat_log() const
366 {
367  message_log.clear();
368  std::vector<int>::const_iterator loc_it;
369  int last_location = 0;
370  std::back_insert_iterator<std::vector < chat_msg >> chat_log_appender( back_inserter(message_log));
371  for (loc_it = message_locations.begin(); loc_it != message_locations.end(); ++loc_it)
372  {
373  last_location = *loc_it;
374 
375  const config &speak = command(last_location).mandatory_child("speak");
376  add_chat_log_entry(speak, chat_log_appender);
377 
378  }
379  return message_log;
380 }
381 
383 {
384  config res;
385  for (int cmd = sent_upto_; cmd < ncommands(); ++cmd)
386  {
387  config &c = command(cmd);
388  //prevent creating 'blank' attribute values during checks
389  const config &cc = c;
390  if ((data_type == ALL_DATA || !cc["undo"].to_bool(true)) && !cc["sent"].to_bool(false))
391  {
392  res.add_child("command", c);
393  c["sent"] = true;
394  }
395  }
396  if(data_type == ALL_DATA) {
397  sent_upto_ = ncommands();
398  }
399  return res;
400 }
401 
402 void replay::redo(const config& cfg, bool set_to_end)
403 {
404  assert(base_->get_pos() == ncommands());
405  int old_pos = base_->get_pos();
406  for (const config &cmd : cfg.child_range("command"))
407  {
408  base_->add_child() = cmd;
409  }
410  if(set_to_end) {
411  //The engine does not execute related wml events so mark ad dpendent actions as handled
412  base_->set_to_end();
413  }
414  else {
415  //The engine does execute related wml events so it needs to reprocess depndent choices
416  base_->set_pos(old_pos + 1);
417  }
418 
419 }
420 
421 
422 
424 {
425  for (int cmd_num = base_->get_pos() - 1; cmd_num >= 0; --cmd_num)
426  {
427  config &c = command(cmd_num);
428  const config &cc = c;
429  if (cc["dependent"].to_bool(false) || !cc["undo"].to_bool(true) || cc["async"].to_bool(false))
430  {
431  continue;
432  }
433  return c;
434  }
435  ERR_REPLAY << "replay::get_last_real_command called with no existent command.";
436  assert(false && "replay::get_last_real_command called with no existent command.");
437  throw "replay::get_last_real_command called with no existent command.";
438 }
439 /**
440  * fixes a rename command when undoing a earlier command.
441  * @return: true if the command should be removed.
442  */
443 static bool fix_rename_command(const config& c, config& async_child)
444 {
445  if (const auto child = c.optional_child("move"))
446  {
447  // A unit's move is being undone.
448  // Repair unsynced cmds whose locations depend on that unit's location.
449  std::vector<map_location> steps;
450 
451  try {
452  read_locations(child.value(), steps);
453  } catch(const bad_lexical_cast &) {
454  WRN_REPLAY << "Warning: Path data contained something which could not be parsed to a sequence of locations:" << "\n config = " << child->debug();
455  }
456 
457  if (steps.empty()) {
458  ERR_REPLAY << "trying to undo a move using an empty path";
459  }
460  else {
461  const map_location &src = steps.front();
462  const map_location &dst = steps.back();
463  map_location aloc(async_child);
464  if (dst == aloc) src.write(async_child);
465  }
466  }
467  else
468  {
469  auto loc = c.optional_child("recruit");
470  if(!loc) {
471  loc = c.optional_child("recall");
472  }
473 
474  if(loc) {
475  // A unit is being un-recruited or un-recalled.
476  // Remove unsynced commands that would act on that unit.
477  map_location src(loc.value());
478  map_location aloc(async_child);
479  if (src == aloc) {
480  return true;
481  }
482  }
483  }
484  return false;
485 }
486 
488 {
489  assert(dst.empty());
490  //assert that we are not undoing a command which we didn't execute yet.
491  assert(at_end());
492 
493  //calculate the index of the last synced user action (which we want to undo).
494  int cmd_index = ncommands() - 1;
495  for (; cmd_index >= 0; --cmd_index)
496  {
497  //"undo"=no means speak/label/remove_label, especially attack, recruits etc. have "undo"=yes
498  //"async"=yes means rename_unit
499  //"dependent"=true means user input
500  const config &c = command(cmd_index);
501 
502  if(c["undo"].to_bool(true) && !c["async"].to_bool(false) && !c["dependent"].to_bool(false))
503  {
504  if(c["sent"].to_bool(false))
505  {
506  ERR_REPLAY << "trying to undo a command that was already sent.";
507  return;
508  }
509  else
510  {
511  break;
512  }
513  }
514  }
515 
516  if (cmd_index < 0)
517  {
518  ERR_REPLAY << "trying to undo a command but no command was found.";
519  return;
520  }
521  //Fix the [command]s after the undone action. This includes dependent commands for that user actions and async user action.
522  for(int i = ncommands() - 1; i >= cmd_index; --i)
523  {
524  config &c = command(i);
525  const config &cc = c;
526  if(!cc["undo"].to_bool(true))
527  {
528  //Leave these commands on the replay.
529  }
530  else if(cc["async"].to_bool(false))
531  {
532  if(auto rename = c.optional_child("rename"))
533  {
534  if(fix_rename_command(command(cmd_index), rename.value()))
535  {
536  //remove the command from the replay if fix_rename_command requested it.
537  remove_command(i);
538  }
539  }
540  }
541  else if(cc["dependent"].to_bool(false) || i == cmd_index)
542  {
543  //we loop backwars so we must insert new insert at beginning to preserve order.
544  dst.add_child_at("command", config(), 0).swap(c);
545  remove_command(i);
546  }
547  else
548  {
549  ERR_REPLAY << "Couldn't handle command:\n" << cc << "\nwhen undoing.";
550  }
551  }
552  set_to_end();
553 }
554 
556 {
557  config dummy;
558  undo_cut(dummy);
559 }
560 
562 {
563  config & retv = base_->get_command_at(n);
564  return retv;
565 }
566 
567 int replay::ncommands() const
568 {
569  return base_->size();
570 }
571 
573 {
574  // If we weren't at the end of the replay we should skip one or more
575  // commands.
576  assert(at_end());
577  config& retv = base_->add_child();
578  set_to_end();
579  return retv;
580 }
581 
583 {
584  const bool was_at_end = at_end();
586  r["undo"] = false;
587  if(was_at_end) {
588  base_->set_pos(base_->get_pos() + 1);
589  }
590  assert(was_at_end == at_end());
591  return r;
592 }
593 
595 {
596  base_->set_pos(0);
597 }
598 
600 {
601 
602  if (base_->get_pos() > 0)
603  base_->set_pos(base_->get_pos() - 1);
604 }
605 
607 {
608  if (at_end())
609  return nullptr;
610 
611  LOG_REPLAY << "up to replay action " << base_->get_pos() + 1 << '/' << ncommands();
612 
613  config* retv = &command(base_->get_pos());
614  base_->set_pos(base_->get_pos() + 1);
615  return retv;
616 }
617 
619 {
620  if (at_end())
621  return nullptr;
622 
623  LOG_REPLAY << "up to replay action " << base_->get_pos() + 1 << '/' << ncommands();
624 
625  config* retv = &command(base_->get_pos());
626  return retv;
627 }
628 
629 
630 bool replay::at_end() const
631 {
632  assert(base_->get_pos() <= ncommands());
633  return base_->get_pos() == ncommands();
634 }
635 
637 {
638  base_->set_to_end();
639 }
640 
641 bool replay::empty() const
642 {
643  return ncommands() == 0;
644 }
645 
647 {
648  for (const config &cmd : cfg.child_range("command"))
649  {
650  config &cmd_cfg = base_->insert_command(base_->size());
651  cmd_cfg = cmd;
652  if(mark == MARK_AS_SENT) {
653  cmd_cfg["sent"] = true;
654  }
655  if(cmd_cfg.has_child("speak")) {
656  cmd_cfg["undo"] = false;
657  }
658  }
659 }
661 {
662  //this method would confuse the value of 'pos' otherwise
663  VALIDATE(base_->get_pos() == 0, _("The file you have tried to load is corrupt"));
664  //since pos is 0, at_end() is equivalent to empty()
665  if(at_end() || !base_->get_command_at(0).has_child("start"))
666  {
667  base_->insert_command(0) = config {"start", config(), "sent", true};
668  return true;
669  }
670  else
671  {
672  return false;
673  }
674 }
675 
677 {
678  if(command.all_children_count() != 1) {
680  }
681  auto [key, _] = command.all_children_view().front();
682  if(key == "speak" || key == "label" || key == "surrender" || key == "clear_labels" || key == "rename" || key == "countdown_update") {
684  }
685  if(command["dependent"].to_bool(false)) {
687  }
689 }
690 
691 REPLAY_RETURN do_replay(bool one_move)
692 {
693  log_scope("do replay");
694 
695  if (!resources::controller->is_skipping_replay()) {
697  }
698 
699  return do_replay_handle(one_move);
700 }
701 /**
702  @returns:
703  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)
704  else if we found an [end_turn] we return REPLAY_FOUND_END_TURN
705  else if we found a player action and one_move=true we return REPLAY_FOUND_END_MOVE
706  else (<=> we reached the end of the replay) we return REPLAY_RETURN_AT_END
707 */
709 {
710 
711  //team &current_team = resources::gameboard->get_team(side_num);
712 
713  const int side_num = resources::controller->current_side();
714  while(true)
715  {
717  const bool is_synced = synced_context::is_synced();
718  const bool is_unsynced = synced_context::get_synced_state() == synced_context::UNSYNCED;
719 
720  DBG_REPLAY << "in do replay with is_synced=" << is_synced << "is_unsynced=" << is_unsynced;
721 
722  if (cfg != nullptr)
723  {
724  DBG_REPLAY << "Replay data:\n" << *cfg;
725  }
726  else
727  {
728  DBG_REPLAY << "Replay data at end";
729  return REPLAY_RETURN_AT_END;
730  }
731 
732 
733  const auto ch_itors = cfg->all_children_view();
734  //if there is an empty command tag or a start tag
735  if (ch_itors.empty() || cfg->has_child("start"))
736  {
737  //this shouldn't happen anymore because replaycontroller now moves over the [start] with get_next_action
738  //also we removed the the "add empty replay entry at scenario reload" behavior.
739  ERR_REPLAY << "found "<< cfg->debug() <<" in replay";
740  //do nothing
741  }
742  else if (auto speak = cfg->optional_child("speak"))
743  {
744  const std::string &team_name = speak["to_sides"];
745  const std::string &speaker_name = speak["id"];
746  const std::string &message = speak["message"];
747 
748  bool is_whisper = (speaker_name.find("whisper: ") == 0);
749  if(resources::recorder->add_chat_message_location()) {
750  DBG_REPLAY << "tried to add a chat message twice.";
751  if (!resources::controller->is_skipping_replay() || is_whisper) {
752  int side = speak["side"].to_int();
753  auto as_time_t = std::chrono::system_clock::to_time_t(get_time(*speak)); // FIXME: remove
754  game_display::get_singleton()->get_chat_manager().add_chat_message(as_time_t, 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
virtual ~chat_msg()
Definition: replay.cpp:163
std::string color_
Definition: replay.hpp:40
std::string nick_
Definition: replay.hpp:41
chat_msg(const config &cfg)
Definition: replay.cpp:142
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:567
config * peek_next_action()
Definition: replay.cpp:618
void add_start()
Definition: replay.cpp:212
void add_rename(const std::string &name, const map_location &loc)
Definition: replay.cpp:279
config & add_nonundoable_command()
adds a new command to the command list at the current position.
Definition: replay.cpp:582
bool add_start_if_not_there_yet()
Definition: replay.cpp:660
void undo_cut(config &dst)
Definition: replay.cpp:487
void set_to_end()
Definition: replay.cpp:636
config & get_last_real_command()
Definition: replay.cpp:423
void add_config(const config &cfg, MARK_SENT mark=MARK_AS_UNSENT)
Definition: replay.cpp:646
void add_label(const terrain_label *)
Definition: replay.cpp:258
void add_synced_command(const std::string &name, const config &command)
Definition: replay.cpp:233
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:290
void add_surrender(int side_number)
Definition: replay.cpp:219
void speak(const config &cfg)
Definition: replay.cpp:336
void clear_labels(const std::string &, bool)
Definition: replay.cpp:269
const std::vector< chat_msg > & build_chat_log() const
Definition: replay.cpp:365
config & command(int) const
Definition: replay.cpp:561
replay(replay_recorder_base &base)
Definition: replay.cpp:167
void remove_command(int)
Definition: replay.cpp:351
void user_input(const std::string &name, const config &input, int from_side)
adds a user_input to the replay
Definition: replay.cpp:243
void redo(const config &dst, bool set_to_end=false)
Definition: replay.cpp:402
bool at_end() const
Definition: replay.cpp:630
void add_unit_checksum(const map_location &loc, config &cfg)
Definition: replay.cpp:191
void revert_action()
Definition: replay.cpp:599
void undo()
Definition: replay.cpp:555
std::vector< int > message_locations
Definition: replay.hpp:163
static void process_error(const std::string &msg)
Definition: replay.cpp:184
void init_side()
Definition: replay.cpp:204
void start_replay()
Definition: replay.cpp:594
void delete_upcoming_commands()
Definition: replay.cpp:173
config get_unsent_commands(DATA_TYPE data_type)
Definition: replay.cpp:382
void add_countdown_update(int value, int team)
Definition: replay.cpp:225
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:319
bool empty() const
Definition: replay.cpp:641
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:343
void add_log_data(const std::string &key, const std::string &var)
Definition: replay.cpp:299
config & add_command()
Adds a new empty command to the command list at the end.
Definition: replay.cpp:572
config * get_next_action()
Definition: replay.cpp:606
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:1030
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:1029
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:276
int side_number
Definition: game_info.hpp:40
auto parse_timestamp(long long val)
Definition: chrono.hpp:46
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
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:140
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:676
static bool fix_rename_command(const config &c, config &async_child)
fixes a rename command when undoing a earlier command.
Definition: replay.cpp:443
#define WRN_REPLAY
Definition: replay.cpp:50
REPLAY_RETURN do_replay_handle(bool one_move)
Definition: replay.cpp:708
static std::chrono::system_clock::time_point get_time(const config &speak)
Definition: replay.cpp:132
#define DBG_REPLAY
Definition: replay.cpp:48
static lg::log_domain log_replay("replay")
REPLAY_RETURN do_replay(bool one_move)
Definition: replay.cpp:691
#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::vector< chat_msg > message_log
Definition: replay.cpp:362
#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:2830
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.