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