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