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