The Battle for Wesnoth  1.17.0-dev
configuration.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2021
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 
188 
189 bool configuration::get_side_config_from_file(const std::string& file, config& cfg ){
190  try {
192  read(cfg, *stream);
193  LOG_AI_CONFIGURATION << "Reading AI configuration from file '" << file << "'" << std::endl;
194  } catch(const config::error &) {
195  ERR_AI_CONFIGURATION << "Error while reading AI configuration from file '" << file << "'" << std::endl;
196  return false;
197  }
198  LOG_AI_CONFIGURATION << "Successfully read AI configuration from file '" << file << "'" << std::endl;
199  return true;
200 }
201 
203 {
204  return default_config_;
205 }
206 
207 
208 bool configuration::parse_side_config(side_number side, const config& original_cfg, config &cfg )
209 {
210  LOG_AI_CONFIGURATION << "side "<< side <<": parsing AI configuration from config" << std::endl;
211 
212  //leave only the [ai] children
213  cfg.clear();
214  for (const config &aiparam : original_cfg.child_range("ai")) {
215  cfg.add_child("ai",aiparam);
216  }
217 
218  //backward-compatibility hack: put ai_algorithm if it is present
219  if (const config::attribute_value *v = original_cfg.get("ai_algorithm")) {
220  config ai_a;
221  ai_a["ai_algorithm"] = *v;
222  cfg.add_child("ai",ai_a);
223  }
224  DBG_AI_CONFIGURATION << "side " << side << ": config contains:"<< std::endl << cfg << std::endl;
225 
226  //insert default config at the beginning
227  if (default_config_) {
228  DBG_AI_CONFIGURATION << "side "<< side <<": applying default configuration" << std::endl;
229  cfg.add_child_at("ai",default_config_,0);
230  } else {
231  ERR_AI_CONFIGURATION << "side "<< side <<": default configuration is not available, not applying it" << std::endl;
232  }
233 
234  LOG_AI_CONFIGURATION << "side "<< side << ": expanding simplified aspects into full facets"<< std::endl;
235  expand_simplified_aspects(side, cfg);
236 
237  //construct new-style integrated config
238  LOG_AI_CONFIGURATION << "side "<< side << ": doing final operations on AI config"<< std::endl;
239  config parsed_cfg = config();
240 
241  LOG_AI_CONFIGURATION << "side "<< side <<": merging AI configurations"<< std::endl;
242  for (const config &aiparam : cfg.child_range("ai")) {
243  parsed_cfg.append(aiparam);
244  }
245 
246 
247  LOG_AI_CONFIGURATION << "side "<< side <<": merging AI aspect with the same id"<< std::endl;
248  parsed_cfg.merge_children_by_attribute("aspect","id");
249 
250  LOG_AI_CONFIGURATION << "side "<< side <<": removing duplicate [default] tags from aspects"<< std::endl;
251  for (config &aspect_cfg : parsed_cfg.child_range("aspect")) {
252  if (aspect_cfg["name"] != "composite_aspect") {
253  // No point in warning about Lua or standard aspects lacking [default]
254  continue;
255  }
256  if (!aspect_cfg.child("default")) {
257  WRN_AI_CONFIGURATION << "side "<< side <<": aspect with id=["<<aspect_cfg["id"]<<"] lacks default config facet!" <<std::endl;
258  continue;
259  }
260  aspect_cfg.merge_children("default");
261  config& dflt = aspect_cfg.child("default");
262  if (dflt.has_child("value")) {
263  while (dflt.child_count("value") > 1) {
264  dflt.remove_child("value", 0);
265  }
266  }
267  }
268 
269  DBG_AI_CONFIGURATION << "side "<< side <<": done parsing side config, it contains:"<< std::endl << parsed_cfg << std::endl;
270  LOG_AI_CONFIGURATION << "side "<< side <<": done parsing side config"<< std::endl;
271 
272  cfg = parsed_cfg;
273  return true;
274 
275 }
276 
277 static const std::set<std::string> non_aspect_attributes {"turns", "time_of_day", "engine", "ai_algorithm", "id", "description", "hidden", "mp_rank"};
278 static const std::set<std::string> just_copy_tags {"engine", "stage", "aspect", "goal", "modify_ai", "micro_ai"};
279 static const std::set<std::string> old_goal_tags {"target", "target_location", "protect_unit", "protect_location"};
280 
282  std::string algorithm;
283  config base_config, parsed_config;
284  for (const config &aiparam : cfg.child_range("ai")) {
285  std::string turns, time_of_day, engine = "cpp";
286  if (aiparam.has_attribute("turns")) {
287  turns = aiparam["turns"].str();
288  }
289  if (aiparam.has_attribute("time_of_day")) {
290  time_of_day = aiparam["time_of_day"].str();
291  }
292  if (aiparam.has_attribute("engine")) {
293  engine = aiparam["engine"].str();
294  if(engine == "fai") {
295  deprecated_message("FormulaAI", DEP_LEVEL::FOR_REMOVAL, "1.17", "FormulaAI is slated to be removed. Use equivalent Lua AIs instead");
296  }
297  }
298  if (aiparam.has_attribute("ai_algorithm")) {
299  if (algorithm.empty()) {
300  algorithm = aiparam["ai_algorithm"].str();
301  base_config = get_ai_config_for(algorithm);
302  } else if(algorithm != aiparam["ai_algorithm"]) {
303  lg::log_to_chat() << "side " << side << " has two [ai] tags with contradictory ai_algorithm - the first one will take precedence.\n";
304  ERR_WML << "side " << side << " has two [ai] tags with contradictory ai_algorithm - the first one will take precedence.";
305  }
306  }
307  std::deque<std::pair<std::string, config>> facet_configs;
308  for (const config::attribute &attr : aiparam.attribute_range()) {
309  if (non_aspect_attributes.count(attr.first)) {
310  continue;
311  }
312  config facet_config;
313  facet_config["engine"] = engine;
314  facet_config["name"] = "standard_aspect";
315  facet_config["turns"] = turns;
316  facet_config["time_of_day"] = time_of_day;
317  facet_config["value"] = attr.second;
318  facet_configs.emplace_back(attr.first, facet_config);
319  }
320  for (const config::any_child child : aiparam.all_children_range()) {
321  if (just_copy_tags.count(child.key)) {
322  // These aren't simplified, so just copy over unchanged.
323  parsed_config.add_child(child.key, child.cfg);
324  if(
325  (child.key != "modify_ai" && child.cfg["engine"] == "fai") ||
326  (child.key == "modify_ai" && child.cfg.all_children_count() > 0 && child.cfg.all_children_range().front().cfg["engine"] == "fai")
327  ) {
328  deprecated_message("FormulaAI", DEP_LEVEL::FOR_REMOVAL, "1.17", "FormulaAI is slated to be removed. Use equivalent Lua AIs instead");
329  }
330  continue;
331  } else if(old_goal_tags.count(child.key)) {
332  // A simplified goal, mainly kept around just for backwards compatibility.
333  config goal_config, criteria_config = child.cfg;
334  goal_config["name"] = child.key;
335  goal_config["turns"] = turns;
336  goal_config["time_of_day"] = time_of_day;
337  if(child.key.substr(0,7) == "protect" && criteria_config.has_attribute("protect_radius")) {
338  goal_config["protect_radius"] = criteria_config["protect_radius"];
339  criteria_config.remove_attribute("protect_radius");
340  }
341  if(criteria_config.has_attribute("value")) {
342  goal_config["value"] = criteria_config["value"];
343  criteria_config.remove_attribute("value");
344  }
345  goal_config.add_child("criteria", criteria_config);
346  parsed_config.add_child("goal", std::move(goal_config));
347  continue;
348  }
349  // Now there's two possibilities. If the tag is [attacks] or contains either value= or [value],
350  // then it can be copied verbatim as a [facet] tag.
351  // Otherwise, it needs to be placed as a [value] within a [facet] tag.
352  if (child.key == "attacks" || child.cfg.has_attribute("value") || child.cfg.has_child("value")) {
353  facet_configs.emplace_back(child.key, child.cfg);
354  } else {
355  config facet_config;
356  facet_config["engine"] = engine;
357  facet_config["name"] = "standard_aspect";
358  facet_config["turns"] = turns;
359  facet_config["time_of_day"] = time_of_day;
360  facet_config.add_child("value", child.cfg);
361  if (child.key == "leader_goal" && !child.cfg["id"].empty()) {
362  // Use id= attribute (if present) as the facet ID
363  const std::string& id = child.cfg["id"];
364  if(id != "*" && id.find_first_not_of("0123456789") != std::string::npos) {
365  facet_config["id"] = child.cfg["id"];
366  }
367  }
368  facet_configs.emplace_back(child.key, facet_config);
369  }
370  }
371  std::map<std::string, config> aspect_configs;
372  while (!facet_configs.empty()) {
373  const std::string &aspect = facet_configs.front().first;
374  const config &facet_config = facet_configs.front().second;
375  aspect_configs[aspect]["id"] = aspect; // Will sometimes be redundant assignment
376  aspect_configs[aspect]["name"] = "composite_aspect";
377  aspect_configs[aspect].add_child("facet", facet_config);
378  facet_configs.pop_front();
379  }
380  typedef std::map<std::string, config>::value_type aspect_pair;
381  for (const aspect_pair& p : aspect_configs) {
382  parsed_config.add_child("aspect", p.second);
383  }
384  }
385  // Support old recruitment aspect syntax
386  for(auto& child : parsed_config.child_range("aspect")) {
387  if(child["id"] == "recruitment") {
388  deprecated_message("AI recruitment aspect", DEP_LEVEL::INDEFINITE, "", "Use the recruitment_instructions aspect instead");
389  child["id"] = "recruitment_instructions";
390  }
391  }
392  if (algorithm.empty() && !parsed_config.has_child("stage")) {
394  }
395  for (const config::any_child child : parsed_config.all_children_range()) {
396  base_config.add_child(child.key, child.cfg);
397  }
398  cfg.clear_children("ai");
399  cfg.add_child("ai", std::move(base_config));
400 }
401 
402 } //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:557
std::string era()
Definition: game.cpp:695
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:222
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 description_map era_ai_configurations_
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
static description_map ai_configurations_
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:206
#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)
mock_party p
Game configuration data as global variables.
Definition: build_info.cpp:59
std::map< std::string, description > description_map
Declarations for File-IO.
static description_map mod_ai_configurations_
config & add_child(config_key_type key)
Definition: config.cpp:514
int turns()
Definition: game.cpp:559
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_
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
filesystem::scoped_istream preprocess_file(const std::string &fname, preproc_map *defines)
Function to use the WML preprocessor on a file.
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