The Battle for Wesnoth  1.17.17+dev
attack_type.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2023
3  by David White <dave@whitevine.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  * @file
18  * Handle unit-type specific attributes, animations, advancement.
19  */
20 
21 #include "units/attack_type.hpp"
22 #include "formula/callable_objects.hpp"
23 #include "formula/formula.hpp"
24 #include "formula/string_utils.hpp"
26 #include "deprecation.hpp"
27 #include "game_version.hpp"
28 
29 #include "lexical_cast.hpp"
30 #include "log.hpp"
32 #include "gettext.hpp"
33 #include "utils/math.hpp"
34 
35 #include <cassert>
36 
37 static lg::log_domain log_config("config");
38 #define ERR_CF LOG_STREAM(err, log_config)
39 #define WRN_CF LOG_STREAM(warn, log_config)
40 #define LOG_CONFIG LOG_STREAM(info, log_config)
41 #define DBG_CF LOG_STREAM(debug, log_config)
42 
43 static lg::log_domain log_unit("unit");
44 #define DBG_UT LOG_STREAM(debug, log_unit)
45 #define ERR_UT LOG_STREAM(err, log_unit)
46 
47 static lg::log_domain log_wml("wml");
48 #define ERR_WML LOG_STREAM(err, log_wml)
49 
51  self_loc_(),
52  other_loc_(),
53  is_attacker_(false),
54  other_attack_(nullptr),
55  description_(cfg["description"].t_str()),
56  id_(cfg["name"]),
57  type_(cfg["type"]),
58  icon_(cfg["icon"]),
59  range_(cfg["range"]),
60  min_range_(cfg["min_range"].to_int(1)),
61  max_range_(cfg["max_range"].to_int(1)),
62  damage_(cfg["damage"]),
63  num_attacks_(cfg["number"]),
64  attack_weight_(cfg["attack_weight"].to_double(1.0)),
65  defense_weight_(cfg["defense_weight"].to_double(1.0)),
66  accuracy_(cfg["accuracy"]),
67  movement_used_(cfg["movement_used"].to_int(100000)),
68  attacks_used_(cfg["attacks_used"].to_int(1)),
69  parry_(cfg["parry"]),
70  specials_(cfg.child_or_empty("specials")),
71  changed_(true)
72 {
73  if (description_.empty())
75 
76  if(icon_.empty()){
77  if (!id_.empty())
78  icon_ = "attacks/" + id_ + ".png";
79  else
80  icon_ = "attacks/blank-attack.png";
81  }
82 }
83 
85 {
86  if(accuracy_ == 0 && parry_ == 0) {
87  return "";
88  }
89 
90  std::ostringstream s;
92 
93  if(parry_ != 0) {
94  s << "/" << utils::signed_percent(parry_);
95  }
96 
97  return s.str();
98 }
99 
100 /**
101  * Returns whether or not *this matches the given @a filter, ignoring the
102  * complexities introduced by [and], [or], and [not].
103  */
104 static bool matches_simple_filter(const attack_type & attack, const config & filter)
105 {
106  const std::vector<std::string>& filter_range = utils::split(filter["range"]);
107  const std::string& filter_damage = filter["damage"];
108  const std::string& filter_attacks = filter["number"];
109  const std::string& filter_accuracy = filter["accuracy"];
110  const std::string& filter_parry = filter["parry"];
111  const std::string& filter_movement = filter["movement_used"];
112  const std::string& filter_attacks_used = filter["attacks_used"];
113  const std::vector<std::string> filter_name = utils::split(filter["name"]);
114  const std::vector<std::string> filter_type = utils::split(filter["type"]);
115  const std::vector<std::string> filter_special = utils::split(filter["special"]);
116  const std::vector<std::string> filter_special_id = utils::split(filter["special_id"]);
117  const std::vector<std::string> filter_special_type = utils::split(filter["special_type"]);
118  const std::vector<std::string> filter_special_active = utils::split(filter["special_active"]);
119  const std::vector<std::string> filter_special_id_active = utils::split(filter["special_id_active"]);
120  const std::vector<std::string> filter_special_type_active = utils::split(filter["special_type_active"]);
121  const std::string filter_formula = filter["formula"];
122 
123  if ( !filter_range.empty() && std::find(filter_range.begin(), filter_range.end(), attack.range()) == filter_range.end() )
124  return false;
125 
126  if ( !filter_damage.empty() && !in_ranges(attack.damage(), utils::parse_ranges(filter_damage)) )
127  return false;
128 
129  if (!filter_attacks.empty() && !in_ranges(attack.num_attacks(), utils::parse_ranges(filter_attacks)))
130  return false;
131 
132  if (!filter_accuracy.empty() && !in_ranges(attack.accuracy(), utils::parse_ranges(filter_accuracy)))
133  return false;
134 
135  if (!filter_parry.empty() && !in_ranges(attack.parry(), utils::parse_ranges(filter_parry)))
136  return false;
137 
138  if (!filter_movement.empty() && !in_ranges(attack.movement_used(), utils::parse_ranges(filter_movement)))
139  return false;
140 
141  if (!filter_attacks_used.empty() && !in_ranges(attack.attacks_used(), utils::parse_ranges(filter_attacks_used)))
142  return false;
143 
144  if ( !filter_name.empty() && std::find(filter_name.begin(), filter_name.end(), attack.id()) == filter_name.end() )
145  return false;
146 
147  if ( !filter_type.empty() && std::find(filter_type.begin(), filter_type.end(), attack.type()) == filter_type.end() )
148  return false;
149 
150  if(!filter_special.empty()) {
151  deprecated_message("special=", DEP_LEVEL::PREEMPTIVE, {1, 17, 0}, "Please use special_id or special_type instead");
152  bool found = false;
153  for(auto& special : filter_special) {
154  if(attack.has_special(special, true)) {
155  found = true;
156  break;
157  }
158  }
159  if(!found) {
160  return false;
161  }
162  }
163  if(!filter_special_id.empty()) {
164  bool found = false;
165  for(auto& special : filter_special_id) {
166  if(attack.has_special(special, true, true, false)) {
167  found = true;
168  break;
169  }
170  }
171  if(!found) {
172  return false;
173  }
174  }
175 
176  if(!filter_special_active.empty()) {
177  deprecated_message("special_active=", DEP_LEVEL::PREEMPTIVE, {1, 17, 0}, "Please use special_id_active or special_type_active instead");
178  bool found = false;
179  for(auto& special : filter_special_active) {
180  if(attack.has_special(special, false)) {
181  found = true;
182  break;
183  }
184  }
185  if(!found) {
186  return false;
187  }
188  }
189  if(!filter_special_id_active.empty()) {
190  bool found = false;
191  for(auto& special : filter_special_id_active) {
192  if(attack.has_special_or_ability(special, true, false)) {
193  found = true;
194  break;
195  }
196  }
197  if(!found) {
198  return false;
199  }
200  }
201  if(!filter_special_type.empty()) {
202  bool found = false;
203  for(auto& special : filter_special_type) {
204  if(attack.has_special(special, true, false)) {
205  found = true;
206  break;
207  }
208  }
209  if(!found) {
210  return false;
211  }
212  }
213  if(!filter_special_type_active.empty()) {
214  bool found = false;
215  for(auto& special : filter_special_type_active) {
216  if(attack.has_special_or_ability(special, false)) {
217  found = true;
218  break;
219  }
220  }
221  if(!found) {
222  return false;
223  }
224  }
225 
226  if (!filter_formula.empty()) {
227  try {
228  const wfl::attack_type_callable callable(attack);
229  const wfl::formula form(filter_formula, new wfl::gamestate_function_symbol_table);
230  if(!form.evaluate(callable).as_bool()) {
231  return false;
232  }
233  } catch(const wfl::formula_error& e) {
234  lg::log_to_chat() << "Formula error in weapon filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
235  ERR_WML << "Formula error in weapon filter: " << e.type << " at " << e.filename << ':' << e.line << ")";
236  // Formulae with syntax errors match nothing
237  return false;
238  }
239  }
240 
241  // Passed all tests.
242  return true;
243 }
244 
245 /**
246  * Returns whether or not *this matches the given @a filter.
247  */
248 bool attack_type::matches_filter(const config& filter) const
249 {
250  // Handle the basic filter.
251  bool matches = matches_simple_filter(*this, filter);
252 
253  // Handle [and], [or], and [not] with in-order precedence
254  for (const config::any_child condition : filter.all_children_range() )
255  {
256  // Handle [and]
257  if ( condition.key == "and" )
258  matches = matches && matches_filter(condition.cfg);
259 
260  // Handle [or]
261  else if ( condition.key == "or" )
262  matches = matches || matches_filter(condition.cfg);
263 
264  // Handle [not]
265  else if ( condition.key == "not" )
266  matches = matches && !matches_filter(condition.cfg);
267  }
268 
269  return matches;
270 }
271 
272 /**
273  * Modifies *this using the specifications in @a cfg, but only if *this matches
274  * @a cfg viewed as a filter.
275  *
276  * @returns whether or not @c this matched the @a cfg as a filter.
277  */
279 {
280  if( !matches_filter(cfg) )
281  return false;
282 
283  set_changed(true);
284  const std::string& set_name = cfg["set_name"];
285  const t_string& set_desc = cfg["set_description"];
286  const std::string& set_type = cfg["set_type"];
287  const std::string& set_range = cfg["set_range"];
288  const std::string& set_icon = cfg["set_icon"];
289  const std::string& del_specials = cfg["remove_specials"];
290  auto set_specials = cfg.optional_child("set_specials");
291  const std::string& increase_damage = cfg["increase_damage"];
292  const std::string& set_damage = cfg["set_damage"];
293  const std::string& increase_attacks = cfg["increase_attacks"];
294  const std::string& set_attacks = cfg["set_attacks"];
295  const std::string& set_attack_weight = cfg["attack_weight"];
296  const std::string& set_defense_weight = cfg["defense_weight"];
297  const std::string& increase_accuracy = cfg["increase_accuracy"];
298  const std::string& set_accuracy = cfg["set_accuracy"];
299  const std::string& increase_parry = cfg["increase_parry"];
300  const std::string& set_parry = cfg["set_parry"];
301  const std::string& increase_movement = cfg["increase_movement_used"];
302  const std::string& set_movement = cfg["set_movement_used"];
303  const std::string& increase_attacks_used = cfg["increase_attacks_used"];
304  const std::string& set_attacks_used = cfg["set_attacks_used"];
305  // NB: If you add something here that requires a description,
306  // it needs to be added to describe_modification as well.
307 
308  if(set_name.empty() == false) {
309  id_ = set_name;
310  }
311 
312  if(set_desc.empty() == false) {
313  description_ = set_desc;
314  }
315 
316  if(set_type.empty() == false) {
317  type_ = set_type;
318  }
319 
320  if(set_range.empty() == false) {
321  range_ = set_range;
322  }
323 
324  if(set_icon.empty() == false) {
325  icon_ = set_icon;
326  }
327 
328  if(del_specials.empty() == false) {
329  const std::vector<std::string>& dsl = utils::split(del_specials);
330  config new_specials;
331  for (const config::any_child vp : specials_.all_children_range()) {
332  std::vector<std::string>::const_iterator found_id =
333  std::find(dsl.begin(), dsl.end(), vp.cfg["id"].str());
334  if (found_id == dsl.end()) {
335  new_specials.add_child(vp.key, vp.cfg);
336  }
337  }
338  specials_ = new_specials;
339  }
340 
341  if(set_specials) {
342  const std::string &mode = set_specials["mode"];
343  if(mode.empty()){
344  deprecated_message("[set_specials]mode=<unset>", DEP_LEVEL::INDEFINITE, "",
345  "The mode defaults to 'replace', but should often be 'append' instead. The default may change in a future version, or the attribute may become mandatory.");
346  // fall through to mode != "append"
347  }
348  if(mode != "append") {
349  specials_.clear();
350  }
351  for(const config::any_child value : set_specials->all_children_range()) {
352  specials_.add_child(value.key, value.cfg);
353  }
354  }
355 
356  if(set_damage.empty() == false) {
357  damage_ = std::stoi(set_damage);
358  if (damage_ < 0) {
359  damage_ = 0;
360  }
361  }
362 
363  if(increase_damage.empty() == false) {
364  damage_ = utils::apply_modifier(damage_, increase_damage);
365  if(damage_ < 0) {
366  damage_ = 0;
367  }
368  }
369 
370  if(set_attacks.empty() == false) {
371  num_attacks_ = std::stoi(set_attacks);
372  if (num_attacks_ < 0) {
373  num_attacks_ = 0;
374  }
375 
376  }
377 
378  if(increase_attacks.empty() == false) {
379  num_attacks_ = utils::apply_modifier(num_attacks_, increase_attacks, 1);
380  }
381 
382  if(set_accuracy.empty() == false) {
383  accuracy_ = std::stoi(set_accuracy);
384  }
385 
386  if(increase_accuracy.empty() == false) {
387  accuracy_ = utils::apply_modifier(accuracy_, increase_accuracy);
388  }
389 
390  if(set_parry.empty() == false) {
391  parry_ = std::stoi(set_parry);
392  }
393 
394  if(increase_parry.empty() == false) {
395  parry_ = utils::apply_modifier(parry_, increase_parry);
396  }
397 
398  if(set_movement.empty() == false) {
399  movement_used_ = std::stoi(set_movement);
400  }
401 
402  if(increase_movement.empty() == false) {
403  movement_used_ = utils::apply_modifier(movement_used_, increase_movement, 1);
404  }
405 
406  if(set_attacks_used.empty() == false) {
407  attacks_used_ = std::stoi(set_attacks_used);
408  }
409 
410  if(increase_attacks_used.empty() == false) {
411  attacks_used_ = utils::apply_modifier(attacks_used_, increase_attacks_used, 1);
412  }
413 
414  if(set_attack_weight.empty() == false) {
415  attack_weight_ = lexical_cast_default<double>(set_attack_weight,1.0);
416  }
417 
418  if(set_defense_weight.empty() == false) {
419  defense_weight_ = lexical_cast_default<double>(set_defense_weight,1.0);
420  }
421 
422  return true;
423 }
424 
425 /**
426  * Trimmed down version of apply_modification(), with no modifications actually
427  * made. This can be used to get a description of the modification(s) specified
428  * by @a cfg (if *this matches cfg as a filter).
429  *
430  * If *description is provided, it will be set to a (translated) description
431  * of the modification(s) applied (currently only changes to the number of
432  * strikes, damage, accuracy, and parry are included in this description).
433  *
434  * @returns whether or not @c this matched the @a cfg as a filter.
435  */
436 bool attack_type::describe_modification(const config& cfg,std::string* description)
437 {
438  if( !matches_filter(cfg) )
439  return false;
440 
441  // Did the caller want the description?
442  if(description != nullptr) {
443  const std::string& increase_damage = cfg["increase_damage"];
444  const std::string& set_damage = cfg["set_damage"];
445  const std::string& increase_attacks = cfg["increase_attacks"];
446  const std::string& set_attacks = cfg["set_attacks"];
447  const std::string& increase_accuracy = cfg["increase_accuracy"];
448  const std::string& set_accuracy = cfg["set_accuracy"];
449  const std::string& increase_parry = cfg["increase_parry"];
450  const std::string& set_parry = cfg["set_parry"];
451  const std::string& increase_movement = cfg["increase_movement_used"];
452  const std::string& set_movement = cfg["set_movement_used"];
453  const std::string& increase_attacks_used = cfg["increase_attacks_used"];
454  const std::string& set_attacks_used = cfg["set_attacks_used"];
455 
456  std::vector<t_string> desc;
457 
458  if(!increase_damage.empty()) {
459  desc.emplace_back(VNGETTEXT(
460  // TRANSLATORS: Current value for WML code increase_damage, documented in https://wiki.wesnoth.org/EffectWML
461  "<span color=\"$color\">$number_or_percent</span> damage",
462  "<span color=\"$color\">$number_or_percent</span> damage",
463  std::stoi(increase_damage),
464  {{"number_or_percent", utils::print_modifier(increase_damage)}, {"color", increase_damage[0] == '-' ? "#f00" : "#0f0"}}));
465  }
466 
467  if(!set_damage.empty()) {
468  // TRANSLATORS: Current value for WML code set_damage, documented in https://wiki.wesnoth.org/EffectWML
469  desc.emplace_back(VNGETTEXT(
470  "$number damage",
471  "$number damage",
472  std::stoi(set_damage),
473  {{"number", set_damage}}));
474  }
475 
476  if(!increase_attacks.empty()) {
477  desc.emplace_back(VNGETTEXT(
478  // TRANSLATORS: Current value for WML code increase_attacks, documented in https://wiki.wesnoth.org/EffectWML
479  "<span color=\"$color\">$number_or_percent</span> strike",
480  "<span color=\"$color\">$number_or_percent</span> strikes",
481  std::stoi(increase_attacks),
482  {{"number_or_percent", utils::print_modifier(increase_attacks)}, {"color", increase_attacks[0] == '-' ? "#f00" : "#0f0"}}));
483  }
484 
485  if(!set_attacks.empty()) {
486  desc.emplace_back(VNGETTEXT(
487  // TRANSLATORS: Current value for WML code set_attacks, documented in https://wiki.wesnoth.org/EffectWML
488  "$number strike",
489  "$number strikes",
490  std::stoi(set_attacks),
491  {{"number", set_attacks}}));
492  }
493 
494  if(!set_accuracy.empty()) {
495  desc.emplace_back(VGETTEXT(
496  // TRANSLATORS: Current value for WML code set_accuracy, documented in https://wiki.wesnoth.org/EffectWML
497  "$number| accuracy",
498  {{"number", set_accuracy}}));
499  }
500 
501  if(!increase_accuracy.empty()) {
502  desc.emplace_back(VGETTEXT(
503  // TRANSLATORS: Current value for WML code increase_accuracy, documented in https://wiki.wesnoth.org/EffectWML
504  "<span color=\"$color\">$number_or_percent|%</span> accuracy",
505  {{"number_or_percent", utils::print_modifier(increase_accuracy)}, {"color", increase_accuracy[0] == '-' ? "#f00" : "#0f0"}}));
506  }
507 
508  if(!set_parry.empty()) {
509  desc.emplace_back(VGETTEXT(
510  // TRANSLATORS: Current value for WML code set_parry, documented in https://wiki.wesnoth.org/EffectWML
511  "$number parry",
512  {{"number", set_parry}}));
513  }
514 
515  if(!increase_parry.empty()) {
516  desc.emplace_back(VGETTEXT(
517  // TRANSLATORS: Current value for WML code increase_parry, documented in https://wiki.wesnoth.org/EffectWML
518  "<span color=\"$color\">$number_or_percent</span> parry",
519  {{"number_or_percent", utils::print_modifier(increase_parry)}, {"color", increase_parry[0] == '-' ? "#f00" : "#0f0"}}));
520  }
521 
522  if(!set_movement.empty()) {
523  desc.emplace_back(VNGETTEXT(
524  // TRANSLATORS: Current value for WML code set_movement_used, documented in https://wiki.wesnoth.org/EffectWML
525  "$number movement point",
526  "$number movement points",
527  std::stoi(set_movement),
528  {{"number", set_movement}}));
529  }
530 
531  if(!increase_movement.empty()) {
532  desc.emplace_back(VNGETTEXT(
533  // TRANSLATORS: Current value for WML code increase_movement_used, documented in https://wiki.wesnoth.org/EffectWML
534  "<span color=\"$color\">$number_or_percent</span> movement point",
535  "<span color=\"$color\">$number_or_percent</span> movement points",
536  std::stoi(increase_movement),
537  {{"number_or_percent", utils::print_modifier(increase_movement)}, {"color", increase_movement[0] == '-' ? "#f00" : "#0f0"}}));
538  }
539 
540  if(!set_attacks_used.empty()) {
541  desc.emplace_back(VNGETTEXT(
542  // TRANSLATORS: Current value for WML code set_attacks_used, documented in https://wiki.wesnoth.org/EffectWML
543  "$number attack used",
544  "$number attacks used",
545  std::stoi(set_attacks_used),
546  {{"number", set_attacks_used}}));
547  }
548 
549  if(!increase_attacks_used.empty()) {
550  desc.emplace_back(VNGETTEXT(
551  // TRANSLATORS: Current value for WML code increase_attacks_used, documented in https://wiki.wesnoth.org/EffectWML
552  "<span color=\"$color\">$number_or_percent</span> attack used",
553  "<span color=\"$color\">$number_or_percent</span> attacks used",
554  std::stoi(increase_attacks_used),
555  {{"number_or_percent", utils::print_modifier(increase_attacks_used)}, {"color", increase_attacks_used[0] == '-' ? "#f00" : "#0f0"}}));
556  }
557 
558  *description = utils::format_conjunct_list("", desc);
559  }
560 
561  return true;
562 }
563 
564 void attack_type::write(config& cfg) const
565 {
566  cfg["description"] = description_;
567  cfg["name"] = id_;
568  cfg["type"] = type_;
569  cfg["icon"] = icon_;
570  cfg["range"] = range_;
571  cfg["min_range"] = min_range_;
572  cfg["max_range"] = max_range_;
573  cfg["damage"] = damage_;
574  cfg["number"] = num_attacks_;
575  cfg["attack_weight"] = attack_weight_;
576  cfg["defense_weight"] = defense_weight_;
577  cfg["accuracy"] = accuracy_;
578  cfg["movement_used"] = movement_used_;
579  cfg["attacks_used"] = attacks_used_;
580  cfg["parry"] = parry_;
581  cfg.add_child("specials", specials_);
582 }
static lg::log_domain log_unit("unit")
static bool matches_simple_filter(const attack_type &attack, const config &filter)
Returns whether or not *this matches the given filter, ignoring the complexities introduced by [and],...
#define ERR_WML
Definition: attack_type.cpp:48
static lg::log_domain log_wml("wml")
static lg::log_domain log_config("config")
bool has_special(const std::string &special, bool simple_check=false, bool special_id=true, bool special_tags=true) const
Returns whether or not *this has a special with a tag or id equal to special.
Definition: abilities.cpp:699
const std::string & range() const
Definition: attack_type.hpp:47
void set_attacks_used(int value)
int movement_used() const
void set_accuracy(int value)
Definition: attack_type.hpp:64
const std::string & type() const
Definition: attack_type.hpp:45
int parry() const
Definition: attack_type.hpp:52
std::string accuracy_parry_description() const
Definition: attack_type.cpp:84
bool matches_filter(const config &filter) const
Returns whether or not *this matches the given filter.
bool apply_modification(const config &cfg)
Modifies *this using the specifications in cfg, but only if *this matches cfg viewed as a filter.
void set_specials(config value)
Definition: attack_type.hpp:70
config specials_
void set_defense_weight(double value)
Definition: attack_type.hpp:69
int num_attacks() const
Definition: attack_type.hpp:54
void set_changed(bool value)
std::string type_
std::string icon_
void set_parry(int value)
Definition: attack_type.hpp:65
void set_attack_weight(double value)
Definition: attack_type.hpp:68
void set_damage(int value)
Definition: attack_type.hpp:66
bool describe_modification(const config &cfg, std::string *description)
Trimmed down version of apply_modification(), with no modifications actually made.
int attacks_used() const
const std::string & id() const
Definition: attack_type.hpp:44
void set_icon(const std::string &value)
Definition: attack_type.hpp:62
double defense_weight_
std::string id_
double attack_weight_
std::string range_
attack_type(const config &cfg)
Definition: attack_type.cpp:50
void set_type(const std::string &value)
Definition: attack_type.hpp:61
void write(config &cfg) const
int accuracy() const
Definition: attack_type.hpp:51
void set_range(const std::string &value)
Definition: attack_type.hpp:63
int damage() const
Definition: attack_type.hpp:53
bool has_special_or_ability(const std::string &special, bool special_id=true, bool special_tags=true) const
used for abilities used like weapon and true specials
Definition: abilities.cpp:1509
t_string description_
void set_name(const t_string &value)
Definition: attack_type.hpp:59
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:161
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:891
void clear()
Definition: config.cpp:835
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Euivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:389
config & add_child(config_key_type key)
Definition: config.cpp:445
bool empty() const
Definition: tstring.hpp:187
static variant evaluate(const const_formula_ptr &f, const formula_callable &variables, formula_debugger *fdb=nullptr, variant default_res=variant(0))
Definition: formula.hpp:40
bool as_bool() const
Returns a boolean state of the variant value.
Definition: variant.cpp:316
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:30
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
#define VNGETTEXT(msgid, msgid_plural, count,...)
Interfaces for manipulating version numbers of engine, add-ons, etc.
New lexcical_cast header.
Standard logging facilities (interface).
General math utility functions.
bool in_ranges(const Cmp c, const std::vector< std::pair< Cmp, Cmp >> &ranges)
Definition: math.hpp:87
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:462
std::string egettext(char const *msgid)
Definition: gettext.cpp:427
std::vector< std::pair< int, int > > parse_ranges(const std::string &str)
int apply_modifier(const int number, const std::string &amount, const int minimum)
std::string format_conjunct_list(const t_string &empty, const std::vector< t_string > &elems)
Format a conjunctive list.
std::string signed_percent(int val)
Convert into a percentage (using the Unicode "−" and +0% convention.
std::vector< std::string > split(const config_attribute_value &val)
std::string print_modifier(const std::string &mod)
Add a "+" or replace the "-" par Unicode minus.
static map_location::DIRECTION s
#define e