The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
configuration.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2009 - 2025
3 by Yurii Chernyi <terraninfo@terraninfo.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/**
18 * Managing the AI configuration
19 * @file
20 */
21
22#include "ai/configuration.hpp"
23
24#include "filesystem.hpp"
25#include "log.hpp"
28#include "game_config_view.hpp"
29#include "deprecation.hpp"
30#include <vector>
31#include <deque>
32#include <set>
33
34namespace ai {
35
37#define DBG_AI_CONFIGURATION LOG_STREAM(debug, log_ai_configuration)
38#define LOG_AI_CONFIGURATION LOG_STREAM(info, log_ai_configuration)
39#define WRN_AI_CONFIGURATION LOG_STREAM(warn, log_ai_configuration)
40#define ERR_AI_CONFIGURATION LOG_STREAM(err, log_ai_configuration)
41
42static lg::log_domain log_wml("wml");
43#define ERR_WML LOG_STREAM(err, log_wml)
44
46{
47 ai_configurations_.clear();
50
51 const config& ais = game_config.mandatory_child("ais");
52 if (auto default_config = ais.optional_child("default_config")) {
53 default_config_ = *default_config;
54 } else {
55 ERR_AI_CONFIGURATION << "Missing AI [default_config]. Therefore, default_config_ set to empty.";
57 }
58 default_ai_algorithm_ = ais["default_ai_algorithm"].str();
59 if (default_ai_algorithm_.empty()) {
60 ERR_AI_CONFIGURATION << "Missing default_ai_algorithm. This will result in no AI being loaded by default.";
61 }
62
63
64 for (const config& ai_configuration : ais.child_range("ai")) {
65 const std::string& id = ai_configuration["id"];
66 if (id.empty()){
67
68 ERR_AI_CONFIGURATION << "skipped AI config due to missing id" << ". Config contains:"<< std::endl << ai_configuration;
69 continue;
70 }
71 if (ai_configurations_.count(id)>0){
72 ERR_AI_CONFIGURATION << "skipped AI config due to duplicate id [" << id << "]. Config contains:"<< std::endl << ai_configuration;
73 continue;
74 }
75
76 description desc;
77 desc.id=id;
78 desc.mp_rank=ai_configuration["mp_rank"].to_int(std::numeric_limits<int>::max());
79 desc.text = ai_configuration["description"].t_str();
80 desc.cfg=ai_configuration;
81
82 ai_configurations_.emplace(id, desc);
83 LOG_AI_CONFIGURATION << "loaded AI config: " << ai_configuration["description"];
84 }
85}
86
87namespace {
88void extract_ai_configurations(std::map<std::string, description>& storage, const config& input)
89{
90 for (const config& ai_configuration : input.child_range("ai")) {
91 const std::string& id = ai_configuration["id"];
92 if (id.empty()){
93
94 ERR_AI_CONFIGURATION << "skipped AI config due to missing id" << ". Config contains:"<< std::endl << ai_configuration;
95 continue;
96 }
97 if (storage.count(id)>0){
98 ERR_AI_CONFIGURATION << "skipped AI config due to duplicate id [" << id << "]. Config contains:"<< std::endl << ai_configuration;
99 continue;
100 }
101
102 description desc;
103 desc.id=id;
104 desc.text = ai_configuration["description"].t_str();
105 desc.mp_rank = ai_configuration["mp_rank"].to_int(std::numeric_limits<int>::max());
106 desc.cfg=ai_configuration;
107
108 storage.emplace(id, desc);
109 LOG_AI_CONFIGURATION << "loaded AI config: " << ai_configuration["description"];
110 }
111}
112}
113
115{
117 extract_ai_configurations(era_ai_configurations_, era);
118}
119
121{
123 for (const config& mod : mods) {
124 extract_ai_configurations(mod_ai_configurations_, mod);
125 }
126}
127
128std::vector<description*> configuration::get_available_ais()
129{
130 std::vector<description*> ais_list;
131
132 const auto add_if_not_hidden = [&ais_list](description* d) {
133 const config& cfg = d->cfg;
134
135 if(!cfg["hidden"].to_bool(false)) {
136 ais_list.push_back(d);
137
138 DBG_AI_CONFIGURATION << "has ai with config: " << std::endl << cfg;
139 }
140 };
141
142 for(auto& a_config : ai_configurations_) {
143 add_if_not_hidden(&a_config.second);
144 }
145
146 for(auto& e_config : era_ai_configurations_) {
147 add_if_not_hidden(&e_config.second);
148 }
149
150 for(auto& m_config : mod_ai_configurations_) {
151 add_if_not_hidden(&m_config.second);
152 }
153
154 // Sort by mp_rank. For same mp_rank, keep alphabetical order.
155 std::stable_sort(ais_list.begin(), ais_list.end(),
156 [](const description* a, const description* b) {
157 return a->mp_rank < b->mp_rank;
158 }
159 );
160
161 return ais_list;
162}
163
164const config& configuration::get_ai_config_for(const std::string& id)
165{
166 description_map::iterator cfg_it = ai_configurations_.find(id);
167 if (cfg_it==ai_configurations_.end()){
168 description_map::iterator era_cfg_it = era_ai_configurations_.find(id);
169 if (era_cfg_it==era_ai_configurations_.end()){
170 description_map::iterator mod_cfg_it = mod_ai_configurations_.find(id);
171 if (mod_cfg_it==mod_ai_configurations_.end()) {
172 return default_config_;
173 } else {
174 return mod_cfg_it->second.cfg;
175 }
176 } else {
177 return era_cfg_it->second.cfg;
178 }
179 }
180 return cfg_it->second.cfg;
181}
182
184{
185 try {
187 cfg = io::read(*stream);
188 LOG_AI_CONFIGURATION << "Reading AI configuration from file '" << file << "'";
189 } catch(const config::error&) {
190 ERR_AI_CONFIGURATION << "Error while reading AI configuration from file '" << file << "'";
191 return false;
192 } catch(const std::exception&) {
193 //value() now throws on invalid paths.
194 ERR_AI_CONFIGURATION << "Error while reading AI configuration from file '" << file << "'";
195 return false;
196 }
197 LOG_AI_CONFIGURATION << "Successfully read AI configuration from file '" << file << "'";
198 return true;
199}
200
205
206
208{
209 LOG_AI_CONFIGURATION << "side "<< side <<": parsing AI configuration from config";
210
211 //leave only the [ai] children
212 cfg.clear();
213 for (const config& aiparam : original_cfg.child_range("ai")) {
214 cfg.add_child("ai",aiparam);
215 }
216
217 //backward-compatibility hack: put ai_algorithm if it is present
218 if (const config::attribute_value *v = original_cfg.get("ai_algorithm")) {
219 config ai_a;
220 ai_a["ai_algorithm"] = *v;
221 cfg.add_child("ai",ai_a);
222 }
223 DBG_AI_CONFIGURATION << "side " << side << ": config contains:"<< std::endl << cfg;
224
225 //insert default config at the beginning
226 if (!default_config_.empty()) {
227 DBG_AI_CONFIGURATION << "side "<< side <<": applying default configuration";
229 } else {
230 ERR_AI_CONFIGURATION << "side "<< side <<": default configuration is not available, not applying it";
231 }
232
233 LOG_AI_CONFIGURATION << "side "<< side << ": expanding simplified aspects into full facets";
235
236 //construct new-style integrated config
237 LOG_AI_CONFIGURATION << "side "<< side << ": doing final operations on AI config";
238 config parsed_cfg = config();
239
240 LOG_AI_CONFIGURATION << "side "<< side <<": merging AI configurations";
241 for (const config& aiparam : cfg.child_range("ai")) {
242 parsed_cfg.append(aiparam);
243 }
244
245
246 LOG_AI_CONFIGURATION << "side "<< side <<": merging AI aspect with the same id";
247 parsed_cfg.merge_children_by_attribute("aspect","id");
248
249 LOG_AI_CONFIGURATION << "side "<< side <<": removing duplicate [default] tags from aspects";
250 for (config& aspect_cfg : parsed_cfg.child_range("aspect")) {
251 if (aspect_cfg["name"] != "composite_aspect") {
252 // No point in warning about Lua or standard aspects lacking [default]
253 continue;
254 }
255 if (!aspect_cfg.has_child("default")) {
256 WRN_AI_CONFIGURATION << "side "<< side <<": aspect with id=["<<aspect_cfg["id"]<<"] lacks default config facet!";
257 continue;
258 }
259 aspect_cfg.merge_children("default");
260 config& dflt = aspect_cfg.mandatory_child("default");
261 if (dflt.has_child("value")) {
262 while (dflt.child_count("value") > 1) {
263 dflt.remove_child("value", 0);
264 }
265 }
266 }
267
268 DBG_AI_CONFIGURATION << "side "<< side <<": done parsing side config, it contains:"<< std::endl << parsed_cfg;
269 LOG_AI_CONFIGURATION << "side "<< side <<": done parsing side config";
270
271 cfg = parsed_cfg;
272 return true;
273
274}
275
276static const std::set<std::string> non_aspect_attributes {"turns", "time_of_day", "engine", "ai_algorithm", "id", "description", "hidden", "mp_rank"};
277static const std::set<std::string> just_copy_tags {"engine", "stage", "aspect", "goal", "modify_ai", "micro_ai"};
278static const std::set<std::string> old_goal_tags {"target", "target_location", "protect_unit", "protect_location"};
279
281 std::string algorithm;
282 config base_config, parsed_config;
283 for (const config& aiparam : cfg.child_range("ai")) {
284 std::string turns, time_of_day, engine = "cpp";
285 if (aiparam.has_attribute("turns")) {
286 turns = aiparam["turns"].str();
287 }
288 if (aiparam.has_attribute("time_of_day")) {
289 time_of_day = aiparam["time_of_day"].str();
290 }
291 if (aiparam.has_attribute("engine")) {
292 engine = aiparam["engine"].str();
293 }
294 if (aiparam.has_attribute("ai_algorithm")) {
295 if (algorithm.empty()) {
296 algorithm = aiparam["ai_algorithm"].str();
297 base_config = get_ai_config_for(algorithm);
298 } else if(aiparam["ai_algorithm"] != algorithm) {
299 lg::log_to_chat() << "side " << side << " has two [ai] tags with contradictory ai_algorithm - the first one will take precedence.\n";
300 ERR_WML << "side " << side << " has two [ai] tags with contradictory ai_algorithm - the first one will take precedence.";
301 }
302 }
303 std::deque<std::pair<std::string, config>> facet_configs;
304 for(const auto& [key, value] : aiparam.attribute_range()) {
305 if (non_aspect_attributes.count(key)) {
306 continue;
307 }
308 config facet_config;
309 facet_config["engine"] = engine;
310 facet_config["name"] = "standard_aspect";
311 facet_config["turns"] = turns;
312 facet_config["time_of_day"] = time_of_day;
313 facet_config["value"] = value;
314 facet_configs.emplace_back(key, facet_config);
315 }
316 for(const auto [child_key, child_cfg] : aiparam.all_children_view()) {
317 if (just_copy_tags.count(child_key)) {
318 // These aren't simplified, so just copy over unchanged.
319 parsed_config.add_child(child_key, child_cfg);
320 continue;
321 } else if(old_goal_tags.count(child_key)) {
322 // A simplified goal, mainly kept around just for backwards compatibility.
323 config goal_config, criteria_config = child_cfg;
324 goal_config["name"] = child_key;
325 goal_config["turns"] = turns;
326 goal_config["time_of_day"] = time_of_day;
327 if(child_key.substr(0,7) == "protect" && criteria_config.has_attribute("protect_radius")) {
328 goal_config["protect_radius"] = criteria_config["protect_radius"];
329 criteria_config.remove_attribute("protect_radius");
330 }
331 if(criteria_config.has_attribute("value")) {
332 goal_config["value"] = criteria_config["value"];
333 criteria_config.remove_attribute("value");
334 }
335 goal_config.add_child("criteria", criteria_config);
336 parsed_config.add_child("goal", std::move(goal_config));
337 continue;
338 }
339 // Now there's two possibilities. If the tag is [attacks] or contains either value= or [value],
340 // then it can be copied verbatim as a [facet] tag.
341 // Otherwise, it needs to be placed as a [value] within a [facet] tag.
342 if (child_key == "attacks" || child_cfg.has_attribute("value") || child_cfg.has_child("value")) {
343 facet_configs.emplace_back(child_key, child_cfg);
344 } else {
345 config facet_config;
346 facet_config["engine"] = engine;
347 facet_config["name"] = "standard_aspect";
348 facet_config["turns"] = turns;
349 facet_config["time_of_day"] = time_of_day;
350 facet_config.add_child("value", child_cfg);
351 if (child_key == "leader_goal" && !child_cfg["id"].empty()) {
352 // Use id= attribute (if present) as the facet ID
353 const std::string& id = child_cfg["id"];
354 if(id != "*" && id.find_first_not_of("0123456789") != std::string::npos) {
355 facet_config["id"] = child_cfg["id"];
356 }
357 }
358 facet_configs.emplace_back(child_key, facet_config);
359 }
360 }
361 std::map<std::string, config> aspect_configs;
362 while (!facet_configs.empty()) {
363 const std::string& aspect = facet_configs.front().first;
364 const config& facet_config = facet_configs.front().second;
365 aspect_configs[aspect]["id"] = aspect; // Will sometimes be redundant assignment
366 aspect_configs[aspect]["name"] = "composite_aspect";
367 aspect_configs[aspect].add_child("facet", facet_config);
368 facet_configs.pop_front();
369 }
370 typedef std::map<std::string, config>::value_type aspect_pair;
371 for (const aspect_pair& p : aspect_configs) {
372 parsed_config.add_child("aspect", p.second);
373 }
374 }
375 // Support old recruitment aspect syntax
376 for(auto& child : parsed_config.child_range("aspect")) {
377 if(child["id"] == "recruitment") {
378 deprecated_message("AI recruitment aspect", DEP_LEVEL::INDEFINITE, "", "Use the recruitment_instructions aspect instead");
379 child["id"] = "recruitment_instructions";
380 }
381 }
382 if (algorithm.empty() && !parsed_config.has_child("stage")) {
384 }
385 for(const auto [child_key, child_cfg] : parsed_config.all_children_view()) {
386 base_config.add_child(child_key, child_cfg);
387 }
388 cfg.clear_children("ai");
389 cfg.add_child("ai", std::move(base_config));
390}
391
392} //end of namespace ai
virtual bool add_child(const path_element &child, const config &cfg)
Definition component.cpp:70
static description_map ai_configurations_
static config default_config_
static std::vector< description * > get_available_ais()
Returns a list of available AIs.
static description_map era_ai_configurations_
static bool get_side_config_from_file(const std::string &file, config &cfg)
get side config from file
static std::string default_ai_algorithm_
static void init(const game_config_view &game_config)
Init the parameters of ai configuration parser.
static const config & get_ai_config_for(const std::string &id)
Return the config for a specified ai.
static bool parse_side_config(side_number side, const config &original_cfg, config &cfg)
static void add_era_ai_from_config(const config &game_config)
static void expand_simplified_aspects(side_number side, config &cfg)
Expand simplified aspects, similar to the change from 1.7.2 to 1.7.3 but with some additional syntax ...
static const config & get_default_ai_parameters()
get default AI parameters
static description_map mod_ai_configurations_
static void add_mod_ai_from_config(const config::const_child_itors &configs)
Variant for storing WML attributes.
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
void merge_children_by_attribute(std::string_view key, std::string_view attribute)
All children with the given key and with equal values of the specified attribute will be merged into ...
Definition config.cpp:250
void append(const config &cfg)
Append data from another config object to this one.
Definition config.cpp:188
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
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
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
void clear_children(T... keys)
Definition config.hpp:601
bool has_attribute(std::string_view key) const
Definition config.cpp:157
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
config & add_child_at(std::string_view key, const config &val, std::size_t index)
Definition config.cpp:465
boost::iterator_range< const_child_iterator > const_child_itors
Definition config.hpp:281
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
A class grating read only view to a vector of config objects, viewed as one config with all children ...
#define DBG_AI_CONFIGURATION
#define ERR_AI_CONFIGURATION
#define LOG_AI_CONFIGURATION
#define ERR_WML
#define WRN_AI_CONFIGURATION
Managing the AIs configuration - headers.
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
const config * cfg
Declarations for File-IO.
std::string id
Text to match against addon_info.tags()
Definition manager.cpp:199
Standard logging facilities (interface).
A small explanation about what's going on here: Each action has access to two game_info objects First...
Definition actions.cpp:59
static const std::set< std::string > non_aspect_attributes
static const std::set< std::string > old_goal_tags
static lg::log_domain log_wml("wml")
int side_number
Definition game_info.hpp:40
static const std::set< std::string > just_copy_tags
static lg::log_domain log_ai_configuration("ai/config")
utils::optional< std::string > get_wml_location(const std::string &path, const utils::optional< std::string > &current_dir=utils::nullopt)
Returns a translated path to the actual file or directory, if it exists.
std::unique_ptr< std::istream > scoped_istream
Game configuration data as global variables.
config read(std::istream &in, abstract_validator *validator)
Definition parser.cpp:610
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition log.cpp:550
filesystem::scoped_istream preprocess_file(const std::string &fname, preproc_map &defines)
Function to use the WML preprocessor on a file.
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
AI parameters.
Object which defines a time of day with associated bonuses, image, sounds etc.
mock_party p
#define d
#define b