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 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  * Managing the AI configuration
17  * @file
18  */
19 
20 #include "ai/configuration.hpp"
21 
22 #include "filesystem.hpp"
23 #include "log.hpp"
24 #include "serialization/parser.hpp"
26 
27 #include <deque>
28 #include <set>
29 #include <vector>
30 
31 namespace ai
32 {
33 static lg::log_domain log_ai_configuration("ai/config");
34 #define DBG_AI_CONFIGURATION LOG_STREAM(debug, log_ai_configuration)
35 #define LOG_AI_CONFIGURATION LOG_STREAM(info, log_ai_configuration)
36 #define WRN_AI_CONFIGURATION LOG_STREAM(warn, log_ai_configuration)
37 #define ERR_AI_CONFIGURATION LOG_STREAM(err, log_ai_configuration)
38 
40 {
41  ai_configurations_.clear();
42  era_ai_configurations_.clear();
43  mod_ai_configurations_.clear();
44 
45  const config& ais = game_config.child("ais");
46 
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 
53  for(const config& ai_configuration : ais.child_range("ai")) {
54  const std::string& id = ai_configuration["id"];
55  if(id.empty()) {
57  << "skipped AI config due to missing id. Config contains:" << std::endl
58  << ai_configuration << std::endl;
59  continue;
60  }
61 
62  if(ai_configurations_.count(id) > 0) {
64  << "skipped AI config due to duplicate id [" << id << "]. Config contains:" << std::endl
65  << ai_configuration << std::endl;
66  continue;
67  }
68 
69  description desc;
70  desc.id = id;
71  desc.text = ai_configuration["description"].t_str();
72  desc.cfg = ai_configuration;
73 
74  ai_configurations_.emplace(id, desc);
75  LOG_AI_CONFIGURATION << "loaded AI config: " << ai_configuration["description"] << std::endl;
76  }
77 }
78 
79 namespace
80 {
81 void extract_ai_configurations(std::map<std::string, description>& storage, const config& input)
82 {
83  for(const config& ai_configuration : input.child_range("ai")) {
84  const std::string& id = ai_configuration["id"];
85 
86  if(id.empty()) {
88  << "skipped AI config due to missing id. Config contains:" << std::endl
89  << ai_configuration << std::endl;
90  continue;
91  }
92 
93  if(storage.count(id) > 0) {
95  << "skipped AI config due to duplicate id [" << id << "]. Config contains:" << std::endl
96  << ai_configuration << std::endl;
97  continue;
98  }
99 
100  description desc;
101  desc.id = id;
102  desc.text = ai_configuration["description"].t_str();
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 } // end anon namespace
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  return ais_list;
152 }
153 
154 const config& configuration::get_ai_config_for(const std::string& id)
155 {
156  auto cfg_it = ai_configurations_.find(id);
157  if(cfg_it == ai_configurations_.end()) {
158  auto era_cfg_it = era_ai_configurations_.find(id);
159 
160  if(era_cfg_it == era_ai_configurations_.end()) {
161  auto mod_cfg_it = mod_ai_configurations_.find(id);
162 
163  if(mod_cfg_it == mod_ai_configurations_.end()) {
164  return default_config_;
165  } else {
166  return mod_cfg_it->second.cfg;
167  }
168  } else {
169  return era_cfg_it->second.cfg;
170  }
171  }
172 
173  return cfg_it->second.cfg;
174 }
175 
180 
181 bool configuration::get_side_config_from_file(const std::string& file, config& cfg)
182 {
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 
192  LOG_AI_CONFIGURATION << "Successfully read AI configuration from file '" << file << "'" << std::endl;
193  return true;
194 }
195 
197 {
198  return default_config_;
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 
218  DBG_AI_CONFIGURATION << "side " << side << ": config contains:" << std::endl << cfg << std::endl;
219 
220  // insert default config at the beginning
221  if(default_config_) {
222  DBG_AI_CONFIGURATION << "side " << side << ": applying default configuration" << std::endl;
223  cfg.add_child_at("ai", default_config_, 0);
224  } else {
225  ERR_AI_CONFIGURATION << "side " << side << ": default configuration is not available, not applying it" << std::endl;
226  }
227 
228  LOG_AI_CONFIGURATION << "side " << side << ": expanding simplified aspects into full facets" << std::endl;
229  expand_simplified_aspects(side, cfg);
230 
231  // construct new-style integrated config
232  LOG_AI_CONFIGURATION << "side " << side << ": doing final operations on AI config" << std::endl;
233  config parsed_cfg;
234 
235  LOG_AI_CONFIGURATION << "side " << side << ": merging AI configurations" << std::endl;
236  for(const config& aiparam : cfg.child_range("ai")) {
237  parsed_cfg.append(aiparam);
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 
250  if(!aspect_cfg.child("default")) {
252  << "side " << side << ": aspect with id=[" << aspect_cfg["id"]
253  << "] lacks default config facet!" << std::endl;
254  continue;
255  }
256 
257  aspect_cfg.merge_children("default");
258  config& dflt = aspect_cfg.child("default");
259 
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 = std::move(parsed_cfg);
271  return true;
272 }
273 
274 static const std::set<std::string> non_aspect_attributes {"turns", "time_of_day", "engine", "ai_algorithm", "id", "description", "hidden"};
275 static const std::set<std::string> just_copy_tags {"engine", "stage", "aspect", "goal", "modify_ai"};
276 static const std::set<std::string> old_goal_tags {"target", "target_location", "protect_unit", "protect_location"};
277 
279 {
280  std::string algorithm;
281  config base_config, parsed_config;
282 
283  for(const config& aiparam : cfg.child_range("ai")) {
284  std::string turns, time_of_day, engine = "cpp";
285 
286  if(aiparam.has_attribute("turns")) {
287  turns = aiparam["turns"].str();
288  }
289 
290  if(aiparam.has_attribute("time_of_day")) {
291  time_of_day = aiparam["time_of_day"].str();
292  }
293 
294  if(aiparam.has_attribute("engine")) {
295  engine = aiparam["engine"].str();
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::wml_error()
304  << "side " << side
305  << " has two [ai] tags with contradictory ai_algorithm - the first one will take precedence.\n";
306  }
307  }
308 
309  std::deque<std::pair<std::string, config>> facet_configs;
310 
311  for(const config::attribute& attr : aiparam.attribute_range()) {
312  if(non_aspect_attributes.count(attr.first)) {
313  continue;
314  }
315 
316  config facet_config;
317  facet_config["engine"] = engine;
318  facet_config["name"] = "standard_aspect";
319  facet_config["turns"] = turns;
320  facet_config["time_of_day"] = time_of_day;
321  facet_config["value"] = attr.second;
322  facet_configs.emplace_back(attr.first, std::move(facet_config));
323  }
324 
325  for(const config::any_child& child : aiparam.all_children_range()) {
326  if(just_copy_tags.count(child.key)) {
327  // These aren't simplified, so just copy over unchanged.
328  parsed_config.add_child(child.key, child.cfg);
329  continue;
330  } else if(old_goal_tags.count(child.key)) {
331  // A simplified goal, mainly kept around just for backwards compatibility.
332  config goal_config, criteria_config = child.cfg;
333  goal_config["name"] = child.key;
334  goal_config["turns"] = turns;
335  goal_config["time_of_day"] = time_of_day;
336 
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 
342  if(criteria_config.has_attribute("value")) {
343  goal_config["value"] = criteria_config["value"];
344  criteria_config.remove_attribute("value");
345  }
346 
347  parsed_config.add_child("goal", std::move(goal_config));
348  continue;
349  }
350 
351  // Now there's two possibilities. If the tag is [attacks] or contains either value= or [value],
352  // then it can be copied verbatim as a [facet] tag.
353  // Otherwise, it needs to be placed as a [value] within a [facet] tag.
354  if(child.key == "attacks" || child.cfg.has_attribute("value") || child.cfg.has_child("value")) {
355  facet_configs.emplace_back(child.key, child.cfg);
356  } else {
357  config facet_config;
358  facet_config["engine"] = engine;
359  facet_config["name"] = "standard_aspect";
360  facet_config["turns"] = turns;
361  facet_config["time_of_day"] = time_of_day;
362  facet_config.add_child("value", child.cfg);
363 
364  if(child.key == "leader_goal" && !child.cfg["id"].empty()) {
365  // Use id= attribute (if present) as the facet ID
366  const std::string& id = child.cfg["id"];
367  if(id != "*" && id.find_first_not_of("0123456789") != std::string::npos) {
368  facet_config["id"] = child.cfg["id"];
369  }
370  }
371 
372  facet_configs.emplace_back(child.key, std::move(facet_config));
373  }
374  }
375 
376  std::map<std::string, config> aspect_configs;
377 
378  while(!facet_configs.empty()) {
379  const std::string& aspect = facet_configs.front().first;
380  config& facet_config = facet_configs.front().second;
381  aspect_configs[aspect]["id"] = aspect; // Will sometimes be redundant assignment
382  aspect_configs[aspect]["name"] = "composite_aspect";
383  aspect_configs[aspect].add_child("facet", std::move(facet_config)); // Safe since we pop immediately after
384  facet_configs.pop_front();
385  }
386 
387  for(const auto& p : aspect_configs) {
388  parsed_config.add_child("aspect", p.second);
389  }
390  }
391 
392  // Support old recruitment aspect syntax
393  for(auto& child : parsed_config.child_range("aspect")) {
394  if(child["id"] == "recruitment") {
395  child["id"] = "recruitment_instructions";
396  }
397  }
398 
399  if(algorithm.empty() && !parsed_config.has_child("stage")) {
400  base_config = get_ai_config_for("ai_default_rca");
401  }
402 
403  for(const config::any_child& child : parsed_config.all_children_range()) {
404  base_config.add_child(child.key, child.cfg);
405  }
406 
407  cfg.clear_children("ai");
408  cfg.add_child("ai", std::move(base_config));
409 }
410 
411 } // 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:423
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:874
void clear_children(T... keys)
Definition: config.hpp:477
std::string era()
Definition: game.cpp:699
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
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:226
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:695
static void add_mod_ai_from_config(config::const_child_itors configs)
void clear()
Definition: config.cpp:816
#define d
#define WRN_AI_CONFIGURATION
void remove_attribute(config_key_type key)
Definition: config.cpp:239
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
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:612
#define LOG_AI_CONFIGURATION
config & add_child_at(config_key_type key, const config &val, unsigned index)
Definition: config.cpp:511
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:210
#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:46
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:479
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:39
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:68
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:647
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