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