The Battle for Wesnoth  1.15.11+dev
replay.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 /**
16  * @file
17  * Replay control code.
18  *
19  * See https://www.wesnoth.org/wiki/ReplayWML for more info.
20  */
21 
22 #include "replay.hpp"
23 
24 #include "actions/undo.hpp"
25 #include "display_chat_manager.hpp"
26 #include "floating_label.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 "statistics.hpp"
38 #include "units/unit.hpp"
39 #include "whiteboard/manager.hpp"
40 #include "replay_recorder_base.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...\n";
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  static const std::array<std::string, 4> fields {{"type","hitpoints","experience","side"}};
109  for(const std::string& field : fields) {
110  if (u_cfg[field] != un[field]) {
111  errbuf << "ERROR IN FIELD '" << field << "' for unit at "
112  << loc << " data source: '" << un[field]
113  << "' local: '" << u_cfg[field] << "'\n";
114  is_ok = false;
115  }
116  }
117 
118  if(!is_ok) {
119  errbuf << "(SYNC VERIFICATION FAILED)\n";
120  replay::process_error(errbuf.str());
121  errbuf.clear();
122  }
123  }
124 
125  LOG_REPLAY << "verification passed\n";
126 }
127 
128 static std::time_t get_time(const config &speak)
129 {
130  std::time_t time;
131  if (!speak["time"].empty())
132  {
133  std::stringstream ss(speak["time"].str());
134  ss >> time;
135  }
136  else
137  {
138  //fallback in case sender uses wesnoth that doesn't send timestamps
139  time = std::time(nullptr);
140  }
141  return time;
142 }
143 
145  : color_()
146  , nick_()
147  , text_(cfg["message"].str())
148 {
149  if(cfg["team_name"].empty() && cfg["to_sides"].empty())
150  {
151  nick_ = cfg["id"].str();
152  } else {
153  nick_ = "*"+cfg["id"].str()+"*";
154  }
155  int side = cfg["side"].to_int(0);
156  LOG_REPLAY << "side in message: " << side << std::endl;
157  if (side==0) {
158  color_ = "white";//observers
159  } else {
161  }
162  time_ = get_time(cfg);
163  /*
164  } else if (side==1) {
165  color_ = "red";
166  } else if (side==2) {
167  color_ = "blue";
168  } else if (side==3) {
169  color_ = "green";
170  } else if (side==4) {
171  color_ = "purple";
172  }*/
173 }
174 
176 {
177 }
178 
180  : base_(&base)
181  , message_locations()
182 {}
183 
185 {
187 }
188 /*
189  TODO: there should be different types of OOS messages:
190  1)the normal OOS message
191  2) the 'is guaranteed you'll get an assertion error after this and therefore you cannot continue' OOS message
192  3) the 'do you want to overwrite calculated data with the data stored in replay' OOS error message.
193 
194 */
195 void replay::process_error(const std::string& msg)
196 {
197  ERR_REPLAY << msg << std::flush;
198 
199  resources::controller->process_oos(msg); // might throw quit_game_exception()
200 }
201 
203 {
204  if(! game_config::mp_debug) {
205  return;
206  }
207  config& cc = cfg.add_child("checksum");
208  loc.write(cc);
210  assert(u.valid());
211  cc["value"] = get_checksum(*u);
212 }
213 
214 
216 {
217  config& cmd = add_command();
219  init_side["side_number"] = resources::controller->current_side();
220  cmd.add_child("init_side", init_side);
221 }
222 
224 {
225  config& cmd = add_command();
226  cmd["sent"] = true;
227  cmd.add_child("start");
228 }
229 
231 {
233  cmd.add_child("surrender")["side_number"] = side_number;
234 }
235 
236 void replay::add_countdown_update(int value, int team)
237 {
238  config& cmd = add_command();
239  config val;
240  val["value"] = value;
241  val["team"] = team;
242  cmd.add_child("countdown_update", std::move(val));
243 }
244 void replay::add_synced_command(const std::string& name, const config& command)
245 {
246  config& cmd = add_command();
247  cmd.add_child(name,command);
248  cmd["from_side"] = resources::controller->current_side();
249  LOG_REPLAY << "add_synced_command: \n" << cmd.debug() << "\n";
250 }
251 
252 
253 
254 void replay::user_input(const std::string &name, const config &input, int from_side)
255 {
256  config& cmd = add_command();
257  cmd["dependent"] = true;
258  if(from_side == -1)
259  {
260  cmd["from_side"] = "server";
261  }
262  else
263  {
264  cmd["from_side"] = from_side;
265  }
266  cmd.add_child(name, input);
267 }
268 
270 {
271  assert(label);
273  config val;
274 
275  label->write(val);
276 
277  cmd.add_child("label",val);
278 }
279 
280 void replay::clear_labels(const std::string& team_name, bool force)
281 {
283 
284  config val;
285  val["team_name"] = team_name;
286  val["force"] = force;
287  cmd.add_child("clear_labels", std::move(val));
288 }
289 
290 void replay::add_rename(const std::string& name, const map_location& loc)
291 {
292  config& cmd = add_command();
293  cmd["async"] = true; // Not undoable, but depends on moves/recruits that are
294  config val;
295  loc.write(val);
296  val["name"] = name;
297  cmd.add_child("rename", std::move(val));
298 }
299 
300 
301 void replay::end_turn(int next_player_number)
302 {
303  config& cmd = add_command();
304  config& end_turn = cmd.add_child("end_turn");
305 
306  end_turn["next_player_number"] = next_player_number;
307 }
308 
309 
310 void replay::add_log_data(const std::string &key, const std::string &var)
311 {
312  config& ulog = base_->get_upload_log();
313  ulog[key] = var;
314 }
315 
316 void replay::add_log_data(const std::string &category, const std::string &key, const std::string &var)
317 {
318  config& ulog = base_->get_upload_log();
319  config& cat = ulog.child_or_add(category);
320  cat[key] = var;
321 }
322 
323 void replay::add_log_data(const std::string &category, const std::string &key, const config &c)
324 {
325  config& ulog = base_->get_upload_log();
326  config& cat = ulog.child_or_add(category);
327  cat.add_child(key,c);
328 }
329 
331 {
332  return add_chat_message_location(base_->get_pos() - 1);
333 }
334 
336 {
337  assert(base_->get_command_at(pos).has_child("speak"));
338  if(std::find(message_locations.begin(), message_locations.end(), pos) == message_locations.end()) {
339  message_locations.push_back(pos);
340  return true;
341  }
342  else {
343  return false;
344  }
345 }
346 
347 void replay::speak(const config& cfg)
348 {
350  cmd.add_child("speak",cfg);
352 }
353 
354 void replay::add_chat_log_entry(const config &cfg, std::back_insert_iterator<std::vector<chat_msg>> &i) const
355 {
356  if (!cfg) return;
357 
358  if (!preferences::parse_should_show_lobby_join(cfg["id"], cfg["message"])) return;
359  if (preferences::is_ignored(cfg["id"])) return;
360  *i = chat_msg(cfg);
361 }
362 
364 {
365  base_->remove_command(index);
366  std::vector<int>::reverse_iterator loc_it;
367  for (loc_it = message_locations.rbegin(); loc_it != message_locations.rend() && index < *loc_it;++loc_it)
368  {
369  --(*loc_it);
370  }
371 }
372 
373 // cached message log
374 static std::vector< chat_msg > message_log;
375 
376 
377 const std::vector<chat_msg>& replay::build_chat_log() const
378 {
379  message_log.clear();
380  std::vector<int>::const_iterator loc_it;
381  int last_location = 0;
382  std::back_insert_iterator<std::vector < chat_msg >> chat_log_appender( back_inserter(message_log));
383  for (loc_it = message_locations.begin(); loc_it != message_locations.end(); ++loc_it)
384  {
385  last_location = *loc_it;
386 
387  const config &speak = command(last_location).child("speak");
388  assert(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." << std::endl;
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 config &child = c.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,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() << std::endl;
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  const config *chld = &c.child("recruit");
482  if (!*chld) chld = &c.child("recall");
483  if (*chld) {
484  // A unit is being un-recruited or un-recalled.
485  // Remove unsynced commands that would act on that unit.
486  map_location src(*chld);
487  map_location aloc(async_child);
488  if (src == aloc) {
489  return true;
490  }
491  }
492  }
493  return false;
494 }
495 
497 {
498  assert(dst.empty());
499  //assert that we are not undoing a command which we didn't execute yet.
500  assert(at_end());
501 
502  //calculate the index of the last synced user action (which we want to undo).
503  int cmd_index = ncommands() - 1;
504  for (; cmd_index >= 0; --cmd_index)
505  {
506  //"undo"=no means speak/label/remove_label, especially attack, recruits etc. have "undo"=yes
507  //"async"=yes means rename_unit
508  //"dependent"=true means user input
509  const config &c = command(cmd_index);
510 
511  if(c["undo"].to_bool(true) && !c["async"].to_bool(false) && !c["dependent"].to_bool(false))
512  {
513  if(c["sent"].to_bool(false))
514  {
515  ERR_REPLAY << "trying to undo a command that was already sent.\n";
516  return;
517  }
518  else
519  {
520  break;
521  }
522  }
523  }
524 
525  if (cmd_index < 0)
526  {
527  ERR_REPLAY << "trying to undo a command but no command was found.\n";
528  return;
529  }
530  //Fix the [command]s after the undone action. This includes dependent commands for that user actions and async user action.
531  for(int i = ncommands() - 1; i >= cmd_index; --i)
532  {
533  config &c = command(i);
534  const config &cc = c;
535  if(!cc["undo"].to_bool(true))
536  {
537  //Leave these commands on the replay.
538  }
539  else if(cc["async"].to_bool(false))
540  {
541  if(config& rename = c.child("rename"))
542  {
543  if(fix_rename_command(command(cmd_index), rename))
544  {
545  //remove the command from the replay if fix_rename_command requested it.
546  remove_command(i);
547  }
548  }
549  }
550  else if(cc["dependent"].to_bool(false) || i == cmd_index)
551  {
552  //we loop backwars so we must insert new insert at beginning to preserve order.
553  dst.add_child_at("command", config(), 0).swap(c);
554  remove_command(i);
555  }
556  else
557  {
558  ERR_REPLAY << "Couldn't handle command:\n" << cc << "\nwhen undoing.\n";
559  }
560  }
561  set_to_end();
562 }
563 
565 {
566  config dummy;
567  undo_cut(dummy);
568 }
569 
571 {
572  config & retv = base_->get_command_at(n);
573  assert(retv);
574  return retv;
575 }
576 
577 int replay::ncommands() const
578 {
579  return base_->size();
580 }
581 
583 {
584  // If we weren't at the end of the replay we should skip one or more
585  // commands.
586  assert(at_end());
587  config& retv = base_->add_child();
588  set_to_end();
589  return retv;
590 }
591 
593 {
594  const bool was_at_end = at_end();
596  r["undo"] = false;
597  if(was_at_end) {
598  base_->set_pos(base_->get_pos() + 1);
599  }
600  assert(was_at_end == at_end());
601  return r;
602 }
603 
605 {
606  base_->set_pos(0);
607 }
608 
610 {
611 
612  if (base_->get_pos() > 0)
613  base_->set_pos(base_->get_pos() - 1);
614 }
615 
617 {
618  if (at_end())
619  return nullptr;
620 
621  LOG_REPLAY << "up to replay action " << base_->get_pos() + 1 << '/' << ncommands() << '\n';
622 
623  config* retv = &command(base_->get_pos());
624  base_->set_pos(base_->get_pos() + 1);
625  return retv;
626 }
627 
628 
629 bool replay::at_end() const
630 {
631  assert(base_->get_pos() <= ncommands());
632  return base_->get_pos() == ncommands();
633 }
634 
636 {
637  base_->set_to_end();
638 }
639 
640 bool replay::empty() const
641 {
642  return ncommands() == 0;
643 }
644 
646 {
647  for (const config &cmd : cfg.child_range("command"))
648  {
649  config &cmd_cfg = base_->insert_command(base_->size());
650  cmd_cfg = cmd;
651  if(mark == MARK_AS_SENT) {
652  cmd_cfg["sent"] = true;
653  }
654  if(cmd_cfg.has_child("speak")) {
655  cmd_cfg["undo"] = false;
656  }
657  }
658 }
660 {
661  //this method would confuse the value of 'pos' otherwise
662  assert(base_->get_pos() == 0);
663  //since pos is 0, at_end() is equivalent to empty()
664  if(at_end() || !base_->get_command_at(0).has_child("start"))
665  {
666  base_->insert_command(0) = config {"start", config(), "sent", true};
667  return true;
668  }
669  else
670  {
671  return false;
672  }
673 }
674 
675 static void show_oos_error_error_function(const std::string& message)
676 {
677  replay::process_error(message);
678 }
679 
680 REPLAY_RETURN do_replay(bool one_move)
681 {
682  log_scope("do replay");
683 
684  if (!resources::controller->is_skipping_replay()) {
686  }
687 
688  update_locker lock_update(CVideo::get_singleton(), resources::controller->is_skipping_replay());
689  return do_replay_handle(one_move);
690 }
691 /**
692  @returns:
693  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)
694  else if we found an [end_turn] we return REPLAY_FOUND_END_TURN
695  else if we found a player action and one_move=true we return REPLAY_FOUND_END_MOVE
696  else (<=> we reached the end of the replay) we return REPLAY_RETURN_AT_END
697 */
699 {
700 
701  //team &current_team = resources::gameboard->get_team(side_num);
702 
703  const int side_num = resources::controller->current_side();
704  while(true)
705  {
707  const bool is_synced = synced_context::is_synced();
708  const bool is_unsynced = synced_context::get_synced_state() == synced_context::UNSYNCED;
709 
710  DBG_REPLAY << "in do replay with is_synced=" << is_synced << "is_unsynced=" << is_unsynced << "\n";
711 
712  if (cfg != nullptr)
713  {
714  DBG_REPLAY << "Replay data:\n" << *cfg << "\n";
715  }
716  else
717  {
718  DBG_REPLAY << "Replay data at end\n";
719  return REPLAY_RETURN_AT_END;
720  }
721 
722 
724  //if there is an empty command tag or a start tag
725  if (ch_itors.empty() || cfg->has_child("start"))
726  {
727  //this shouldn't happen anymore because replaycontroller now moves over the [start] with get_next_action
728  //also we removed the the "add empty replay entry at scenario reload" behavior.
729  ERR_REPLAY << "found "<< cfg->debug() <<" in replay" << std::endl;
730  //do nothing
731  }
732  else if (const config &speak = cfg->child("speak"))
733  {
734  const std::string &team_name = speak["to_sides"];
735  const std::string &speaker_name = speak["id"];
736  const std::string &message = speak["message"];
737  //if (!preferences::parse_should_show_lobby_join(speaker_name, message)) return;
738  bool is_whisper = (speaker_name.find("whisper: ") == 0);
740  DBG_REPLAY << "tried to add a chat message twice.\n";
741  if (!resources::controller->is_skipping_replay() || is_whisper) {
742  int side = speak["side"];
744  (team_name.empty() ? events::chat_handler::MESSAGE_PUBLIC
747  }
748  }
749  }
750  else if (cfg->child("surrender"))
751  {
752  //prevent sending of a synced command for surrender
753  }
754  else if (const config &label_config = cfg->child("label"))
755  {
756  terrain_label label(display::get_singleton()->labels(), label_config);
757 
759  label.text(),
760  label.creator(),
761  label.team_name(),
762  label.color());
763  }
764  else if (const config &clear_labels = cfg->child("clear_labels"))
765  {
766  display::get_singleton()->labels().clear(std::string(clear_labels["team_name"]), clear_labels["force"].to_bool());
767  }
768  else if (const config &rename = cfg->child("rename"))
769  {
770  const map_location loc(rename);
771  const std::string &name = rename["name"];
772 
774  if (u.valid() && !u->unrenamable()) {
775  u->rename(name);
776  } else {
777  // Users can rename units while it's being killed or at another machine.
778  // This since the player can rename units when it's not his/her turn.
779  // There's not a simple way to prevent that so in that case ignore the
780  // rename instead of throwing an OOS.
781  // The same way it is possible that an unrenamable unit moves to a
782  // hex where previously a renamable unit was.
783  WRN_REPLAY << "attempt to rename unit at location: "
784  << loc << (u.valid() ? ", which is unrenamable" : ", where none exists (anymore)") << "\n";
785  }
786  }
787 
788  else if (cfg->child("init_side"))
789  {
790 
791  if(!is_unsynced)
792  {
793  replay::process_error("found side initialization in replay expecting a user choice\n" );
795  return REPLAY_FOUND_DEPENDENT;
796  }
797  else
798  {
800  if (one_move) {
801  return REPLAY_FOUND_INIT_TURN;
802  }
803  }
804  }
805 
806  //if there is an end turn directive
807  else if (const config& end_turn = cfg->child("end_turn"))
808  {
809  if(!is_unsynced)
810  {
811  replay::process_error("found turn end in replay while expecting a user choice\n" );
813  return REPLAY_FOUND_DEPENDENT;
814  }
815  else
816  {
817  if (const config &cfg_verify = cfg->child("verify")) {
818  verify(resources::gameboard->units(), cfg_verify);
819  }
820  resources::controller->gamestate().next_player_number_ = end_turn["next_player_number"];
821  return REPLAY_FOUND_END_TURN;
822  }
823  }
824  else if (const config &countdown_update = cfg->child("countdown_update"))
825  {
826  int val = countdown_update["value"];
827  int tval = countdown_update["team"];
828  if (tval <= 0 || tval > static_cast<int>(resources::gameboard->teams().size())) {
829  std::stringstream errbuf;
830  errbuf << "Illegal countdown update \n"
831  << "Received update for :" << tval << " Current user :"
832  << side_num << "\n" << " Updated value :" << val;
833 
834  replay::process_error(errbuf.str());
835  } else {
837  }
838  }
839  else if ((*cfg)["dependent"].to_bool(false))
840  {
841  if(is_unsynced)
842  {
843  replay::process_error("found dependent command in replay while is_synced=false\n" );
844  //ignore this command
845  continue;
846  }
847  //this means user choice.
848  // it never makes sense to try to execute a user choice.
849  // but we are called from
850  // the only other option for "dependent" command is checksum which is already checked.
851  assert(cfg->all_children_count() == 1);
852  std::string child_name = cfg->all_children_range().front().key;
853  DBG_REPLAY << "got an dependent action name = " << child_name <<"\n";
855  return REPLAY_FOUND_DEPENDENT;
856  }
857  else
858  {
859  //we checked for empty commands at the beginning.
860  const std::string & commandname = cfg->ordered_begin()->key;
861  config data = cfg->ordered_begin()->cfg;
862 
863  if(!is_unsynced)
864  {
865  replay::process_error("found [" + commandname + "] command in replay expecting a user choice\n" );
867  return REPLAY_FOUND_DEPENDENT;
868  }
869  else
870  {
871  LOG_REPLAY << "found commandname " << commandname << "in replay";
872 
873  if((*cfg)["from_side"].to_int(0) != resources::controller->current_side()) {
874  ERR_REPLAY << "received a synced [command] from side " << (*cfg)["from_side"].to_int(0) << ". Expacted was a [command] from side " << resources::controller->current_side() << "\n";
875  }
876  else if((*cfg)["side_invalid"].to_bool(false)) {
877  ERR_REPLAY << "received a synced [command] from side " << (*cfg)["from_side"].to_int(0) << ". Sent from wrong client.\n";
878  }
879  /*
880  we need to use the undo stack during replays in order to make delayed shroud updated work.
881  */
882  synced_context::run(commandname, data, true, !resources::controller->is_skipping_replay(), show_oos_error_error_function);
883  if(resources::controller->is_regular_game_end()) {
884  return REPLAY_FOUND_END_LEVEL;
885  }
886  if (one_move) {
887  return REPLAY_FOUND_END_MOVE;
888  }
889  }
890  }
891 
892  if (const config &child = cfg->child("verify")) {
893  verify(resources::gameboard->units(), child);
894  }
895  }
896 }
897 
899 {
900 }
901 
903 {
904  try {
905  commit_and_sync();
906  } catch (...) {}
907 }
908 
910 {
911  if(resources::controller->is_networked_mp()) {
912  resources::whiteboard->send_network_data();
913 
914  config cfg;
916  if(data.empty() == false) {
918  }
919  }
920 }
921 
923 {
924  if(resources::controller->is_networked_mp()) {
925  resources::whiteboard->send_network_data();
926 
927  config cfg;
928  const config& data = cfg.add_child("turn",obj_.get_data_range(upto_,obj_.ncommands()));
929 
930  if(data.empty() == false) {
932  }
933 
934  upto_ = obj_.ncommands();
935  }
936 }
void end_turn(int next_player_number)
Definition: replay.cpp:301
play_controller * controller
Definition: resources.cpp:21
void delete_upcoming_commands()
Definition: replay.cpp:184
std::time_t time_
Definition: replay.hpp:46
const map_location & location() const
Definition: label.hpp:178
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
DATA_TYPE
Definition: replay.hpp:98
static synced_state get_synced_state()
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:414
replay(replay_recorder_base &base)
Definition: replay.cpp:179
const color_t & color() const
Definition: label.hpp:183
unit_iterator end()
Definition: map.hpp:428
void set_countdown_time(const int amount) const
Definition: team.hpp:219
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:90
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:953
replay_network_sender(replay &obj)
Definition: replay.cpp:898
void add_countdown_update(int value, int team)
Definition: replay.cpp:236
virtual const unit_map & units() const override
Definition: game_board.hpp:111
int dummy
Definition: lstrlib.cpp:1347
void speak(const config &cfg)
Definition: replay.cpp:347
bool add_chat_message_location()
adds a chat message if it wasn&#39;t added yet.
Definition: replay.cpp:330
void write(config &res) const
Definition: label.cpp:431
boost::iterator_range< const_all_children_iterator > const_all_children_itors
Definition: config.hpp:688
void add_log_data(const std::string &key, const std::string &var)
Definition: replay.cpp:310
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 &#39;synced&#39;, initialises random context, and calls the given function.
const std::vector< chat_msg > & build_chat_log() const
Definition: replay.cpp:377
New lexcical_cast header.
virtual ~chat_msg()
Definition: replay.cpp:175
config & command(int) const
Definition: replay.cpp:570
void add_start()
Definition: replay.cpp:223
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:406
int next_player_number_
Definition: game_state.hpp:60
const t_string & text() const
Definition: label.hpp:138
bool at_end() const
Definition: replay.cpp:629
child_itors child_range(config_key_type key)
Definition: config.cpp:356
config & insert_command(int index)
static void field(LexState *ls, ConsControl *cc)
Definition: lparser.cpp:891
void sync_non_undoable()
Definition: replay.cpp:909
unit_iterator begin()
Definition: map.hpp:418
bool message_bell()
Definition: general.cpp:677
virtual void process_oos(const std::string &msg) const
Asks the user whether to continue on an OOS error.
const std::string & team_name() const
Definition: label.hpp:153
#define LOG_REPLAY
Definition: replay.cpp:48
static CVideo & get_singleton()
Definition: video.hpp:48
#define WRN_REPLAY
Definition: replay.cpp:49
Replay control code.
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
config & child_or_add(config_key_type key)
Definition: config.cpp:490
int creator() const
Definition: label.hpp:148
REPLAY_RETURN do_replay(bool one_move)
Definition: replay.cpp:680
static void show_oos_error_error_function(const std::string &message)
Definition: replay.cpp:675
config & get_last_real_command()
Definition: replay.cpp:435
#define ERR_REPLAY
Definition: replay.cpp:50
replay_recorder_base * base_
Definition: replay.hpp:151
void add_synced_command(const std::string &name, const config &command)
Definition: replay.cpp:244
int ncommands() const
Definition: replay.cpp:577
MARK_SENT
Definition: replay.hpp:122
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)
static std::string get_side_highlight_pango(int side)
Definition: team.cpp:1018
REPLAY_RETURN
Definition: replay.hpp:155
static void verify(const unit_map &units, const config &cfg)
Definition: replay.cpp:61
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:44
void init_side()
Definition: replay.cpp:215
void swap(config &cfg)
Definition: config.cpp:1422
team & get_team(int i)
Definition: game_board.hpp:96
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:86
void redo(const config &dst, bool set_to_end=false)
Definition: replay.cpp:414
bool add_start_if_not_there_yet()
Definition: replay.cpp:659
static std::time_t get_time(const config &speak)
Definition: replay.cpp:128
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:146
void remove_command(int index)
void clear(const std::string &, bool force)
Definition: label.cpp:210
void recalculate_minimap()
Schedule the minimap for recalculation.
Definition: display.hpp:616
void add_surrender(int side_number)
Definition: replay.cpp:230
config & add_child_at(config_key_type key, const config &val, unsigned index)
Definition: config.cpp:532
void user_input(const std::string &name, const config &input, int from_side)
adds a user_input to the replay
Definition: replay.cpp:254
unsigned all_children_count() const
Definition: config.cpp:396
game_board * gameboard
Definition: resources.cpp:20
void start_replay()
Definition: replay.cpp:604
#define DBG_REPLAY
Definition: replay.cpp:47
void add_rename(const std::string &name, const map_location &loc)
Definition: replay.cpp:290
void set_to_end()
Definition: replay.cpp:635
replay * recorder
Definition: resources.cpp:28
std::size_t count(const map_location &loc) const
Definition: map.hpp:413
std::size_t size() const
Definition: map.hpp:438
An object which will lock the display for the duration of its lifetime.
Definition: video.hpp:296
static bool is_synced()
bool is_ignored(const std::string &nick)
Definition: game.cpp:284
static std::vector< chat_msg > message_log
Definition: replay.cpp:374
void add_unit_checksum(const map_location &loc, config &cfg)
Definition: replay.cpp:202
config & get_command_at(int pos)
void do_init_side()
Called by replay handler or init_side() to do actual work for turn change.
Encapsulates the map of the game.
Definition: location.hpp:37
unit_iterator find(std::size_t id)
Definition: map.cpp:309
std::shared_ptr< wb::manager > whiteboard
Definition: resources.cpp:33
static void process_error(const std::string &msg)
Definition: replay.cpp:195
static std::string mark
Definition: tstring.cpp:70
std::size_t i
Definition: function.cpp:940
std::string color_
Definition: replay.hpp:43
std::string get_checksum(const unit &u)
Gets a checksum for a unit.
Definition: unit.cpp:2628
bool empty() const
Definition: replay.cpp:640
#define log_scope(description)
Definition: log.hpp:206
void remove_command(int)
Definition: replay.cpp:363
static std::string flush(std::ostringstream &s)
Definition: reports.cpp:91
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:71
config & add_child(config_key_type key)
Definition: config.cpp:500
const_all_children_iterator ordered_begin() const
Definition: config.cpp:933
display_chat_manager & get_chat_manager()
To store label data Class implements logic for rendering.
Definition: label.hpp:109
config & add_nonundoable_command()
adds a new command to the command list at the current position.
Definition: replay.cpp:592
game_state & gamestate()
static lg::log_domain log_replay("replay")
void add_config(const config &cfg, MARK_SENT mark=MARK_AS_UNSENT)
Definition: replay.cpp:645
std::vector< int > message_locations
Definition: replay.hpp:152
Various functions that implement the undoing (and redoing) of in-game commands.
void undo_cut(config &dst)
Definition: replay.cpp:496
Standard logging facilities (interface).
int current_side() const
Returns the number of the side whose turn it is.
std::string nick_
Definition: replay.hpp:44
Container associating units to locations.
Definition: map.hpp:97
config * get_next_action()
Definition: replay.cpp:616
void revert_action()
Definition: replay.cpp:609
bool parse_should_show_lobby_join(const std::string &sender, const std::string &message)
Definition: game.cpp:312
config & add_command()
Adds a new empty command to the command list at the end.
Definition: replay.cpp:582
map_labels & labels()
Definition: display.cpp:2524
int side_number
Definition: game_info.hpp:39
static lg::log_domain log_random("random")
void undo()
Definition: replay.cpp:564
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:59
static bool fix_rename_command(const config &c, config &async_child)
fixes a rename command when undoing a earlier command.
Definition: replay.cpp:455
REPLAY_RETURN do_replay_handle(bool one_move)
Definition: replay.cpp:698
bool valid() const
Definition: map.hpp:273
mock_char c
static map_location::DIRECTION n
Thrown when a lexical_cast fails.
void clear_labels(const std::string &, bool)
Definition: replay.cpp:280
bool empty() const
Definition: config.cpp:916
virtual void send_to_wesnothd(const config &, const std::string &="unknown") const
std::string debug() const
Definition: config.cpp:1322
chat_msg(const config &cfg)
Definition: replay.cpp:144
void add_chat_log_entry(const config &speak, std::back_insert_iterator< std::vector< chat_msg >> &i) const
Definition: replay.cpp:354
static game_display * get_singleton()
void add_label(const terrain_label *)
Definition: replay.cpp:269
config get_data_range(int cmd_start, int cmd_end, DATA_TYPE data_type=ALL_DATA) const
Definition: replay.cpp:395
void write(config &cfg) const
Definition: location.cpp:211