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