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