The Battle for Wesnoth  1.19.17+dev
attack_type.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
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 "units/unit.hpp"
24 #include "formula/formula.hpp"
25 #include "formula/string_utils.hpp"
27 #include "deprecation.hpp"
28 #include "game_version.hpp"
29 
30 #include "log.hpp"
32 #include "serialization/markup.hpp"
33 #include "gettext.hpp"
34 #include "utils/math.hpp"
35 
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  , alignment_(unit_alignments::get_enum(cfg["alignment"].str()))
63  , damage_(cfg["damage"].to_int())
64  , num_attacks_(cfg["number"].to_int())
65  , attack_weight_(cfg["attack_weight"].to_double(1.0))
66  , defense_weight_(cfg["defense_weight"].to_double(1.0))
67  , accuracy_(cfg["accuracy"].to_int())
68  , movement_used_(cfg["movement_used"].to_int(100000))
69  , attacks_used_(cfg["attacks_used"].to_int(1))
70  , parry_(cfg["parry"].to_int())
71  , specials_(cfg.child_or_empty("specials"))
72  , changed_(true)
73 {
74  if (description_.empty())
76 
77  if(icon_.empty()){
78  if (!id_.empty())
79  icon_ = "attacks/" + id_ + ".png";
80  else
81  icon_ = "attacks/blank-attack.png";
82  }
83 }
84 
86 {
87  if(accuracy_ == 0 && parry_ == 0) {
88  return "";
89  }
90 
91  std::ostringstream s;
93 
94  if(parry_ != 0) {
95  s << "/" << utils::signed_percent(parry_);
96  }
97 
98  return s.str();
99 }
100 
102 {
103  if(accuracy_ == 0 && parry_ == 0) {
104  return "";
105  }
106 
107  std::stringstream tooltip;
108  if (accuracy_) {
109  tooltip << _("Accuracy:") << " " << markup::bold(utils::signed_percent(accuracy_)) << "\n";
110  }
111  if (parry_) {
112  tooltip << _("Parry:") << " " << markup::bold(utils::signed_percent(parry_));
113  }
114 
115  return tooltip.str();
116 }
117 
118 /**
119  * Returns whether or not *this matches the given @a filter, ignoring the
120  * complexities introduced by [and], [or], and [not].
121  */
122 static bool matches_simple_filter(const attack_type & attack, const config & filter, const std::string& check_if_recursion)
123 {
124  const std::set<std::string> filter_range = utils::split_set(filter["range"].str());
125  const std::string& filter_min_range = filter["min_range"];
126  const std::string& filter_max_range = filter["max_range"];
127  const std::string& filter_damage = filter["damage"];
128  const std::string& filter_attacks = filter["number"];
129  const std::string& filter_accuracy = filter["accuracy"];
130  const std::string& filter_parry = filter["parry"];
131  const std::string& filter_movement = filter["movement_used"];
132  const std::string& filter_attacks_used = filter["attacks_used"];
133  const std::set<std::string> filter_alignment = utils::split_set(filter["alignment"].str());
134  const std::set<std::string> filter_name = utils::split_set(filter["name"].str());
135  const std::set<std::string> filter_type = utils::split_set(filter["type"].str());
136  const std::set<std::string> filter_base_type = utils::split_set(filter["base_type"].str());
137  const std::string filter_formula = filter["formula"];
138 
139  if (!filter_min_range.empty() && !in_ranges(attack.min_range(), utils::parse_ranges_int(filter_min_range)))
140  return false;
141 
142  if (!filter_max_range.empty() && !in_ranges(attack.max_range(), utils::parse_ranges_int(filter_max_range)))
143  return false;
144 
145  if ( !filter_range.empty() && filter_range.count(attack.range()) == 0 )
146  return false;
147 
148  if ( !filter_damage.empty() && !in_ranges(attack.damage(), utils::parse_ranges_unsigned(filter_damage)) )
149  return false;
150 
151  if (!filter_attacks.empty() && !in_ranges(attack.num_attacks(), utils::parse_ranges_unsigned(filter_attacks)))
152  return false;
153 
154  if (!filter_accuracy.empty() && !in_ranges(attack.accuracy(), utils::parse_ranges_int(filter_accuracy)))
155  return false;
156 
157  if (!filter_parry.empty() && !in_ranges(attack.parry(), utils::parse_ranges_int(filter_parry)))
158  return false;
159 
160  if (!filter_movement.empty() && !in_ranges(attack.movement_used(), utils::parse_ranges_unsigned(filter_movement)))
161  return false;
162 
163  if (!filter_attacks_used.empty() && !in_ranges(attack.attacks_used(), utils::parse_ranges_unsigned(filter_attacks_used)))
164  return false;
165 
166  if(!filter_alignment.empty() && filter_alignment.count(attack.alignment_str()) == 0)
167  return false;
168 
169  if ( !filter_name.empty() && filter_name.count(attack.id()) == 0)
170  return false;
171 
172  if (!filter_type.empty()){
173  // Although there's a general guard against infinite recursion, the "damage_type" special
174  // should always use the base type of the weapon. Otherwise it will flip-flop between the
175  // special being active or inactive based on whether ATTACK_RECURSION_LIMIT is even or odd;
176  // without this it will also behave differently when calculating resistance_against.
177  if(check_if_recursion == "damage_type"){
178  if (filter_type.count(attack.type()) == 0){
179  return false;
180  }
181  } else {
182  //if the type is different from "damage_type" then damage_type() can be called for safe checking.
183  if (filter_type.count(attack.effective_damage_type().first) == 0){
184  return false;
185  }
186  }
187  }
188 
189  if ( !filter_base_type.empty() && filter_base_type.count(attack.type()) == 0 )
190  return false;
191 
192  if(filter.has_attribute("special")) {
193  deprecated_message("special=", DEP_LEVEL::PREEMPTIVE, {1, 17, 0}, "Please use special_id or special_type instead");
194  }
195 
196  if(filter.has_attribute("special") || filter.has_attribute("special_id") || filter.has_attribute("special_type")) {
197  if(!attack.has_filter_special_or_ability(filter, true)) {
198  return false;
199  }
200  }
201 
202  if(filter.has_attribute("special_active")) {
203  deprecated_message("special_active=", DEP_LEVEL::PREEMPTIVE, {1, 17, 0}, "Please use special_id_active or special_type_active instead");
204  }
205 
206  if(filter.has_attribute("special_active") || filter.has_attribute("special_id_active") || filter.has_attribute("special_type_active")) {
208  return false;
209  }
210  }
211 
212  //children filter_special are checked later,
213  //but only when the function doesn't return earlier
214  if(auto sub_filter_special = filter.optional_child("filter_special")) {
215  if(!attack.has_special_or_ability_with_filter(*sub_filter_special)) {
216  return false;
217  }
218  }
219 
220  if (!filter_formula.empty()) {
221  try {
222  const wfl::attack_type_callable callable(attack);
224  const wfl::formula form(filter_formula, &symbols);
225  if(!form.evaluate(callable).as_bool()) {
226  return false;
227  }
228  } catch(const wfl::formula_error& e) {
229  lg::log_to_chat() << "Formula error in weapon filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
230  ERR_WML << "Formula error in weapon filter: " << e.type << " at " << e.filename << ':' << e.line << ")";
231  // Formulae with syntax errors match nothing
232  return false;
233  }
234  }
235 
236  // Passed all tests.
237  return true;
238 }
239 
240 /**
241  * Returns whether or not *this matches the given @a filter.
242  */
243 bool attack_type::matches_filter(const config& filter, const std::string& check_if_recursion) const
244 {
245  // Handle the basic filter.
246  bool matches = matches_simple_filter(*this, filter, check_if_recursion);
247 
248  // Handle [and], [or], and [not] with in-order precedence
249  for(const auto [key, condition_cfg] : filter.all_children_view() )
250  {
251  // Handle [and]
252  if ( key == "and" )
253  matches = matches && matches_filter(condition_cfg, check_if_recursion);
254 
255  // Handle [or]
256  else if ( key == "or" )
257  matches = matches || matches_filter(condition_cfg, check_if_recursion);
258 
259  // Handle [not]
260  else if ( key == "not" )
261  matches = matches && !matches_filter(condition_cfg, check_if_recursion);
262  }
263 
264  return matches;
265 }
266 
268 {
270  while (i != specials_.ordered_end()) {
271  if(special_matches_filter(i->cfg, i->key, filter)) {
272  i = specials_.erase(i);
273  } else {
274  ++i;
275  }
276  }
277 }
278 
280 {
281  set_changed(true);
282  const config::attribute_value& set_name = cfg["set_name"];
283  const t_string& set_desc = cfg["set_description"].t_str();
284  const config::attribute_value& set_type = cfg["set_type"];
285  const config::attribute_value& set_range = cfg["set_range"];
286  const config::attribute_value& set_attack_alignment = cfg["set_alignment"];
287  const config::attribute_value& set_icon = cfg["set_icon"];
288  const config::attribute_value& del_specials = cfg["remove_specials"];
289  auto set_specials = cfg.optional_child("set_specials");
290  const config::attribute_value& increase_min_range = cfg["increase_min_range"];
291  const config::attribute_value& set_min_range = cfg["set_min_range"];
292  const config::attribute_value& increase_max_range = cfg["increase_max_range"];
293  const config::attribute_value& set_max_range = cfg["set_max_range"];
294  auto remove_specials = cfg.optional_child("remove_specials");
295  const config::attribute_value& increase_damage = cfg["increase_damage"];
296  const config::attribute_value& set_damage = cfg["set_damage"];
297  const config::attribute_value& increase_attacks = cfg["increase_attacks"];
298  const config::attribute_value& set_attacks = cfg["set_attacks"];
299  const config::attribute_value& set_attack_weight = cfg["attack_weight"];
300  const config::attribute_value& set_defense_weight = cfg["defense_weight"];
301  const config::attribute_value& increase_accuracy = cfg["increase_accuracy"];
302  const config::attribute_value& set_accuracy = cfg["set_accuracy"];
303  const config::attribute_value& increase_parry = cfg["increase_parry"];
304  const config::attribute_value& set_parry = cfg["set_parry"];
305  const config::attribute_value& increase_movement = cfg["increase_movement_used"];
306  const config::attribute_value& set_movement = cfg["set_movement_used"];
307  const config::attribute_value& increase_attacks_used = cfg["increase_attacks_used"];
308  const config::attribute_value& set_attacks_used = cfg["set_attacks_used"];
309  // NB: If you add something here that requires a description,
310  // it needs to be added to describe_effect as well.
311 
312  if(set_name.empty() == false) {
313  id_ = set_name;
314  }
315 
316  if(set_desc.empty() == false) {
317  description_ = set_desc;
318  }
319 
320  if(set_type.empty() == false) {
321  type_ = set_type;
322  }
323 
324  if(set_range.empty() == false) {
325  range_ = set_range;
326  }
327 
328  if(set_attack_alignment.empty() == false) {
330  }
331 
332  if(set_icon.empty() == false) {
333  icon_ = set_icon;
334  }
335 
336  if(del_specials.empty() == false) {
337  const std::vector<std::string>& dsl = utils::split(del_specials);
338  config new_specials;
339  for(const auto [key, cfg] : specials_.all_children_view()) {
340  if(!utils::contains(dsl, cfg["id"].str())) {
341  new_specials.add_child(key, cfg);
342  }
343  }
344  specials_ = new_specials;
345  }
346 
347  if(set_specials) {
348  const std::string &mode = set_specials["mode"];
349  if(mode.empty()){
350  deprecated_message("[set_specials]mode=<unset>", DEP_LEVEL::INDEFINITE, "",
351  "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.");
352  // fall through to mode != "append"
353  }
354  if(mode != "append") {
355  specials_.clear();
356  }
357  for(const auto [key, cfg] : set_specials->all_children_view()) {
358  specials_.add_child(key, cfg);
359  }
360  }
361 
362  if(set_min_range.empty() == false) {
363  min_range_ = set_min_range.to_int();
364  }
365 
366  if(increase_min_range.empty() == false) {
367  min_range_ = utils::apply_modifier(min_range_, increase_min_range);
368  }
369 
370  if(set_max_range.empty() == false) {
371  max_range_ = set_max_range.to_int();
372  }
373 
374  if(increase_max_range.empty() == false) {
375  max_range_ = utils::apply_modifier(max_range_, increase_max_range);
376  }
377 
378  if(remove_specials) {
379  remove_special_by_filter(*remove_specials);
380  }
381 
382  if(set_damage.empty() == false) {
383  damage_ = set_damage.to_int();
384  if (damage_ < 0) {
385  damage_ = 0;
386  }
387  }
388 
389  if(increase_damage.empty() == false) {
390  damage_ = utils::apply_modifier(damage_, increase_damage);
391  if(damage_ < 0) {
392  damage_ = 0;
393  }
394  }
395 
396  if(set_attacks.empty() == false) {
397  num_attacks_ = set_attacks.to_int();
398  if (num_attacks_ < 0) {
399  num_attacks_ = 0;
400  }
401 
402  }
403 
404  if(increase_attacks.empty() == false) {
405  num_attacks_ = utils::apply_modifier(num_attacks_, increase_attacks, 1);
406  }
407 
408  if(set_accuracy.empty() == false) {
409  accuracy_ = set_accuracy.to_int();
410  }
411 
412  if(increase_accuracy.empty() == false) {
413  accuracy_ = utils::apply_modifier(accuracy_, increase_accuracy);
414  }
415 
416  if(set_parry.empty() == false) {
417  parry_ = set_parry.to_int();
418  }
419 
420  if(increase_parry.empty() == false) {
421  parry_ = utils::apply_modifier(parry_, increase_parry);
422  }
423 
424  if(set_movement.empty() == false) {
425  movement_used_ = set_movement.to_int();
426  }
427 
428  if(increase_movement.empty() == false) {
429  movement_used_ = utils::apply_modifier(movement_used_, increase_movement, 1);
430  }
431 
432  if(set_attacks_used.empty() == false) {
433  attacks_used_ = set_attacks_used.to_int();
434  }
435 
436  if(increase_attacks_used.empty() == false) {
437  attacks_used_ = utils::apply_modifier(attacks_used_, increase_attacks_used, 1);
438  }
439 
440  if(set_attack_weight.empty() == false) {
441  attack_weight_ = set_attack_weight.to_double(1.0);
442  }
443 
444  if(set_defense_weight.empty() == false) {
445  defense_weight_ = set_defense_weight.to_double(1.0);
446  }
447 }
448 
450 {
451  const config::attribute_value& increase_min_range = cfg["increase_min_range"];
452  const config::attribute_value& set_min_range = cfg["set_min_range"];
453  const config::attribute_value& increase_max_range = cfg["increase_max_range"];
454  const config::attribute_value& set_max_range = cfg["set_max_range"];
455  const config::attribute_value& increase_damage = cfg["increase_damage"];
456  const config::attribute_value& set_damage = cfg["set_damage"];
457  const config::attribute_value& increase_attacks = cfg["increase_attacks"];
458  const config::attribute_value& set_attacks = cfg["set_attacks"];
459  const config::attribute_value& increase_accuracy = cfg["increase_accuracy"];
460  const config::attribute_value& set_accuracy = cfg["set_accuracy"];
461  const config::attribute_value& increase_parry = cfg["increase_parry"];
462  const config::attribute_value& set_parry = cfg["set_parry"];
463  const config::attribute_value& increase_movement = cfg["increase_movement_used"];
464  const config::attribute_value& set_movement = cfg["set_movement_used"];
465  const config::attribute_value& increase_attacks_used = cfg["increase_attacks_used"];
466  const config::attribute_value& set_attacks_used = cfg["set_attacks_used"];
467 
468  const auto format_modifier = [](const config::attribute_value& attr) -> utils::string_map {
469  return {{"number_or_percent", utils::print_modifier(attr)}, {"color", attr.to_int() < 0 ? "#f00" : "#0f0"}};
470  };
471 
472  std::vector<t_string> desc;
473 
474  if(!set_min_range.empty()) {
475  desc.emplace_back(VGETTEXT(
476  // TRANSLATORS: Current value for WML code set_min_range, documented in https://wiki.wesnoth.org/EffectWML
477  "$number min range",
478  {{"number", set_min_range.str()}}));
479  }
480 
481  if(!increase_min_range.empty()) {
482  desc.emplace_back(VGETTEXT(
483  // TRANSLATORS: Current value for WML code increase_min_range, documented in https://wiki.wesnoth.org/EffectWML
484  "<span color=\"$color\">$number_or_percent</span> min range",
485  format_modifier(increase_min_range)));
486  }
487 
488  if(!set_max_range.empty()) {
489  desc.emplace_back(VGETTEXT(
490  // TRANSLATORS: Current value for WML code set_max_range, documented in https://wiki.wesnoth.org/EffectWML
491  "$number max range",
492  {{"number", set_max_range.str()}}));
493  }
494 
495  if(!increase_max_range.empty()) {
496  desc.emplace_back(VGETTEXT(
497  // TRANSLATORS: Current value for WML code increase_max_range, documented in https://wiki.wesnoth.org/EffectWML
498  "<span color=\"$color\">$number_or_percent</span> max range",
499  format_modifier(increase_max_range)));
500  }
501 
502  if(!increase_damage.empty()) {
503  desc.emplace_back(VNGETTEXT(
504  // TRANSLATORS: Current value for WML code increase_damage, documented in https://wiki.wesnoth.org/EffectWML
505  "<span color=\"$color\">$number_or_percent</span> damage",
506  "<span color=\"$color\">$number_or_percent</span> damage",
507  increase_damage.to_int(),
508  format_modifier(increase_damage)));
509  }
510 
511  if(!set_damage.empty()) {
512  // TRANSLATORS: Current value for WML code set_damage, documented in https://wiki.wesnoth.org/EffectWML
513  desc.emplace_back(VNGETTEXT(
514  "$number damage",
515  "$number damage",
516  set_damage.to_int(),
517  {{"number", set_damage.str()}}));
518  }
519 
520  if(!increase_attacks.empty()) {
521  desc.emplace_back(VNGETTEXT(
522  // TRANSLATORS: Current value for WML code increase_attacks, documented in https://wiki.wesnoth.org/EffectWML
523  "<span color=\"$color\">$number_or_percent</span> strike",
524  "<span color=\"$color\">$number_or_percent</span> strikes",
525  increase_attacks.to_int(),
526  format_modifier(increase_attacks)));
527  }
528 
529  if(!set_attacks.empty()) {
530  desc.emplace_back(VNGETTEXT(
531  // TRANSLATORS: Current value for WML code set_attacks, documented in https://wiki.wesnoth.org/EffectWML
532  "$number strike",
533  "$number strikes",
534  set_attacks.to_int(),
535  {{"number", set_attacks.str()}}));
536  }
537 
538  if(!set_accuracy.empty()) {
539  desc.emplace_back(VGETTEXT(
540  // TRANSLATORS: Current value for WML code set_accuracy, documented in https://wiki.wesnoth.org/EffectWML
541  "$number| accuracy",
542  {{"number", set_accuracy.str()}}));
543  }
544 
545  if(!increase_accuracy.empty()) {
546  desc.emplace_back(VGETTEXT(
547  // TRANSLATORS: Current value for WML code increase_accuracy, documented in https://wiki.wesnoth.org/EffectWML
548  "<span color=\"$color\">$number_or_percent|%</span> accuracy",
549  format_modifier(increase_accuracy)));
550  }
551 
552  if(!set_parry.empty()) {
553  desc.emplace_back(VGETTEXT(
554  // TRANSLATORS: Current value for WML code set_parry, documented in https://wiki.wesnoth.org/EffectWML
555  "$number parry",
556  {{"number", set_parry.str()}}));
557  }
558 
559  if(!increase_parry.empty()) {
560  desc.emplace_back(VGETTEXT(
561  // TRANSLATORS: Current value for WML code increase_parry, documented in https://wiki.wesnoth.org/EffectWML
562  "<span color=\"$color\">$number_or_percent</span> parry",
563  format_modifier(increase_parry)));
564  }
565 
566  if(!set_movement.empty()) {
567  desc.emplace_back(VNGETTEXT(
568  // TRANSLATORS: Current value for WML code set_movement_used, documented in https://wiki.wesnoth.org/EffectWML
569  "$number movement point",
570  "$number movement points",
571  set_movement.to_int(),
572  {{"number", set_movement.str()}}));
573  }
574 
575  if(!increase_movement.empty()) {
576  desc.emplace_back(VNGETTEXT(
577  // TRANSLATORS: Current value for WML code increase_movement_used, documented in https://wiki.wesnoth.org/EffectWML
578  "<span color=\"$color\">$number_or_percent</span> movement point",
579  "<span color=\"$color\">$number_or_percent</span> movement points",
580  increase_movement.to_int(),
581  format_modifier(increase_movement)));
582  }
583 
584  if(!set_attacks_used.empty()) {
585  desc.emplace_back(VNGETTEXT(
586  // TRANSLATORS: Current value for WML code set_attacks_used, documented in https://wiki.wesnoth.org/EffectWML
587  "$number attack used",
588  "$number attacks used",
589  set_attacks_used.to_int(),
590  {{"number", set_attacks_used.str()}}));
591  }
592 
593  if(!increase_attacks_used.empty()) {
594  desc.emplace_back(VNGETTEXT(
595  // TRANSLATORS: Current value for WML code increase_attacks_used, documented in https://wiki.wesnoth.org/EffectWML
596  "<span color=\"$color\">$number_or_percent</span> attack used",
597  "<span color=\"$color\">$number_or_percent</span> attacks used",
598  increase_attacks_used.to_int(),
599  format_modifier(increase_attacks_used)));
600  }
601 
602  return utils::format_conjunct_list("", desc);
603 }
604 
606 {
607  if(utils::contains(open_queries_, &special)) {
608  return recursion_guard();
609  }
610  return recursion_guard(*this, special);
611 }
612 
614 
616  : parent(weapon.shared_from_this())
617 {
618  parent->open_queries_.emplace_back(&special);
619 }
620 
622 {
623  std::swap(parent, other.parent);
624 }
625 
626 attack_type::recursion_guard::operator bool() const {
627  return bool(parent);
628 }
629 
631 {
632  // This is only intended to move ownership to a longer-living variable. Assigning to an instance that
633  // already has a parent implies that the caller is going to recurse and needs a recursion allocation,
634  // but is accidentally dropping one of the allocations that it already has; hence the asserts.
635  assert(this != &other);
636  assert(!parent);
637  std::swap(parent, other.parent);
638  return *this;
639 }
640 
642 {
643  if(parent) {
644  // As this only expects nested recursion, simply pop the top of the open_queries_ stack
645  // without checking that the top of the stack matches the filter passed to the constructor.
646  assert(!parent->open_queries_.empty());
647  parent->open_queries_.pop_back();
648  }
649 }
650 
652 {
653  cfg["description"] = description_;
654  cfg["name"] = id_;
655  cfg["type"] = type_;
656  cfg["icon"] = icon_;
657  cfg["range"] = range_;
658  cfg["min_range"] = min_range_;
659  cfg["max_range"] = max_range_;
660  cfg["alignment"] = alignment_str();
661  cfg["damage"] = damage_;
662  cfg["number"] = num_attacks_;
663  cfg["attack_weight"] = attack_weight_;
664  cfg["defense_weight"] = defense_weight_;
665  cfg["accuracy"] = accuracy_;
666  cfg["movement_used"] = movement_used_;
667  cfg["attacks_used"] = attacks_used_;
668  cfg["parry"] = parry_;
669  cfg.add_child("specials", specials_);
670 }
static lg::log_domain log_unit("unit")
#define ERR_WML
Definition: attack_type.cpp:48
static lg::log_domain log_wml("wml")
static bool matches_simple_filter(const attack_type &attack, const config &filter, const std::string &check_if_recursion)
Returns whether or not *this matches the given filter, ignoring the complexities introduced by [and],...
static lg::log_domain log_config("config")
Helper similar to std::unique_lock for detecting when calculations such as has_special have entered i...
std::shared_ptr< const attack_type > parent
recursion_guard()
Construct an empty instance, only useful for extending the lifetime of a recursion_guard returned fro...
recursion_guard & operator=(recursion_guard &&) noexcept
std::string alignment_str() const
Returns alignment specified by alignment() for filtering when exist.
Definition: attack_type.hpp:95
void set_min_range(int value)
Definition: attack_type.hpp:64
int min_range() const
Definition: attack_type.hpp:47
const std::string & range() const
Definition: attack_type.hpp:46
void set_attacks_used(int value)
int movement_used() const
bool has_special_or_ability_with_filter(const config &filter) const
check if special matche
Definition: abilities.cpp:2126
void set_accuracy(int value)
Definition: attack_type.hpp:67
const std::string & type() const
Definition: attack_type.hpp:44
int parry() const
Definition: attack_type.hpp:52
std::string accuracy_parry_tooltip() const
std::string accuracy_parry_description() const
Definition: attack_type.cpp:85
void apply_effect(const config &cfg)
Applies effect modifications described by cfg.
bool matches_filter(const config &filter, const std::string &check_if_recursion="") const
Returns whether or not *this matches the given filter.
void set_specials(config value)
Definition: attack_type.hpp:73
config specials_
void set_defense_weight(double value)
Definition: attack_type.hpp:72
int num_attacks() const
Definition: attack_type.hpp:54
void set_changed(bool value)
recursion_guard update_variables_recursion(const config &special) const
Tests which might otherwise cause infinite recursion should call this, check that the returned object...
bool has_filter_special_or_ability(const config &filter, bool simple_check=false) const
check if special matche
Definition: abilities.cpp:1881
std::string type_
std::string icon_
void set_parry(int value)
Definition: attack_type.hpp:68
void set_attack_weight(double value)
Definition: attack_type.hpp:71
void set_damage(int value)
Definition: attack_type.hpp:69
int attacks_used() const
const std::string & id() const
Definition: attack_type.hpp:43
void set_icon(const std::string &value)
Definition: attack_type.hpp:62
std::vector< const config * > open_queries_
While processing a recursive match, all the filters that are currently being checked,...
double defense_weight_
std::string id_
bool special_matches_filter(const config &cfg, const std::string &tag_name, const config &filter) const
Filter a list of abilities or weapon specials.
Definition: abilities.cpp:2121
double attack_weight_
std::string range_
void remove_special_by_filter(const config &filter)
remove special if matche condition
void set_max_range(int value)
Definition: attack_type.hpp:65
utils::optional< unit_alignments::type > alignment_
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
int max_range() const
Definition: attack_type.hpp:48
void set_range(const std::string &value)
Definition: attack_type.hpp:63
static std::string describe_effect(const config &cfg)
Generates a description of the effect specified by cfg, if applicable.
int damage() const
Definition: attack_type.hpp:53
void set_attack_alignment(const std::string &value)
Definition: attack_type.hpp:66
t_string description_
void set_name(const t_string &value)
Definition: attack_type.hpp:59
std::pair< std::string, int > effective_damage_type() const
The type of attack used and the resistance value that does the most damage.
Definition: abilities.cpp:1321
Variant for storing WML attributes.
bool empty() const
Tests for an attribute that either was never set or was set to "".
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
config & add_child(std::string_view key)
Definition: config.cpp:435
all_children_iterator erase(const all_children_iterator &i)
Definition: config.cpp:617
const_all_children_iterator ordered_begin() const
Definition: config.cpp:837
optional_config_impl< config > optional_child(std::string_view key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:379
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:795
const_all_children_iterator ordered_end() const
Definition: config.cpp:847
void clear()
Definition: config.cpp:801
bool empty() const
Definition: tstring.hpp:199
static variant evaluate(const const_formula_ptr &f, const formula_callable &variables, formula_debugger *fdb=nullptr, variant default_res=variant(0))
Definition: formula.hpp:48
bool as_bool() const
Returns a boolean state of the variant value.
Definition: variant.cpp:313
void swap(config &lhs, config &rhs) noexcept
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1316
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:29
const config * cfg
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
#define VNGETTEXT(msgid, msgid_plural, count,...)
std::size_t i
Definition: function.cpp:1032
Interfaces for manipulating version numbers of engine, add-ons, etc.
static std::string _(const char *str)
Definition: gettext.hpp:97
std::string tooltip
Shown when hovering over an entry in the filter's drop-down list.
Definition: manager.cpp:203
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:85
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:550
std::string bold(Args &&... data)
Applies bold Pango markup to the input.
Definition: markup.hpp:161
std::string egettext(char const *msgid)
Definition: gettext.cpp:429
constexpr auto filter
Definition: ranges.hpp:38
std::set< std::string > split_set(std::string_view s, char sep, const int flags)
std::vector< std::pair< int, int > > parse_ranges_int(const std::string &str)
Handles a comma-separated list of inputs to parse_range.
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:87
std::vector< std::pair< int, int > > parse_ranges_unsigned(const std::string &str)
Handles a comma-separated list of inputs to parse_range, in a context that does not expect negative v...
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::map< std::string, t_string > string_map
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.
The base template for associating string values with enum values.
Definition: enum_base.hpp:33
static constexpr utils::optional< enum_type > get_enum(const std::string_view value)
Converts a string into its enum equivalent.
Definition: enum_base.hpp:57
static map_location::direction s
#define e