The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
saved_game.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2003 - 2025
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 * Some information about savefiles:
17 *
18 * A savefile can contain:
19 *
20 * - General information (toplevel attributes, [multiplayer])
21 * This is present in all savefiles
22 *
23 * - [statistics]
24 * This is present in all savefiles.
25 *
26 * - [snapshot]
27 * If a savegame was saved during a scenario this contains a snapshot of the game at the point when
28 * it was saved.
29 *
30 * - [carryover_sides_start]
31 * At start-of-scenario saves this contains data from the previous scenario that was preserved.
32 *
33 * - [carryover_sides]
34 * In savefile made during the game, this tag contains data from [carryover_sides_start] that was not
35 * used in the current scenario but should be saved for a next scenario
36 *
37 * - [replay_start]
38 * A snapshot made very early to replay the game from.
39 *
40 * - [replay]
41 * A record of game actions that was made between the creation of [replay_start] and [snapshot].
42 *
43 *
44 * The following types of savegames are known:
45 *
46 * - Start of scenario savefiles
47 * These files only contain general information, statistics, and [carryover_sides_start]. When these
48 * saves are loaded, the scenario data is loaded form the game config using the next_scenario attribute
49 * from [carryover_sides_start].
50 *
51 * - Expanded Start of scenario savefiles
52 * Similar to normal Start-of-scenario savefiles, but the also contain a [scenario] that contains the
53 * scenario data. This type is only used internally and usually doesn't get written to the disk.
54 *
55 * - In-game savefile
56 * These files contain general information, statistics, [snapshot], [replay], [replay_start], [snapshot],
57 * and [carryover_sides]. These files don't contain a [carryover_sides_start] because both starting points
58 * ([replay_start] and [snapshot]) were made after [carryover_sides_start] was merged into the scenario.
59 *
60 * - Replay savefiles
61 * Like a in-game save made during linger mode, but without the [snapshot].
62 */
63
64#include "saved_game.hpp"
65
66#include "carryover.hpp"
67#include "config.hpp"
68#include "cursor.hpp"
72#include "log.hpp"
73#include "random.hpp"
75#include "side_controller.hpp"
76#include "utils/general.hpp"
77#include "team.hpp" // for team::attributes, team::variables
78#include "variable.hpp" // for config_variable_set
79#include "variable_info.hpp"
80
81#include <cassert>
82#include <iomanip>
83
84static lg::log_domain log_engine("engine");
85#define ERR_NG LOG_STREAM(err, log_engine)
86#define WRN_NG LOG_STREAM(warn, log_engine)
87#define LOG_NG LOG_STREAM(info, log_engine)
88#define DBG_NG LOG_STREAM(debug, log_engine)
89
90namespace
91{
92bool variable_to_bool(const config& vars, const std::string& expression)
93{
94 std::string res = utils::interpolate_variables_into_string(expression, config_variable_set(vars));
95 return res == "true" || res == "yes" || res == "1";
96}
97
98// helper objects for saved_game::expand_mp_events()
99struct modevents_entry
100{
101 modevents_entry(const std::string& _type, const std::string& _id)
102 : type(_type)
103 , id(_id)
104 {
105 }
106
107 std::string type;
108 std::string id;
109};
110
111bool is_illegal_file_char(char c)
112{
113 return c == '/' || c == '\\' || c == ':' || (c >= 0x00 && c < 0x20)
114#ifdef _WIN32
115 || c == '?' || c == '|' || c == '<' || c == '>' || c == '*' || c == '"'
116#endif
117 ;
118}
119
120} // end anon namespace
121
123 : has_carryover_expanded_(false)
124 , carryover_(carryover_info().to_config())
125 , replay_start_()
126 , classification_()
127 , mp_settings_()
128 , starting_point_type_(starting_point::NONE)
129 , starting_point_()
130 , replay_data_()
131 , statistics_()
132 , skip_story_(false)
133{
134}
135
137 : has_carryover_expanded_(false)
138 , carryover_()
139 , replay_start_()
140 , classification_(cfg)
141 , mp_settings_()
142 , starting_point_type_(starting_point::NONE)
143 , starting_point_()
144 , replay_data_()
145 , statistics_()
146 , skip_story_(false)
147{
148 set_data(std::move(cfg));
149}
150
152 : has_carryover_expanded_(state.has_carryover_expanded_)
153 , carryover_(state.carryover_)
154 , replay_start_(state.replay_start_)
155 , classification_(state.classification_)
156 , mp_settings_(state.mp_settings_)
157 , starting_point_type_(state.starting_point_type_)
158 , starting_point_(state.starting_point_)
159 , replay_data_(state.replay_data_)
160 , statistics_(state.statistics_)
161 , skip_story_(state.skip_story_)
162{
163}
164
166{
167 carryover_.swap(carryover_sides_start);
169}
170
172{
173 if(has_carryover_expanded_ || !carryover_["random_seed"].empty()) {
174 return;
175 }
176
177 std::stringstream stream;
178 stream << std::setfill('0') << std::setw(8) << std::hex << randomness::generator->get_random_int(0, std::numeric_limits<int>::max());
179 carryover_["random_seed"] = stream.str();
180 carryover_["random_calls"] = 0;
181}
182
184{
187
188 if(!replay_start_.empty()) {
189 out.write_child("replay_start", replay_start_);
190 }
191
192 out.open_child("replay");
193 replay_data_.write(out);
194 out.close_child("replay");
195 write_carryover(out);
196}
197
206
208{
209 assert(not_corrupt());
210 out.write_child(has_carryover_expanded_ ? "carryover_sides" : "carryover_sides_start", carryover_);
211}
212
214{
216 out.write_child("multiplayer", mp_settings_.to_config());
217 out.open_child("statistics");
218 statistics().write(out);
219 out.close_child("statistics");
220}
221
223{
224 const bool is_loaded_game = starting_point_type_ != starting_point::SCENARIO;
225 const bool is_multiplayer_tag = classification().get_tagname() == "multiplayer";
227
228 static const std::vector<std::string> team_defaults {
229 "carryover_percentage",
230 "carryover_add",
231 };
232
233 if(auto campaign = game_config.find_child("campaign", "id", classification_.campaign)) {
234 // FIXME: The mp code could use `require_scenario` to check whether we have the addon in question installed.
235 // But since [scenario]s are usually hidden behind `#ifdef CAMPAIGN_DEFINE` it would not be able to find them.
236 // Investigate how this should actually work.
237 bool require_campaign = campaign["require_campaign"].to_bool(true);
238 starting_point_["require_scenario"] = require_campaign;
239 }
240
241 for(config& side : starting_point_.child_range("side")) {
242 // Set save_id default value directly after loading to its default to prevent different default behaviour in
243 // mp_connect code and sp code.
244
245 if(side["no_leader"].to_bool()) {
246 side["leader_lock"] = true;
247 side.remove_attribute("type");
248 }
249
250 if(side["save_id"].empty()) {
251 side["save_id"] = side["id"];
252 }
253 if(side["save_id"].empty()) {
254 side["save_id"] = side.child_or_empty("leader")["id"];
255 }
256
257 // If this side tag describes the leader of the side, convert it into a [leader] tag here, by doing this here,
258 // all code that follows, no longer has to hande the possibility of leader information directly in [side].
259
260 // If this side tag describes the leader of the side
261 if(!side["type"].empty() && side["type"] != "null") {
262 auto temp = config{};
263
264 for(const std::string& tag : team::tags) {
265 temp.append_children_by_move(side, tag);
266 }
267 for(const std::string& attr : team::attributes) {
268 if(side.has_attribute(attr)) {
269 temp[attr] = side[attr];
270 side.remove_attribute(attr);
271 }
272 }
273 temp["side"] = side["side"];
274 temp.swap(side);
275 temp.swap(side.add_child_at("leader", config(), 0));
276 }
277
278 if(!is_multiplayer_tag) {
279 if(side["name"].blank()) {
280 side["name"] = side.child_or_empty("leader")["name"];
281 }
282 if(side["side_name"].blank()) {
283 side["side_name"] = side["name"];
284 }
285 }
286
287 if(!is_loaded_game && !side["current_player"].empty()) {
288 ERR_NG << "Removed invalid 'current_player' attribute from [side] while loading a scenario. Consider using "
289 "'side_name' instead";
290
291 side["current_player"] = config::attribute_value();
292 }
293
294 // Set some team specific values to their defaults specified in scenario
295 for(const std::string& att_name : team_defaults) {
296 const config::attribute_value* scenario_value = starting_point_.get(att_name);
297 config::attribute_value& team_value = side[att_name];
298
299 if(scenario_value && team_value.empty()) {
300 team_value = *scenario_value;
301 }
302 }
303 }
304}
305
307{
310
312 auto scenario =
313 game_config.find_child(classification().get_tagname(), "id", carryover_["next_scenario"]);
314
315 if(scenario) {
317 starting_point_ = *scenario;
318
319 // A hash has to be generated using an unmodified scenario data.
320 mp_settings_.hash = scenario->hash();
321
323
324 update_label();
325 set_defaults();
326 } else {
327 ERR_NG << "Couldn't find [" << classification().get_tagname() << "] with id=" << carryover_["next_scenario"];
330 }
331 }
332}
333
335{
336 const std::string version_default = starting_point_["addon_id"].empty() ? game_config::wesnoth_version.str() : "";
337 config scenario;
338 scenario["id"] = starting_point_["addon_id"].str("mainline");
339 scenario["name"] = starting_point_["addon_title"].str("mainline");
340 scenario["version"] = starting_point_["addon_version"].str(version_default);
341 scenario["min_version"] = starting_point_["addon_min_version"];
342 scenario["required"] = starting_point_["require_scenario"].to_bool(false);
343 config& content = scenario.add_child("content");
344 content["id"] = starting_point_["id"];
345 content["name"] = starting_point_["name"];
346 // TODO: would it be better if this used the actual tagname ([multiplayer]/[scenario]) instead of always using [scenario]?
347 content["type"] = "scenario";
348
350}
351
352// "non scenario" at the time of writing this meaning any era, campaign, mods, or resources (see expand_mp_events() below).
353void saved_game::load_non_scenario(const std::string& type, const std::string& id, std::size_t pos)
354{
355 if(auto cfg = game_config_manager::get()->game_config().find_child(type, "id", id)) {
356 // Note the addon_id if this mod is required to play the game in mp.
357 std::string require_attr = "require_" + type;
358
359 // anything with no addon_id is from mainline, and therefore isn't required in the sense that all players already have it
360 const std::string version_default = cfg["addon_id"].empty() ? game_config::wesnoth_version.str() : "";
361 config non_scenario;
362 // if there's no addon_id, then this isn't an add-on
363 non_scenario["id"] = cfg["addon_id"].str("mainline");
364 non_scenario["name"] = cfg["addon_title"].str("mainline");
365 non_scenario["version"] = cfg["addon_version"].str(version_default);
366 non_scenario["min_version"] = cfg["addon_min_version"];
367 non_scenario["required"] = cfg[require_attr].to_bool(!cfg["addon_id"].empty());
368 config& content = non_scenario.add_child("content");
369 content["id"] = id;
370 content["name"] = cfg["addon_title"].str(cfg["name"].str(""));
371 content["type"] = type;
372
374
375 // Copy events
376 for(const config& modevent : cfg->child_range("event")) {
377 if(modevent["enable_if"].empty()
378 || variable_to_bool(carryover_.child_or_empty("variables"), modevent["enable_if"])
379 ) {
380 starting_point_.add_child_at_total("event", modevent, pos++);
381 }
382 }
383
384 // Copy lua
385 for(const config& modlua : cfg->child_range("lua")) {
386 starting_point_.add_child_at_total("lua", modlua, pos++);
387 }
388
389 // Copy modify_unit_type
390 for(const config& modlua : cfg->child_range("modify_unit_type")) {
391 starting_point_.add_child_at_total("modify_unit_type", modlua, pos++);
392 }
393
394 // Copy load_resource
395 for(const config& load_resource : cfg->child_range("load_resource")) {
396 starting_point_.add_child_at_total("load_resource", load_resource, pos++);
397 }
398 } else {
399 // TODO: A user message instead?
400 ERR_NG << "Couldn't find [" << type << "] with id=" << id;
401 }
402}
403
404// Gets the ids of the mp_era and modifications which were set to be active, then fetches these configs from the
405// game_config and copies their [event] and [lua] to the starting_point_.
406// At this time, also collect the addon_id attributes which appeared in them and put this list in the addon_ids
407// attribute of the mp_settings.
409{
411
412 if(starting_point_type_ == starting_point::SCENARIO && !starting_point_["has_mod_events"].to_bool(false)) {
413 std::vector<modevents_entry> mods;
414 std::set<std::string> loaded_resources;
415
416 std::transform(classification_.active_mods.begin(), classification_.active_mods.end(), std::back_inserter(mods),
417 [](const std::string& id) { return modevents_entry("modification", id); }
418 );
419
420 // We don't want the error message below if there is no era (= if this is a sp game).
421 if(!classification_.era_id.empty()) {
422 mods.emplace_back("era", classification_.era_id);
423 }
424
425 if(!classification_.campaign.empty()) {
426 mods.emplace_back("campaign", classification_.campaign);
427 }
428
429 for(modevents_entry& mod : mods) {
431 }
432 mods.clear();
433
434 while(starting_point_.has_child("load_resource")) {
435 assert(starting_point_.child_count("load_resource") > 0);
436 std::string id = starting_point_.mandatory_child("load_resource")["id"];
437 std::size_t pos = starting_point_.find_total_first_of("load_resource");
438 starting_point_.remove_child("load_resource", 0);
439 if(loaded_resources.find(id) == loaded_resources.end()) {
440 loaded_resources.insert(id);
441 load_non_scenario("resource", id, pos);
442 }
443 }
444 starting_point_["has_mod_events"] = true;
445 starting_point_["loaded_resources"] = utils::join(loaded_resources);
446 }
447}
448
450{
452 std::vector<modevents_entry> mods;
453
454 std::transform(classification_.active_mods.begin(), classification_.active_mods.end(), std::back_inserter(mods),
455 [](const std::string& id) { return modevents_entry("modification", id); }
456 );
457
458 mods.emplace_back("era", classification_.era_id);
459 mods.emplace_back("multiplayer", get_scenario_id());
460 mods.emplace_back("campaign", classification().campaign);
461
462 config& variables = carryover_.child_or_add("variables");
463
464 for(modevents_entry& mod : mods) {
465 if(auto cfg = mp_settings().options.find_child(mod.type, "id", mod.id)) {
466 for(const config& option : cfg->child_range("option")) {
467 try {
468 variable_access_create(option["id"], variables).as_scalar() = option["value"];
469 } catch(const invalid_variablename_exception&) {
470 ERR_NG << "variable " << option["id"] << "cannot be set to " << option["value"];
471 }
472 }
473 } else {
474 LOG_NG << "Couldn't find [" << mod.type << "] with id=" << mod.id << " for [option]s";
475 }
476 }
477 }
478}
479
480static void inherit_scenario(config& scenario, config&& src)
481{
482 config map_scen = std::move(src);
483 config& map_scenario = map_scen.has_child("multiplayer") ? map_scen.mandatory_child("multiplayer") : (map_scen.has_child("scenario") ? map_scen.mandatory_child("scenario") : map_scen);
484 config sides;
485 sides.splice_children(map_scenario, "side");
486 scenario.append_children(map_scenario);
487 scenario.inherit_attributes(map_scenario);
488 for(config& side_from : sides.child_range("side")) {
489 auto side_to = scenario.find_child("side", "side", side_from["side"]);
490 if(side_to) {
491 side_to->inherit_attributes(side_from);
492 side_to->append_children(side_from);
493 } else {
494 scenario.add_child("side", side_from);
495 }
496 }
497}
498
500{
501 if(!scenario["include_file"].empty()) {
502 std::string include_data = filesystem::read_scenario(scenario["include_file"]);
503 if(!include_data.empty()) {
504 inherit_scenario(scenario, io::read(include_data));
505 }
506 // this method gets called two additional times, so without this you end up calling inherit_scenario() three times total
507 // this is equivalent to the below check for map_data being empty
508 scenario["include_file"] = "";
509 }
510
511 if(scenario["map_data"].empty() && !scenario["map_file"].empty()) {
512 std::string map_data = filesystem::read_map(scenario["map_file"]);
513 if(map_data.find("map_data") != std::string::npos) {
514 // we have a scenario, generated by the editor
515 deprecated_message("map_file cfg", DEP_LEVEL::FOR_REMOVAL, "1.19", "Providing a .cfg file to the map_file attribute is deprecated. Use map_file for .map files and include_file for .cfg files.");
516 inherit_scenario(scenario, io::read(map_data));
517 } else {
518 // we have an plain map_data file
519 scenario["map_data"] = map_data;
520 }
521 }
522}
523
525{
527
529 // If the entire scenario should be randomly generated
530 if(!starting_point_["scenario_generation"].empty()) {
531 LOG_NG << "randomly generating scenario...";
532 const cursor::setter cursor_setter(cursor::WAIT);
533
534 config scenario_new =
535 random_generate_scenario(starting_point_["scenario_generation"], starting_point_.mandatory_child("generator"), &carryover_.child_or_empty("variables"));
536
538 starting_point_ = std::move(scenario_new);
539
540 update_label();
541 set_defaults();
542 }
543
544 // If no map_data is provided, try to load the specified file directly
546 // If the map should be randomly generated
547 // We don’t want that we accidentally to this twice so we check for starting_point_["map_data"].empty()
548 if(starting_point_["map_data"].empty() && !starting_point_["map_generation"].empty()) {
549 LOG_NG << "randomly generating map...";
550 const cursor::setter cursor_setter(cursor::WAIT);
551
552 starting_point_["map_data"] =
553 random_generate_map(starting_point_["map_generation"], starting_point_.mandatory_child("generator"), &carryover_.child_or_empty("variables"));
554 }
555 }
556}
557
558void saved_game::post_scenario_generation(const config& old_scenario, config& generated_scenario)
559{
560 static const std::vector<std::string> attributes_to_copy {
561 "id",
562 "addon_id",
563 "addon_title",
564 "addon_version",
565 "addon_min_version",
566 "require_scenario",
567 };
568
569 // TODO: should we add "description" to this list?
570 // TODO: in theory it is possible that whether the scenario is required depends on the generated scenario, so maybe remove require_scenario from this list.
571
572 for(const auto& str : attributes_to_copy) {
573 generated_scenario[str] = old_scenario[str];
574 }
575
576 // Preserve "story" from the scenario toplevel.
577 // Note that it does not delete [story] tags in generated_scenario, so you can still have your story
578 // dependent on the generated scenario.
579 for(const config& story : old_scenario.child_range("story")) {
580 generated_scenario.add_child("story", story);
581 }
582}
583
584
586{
590
592 for(config& side_cfg : get_starting_point().child_range("side")) {
593 sides.transfer_all_to(side_cfg);
594 }
595
596 carryover_ = sides.to_config();
599 }
600}
601
606
614
624
630
635
637{
638 if(!replay_start_.empty()) {
639 return replay_start_;
640 }
641
643 // Try to load the scenario form game config or from [scenario] if there is no [replay_start]
646 }
647
649 return starting_point_;
650 }
651
652 throw config::error("No replay_start found");
653}
654
673
675{
676 // TODO: remove this code duplication with write_... functions.
678
679 if(!replay_start_.empty()) {
680 r.add_child("replay_start", replay_start_);
681 }
682
683 replay_data_.write(r.add_child("replay"));
684
686 r.add_child("snapshot", starting_point_);
688 r.add_child("scenario", starting_point_);
689 }
690
691 r.add_child(has_carryover_expanded_ ? "carryover_sides" : "carryover_sides_start", carryover_);
692 r.add_child("multiplayer", mp_settings_.to_config());
693 r.add_child("statistics", statistics_.to_config());
694
695 return r;
696}
697
699{
700 std::string scenario_id;
701
703 scenario_id = starting_point_["id"].str();
704 } else if(!has_carryover_expanded_) {
705 scenario_id = carryover_["next_scenario"].str();
706 } else if(!replay_start_.empty()) {
707 scenario_id = replay_start_["id"].str();
708 } else {
709 assert(!"cannot figure out scenario_id");
710 throw "assertion ignored";
711 }
712
713 return scenario_id == "null" ? "" : scenario_id;
714}
715
717{
718 return true;
719}
720
722{
723 std::string& label = classification().label;
724
725 if(classification().abbrev.empty()) {
726 label = starting_point_["name"].str();
727 } else {
728 label = classification().abbrev + "-" + starting_point_["name"].t_str();
729 }
730
731 utils::erase_if(label, is_illegal_file_char);
732 std::replace(label.begin(), label.end(), '_', ' ');
733}
734
736{
737 for(config& side : starting_point_.child_range("side")) {
738 // for humans "goto_x/y" is used for multi-turn-moves
739 // for the ai "goto_x/y" is a way for wml to order the ai to move a unit to a certain place.
740 // we want to cancel human order but not to break wml.
741 if(side["controller"] != side_controller::human) {
742 continue;
743 }
744
745 for(config& unit : side.child_range("unit")) {
746 unit["goto_x"] = -999;
747 unit["goto_y"] = -999;
748 }
749 }
750}
751
753{
754 for(config& side : starting_point_.child_range("side")) {
755 side.remove_attribute("is_local");
756 }
757}
758
760{
761 swap(other);
762 return *this;
763}
764
765void saved_game::swap(saved_game& other) noexcept
766{
767 carryover_.swap(other.carryover_);
768
769 std::swap(classification_, other.classification_);
770 std::swap(has_carryover_expanded_, other.has_carryover_expanded_);
771 std::swap(mp_settings_, other.mp_settings_);
772
773 replay_data_.swap(other.replay_data_);
774 replay_start_.swap(other.replay_start_);
775 starting_point_.swap(other.starting_point_);
776
777 std::swap(starting_point_type_, other.starting_point_type_);
778}
779
781{
782 log_scope("read_game");
783 config cfg = std::move(input_cfg);
784
785 if(auto caryover_sides = cfg.optional_child("carryover_sides")) {
786 carryover_.swap(*caryover_sides);
788 } else if(auto caryover_sides_start = cfg.optional_child("carryover_sides_start")) {
789 carryover_.swap(*caryover_sides_start);
791 } else {
794 }
795
796 if(auto replay_start = cfg.optional_child("replay_start")) {
798 } else {
800 }
801
803
804 // Serversided replays can contain multiple [replay]
805 for(config& replay : cfg.child_range("replay")) {
807 }
808
810
811 if(auto snapshot = cfg.optional_child("snapshot")) {
813 starting_point_.swap(*snapshot);
814 } else if(auto scenario = cfg.optional_child("scenario")) {
816 starting_point_.swap(*scenario);
817 } else {
820 }
821
822 LOG_NG << "scenario: '" << carryover_["next_scenario"].str() << "'";
823
824 if(auto stats = cfg.optional_child("statistics")) {
825 statistics_.read(*stats);
826 }
827
829 mp_settings_ = { cfg.child_or_empty("multiplayer") };
830}
831
844
845void swap(saved_game& lhs, saved_game& rhs) noexcept
846{
847 lhs.swap(rhs);
848}
#define ERR_NG
#define LOG_NG
const randomness::mt_rng & rng() const
Definition carryover.hpp:92
const config to_config()
void merge_old_carryover(const carryover_info &old_carryover)
void transfer_to(config &level)
void transfer_all_to(config &side_cfg)
Variant for storing WML attributes.
bool empty() const
Tests for an attribute that either was never set or was set to "".
Class for writing a config out to a file in pieces.
void close_child(const std::string &key)
void write_child(const std::string &key, const config &cfg)
void open_child(const std::string &key)
void write(const config &cfg, bool strong_quotes=false)
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
void remove_attribute(std::string_view key)
Definition config.cpp:162
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
void append_children_by_move(config &cfg, std::string_view key)
Moves children with the given name from the given config to this one.
Definition config.cpp:224
config_attribute_value attribute_value
Variant for storing WML attributes.
Definition config.hpp:290
config & child_or_add(std::string_view key)
Returns a reference to the first child with the given key.
Definition config.cpp:401
child_itors child_range(std::string_view key)
Definition config.cpp:268
const attribute_value * get(std::string_view key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
Definition config.cpp:665
config & add_child_at_total(std::string_view key, const config &val, std::size_t pos)
Definition config.cpp:512
void swap(config &cfg) noexcept
Definition config.cpp:1280
optional_config_impl< config > find_child(std::string_view key, const std::string &name, const std::string &value)
Returns the first child of tag key with a name attribute containing value.
Definition config.cpp:764
void splice_children(config &src, std::string_view key)
Moves all the children with tag key from src to this.
Definition config.cpp:558
std::size_t all_children_count() const
Definition config.cpp:302
void inherit_attributes(const config &c)
Merge the attributes of config 'c' into this config, preserving this config's values.
Definition config.cpp:1159
void remove_child(std::string_view key, std::size_t index)
Definition config.cpp:623
std::size_t child_count(std::string_view key) const
Definition config.cpp:292
bool has_child(std::string_view key) const
Determine whether a config has a child or not.
Definition config.cpp:312
const config & child_or_empty(std::string_view key) const
Returns the first child with the given key, or an empty config if there is none.
Definition config.cpp:390
std::size_t find_total_first_of(std::string_view key, std::size_t start=0)
Definition config.cpp:498
void append_children(const config &cfg)
Adds children from cfg.
Definition config.cpp:167
bool empty() const
Definition config.cpp:823
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 clear()
Definition config.cpp:802
std::vector< std::string > active_mods
std::string label
Name of the game (e.g.
std::string get_tagname() const
std::string campaign
The id of the campaign being played.
std::string abbrev
the campaign abbreviation
static game_config_manager * get()
void load_game_config_for_game(const game_classification &classification, const std::string &scenario_id)
const game_config_view & game_config() const
A class grating read only view to a vector of config objects, viewed as one config with all children ...
void rotate_random()
Resets the random to the 0 calls and the seed to the random this way we stay in the same sequence but...
Definition mt_rng.cpp:73
int get_random_int(int min, int max)
Definition random.hpp:51
void write(config_writer &out) const
void append_config(const config &data)
void swap(replay_recorder_base &other) noexcept
bool not_corrupt() const
void set_data(config &&cfg)
config & replay_start()
void load_non_scenario(const std::string &type, const std::string &id, std::size_t pos)
helper for expand_mp_events();
void set_random_seed()
sets the random seed if that didn't already happen.
config replay_start_
snapshot made before the start event.
void write_config(config_writer &out) const
writes the config information into a stream (file)
void expand_scenario()
copies the content of a [scenario] with the correct id attribute from the game config into this objec...
void expand_mp_options()
adds values of [option]s into [carryover_sides_start][variables] so that they are applied in the next...
void unify_controllers()
void set_carryover_sides_start(config carryover_sides_start)
config to_config() const
starting_point starting_point_type_
std::string get_scenario_id() const
static void post_scenario_generation(const config &old_scenario, config &generated_scenario)
copies attributes & tags from the 'outer' [scenario] to the scenario that is generated by scenario_ge...
replay_recorder_base replay_data_
void update_label()
sets classification().label to the correct value.
game_classification & classification()
statistics_record::campaign_stats_t statistics_
void set_scenario(config scenario)
bool has_carryover_expanded_
static void expand_map_file(config &scenario)
reads scenario["map_file"]
void remove_snapshot()
config & get_starting_point()
config starting_point_
The starting pos where the (non replay) game will be started from.
const config & get_replay_starting_point()
void check_require_scenario()
Add addon_id information if needed.
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
void write_starting_point(config_writer &out) const
void expand_mp_events()
adds [event]s from [era] and [modification] into this scenario does NOT expand [option]s because vari...
config carryover_
depends on has_carryover_expanded_: if true: The carryover information for all sides from the previou...
void expand_random_scenario()
takes care of generate_map=, generate_scenario=, map= attributes This should be called before expandi...
mp_game_settings mp_settings_
void expand_carryover()
merges [carryover_sides_start] into [scenario] and saves the rest into [carryover_sides] Removes [car...
@ SNAPSHOT
We have a [snapshot] (mid-game-savefile).
@ SCENARIO
We have a [scenario] (start-of-scenario) savefile.
@ NONE
There is no scenario stating pos data (start-of-scenario).
@ INVALID
We failed to get a starting pos in expand_scenario.
void cancel_orders()
void convert_to_start_save()
converts a normal savegame form the end of a scenaio to a start-of-scenario savefile for the next sce...
bool valid() const
void set_defaults()
does some post loading stuff must be used before passing the data to connect_engine
void write_general_info(config_writer &out) const
game_classification classification_
some general information of the game that doesn't change during the game
config & set_snapshot(config snapshot)
void swap(saved_game &other) noexcept
void write_carryover(config_writer &out) const
statistics_record::campaign_stats_t & statistics()
saved_game & operator=(const saved_game &other)=delete
static const std::set< std::string > tags
Stores the child tags recognized by [side].
Definition team.hpp:120
static const std::set< std::string > attributes
Stores the attributes recognized by [side].
Definition team.hpp:54
This class represents a single unit of a specific type.
Definition unit.hpp:39
std::string str() const
Serializes the version number into string form.
Definitions for the interface to Wesnoth Markup Language (WML).
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
const config * cfg
std::string label
What to show in the filter's drop-down list.
Definition manager.cpp:201
std::string id
Text to match against addon_info.tags()
Definition manager.cpp:199
Standard logging facilities (interface).
#define log_scope(description)
Definition log.hpp:275
std::string random_generate_map(const std::string &name, const config &cfg, const config *vars)
config random_generate_scenario(const std::string &name, const config &cfg, const config *vars)
@ WAIT
Definition cursor.hpp:28
std::string read_scenario(const std::string &name)
std::string read_map(const std::string &name)
Game configuration data as global variables.
const version_info wesnoth_version(VERSION)
config read(std::istream &in, abstract_validator *validator)
Definition parser.cpp:610
rng * generator
This generator is automatically synced during synced context.
Definition random.cpp:60
std::string interpolate_variables_into_string(const std::string &str, const string_map *const symbols)
Function which will interpolate variables, starting with '$' in the string 'str' with the equivalent ...
std::string join(const Range &v, const std::string &s=",")
Generates a new string joining container items in a list.
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition general.hpp:108
static lg::log_domain log_engine("engine")
void swap(saved_game &lhs, saved_game &rhs) noexcept
Implement non-member swap function for std::swap (calls saved_game::swap).
static void inherit_scenario(config &scenario, config &&src)
rect src
Non-transparent portion of the surface to compose.
void update_addon_requirements(const config &addon_cfg)
Takes a config with addon metadata (id, name, version, min_version) and adds it as a requirement for ...
config to_config() const
void new_scenario(const std::string &scenario_name)
Adds an entry for anew scenario to wrte to.
void read(const config &cfg, bool append=false)
void write(config_writer &out) const
variable_info_mutable< variable_info_implementation::vi_policy_create > variable_access_create
'Create if nonexistent' access.
#define c