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