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