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"
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 "serialization/markup.hpp"
34 #include "gettext.hpp"
35 #include "utils/math.hpp"
36 
37 
38 static lg::log_domain log_config("config");
39 #define ERR_CF LOG_STREAM(err, log_config)
40 #define WRN_CF LOG_STREAM(warn, log_config)
41 #define LOG_CONFIG LOG_STREAM(info, log_config)
42 #define DBG_CF LOG_STREAM(debug, log_config)
43 
44 static lg::log_domain log_unit("unit");
45 #define DBG_UT LOG_STREAM(debug, log_unit)
46 #define ERR_UT LOG_STREAM(err, log_unit)
47 
48 static lg::log_domain log_wml("wml");
49 #define ERR_WML LOG_STREAM(err, log_wml)
50 
52  : self_loc_()
53  , other_loc_()
54  , is_attacker_(false)
55  , other_attack_(nullptr)
56  , description_(cfg["description"].t_str())
57  , id_(cfg["name"])
58  , type_(cfg["type"])
59  , icon_(cfg["icon"])
60  , range_(cfg["range"])
61  , min_range_(cfg["min_range"].to_int(1))
62  , max_range_(cfg["max_range"].to_int(1))
63  , alignment_(unit_alignments::get_enum(cfg["alignment"].str()))
64  , damage_(cfg["damage"].to_int())
65  , num_attacks_(cfg["number"].to_int())
66  , attack_weight_(cfg["attack_weight"].to_double(1.0))
67  , defense_weight_(cfg["defense_weight"].to_double(1.0))
68  , accuracy_(cfg["accuracy"].to_int())
69  , movement_used_(cfg["movement_used"].to_int(100000))
70  , attacks_used_(cfg["attacks_used"].to_int(1))
71  , parry_(cfg["parry"].to_int())
72  , specials_(cfg.child_or_empty("specials"))
73  , changed_(true)
74 {
75  if (description_.empty())
77 
78  if(icon_.empty()){
79  if (!id_.empty())
80  icon_ = "attacks/" + id_ + ".png";
81  else
82  icon_ = "attacks/blank-attack.png";
83  }
84 }
85 
87 {
88  if(accuracy_ == 0 && parry_ == 0) {
89  return "";
90  }
91 
92  std::ostringstream s;
94 
95  if(parry_ != 0) {
96  s << "/" << utils::signed_percent(parry_);
97  }
98 
99  return s.str();
100 }
101 
103 {
104  if(accuracy_ == 0 && parry_ == 0) {
105  return "";
106  }
107 
108  std::stringstream tooltip;
109  if (accuracy_) {
110  tooltip << _("Accuracy:") << " " << markup::bold(utils::signed_percent(accuracy_)) << "\n";
111  }
112  if (parry_) {
113  tooltip << _("Parry:") << " " << markup::bold(utils::signed_percent(parry_));
114  }
115 
116  return tooltip.str();
117 }
118 
119 /**
120  * Returns whether or not *this matches the given @a filter, ignoring the
121  * complexities introduced by [and], [or], and [not].
122  */
123 static bool matches_simple_filter(const attack_type & attack, const config & filter, const std::string& check_if_recursion)
124 {
125  const std::set<std::string> filter_range = utils::split_set(filter["range"].str());
126  const std::string& filter_min_range = filter["min_range"];
127  const std::string& filter_max_range = filter["max_range"];
128  const std::string& filter_damage = filter["damage"];
129  const std::string& filter_attacks = filter["number"];
130  const std::string& filter_accuracy = filter["accuracy"];
131  const std::string& filter_parry = filter["parry"];
132  const std::string& filter_movement = filter["movement_used"];
133  const std::string& filter_attacks_used = filter["attacks_used"];
134  const std::set<std::string> filter_alignment = utils::split_set(filter["alignment"].str());
135  const std::set<std::string> filter_name = utils::split_set(filter["name"].str());
136  const std::set<std::string> filter_type = utils::split_set(filter["type"].str());
137  const std::set<std::string> filter_base_type = utils::split_set(filter["base_type"].str());
138  const std::string filter_formula = filter["formula"];
139 
140  if (!filter_min_range.empty() && !in_ranges(attack.min_range(), utils::parse_ranges_int(filter_min_range)))
141  return false;
142 
143  if (!filter_max_range.empty() && !in_ranges(attack.max_range(), utils::parse_ranges_int(filter_max_range)))
144  return false;
145 
146  if ( !filter_range.empty() && filter_range.count(attack.range()) == 0 )
147  return false;
148 
149  if ( !filter_damage.empty() && !in_ranges(attack.damage(), utils::parse_ranges_unsigned(filter_damage)) )
150  return false;
151 
152  if (!filter_attacks.empty() && !in_ranges(attack.num_attacks(), utils::parse_ranges_unsigned(filter_attacks)))
153  return false;
154 
155  if (!filter_accuracy.empty() && !in_ranges(attack.accuracy(), utils::parse_ranges_int(filter_accuracy)))
156  return false;
157 
158  if (!filter_parry.empty() && !in_ranges(attack.parry(), utils::parse_ranges_int(filter_parry)))
159  return false;
160 
161  if (!filter_movement.empty() && !in_ranges(attack.movement_used(), utils::parse_ranges_unsigned(filter_movement)))
162  return false;
163 
164  if (!filter_attacks_used.empty() && !in_ranges(attack.attacks_used(), utils::parse_ranges_unsigned(filter_attacks_used)))
165  return false;
166 
167  if(!filter_alignment.empty() && filter_alignment.count(attack.alignment_str()) == 0)
168  return false;
169 
170  if ( !filter_name.empty() && filter_name.count(attack.id()) == 0)
171  return false;
172 
173  if (!filter_type.empty()){
174  // Although there's a general guard against infinite recursion, the "damage_type" special
175  // should always use the base type of the weapon. Otherwise it will flip-flop between the
176  // special being active or inactive based on whether ATTACK_RECURSION_LIMIT is even or odd;
177  // without this it will also behave differently when calculating resistance_against.
178  if(check_if_recursion == "damage_type"){
179  if (filter_type.count(attack.type()) == 0){
180  return false;
181  }
182  } else {
183  //if the type is different from "damage_type" then damage_type() can be called for safe checking.
184  if (filter_type.count(attack.effective_damage_type().first) == 0){
185  return false;
186  }
187  }
188  }
189 
190  if ( !filter_base_type.empty() && filter_base_type.count(attack.type()) == 0 )
191  return false;
192 
193  if(filter.has_attribute("special")) {
194  deprecated_message("special=", DEP_LEVEL::PREEMPTIVE, {1, 17, 0}, "Please use special_id or special_type instead");
195  }
196 
197  if(filter.has_attribute("special") || filter.has_attribute("special_id") || filter.has_attribute("special_type")) {
198  if(!attack.has_filter_special_or_ability(filter, true)) {
199  return false;
200  }
201  }
202 
203  if(filter.has_attribute("special_active")) {
204  deprecated_message("special_active=", DEP_LEVEL::PREEMPTIVE, {1, 17, 0}, "Please use special_id_active or special_type_active instead");
205  }
206 
207  if(filter.has_attribute("special_active") || filter.has_attribute("special_id_active") || filter.has_attribute("special_type_active")) {
209  return false;
210  }
211  }
212 
213  //children filter_special are checked later,
214  //but only when the function doesn't return earlier
215  if(auto sub_filter_special = filter.optional_child("filter_special")) {
216  if(!attack.has_special_or_ability_with_filter(*sub_filter_special)) {
217  return false;
218  }
219  }
220 
221  if (!filter_formula.empty()) {
222  try {
223  const wfl::attack_type_callable callable(attack);
225  const wfl::formula form(filter_formula, &symbols);
226  if(!form.evaluate(callable).as_bool()) {
227  return false;
228  }
229  } catch(const wfl::formula_error& e) {
230  lg::log_to_chat() << "Formula error in weapon filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
231  ERR_WML << "Formula error in weapon filter: " << e.type << " at " << e.filename << ':' << e.line << ")";
232  // Formulae with syntax errors match nothing
233  return false;
234  }
235  }
236 
237  // Passed all tests.
238  return true;
239 }
240 
241 /**
242  * Returns whether or not *this matches the given @a filter.
243  */
244 bool attack_type::matches_filter(const config& filter, const std::string& check_if_recursion) const
245 {
246  // Handle the basic filter.
247  bool matches = matches_simple_filter(*this, filter, check_if_recursion);
248 
249  // Handle [and], [or], and [not] with in-order precedence
250  for(const auto [key, condition_cfg] : filter.all_children_view() )
251  {
252  // Handle [and]
253  if ( key == "and" )
254  matches = matches && matches_filter(condition_cfg, check_if_recursion);
255 
256  // Handle [or]
257  else if ( key == "or" )
258  matches = matches || matches_filter(condition_cfg, check_if_recursion);
259 
260  // Handle [not]
261  else if ( key == "not" )
262  matches = matches && !matches_filter(condition_cfg, check_if_recursion);
263  }
264 
265  return matches;
266 }
267 
269 {
271  while (i != specials_.ordered_end()) {
272  if(special_matches_filter(i->cfg, i->key, filter)) {
273  i = specials_.erase(i);
274  } else {
275  ++i;
276  }
277  }
278 }
279 
280 /**
281  * Modifies *this using the specifications in @a cfg, but only if *this matches
282  * @a cfg viewed as a filter.
283  *
284  * @returns whether or not @c this matched the @a cfg as a filter.
285  */
287 {
288  if( !matches_filter(cfg) )
289  return false;
290 
291  set_changed(true);
292  const std::string& set_name = cfg["set_name"];
293  const t_string& set_desc = cfg["set_description"];
294  const std::string& set_type = cfg["set_type"];
295  const std::string& set_range = cfg["set_range"];
296  const std::string& set_attack_alignment = cfg["set_alignment"];
297  const std::string& set_icon = cfg["set_icon"];
298  const std::string& del_specials = cfg["remove_specials"];
299  auto set_specials = cfg.optional_child("set_specials");
300  const std::string& increase_min_range = cfg["increase_min_range"];
301  const std::string& set_min_range = cfg["set_min_range"];
302  const std::string& increase_max_range = cfg["increase_max_range"];
303  const std::string& set_max_range = cfg["set_max_range"];
304  auto remove_specials = cfg.optional_child("remove_specials");
305  const std::string& increase_damage = cfg["increase_damage"];
306  const std::string& set_damage = cfg["set_damage"];
307  const std::string& increase_attacks = cfg["increase_attacks"];
308  const std::string& set_attacks = cfg["set_attacks"];
309  const std::string& set_attack_weight = cfg["attack_weight"];
310  const std::string& set_defense_weight = cfg["defense_weight"];
311  const std::string& increase_accuracy = cfg["increase_accuracy"];
312  const std::string& set_accuracy = cfg["set_accuracy"];
313  const std::string& increase_parry = cfg["increase_parry"];
314  const std::string& set_parry = cfg["set_parry"];
315  const std::string& increase_movement = cfg["increase_movement_used"];
316  const std::string& set_movement = cfg["set_movement_used"];
317  const std::string& increase_attacks_used = cfg["increase_attacks_used"];
318  const std::string& set_attacks_used = cfg["set_attacks_used"];
319  // NB: If you add something here that requires a description,
320  // it needs to be added to describe_modification as well.
321 
322  if(set_name.empty() == false) {
323  id_ = set_name;
324  }
325 
326  if(set_desc.empty() == false) {
327  description_ = set_desc;
328  }
329 
330  if(set_type.empty() == false) {
331  type_ = set_type;
332  }
333 
334  if(set_range.empty() == false) {
335  range_ = set_range;
336  }
337 
338  if(set_attack_alignment.empty() == false) {
340  }
341 
342  if(set_icon.empty() == false) {
343  icon_ = set_icon;
344  }
345 
346  if(del_specials.empty() == false) {
347  const std::vector<std::string>& dsl = utils::split(del_specials);
348  config new_specials;
349  for(const auto [key, cfg] : specials_.all_children_view()) {
350  std::vector<std::string>::const_iterator found_id =
351  std::find(dsl.begin(), dsl.end(), cfg["id"].str());
352  if (found_id == dsl.end()) {
353  new_specials.add_child(key, cfg);
354  }
355  }
356  specials_ = new_specials;
357  }
358 
359  if(set_specials) {
360  const std::string &mode = set_specials["mode"];
361  if(mode.empty()){
362  deprecated_message("[set_specials]mode=<unset>", DEP_LEVEL::INDEFINITE, "",
363  "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.");
364  // fall through to mode != "append"
365  }
366  if(mode != "append") {
367  specials_.clear();
368  }
369  for(const auto [key, cfg] : set_specials->all_children_view()) {
370  specials_.add_child(key, cfg);
371  }
372  }
373 
374  if(set_min_range.empty() == false) {
376  }
377 
378  if(increase_min_range.empty() == false) {
379  min_range_ = utils::apply_modifier(min_range_, increase_min_range);
380  }
381 
382  if(set_max_range.empty() == false) {
384  }
385 
386  if(increase_max_range.empty() == false) {
387  max_range_ = utils::apply_modifier(max_range_, increase_max_range);
388  }
389 
390  if(remove_specials) {
391  remove_special_by_filter(*remove_specials);
392  }
393 
394  if(set_damage.empty() == false) {
396  if (damage_ < 0) {
397  damage_ = 0;
398  }
399  }
400 
401  if(increase_damage.empty() == false) {
402  damage_ = utils::apply_modifier(damage_, increase_damage);
403  if(damage_ < 0) {
404  damage_ = 0;
405  }
406  }
407 
408  if(set_attacks.empty() == false) {
409  num_attacks_ = std::stoi(set_attacks);
410  if (num_attacks_ < 0) {
411  num_attacks_ = 0;
412  }
413 
414  }
415 
416  if(increase_attacks.empty() == false) {
417  num_attacks_ = utils::apply_modifier(num_attacks_, increase_attacks, 1);
418  }
419 
420  if(set_accuracy.empty() == false) {
422  }
423 
424  if(increase_accuracy.empty() == false) {
425  accuracy_ = utils::apply_modifier(accuracy_, increase_accuracy);
426  }
427 
428  if(set_parry.empty() == false) {
430  }
431 
432  if(increase_parry.empty() == false) {
433  parry_ = utils::apply_modifier(parry_, increase_parry);
434  }
435 
436  if(set_movement.empty() == false) {
437  movement_used_ = std::stoi(set_movement);
438  }
439 
440  if(increase_movement.empty() == false) {
441  movement_used_ = utils::apply_modifier(movement_used_, increase_movement, 1);
442  }
443 
444  if(set_attacks_used.empty() == false) {
446  }
447 
448  if(increase_attacks_used.empty() == false) {
449  attacks_used_ = utils::apply_modifier(attacks_used_, increase_attacks_used, 1);
450  }
451 
452  if(set_attack_weight.empty() == false) {
453  attack_weight_ = lexical_cast_default<double>(set_attack_weight,1.0);
454  }
455 
456  if(set_defense_weight.empty() == false) {
457  defense_weight_ = lexical_cast_default<double>(set_defense_weight,1.0);
458  }
459 
460  return true;
461 }
462 
463 /**
464  * Trimmed down version of apply_modification(), with no modifications actually
465  * made. This can be used to get a description of the modification(s) specified
466  * by @a cfg (if *this matches cfg as a filter).
467  *
468  * If *description is provided, it will be set to a (translated) description
469  * of the modification(s) applied (currently only changes to the number of
470  * strikes, damage, accuracy, and parry are included in this description).
471  *
472  * @returns whether or not @c this matched the @a cfg as a filter.
473  */
474 bool attack_type::describe_modification(const config& cfg,std::string* description)
475 {
476  if( !matches_filter(cfg) )
477  return false;
478 
479  // Did the caller want the description?
480  if(description != nullptr) {
481  const std::string& increase_min_range = cfg["increase_min_range"];
482  const std::string& set_min_range = cfg["set_min_range"];
483  const std::string& increase_max_range = cfg["increase_max_range"];
484  const std::string& set_max_range = cfg["set_max_range"];
485  const std::string& increase_damage = cfg["increase_damage"];
486  const std::string& set_damage = cfg["set_damage"];
487  const std::string& increase_attacks = cfg["increase_attacks"];
488  const std::string& set_attacks = cfg["set_attacks"];
489  const std::string& increase_accuracy = cfg["increase_accuracy"];
490  const std::string& set_accuracy = cfg["set_accuracy"];
491  const std::string& increase_parry = cfg["increase_parry"];
492  const std::string& set_parry = cfg["set_parry"];
493  const std::string& increase_movement = cfg["increase_movement_used"];
494  const std::string& set_movement = cfg["set_movement_used"];
495  const std::string& increase_attacks_used = cfg["increase_attacks_used"];
496  const std::string& set_attacks_used = cfg["set_attacks_used"];
497 
498  std::vector<t_string> desc;
499 
500  if(!set_min_range.empty()) {
501  desc.emplace_back(VGETTEXT(
502  // TRANSLATORS: Current value for WML code set_min_range, documented in https://wiki.wesnoth.org/EffectWML
503  "$number min range",
504  {{"number", set_min_range}}));
505  }
506 
507  if(!increase_min_range.empty()) {
508  desc.emplace_back(VGETTEXT(
509  // TRANSLATORS: Current value for WML code increase_min_range, documented in https://wiki.wesnoth.org/EffectWML
510  "<span color=\"$color\">$number_or_percent</span> min range",
511  {{"number_or_percent", utils::print_modifier(increase_min_range)}, {"color", increase_min_range[0] == '-' ? "#f00" : "#0f0"}}));
512  }
513 
514  if(!set_max_range.empty()) {
515  desc.emplace_back(VGETTEXT(
516  // TRANSLATORS: Current value for WML code set_max_range, documented in https://wiki.wesnoth.org/EffectWML
517  "$number max range",
518  {{"number", set_max_range}}));
519  }
520 
521  if(!increase_max_range.empty()) {
522  desc.emplace_back(VGETTEXT(
523  // TRANSLATORS: Current value for WML code increase_max_range, documented in https://wiki.wesnoth.org/EffectWML
524  "<span color=\"$color\">$number_or_percent</span> max range",
525  {{"number_or_percent", utils::print_modifier(increase_max_range)}, {"color", increase_max_range[0] == '-' ? "#f00" : "#0f0"}}));
526  }
527 
528  if(!increase_damage.empty()) {
529  desc.emplace_back(VNGETTEXT(
530  // TRANSLATORS: Current value for WML code increase_damage, documented in https://wiki.wesnoth.org/EffectWML
531  "<span color=\"$color\">$number_or_percent</span> damage",
532  "<span color=\"$color\">$number_or_percent</span> damage",
533  std::stoi(increase_damage),
534  {{"number_or_percent", utils::print_modifier(increase_damage)}, {"color", increase_damage[0] == '-' ? "#f00" : "#0f0"}}));
535  }
536 
537  if(!set_damage.empty()) {
538  // TRANSLATORS: Current value for WML code set_damage, documented in https://wiki.wesnoth.org/EffectWML
539  desc.emplace_back(VNGETTEXT(
540  "$number damage",
541  "$number damage",
543  {{"number", set_damage}}));
544  }
545 
546  if(!increase_attacks.empty()) {
547  desc.emplace_back(VNGETTEXT(
548  // TRANSLATORS: Current value for WML code increase_attacks, documented in https://wiki.wesnoth.org/EffectWML
549  "<span color=\"$color\">$number_or_percent</span> strike",
550  "<span color=\"$color\">$number_or_percent</span> strikes",
551  std::stoi(increase_attacks),
552  {{"number_or_percent", utils::print_modifier(increase_attacks)}, {"color", increase_attacks[0] == '-' ? "#f00" : "#0f0"}}));
553  }
554 
555  if(!set_attacks.empty()) {
556  desc.emplace_back(VNGETTEXT(
557  // TRANSLATORS: Current value for WML code set_attacks, documented in https://wiki.wesnoth.org/EffectWML
558  "$number strike",
559  "$number strikes",
560  std::stoi(set_attacks),
561  {{"number", set_attacks}}));
562  }
563 
564  if(!set_accuracy.empty()) {
565  desc.emplace_back(VGETTEXT(
566  // TRANSLATORS: Current value for WML code set_accuracy, documented in https://wiki.wesnoth.org/EffectWML
567  "$number| accuracy",
568  {{"number", set_accuracy}}));
569  }
570 
571  if(!increase_accuracy.empty()) {
572  desc.emplace_back(VGETTEXT(
573  // TRANSLATORS: Current value for WML code increase_accuracy, documented in https://wiki.wesnoth.org/EffectWML
574  "<span color=\"$color\">$number_or_percent|%</span> accuracy",
575  {{"number_or_percent", utils::print_modifier(increase_accuracy)}, {"color", increase_accuracy[0] == '-' ? "#f00" : "#0f0"}}));
576  }
577 
578  if(!set_parry.empty()) {
579  desc.emplace_back(VGETTEXT(
580  // TRANSLATORS: Current value for WML code set_parry, documented in https://wiki.wesnoth.org/EffectWML
581  "$number parry",
582  {{"number", set_parry}}));
583  }
584 
585  if(!increase_parry.empty()) {
586  desc.emplace_back(VGETTEXT(
587  // TRANSLATORS: Current value for WML code increase_parry, documented in https://wiki.wesnoth.org/EffectWML
588  "<span color=\"$color\">$number_or_percent</span> parry",
589  {{"number_or_percent", utils::print_modifier(increase_parry)}, {"color", increase_parry[0] == '-' ? "#f00" : "#0f0"}}));
590  }
591 
592  if(!set_movement.empty()) {
593  desc.emplace_back(VNGETTEXT(
594  // TRANSLATORS: Current value for WML code set_movement_used, documented in https://wiki.wesnoth.org/EffectWML
595  "$number movement point",
596  "$number movement points",
597  std::stoi(set_movement),
598  {{"number", set_movement}}));
599  }
600 
601  if(!increase_movement.empty()) {
602  desc.emplace_back(VNGETTEXT(
603  // TRANSLATORS: Current value for WML code increase_movement_used, documented in https://wiki.wesnoth.org/EffectWML
604  "<span color=\"$color\">$number_or_percent</span> movement point",
605  "<span color=\"$color\">$number_or_percent</span> movement points",
606  std::stoi(increase_movement),
607  {{"number_or_percent", utils::print_modifier(increase_movement)}, {"color", increase_movement[0] == '-' ? "#f00" : "#0f0"}}));
608  }
609 
610  if(!set_attacks_used.empty()) {
611  desc.emplace_back(VNGETTEXT(
612  // TRANSLATORS: Current value for WML code set_attacks_used, documented in https://wiki.wesnoth.org/EffectWML
613  "$number attack used",
614  "$number attacks used",
616  {{"number", set_attacks_used}}));
617  }
618 
619  if(!increase_attacks_used.empty()) {
620  desc.emplace_back(VNGETTEXT(
621  // TRANSLATORS: Current value for WML code increase_attacks_used, documented in https://wiki.wesnoth.org/EffectWML
622  "<span color=\"$color\">$number_or_percent</span> attack used",
623  "<span color=\"$color\">$number_or_percent</span> attacks used",
624  std::stoi(increase_attacks_used),
625  {{"number_or_percent", utils::print_modifier(increase_attacks_used)}, {"color", increase_attacks_used[0] == '-' ? "#f00" : "#0f0"}}));
626  }
627 
628  *description = utils::format_conjunct_list("", desc);
629  }
630 
631  return true;
632 }
633 
635 {
636  if(utils::contains(open_queries_, &special)) {
637  return recursion_guard();
638  }
639  return recursion_guard(*this, special);
640 }
641 
643 
645  : parent(weapon.shared_from_this())
646 {
647  parent->open_queries_.emplace_back(&special);
648 }
649 
651 {
652  std::swap(parent, other.parent);
653 }
654 
655 attack_type::recursion_guard::operator bool() const {
656  return bool(parent);
657 }
658 
660 {
661  // This is only intended to move ownership to a longer-living variable. Assigning to an instance that
662  // already has a parent implies that the caller is going to recurse and needs a recursion allocation,
663  // but is accidentally dropping one of the allocations that it already has; hence the asserts.
664  assert(this != &other);
665  assert(!parent);
666  std::swap(parent, other.parent);
667  return *this;
668 }
669 
671 {
672  if(parent) {
673  // As this only expects nested recursion, simply pop the top of the open_queries_ stack
674  // without checking that the top of the stack matches the filter passed to the constructor.
675  assert(!parent->open_queries_.empty());
676  parent->open_queries_.pop_back();
677  }
678 }
679 
681 {
682  cfg["description"] = description_;
683  cfg["name"] = id_;
684  cfg["type"] = type_;
685  cfg["icon"] = icon_;
686  cfg["range"] = range_;
687  cfg["min_range"] = min_range_;
688  cfg["max_range"] = max_range_;
689  cfg["alignment"] = alignment_str();
690  cfg["damage"] = damage_;
691  cfg["number"] = num_attacks_;
692  cfg["attack_weight"] = attack_weight_;
693  cfg["defense_weight"] = defense_weight_;
694  cfg["accuracy"] = accuracy_;
695  cfg["movement_used"] = movement_used_;
696  cfg["attacks_used"] = attacks_used_;
697  cfg["parry"] = parry_;
698  cfg.add_child("specials", specials_);
699 }
static lg::log_domain log_unit("unit")
#define ERR_WML
Definition: attack_type.cpp:49
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:2147
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:86
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: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:1858
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
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: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:2142
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:51
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
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:1330
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
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
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 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)
int stoi(std::string_view str)
Same interface as std::stoi and meant as a drop in replacement, except:
Definition: charconv.hpp:155
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