The Battle for Wesnoth  1.19.13+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"
23 #include "formula/callable_objects.hpp"
24 #include "formula/formula.hpp"
25 #include "formula/string_utils.hpp"
27 #include "deprecation.hpp"
28 #include "game_version.hpp"
29 
30 #include "lexical_cast.hpp"
31 #include "log.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 
101 /**
102  * Returns whether or not *this matches the given @a filter, ignoring the
103  * complexities introduced by [and], [or], and [not].
104  */
105 static bool matches_simple_filter(const attack_type & attack, const config & filter, const std::string& check_if_recursion)
106 {
107  const std::set<std::string> filter_range = utils::split_set(filter["range"].str());
108  const std::string& filter_min_range = filter["min_range"];
109  const std::string& filter_max_range = filter["max_range"];
110  const std::string& filter_damage = filter["damage"];
111  const std::string& filter_attacks = filter["number"];
112  const std::string& filter_accuracy = filter["accuracy"];
113  const std::string& filter_parry = filter["parry"];
114  const std::string& filter_movement = filter["movement_used"];
115  const std::string& filter_attacks_used = filter["attacks_used"];
116  const std::set<std::string> filter_alignment = utils::split_set(filter["alignment"].str());
117  const std::set<std::string> filter_name = utils::split_set(filter["name"].str());
118  const std::set<std::string> filter_type = utils::split_set(filter["type"].str());
119  const std::set<std::string> filter_base_type = utils::split_set(filter["base_type"].str());
120  const std::string filter_formula = filter["formula"];
121 
122  if (!filter_min_range.empty() && !in_ranges(attack.min_range(), utils::parse_ranges_int(filter_min_range)))
123  return false;
124 
125  if (!filter_max_range.empty() && !in_ranges(attack.max_range(), utils::parse_ranges_int(filter_max_range)))
126  return false;
127 
128  if ( !filter_range.empty() && filter_range.count(attack.range()) == 0 )
129  return false;
130 
131  if ( !filter_damage.empty() && !in_ranges(attack.damage(), utils::parse_ranges_unsigned(filter_damage)) )
132  return false;
133 
134  if (!filter_attacks.empty() && !in_ranges(attack.num_attacks(), utils::parse_ranges_unsigned(filter_attacks)))
135  return false;
136 
137  if (!filter_accuracy.empty() && !in_ranges(attack.accuracy(), utils::parse_ranges_int(filter_accuracy)))
138  return false;
139 
140  if (!filter_parry.empty() && !in_ranges(attack.parry(), utils::parse_ranges_int(filter_parry)))
141  return false;
142 
143  if (!filter_movement.empty() && !in_ranges(attack.movement_used(), utils::parse_ranges_unsigned(filter_movement)))
144  return false;
145 
146  if (!filter_attacks_used.empty() && !in_ranges(attack.attacks_used(), utils::parse_ranges_unsigned(filter_attacks_used)))
147  return false;
148 
149  if(!filter_alignment.empty() && filter_alignment.count(attack.alignment_str()) == 0)
150  return false;
151 
152  if ( !filter_name.empty() && filter_name.count(attack.id()) == 0)
153  return false;
154 
155  if (!filter_type.empty()){
156  // Although there's a general guard against infinite recursion, the "damage_type" special
157  // should always use the base type of the weapon. Otherwise it will flip-flop between the
158  // special being active or inactive based on whether ATTACK_RECURSION_LIMIT is even or odd;
159  // without this it will also behave differently when calculating resistance_against.
160  if(check_if_recursion == "damage_type"){
161  if (filter_type.count(attack.type()) == 0){
162  return false;
163  }
164  } else {
165  //if the type is different from "damage_type" then damage_type() can be called for safe checking.
166  if (filter_type.count(attack.effective_damage_type().first) == 0){
167  return false;
168  }
169  }
170  }
171 
172  if ( !filter_base_type.empty() && filter_base_type.count(attack.type()) == 0 )
173  return false;
174 
175  if(filter.has_attribute("special")) {
176  deprecated_message("special=", DEP_LEVEL::PREEMPTIVE, {1, 17, 0}, "Please use special_id or special_type instead");
177  }
178 
179  if(filter.has_attribute("special") || filter.has_attribute("special_id") || filter.has_attribute("special_type")) {
180  if(!attack.has_filter_special_or_ability(filter, true)) {
181  return false;
182  }
183  }
184 
185  if(filter.has_attribute("special_active")) {
186  deprecated_message("special_active=", DEP_LEVEL::PREEMPTIVE, {1, 17, 0}, "Please use special_id_active or special_type_active instead");
187  }
188 
189  if(filter.has_attribute("special_active") || filter.has_attribute("special_id_active") || filter.has_attribute("special_type_active")) {
191  return false;
192  }
193  }
194 
195  //children filter_special are checked later,
196  //but only when the function doesn't return earlier
197  if(auto sub_filter_special = filter.optional_child("filter_special")) {
198  if(!attack.has_special_or_ability_with_filter(*sub_filter_special)) {
199  return false;
200  }
201  }
202 
203  if (!filter_formula.empty()) {
204  try {
205  const wfl::attack_type_callable callable(attack);
207  const wfl::formula form(filter_formula, &symbols);
208  if(!form.evaluate(callable).as_bool()) {
209  return false;
210  }
211  } catch(const wfl::formula_error& e) {
212  lg::log_to_chat() << "Formula error in weapon filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
213  ERR_WML << "Formula error in weapon filter: " << e.type << " at " << e.filename << ':' << e.line << ")";
214  // Formulae with syntax errors match nothing
215  return false;
216  }
217  }
218 
219  // Passed all tests.
220  return true;
221 }
222 
223 /**
224  * Returns whether or not *this matches the given @a filter.
225  */
226 bool attack_type::matches_filter(const config& filter, const std::string& check_if_recursion) const
227 {
228  // Handle the basic filter.
229  bool matches = matches_simple_filter(*this, filter, check_if_recursion);
230 
231  // Handle [and], [or], and [not] with in-order precedence
232  for(const auto [key, condition_cfg] : filter.all_children_view() )
233  {
234  // Handle [and]
235  if ( key == "and" )
236  matches = matches && matches_filter(condition_cfg, check_if_recursion);
237 
238  // Handle [or]
239  else if ( key == "or" )
240  matches = matches || matches_filter(condition_cfg, check_if_recursion);
241 
242  // Handle [not]
243  else if ( key == "not" )
244  matches = matches && !matches_filter(condition_cfg, check_if_recursion);
245  }
246 
247  return matches;
248 }
249 
251 {
253  while (i != specials_.ordered_end()) {
254  if(special_matches_filter(i->cfg, i->key, filter)) {
255  i = specials_.erase(i);
256  } else {
257  ++i;
258  }
259  }
260 }
261 
262 /**
263  * Modifies *this using the specifications in @a cfg, but only if *this matches
264  * @a cfg viewed as a filter.
265  *
266  * @returns whether or not @c this matched the @a cfg as a filter.
267  */
269 {
270  if( !matches_filter(cfg) )
271  return false;
272 
273  set_changed(true);
274  const std::string& set_name = cfg["set_name"];
275  const t_string& set_desc = cfg["set_description"];
276  const std::string& set_type = cfg["set_type"];
277  const std::string& set_range = cfg["set_range"];
278  const std::string& set_attack_alignment = cfg["set_alignment"];
279  const std::string& set_icon = cfg["set_icon"];
280  const std::string& del_specials = cfg["remove_specials"];
281  auto set_specials = cfg.optional_child("set_specials");
282  const std::string& increase_min_range = cfg["increase_min_range"];
283  const std::string& set_min_range = cfg["set_min_range"];
284  const std::string& increase_max_range = cfg["increase_max_range"];
285  const std::string& set_max_range = cfg["set_max_range"];
286  auto remove_specials = cfg.optional_child("remove_specials");
287  const std::string& increase_damage = cfg["increase_damage"];
288  const std::string& set_damage = cfg["set_damage"];
289  const std::string& increase_attacks = cfg["increase_attacks"];
290  const std::string& set_attacks = cfg["set_attacks"];
291  const std::string& set_attack_weight = cfg["attack_weight"];
292  const std::string& set_defense_weight = cfg["defense_weight"];
293  const std::string& increase_accuracy = cfg["increase_accuracy"];
294  const std::string& set_accuracy = cfg["set_accuracy"];
295  const std::string& increase_parry = cfg["increase_parry"];
296  const std::string& set_parry = cfg["set_parry"];
297  const std::string& increase_movement = cfg["increase_movement_used"];
298  const std::string& set_movement = cfg["set_movement_used"];
299  const std::string& increase_attacks_used = cfg["increase_attacks_used"];
300  const std::string& set_attacks_used = cfg["set_attacks_used"];
301  // NB: If you add something here that requires a description,
302  // it needs to be added to describe_modification as well.
303 
304  if(set_name.empty() == false) {
305  id_ = set_name;
306  }
307 
308  if(set_desc.empty() == false) {
309  description_ = set_desc;
310  }
311 
312  if(set_type.empty() == false) {
313  type_ = set_type;
314  }
315 
316  if(set_range.empty() == false) {
317  range_ = set_range;
318  }
319 
320  if(set_attack_alignment.empty() == false) {
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 auto [key, cfg] : specials_.all_children_view()) {
332  std::vector<std::string>::const_iterator found_id =
333  std::find(dsl.begin(), dsl.end(), cfg["id"].str());
334  if (found_id == dsl.end()) {
335  new_specials.add_child(key, 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 auto [key, cfg] : set_specials->all_children_view()) {
352  specials_.add_child(key, cfg);
353  }
354  }
355 
356  if(set_min_range.empty() == false) {
358  }
359 
360  if(increase_min_range.empty() == false) {
361  min_range_ = utils::apply_modifier(min_range_, increase_min_range);
362  }
363 
364  if(set_max_range.empty() == false) {
366  }
367 
368  if(increase_max_range.empty() == false) {
369  max_range_ = utils::apply_modifier(max_range_, increase_max_range);
370  }
371 
372  if(remove_specials) {
373  remove_special_by_filter(*remove_specials);
374  }
375 
376  if(set_damage.empty() == false) {
378  if (damage_ < 0) {
379  damage_ = 0;
380  }
381  }
382 
383  if(increase_damage.empty() == false) {
384  damage_ = utils::apply_modifier(damage_, increase_damage);
385  if(damage_ < 0) {
386  damage_ = 0;
387  }
388  }
389 
390  if(set_attacks.empty() == false) {
391  num_attacks_ = std::stoi(set_attacks);
392  if (num_attacks_ < 0) {
393  num_attacks_ = 0;
394  }
395 
396  }
397 
398  if(increase_attacks.empty() == false) {
399  num_attacks_ = utils::apply_modifier(num_attacks_, increase_attacks, 1);
400  }
401 
402  if(set_accuracy.empty() == false) {
404  }
405 
406  if(increase_accuracy.empty() == false) {
407  accuracy_ = utils::apply_modifier(accuracy_, increase_accuracy);
408  }
409 
410  if(set_parry.empty() == false) {
412  }
413 
414  if(increase_parry.empty() == false) {
415  parry_ = utils::apply_modifier(parry_, increase_parry);
416  }
417 
418  if(set_movement.empty() == false) {
419  movement_used_ = std::stoi(set_movement);
420  }
421 
422  if(increase_movement.empty() == false) {
423  movement_used_ = utils::apply_modifier(movement_used_, increase_movement, 1);
424  }
425 
426  if(set_attacks_used.empty() == false) {
428  }
429 
430  if(increase_attacks_used.empty() == false) {
431  attacks_used_ = utils::apply_modifier(attacks_used_, increase_attacks_used, 1);
432  }
433 
434  if(set_attack_weight.empty() == false) {
435  attack_weight_ = lexical_cast_default<double>(set_attack_weight,1.0);
436  }
437 
438  if(set_defense_weight.empty() == false) {
439  defense_weight_ = lexical_cast_default<double>(set_defense_weight,1.0);
440  }
441 
442  return true;
443 }
444 
445 /**
446  * Trimmed down version of apply_modification(), with no modifications actually
447  * made. This can be used to get a description of the modification(s) specified
448  * by @a cfg (if *this matches cfg as a filter).
449  *
450  * If *description is provided, it will be set to a (translated) description
451  * of the modification(s) applied (currently only changes to the number of
452  * strikes, damage, accuracy, and parry are included in this description).
453  *
454  * @returns whether or not @c this matched the @a cfg as a filter.
455  */
456 bool attack_type::describe_modification(const config& cfg,std::string* description)
457 {
458  if( !matches_filter(cfg) )
459  return false;
460 
461  // Did the caller want the description?
462  if(description != nullptr) {
463  const std::string& increase_min_range = cfg["increase_min_range"];
464  const std::string& set_min_range = cfg["set_min_range"];
465  const std::string& increase_max_range = cfg["increase_max_range"];
466  const std::string& set_max_range = cfg["set_max_range"];
467  const std::string& increase_damage = cfg["increase_damage"];
468  const std::string& set_damage = cfg["set_damage"];
469  const std::string& increase_attacks = cfg["increase_attacks"];
470  const std::string& set_attacks = cfg["set_attacks"];
471  const std::string& increase_accuracy = cfg["increase_accuracy"];
472  const std::string& set_accuracy = cfg["set_accuracy"];
473  const std::string& increase_parry = cfg["increase_parry"];
474  const std::string& set_parry = cfg["set_parry"];
475  const std::string& increase_movement = cfg["increase_movement_used"];
476  const std::string& set_movement = cfg["set_movement_used"];
477  const std::string& increase_attacks_used = cfg["increase_attacks_used"];
478  const std::string& set_attacks_used = cfg["set_attacks_used"];
479 
480  std::vector<t_string> desc;
481 
482  if(!set_min_range.empty()) {
483  desc.emplace_back(VGETTEXT(
484  // TRANSLATORS: Current value for WML code set_min_range, documented in https://wiki.wesnoth.org/EffectWML
485  "$number min range",
486  {{"number", set_min_range}}));
487  }
488 
489  if(!increase_min_range.empty()) {
490  desc.emplace_back(VGETTEXT(
491  // TRANSLATORS: Current value for WML code increase_min_range, documented in https://wiki.wesnoth.org/EffectWML
492  "<span color=\"$color\">$number_or_percent</span> min range",
493  {{"number_or_percent", utils::print_modifier(increase_min_range)}, {"color", increase_min_range[0] == '-' ? "#f00" : "#0f0"}}));
494  }
495 
496  if(!set_max_range.empty()) {
497  desc.emplace_back(VGETTEXT(
498  // TRANSLATORS: Current value for WML code set_max_range, documented in https://wiki.wesnoth.org/EffectWML
499  "$number max range",
500  {{"number", set_max_range}}));
501  }
502 
503  if(!increase_max_range.empty()) {
504  desc.emplace_back(VGETTEXT(
505  // TRANSLATORS: Current value for WML code increase_max_range, documented in https://wiki.wesnoth.org/EffectWML
506  "<span color=\"$color\">$number_or_percent</span> max range",
507  {{"number_or_percent", utils::print_modifier(increase_max_range)}, {"color", increase_max_range[0] == '-' ? "#f00" : "#0f0"}}));
508  }
509 
510  if(!increase_damage.empty()) {
511  desc.emplace_back(VNGETTEXT(
512  // TRANSLATORS: Current value for WML code increase_damage, documented in https://wiki.wesnoth.org/EffectWML
513  "<span color=\"$color\">$number_or_percent</span> damage",
514  "<span color=\"$color\">$number_or_percent</span> damage",
515  std::stoi(increase_damage),
516  {{"number_or_percent", utils::print_modifier(increase_damage)}, {"color", increase_damage[0] == '-' ? "#f00" : "#0f0"}}));
517  }
518 
519  if(!set_damage.empty()) {
520  // TRANSLATORS: Current value for WML code set_damage, documented in https://wiki.wesnoth.org/EffectWML
521  desc.emplace_back(VNGETTEXT(
522  "$number damage",
523  "$number damage",
525  {{"number", set_damage}}));
526  }
527 
528  if(!increase_attacks.empty()) {
529  desc.emplace_back(VNGETTEXT(
530  // TRANSLATORS: Current value for WML code increase_attacks, documented in https://wiki.wesnoth.org/EffectWML
531  "<span color=\"$color\">$number_or_percent</span> strike",
532  "<span color=\"$color\">$number_or_percent</span> strikes",
533  std::stoi(increase_attacks),
534  {{"number_or_percent", utils::print_modifier(increase_attacks)}, {"color", increase_attacks[0] == '-' ? "#f00" : "#0f0"}}));
535  }
536 
537  if(!set_attacks.empty()) {
538  desc.emplace_back(VNGETTEXT(
539  // TRANSLATORS: Current value for WML code set_attacks, documented in https://wiki.wesnoth.org/EffectWML
540  "$number strike",
541  "$number strikes",
542  std::stoi(set_attacks),
543  {{"number", set_attacks}}));
544  }
545 
546  if(!set_accuracy.empty()) {
547  desc.emplace_back(VGETTEXT(
548  // TRANSLATORS: Current value for WML code set_accuracy, documented in https://wiki.wesnoth.org/EffectWML
549  "$number| accuracy",
550  {{"number", set_accuracy}}));
551  }
552 
553  if(!increase_accuracy.empty()) {
554  desc.emplace_back(VGETTEXT(
555  // TRANSLATORS: Current value for WML code increase_accuracy, documented in https://wiki.wesnoth.org/EffectWML
556  "<span color=\"$color\">$number_or_percent|%</span> accuracy",
557  {{"number_or_percent", utils::print_modifier(increase_accuracy)}, {"color", increase_accuracy[0] == '-' ? "#f00" : "#0f0"}}));
558  }
559 
560  if(!set_parry.empty()) {
561  desc.emplace_back(VGETTEXT(
562  // TRANSLATORS: Current value for WML code set_parry, documented in https://wiki.wesnoth.org/EffectWML
563  "$number parry",
564  {{"number", set_parry}}));
565  }
566 
567  if(!increase_parry.empty()) {
568  desc.emplace_back(VGETTEXT(
569  // TRANSLATORS: Current value for WML code increase_parry, documented in https://wiki.wesnoth.org/EffectWML
570  "<span color=\"$color\">$number_or_percent</span> parry",
571  {{"number_or_percent", utils::print_modifier(increase_parry)}, {"color", increase_parry[0] == '-' ? "#f00" : "#0f0"}}));
572  }
573 
574  if(!set_movement.empty()) {
575  desc.emplace_back(VNGETTEXT(
576  // TRANSLATORS: Current value for WML code set_movement_used, documented in https://wiki.wesnoth.org/EffectWML
577  "$number movement point",
578  "$number movement points",
579  std::stoi(set_movement),
580  {{"number", set_movement}}));
581  }
582 
583  if(!increase_movement.empty()) {
584  desc.emplace_back(VNGETTEXT(
585  // TRANSLATORS: Current value for WML code increase_movement_used, documented in https://wiki.wesnoth.org/EffectWML
586  "<span color=\"$color\">$number_or_percent</span> movement point",
587  "<span color=\"$color\">$number_or_percent</span> movement points",
588  std::stoi(increase_movement),
589  {{"number_or_percent", utils::print_modifier(increase_movement)}, {"color", increase_movement[0] == '-' ? "#f00" : "#0f0"}}));
590  }
591 
592  if(!set_attacks_used.empty()) {
593  desc.emplace_back(VNGETTEXT(
594  // TRANSLATORS: Current value for WML code set_attacks_used, documented in https://wiki.wesnoth.org/EffectWML
595  "$number attack used",
596  "$number attacks used",
598  {{"number", set_attacks_used}}));
599  }
600 
601  if(!increase_attacks_used.empty()) {
602  desc.emplace_back(VNGETTEXT(
603  // TRANSLATORS: Current value for WML code increase_attacks_used, documented in https://wiki.wesnoth.org/EffectWML
604  "<span color=\"$color\">$number_or_percent</span> attack used",
605  "<span color=\"$color\">$number_or_percent</span> attacks used",
606  std::stoi(increase_attacks_used),
607  {{"number_or_percent", utils::print_modifier(increase_attacks_used)}, {"color", increase_attacks_used[0] == '-' ? "#f00" : "#0f0"}}));
608  }
609 
610  *description = utils::format_conjunct_list("", desc);
611  }
612 
613  return true;
614 }
615 
617 {
618  if(utils::contains(open_queries_, &special)) {
619  return recursion_guard();
620  }
621  return recursion_guard(*this, special);
622 }
623 
625 
627  : parent(weapon.shared_from_this())
628 {
629  parent->open_queries_.emplace_back(&special);
630 }
631 
633 {
634  std::swap(parent, other.parent);
635 }
636 
637 attack_type::recursion_guard::operator bool() const {
638  return bool(parent);
639 }
640 
642 {
643  // This is only intended to move ownership to a longer-living variable. Assigning to an instance that
644  // already has a parent implies that the caller is going to recurse and needs a recursion allocation,
645  // but is accidentally dropping one of the allocations that it already has; hence the asserts.
646  assert(this != &other);
647  assert(!parent);
648  std::swap(parent, other.parent);
649  return *this;
650 }
651 
653 {
654  if(parent) {
655  // As this only expects nested recursion, simply pop the top of the open_queries_ stack
656  // without checking that the top of the stack matches the filter passed to the constructor.
657  assert(!parent->open_queries_.empty());
658  parent->open_queries_.pop_back();
659  }
660 }
661 
662 void attack_type::write(config& cfg) const
663 {
664  cfg["description"] = description_;
665  cfg["name"] = id_;
666  cfg["type"] = type_;
667  cfg["icon"] = icon_;
668  cfg["range"] = range_;
669  cfg["min_range"] = min_range_;
670  cfg["max_range"] = max_range_;
671  cfg["alignment"] = alignment_str();
672  cfg["damage"] = damage_;
673  cfg["number"] = num_attacks_;
674  cfg["attack_weight"] = attack_weight_;
675  cfg["defense_weight"] = defense_weight_;
676  cfg["accuracy"] = accuracy_;
677  cfg["movement_used"] = movement_used_;
678  cfg["attacks_used"] = attacks_used_;
679  cfg["parry"] = parry_;
680  cfg.add_child("specials", specials_);
681 }
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:94
void set_min_range(int value)
Definition: attack_type.hpp:63
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:2142
void set_accuracy(int value)
Definition: attack_type.hpp:66
const std::string & type() const
Definition: attack_type.hpp:44
int parry() const
Definition: attack_type.hpp:51
std::string accuracy_parry_description() const
Definition: attack_type.cpp:85
bool apply_modification(const config &cfg)
Modifies *this using the specifications in cfg, but only if *this matches cfg viewed as a filter.
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:72
config specials_
void set_defense_weight(double value)
Definition: attack_type.hpp:71
int num_attacks() const
Definition: attack_type.hpp:53
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:1853
std::string type_
std::string icon_
void set_parry(int value)
Definition: attack_type.hpp:67
void set_attack_weight(double value)
Definition: attack_type.hpp:70
void set_damage(int value)
Definition: attack_type.hpp:68
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:43
void set_icon(const std::string &value)
Definition: attack_type.hpp:61
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:2137
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:64
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:60
void write(config &cfg) const
int accuracy() const
Definition: attack_type.hpp:50
int max_range() const
Definition: attack_type.hpp:48
void set_range(const std::string &value)
Definition: attack_type.hpp:62
int damage() const
Definition: attack_type.hpp:52
void set_attack_alignment(const std::string &value)
Definition: attack_type.hpp:65
t_string description_
void set_name(const t_string &value)
Definition: attack_type.hpp:58
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:1325
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
all_children_iterator erase(const all_children_iterator &i)
Definition: config.cpp:634
const_all_children_iterator ordered_begin() const
Definition: config.cpp:860
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:796
const_all_children_iterator ordered_end() const
Definition: config.cpp:870
void clear()
Definition: config.cpp:824
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:380
config & add_child(config_key_type key)
Definition: config.cpp:436
bool empty() const
Definition: tstring.hpp:197
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)
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1339
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:29
#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.
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:550
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)
int stoi(std::string_view str)
Same interface as std::stoi and meant as a drop in replacement, except:
Definition: charconv.hpp:154
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:86
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::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)
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:140
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