The Battle for Wesnoth  1.19.8+dev
attack_type.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
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::vector<std::string> filter_special = utils::split(filter["special"]);
121  const std::vector<std::string> filter_special_id = utils::split(filter["special_id"]);
122  const std::vector<std::string> filter_special_type = utils::split(filter["special_type"]);
123  const std::vector<std::string> filter_special_active = utils::split(filter["special_active"]);
124  const std::vector<std::string> filter_special_id_active = utils::split(filter["special_id_active"]);
125  const std::vector<std::string> filter_special_type_active = utils::split(filter["special_type_active"]);
126  const std::string filter_formula = filter["formula"];
127 
128  if (!filter_min_range.empty() && !in_ranges(attack.min_range(), utils::parse_ranges_int(filter_min_range)))
129  return false;
130 
131  if (!filter_max_range.empty() && !in_ranges(attack.max_range(), utils::parse_ranges_int(filter_max_range)))
132  return false;
133 
134  if ( !filter_range.empty() && filter_range.count(attack.range()) == 0 )
135  return false;
136 
137  if ( !filter_damage.empty() && !in_ranges(attack.damage(), utils::parse_ranges_unsigned(filter_damage)) )
138  return false;
139 
140  if (!filter_attacks.empty() && !in_ranges(attack.num_attacks(), utils::parse_ranges_unsigned(filter_attacks)))
141  return false;
142 
143  if (!filter_accuracy.empty() && !in_ranges(attack.accuracy(), utils::parse_ranges_int(filter_accuracy)))
144  return false;
145 
146  if (!filter_parry.empty() && !in_ranges(attack.parry(), utils::parse_ranges_int(filter_parry)))
147  return false;
148 
149  if (!filter_movement.empty() && !in_ranges(attack.movement_used(), utils::parse_ranges_unsigned(filter_movement)))
150  return false;
151 
152  if (!filter_attacks_used.empty() && !in_ranges(attack.attacks_used(), utils::parse_ranges_unsigned(filter_attacks_used)))
153  return false;
154 
155  if(!filter_alignment.empty() && filter_alignment.count(attack.alignment_str()) == 0)
156  return false;
157 
158  if ( !filter_name.empty() && filter_name.count(attack.id()) == 0)
159  return false;
160 
161  if (!filter_type.empty()){
162  // Although there's a general guard against infinite recursion, the "damage_type" special
163  // should always use the base type of the weapon. Otherwise it will flip-flop between the
164  // special being active or inactive based on whether ATTACK_RECURSION_LIMIT is even or odd;
165  // without this it will also behave differently when calculating resistance_against.
166  if(check_if_recursion == "damage_type"){
167  if (filter_type.count(attack.type()) == 0){
168  return false;
169  }
170  } else {
171  //if the type is different from "damage_type" then damage_type() can be called for safe checking.
172  if (filter_type.count(attack.effective_damage_type().first) == 0){
173  return false;
174  }
175  }
176  }
177 
178  if ( !filter_base_type.empty() && filter_base_type.count(attack.type()) == 0 )
179  return false;
180 
181  if(!filter_special.empty()) {
182  deprecated_message("special=", DEP_LEVEL::PREEMPTIVE, {1, 17, 0}, "Please use special_id or special_type instead");
183  bool found = false;
184  for(auto& special : filter_special) {
185  if(attack.has_special(special, true)) {
186  found = true;
187  break;
188  }
189  }
190  if(!found) {
191  return false;
192  }
193  }
194  if(!filter_special_id.empty()) {
195  bool found = false;
196  for(auto& special : filter_special_id) {
197  if(attack.has_special(special, true, true, false)) {
198  found = true;
199  break;
200  }
201  }
202  if(!found) {
203  return false;
204  }
205  }
206 
207  if(!filter_special_active.empty()) {
208  deprecated_message("special_active=", DEP_LEVEL::PREEMPTIVE, {1, 17, 0}, "Please use special_id_active or special_type_active instead");
209  bool found = false;
210  for(auto& special : filter_special_active) {
211  if(attack.has_special(special, false)) {
212  found = true;
213  break;
214  }
215  }
216  if(!found) {
217  return false;
218  }
219  }
220  if(!filter_special_id_active.empty()) {
221  bool found = false;
222  for(auto& special : filter_special_id_active) {
223  if(attack.has_special_or_ability(special, true, false)) {
224  found = true;
225  break;
226  }
227  }
228  if(!found) {
229  return false;
230  }
231  }
232  if(!filter_special_type.empty()) {
233  bool found = false;
234  for(auto& special : filter_special_type) {
235  if(attack.has_special(special, true, false)) {
236  found = true;
237  break;
238  }
239  }
240  if(!found) {
241  return false;
242  }
243  }
244  if(!filter_special_type_active.empty()) {
245  bool found = false;
246  for(auto& special : filter_special_type_active) {
247  if(attack.has_special_or_ability(special, false)) {
248  found = true;
249  break;
250  }
251  }
252  if(!found) {
253  return false;
254  }
255  }
256 
257  //children filter_special are checked later,
258  //but only when the function doesn't return earlier
259  if(auto sub_filter_special = filter.optional_child("filter_special")) {
260  if(!attack.has_special_or_ability_with_filter(*sub_filter_special)) {
261  return false;
262  }
263  }
264 
265  if (!filter_formula.empty()) {
266  try {
267  const wfl::attack_type_callable callable(attack);
268  const wfl::formula form(filter_formula, new wfl::gamestate_function_symbol_table);
269  if(!form.evaluate(callable).as_bool()) {
270  return false;
271  }
272  } catch(const wfl::formula_error& e) {
273  lg::log_to_chat() << "Formula error in weapon filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
274  ERR_WML << "Formula error in weapon filter: " << e.type << " at " << e.filename << ':' << e.line << ")";
275  // Formulae with syntax errors match nothing
276  return false;
277  }
278  }
279 
280  // Passed all tests.
281  return true;
282 }
283 
284 /**
285  * Returns whether or not *this matches the given @a filter.
286  */
287 bool attack_type::matches_filter(const config& filter, const std::string& check_if_recursion) const
288 {
289  // Handle the basic filter.
290  bool matches = matches_simple_filter(*this, filter, check_if_recursion);
291 
292  // Handle [and], [or], and [not] with in-order precedence
293  for(const auto [key, condition_cfg] : filter.all_children_view() )
294  {
295  // Handle [and]
296  if ( key == "and" )
297  matches = matches && matches_filter(condition_cfg, check_if_recursion);
298 
299  // Handle [or]
300  else if ( key == "or" )
301  matches = matches || matches_filter(condition_cfg, check_if_recursion);
302 
303  // Handle [not]
304  else if ( key == "not" )
305  matches = matches && !matches_filter(condition_cfg, check_if_recursion);
306  }
307 
308  return matches;
309 }
310 
312 {
314  while (i != specials_.ordered_end()) {
315  if(special_matches_filter(i->cfg, i->key, filter)) {
316  i = specials_.erase(i);
317  } else {
318  ++i;
319  }
320  }
321 }
322 
323 /**
324  * Modifies *this using the specifications in @a cfg, but only if *this matches
325  * @a cfg viewed as a filter.
326  *
327  * @returns whether or not @c this matched the @a cfg as a filter.
328  */
330 {
331  if( !matches_filter(cfg) )
332  return false;
333 
334  set_changed(true);
335  const std::string& set_name = cfg["set_name"];
336  const t_string& set_desc = cfg["set_description"];
337  const std::string& set_type = cfg["set_type"];
338  const std::string& set_range = cfg["set_range"];
339  const std::string& set_attack_alignment = cfg["set_alignment"];
340  const std::string& set_icon = cfg["set_icon"];
341  const std::string& del_specials = cfg["remove_specials"];
342  auto set_specials = cfg.optional_child("set_specials");
343  const std::string& increase_min_range = cfg["increase_min_range"];
344  const std::string& set_min_range = cfg["set_min_range"];
345  const std::string& increase_max_range = cfg["increase_max_range"];
346  const std::string& set_max_range = cfg["set_max_range"];
347  auto remove_specials = cfg.optional_child("remove_specials");
348  const std::string& increase_damage = cfg["increase_damage"];
349  const std::string& set_damage = cfg["set_damage"];
350  const std::string& increase_attacks = cfg["increase_attacks"];
351  const std::string& set_attacks = cfg["set_attacks"];
352  const std::string& set_attack_weight = cfg["attack_weight"];
353  const std::string& set_defense_weight = cfg["defense_weight"];
354  const std::string& increase_accuracy = cfg["increase_accuracy"];
355  const std::string& set_accuracy = cfg["set_accuracy"];
356  const std::string& increase_parry = cfg["increase_parry"];
357  const std::string& set_parry = cfg["set_parry"];
358  const std::string& increase_movement = cfg["increase_movement_used"];
359  const std::string& set_movement = cfg["set_movement_used"];
360  const std::string& increase_attacks_used = cfg["increase_attacks_used"];
361  const std::string& set_attacks_used = cfg["set_attacks_used"];
362  // NB: If you add something here that requires a description,
363  // it needs to be added to describe_modification as well.
364 
365  if(set_name.empty() == false) {
366  id_ = set_name;
367  }
368 
369  if(set_desc.empty() == false) {
370  description_ = set_desc;
371  }
372 
373  if(set_type.empty() == false) {
374  type_ = set_type;
375  }
376 
377  if(set_range.empty() == false) {
378  range_ = set_range;
379  }
380 
381  if(set_attack_alignment.empty() == false) {
383  }
384 
385  if(set_icon.empty() == false) {
386  icon_ = set_icon;
387  }
388 
389  if(del_specials.empty() == false) {
390  const std::vector<std::string>& dsl = utils::split(del_specials);
391  config new_specials;
392  for(const auto [key, cfg] : specials_.all_children_view()) {
393  std::vector<std::string>::const_iterator found_id =
394  std::find(dsl.begin(), dsl.end(), cfg["id"].str());
395  if (found_id == dsl.end()) {
396  new_specials.add_child(key, cfg);
397  }
398  }
399  specials_ = new_specials;
400  }
401 
402  if(set_specials) {
403  const std::string &mode = set_specials["mode"];
404  if(mode.empty()){
405  deprecated_message("[set_specials]mode=<unset>", DEP_LEVEL::INDEFINITE, "",
406  "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.");
407  // fall through to mode != "append"
408  }
409  if(mode != "append") {
410  specials_.clear();
411  }
412  for(const auto [key, cfg] : set_specials->all_children_view()) {
413  specials_.add_child(key, cfg);
414  }
415  }
416 
417  if(set_min_range.empty() == false) {
419  }
420 
421  if(increase_min_range.empty() == false) {
422  min_range_ = utils::apply_modifier(min_range_, increase_min_range);
423  }
424 
425  if(set_max_range.empty() == false) {
427  }
428 
429  if(increase_max_range.empty() == false) {
430  max_range_ = utils::apply_modifier(max_range_, increase_max_range);
431  }
432 
433  if(remove_specials) {
434  remove_special_by_filter(*remove_specials);
435  }
436 
437  if(set_damage.empty() == false) {
439  if (damage_ < 0) {
440  damage_ = 0;
441  }
442  }
443 
444  if(increase_damage.empty() == false) {
445  damage_ = utils::apply_modifier(damage_, increase_damage);
446  if(damage_ < 0) {
447  damage_ = 0;
448  }
449  }
450 
451  if(set_attacks.empty() == false) {
452  num_attacks_ = std::stoi(set_attacks);
453  if (num_attacks_ < 0) {
454  num_attacks_ = 0;
455  }
456 
457  }
458 
459  if(increase_attacks.empty() == false) {
460  num_attacks_ = utils::apply_modifier(num_attacks_, increase_attacks, 1);
461  }
462 
463  if(set_accuracy.empty() == false) {
465  }
466 
467  if(increase_accuracy.empty() == false) {
468  accuracy_ = utils::apply_modifier(accuracy_, increase_accuracy);
469  }
470 
471  if(set_parry.empty() == false) {
473  }
474 
475  if(increase_parry.empty() == false) {
476  parry_ = utils::apply_modifier(parry_, increase_parry);
477  }
478 
479  if(set_movement.empty() == false) {
480  movement_used_ = std::stoi(set_movement);
481  }
482 
483  if(increase_movement.empty() == false) {
484  movement_used_ = utils::apply_modifier(movement_used_, increase_movement, 1);
485  }
486 
487  if(set_attacks_used.empty() == false) {
489  }
490 
491  if(increase_attacks_used.empty() == false) {
492  attacks_used_ = utils::apply_modifier(attacks_used_, increase_attacks_used, 1);
493  }
494 
495  if(set_attack_weight.empty() == false) {
496  attack_weight_ = lexical_cast_default<double>(set_attack_weight,1.0);
497  }
498 
499  if(set_defense_weight.empty() == false) {
500  defense_weight_ = lexical_cast_default<double>(set_defense_weight,1.0);
501  }
502 
503  return true;
504 }
505 
506 /**
507  * Trimmed down version of apply_modification(), with no modifications actually
508  * made. This can be used to get a description of the modification(s) specified
509  * by @a cfg (if *this matches cfg as a filter).
510  *
511  * If *description is provided, it will be set to a (translated) description
512  * of the modification(s) applied (currently only changes to the number of
513  * strikes, damage, accuracy, and parry are included in this description).
514  *
515  * @returns whether or not @c this matched the @a cfg as a filter.
516  */
517 bool attack_type::describe_modification(const config& cfg,std::string* description)
518 {
519  if( !matches_filter(cfg) )
520  return false;
521 
522  // Did the caller want the description?
523  if(description != nullptr) {
524  const std::string& increase_min_range = cfg["increase_min_range"];
525  const std::string& set_min_range = cfg["set_min_range"];
526  const std::string& increase_max_range = cfg["increase_max_range"];
527  const std::string& set_max_range = cfg["set_max_range"];
528  const std::string& increase_damage = cfg["increase_damage"];
529  const std::string& set_damage = cfg["set_damage"];
530  const std::string& increase_attacks = cfg["increase_attacks"];
531  const std::string& set_attacks = cfg["set_attacks"];
532  const std::string& increase_accuracy = cfg["increase_accuracy"];
533  const std::string& set_accuracy = cfg["set_accuracy"];
534  const std::string& increase_parry = cfg["increase_parry"];
535  const std::string& set_parry = cfg["set_parry"];
536  const std::string& increase_movement = cfg["increase_movement_used"];
537  const std::string& set_movement = cfg["set_movement_used"];
538  const std::string& increase_attacks_used = cfg["increase_attacks_used"];
539  const std::string& set_attacks_used = cfg["set_attacks_used"];
540 
541  std::vector<t_string> desc;
542 
543  if(!set_min_range.empty()) {
544  desc.emplace_back(VGETTEXT(
545  // TRANSLATORS: Current value for WML code set_min_range, documented in https://wiki.wesnoth.org/EffectWML
546  "$number min range",
547  {{"number", set_min_range}}));
548  }
549 
550  if(!increase_min_range.empty()) {
551  desc.emplace_back(VGETTEXT(
552  // TRANSLATORS: Current value for WML code increase_min_range, documented in https://wiki.wesnoth.org/EffectWML
553  "<span color=\"$color\">$number_or_percent</span> min range",
554  {{"number_or_percent", utils::print_modifier(increase_min_range)}, {"color", increase_min_range[0] == '-' ? "#f00" : "#0f0"}}));
555  }
556 
557  if(!set_max_range.empty()) {
558  desc.emplace_back(VGETTEXT(
559  // TRANSLATORS: Current value for WML code set_max_range, documented in https://wiki.wesnoth.org/EffectWML
560  "$number max range",
561  {{"number", set_max_range}}));
562  }
563 
564  if(!increase_max_range.empty()) {
565  desc.emplace_back(VGETTEXT(
566  // TRANSLATORS: Current value for WML code increase_max_range, documented in https://wiki.wesnoth.org/EffectWML
567  "<span color=\"$color\">$number_or_percent</span> max range",
568  {{"number_or_percent", utils::print_modifier(increase_max_range)}, {"color", increase_max_range[0] == '-' ? "#f00" : "#0f0"}}));
569  }
570 
571  if(!increase_damage.empty()) {
572  desc.emplace_back(VNGETTEXT(
573  // TRANSLATORS: Current value for WML code increase_damage, documented in https://wiki.wesnoth.org/EffectWML
574  "<span color=\"$color\">$number_or_percent</span> damage",
575  "<span color=\"$color\">$number_or_percent</span> damage",
576  std::stoi(increase_damage),
577  {{"number_or_percent", utils::print_modifier(increase_damage)}, {"color", increase_damage[0] == '-' ? "#f00" : "#0f0"}}));
578  }
579 
580  if(!set_damage.empty()) {
581  // TRANSLATORS: Current value for WML code set_damage, documented in https://wiki.wesnoth.org/EffectWML
582  desc.emplace_back(VNGETTEXT(
583  "$number damage",
584  "$number damage",
586  {{"number", set_damage}}));
587  }
588 
589  if(!increase_attacks.empty()) {
590  desc.emplace_back(VNGETTEXT(
591  // TRANSLATORS: Current value for WML code increase_attacks, documented in https://wiki.wesnoth.org/EffectWML
592  "<span color=\"$color\">$number_or_percent</span> strike",
593  "<span color=\"$color\">$number_or_percent</span> strikes",
594  std::stoi(increase_attacks),
595  {{"number_or_percent", utils::print_modifier(increase_attacks)}, {"color", increase_attacks[0] == '-' ? "#f00" : "#0f0"}}));
596  }
597 
598  if(!set_attacks.empty()) {
599  desc.emplace_back(VNGETTEXT(
600  // TRANSLATORS: Current value for WML code set_attacks, documented in https://wiki.wesnoth.org/EffectWML
601  "$number strike",
602  "$number strikes",
603  std::stoi(set_attacks),
604  {{"number", set_attacks}}));
605  }
606 
607  if(!set_accuracy.empty()) {
608  desc.emplace_back(VGETTEXT(
609  // TRANSLATORS: Current value for WML code set_accuracy, documented in https://wiki.wesnoth.org/EffectWML
610  "$number| accuracy",
611  {{"number", set_accuracy}}));
612  }
613 
614  if(!increase_accuracy.empty()) {
615  desc.emplace_back(VGETTEXT(
616  // TRANSLATORS: Current value for WML code increase_accuracy, documented in https://wiki.wesnoth.org/EffectWML
617  "<span color=\"$color\">$number_or_percent|%</span> accuracy",
618  {{"number_or_percent", utils::print_modifier(increase_accuracy)}, {"color", increase_accuracy[0] == '-' ? "#f00" : "#0f0"}}));
619  }
620 
621  if(!set_parry.empty()) {
622  desc.emplace_back(VGETTEXT(
623  // TRANSLATORS: Current value for WML code set_parry, documented in https://wiki.wesnoth.org/EffectWML
624  "$number parry",
625  {{"number", set_parry}}));
626  }
627 
628  if(!increase_parry.empty()) {
629  desc.emplace_back(VGETTEXT(
630  // TRANSLATORS: Current value for WML code increase_parry, documented in https://wiki.wesnoth.org/EffectWML
631  "<span color=\"$color\">$number_or_percent</span> parry",
632  {{"number_or_percent", utils::print_modifier(increase_parry)}, {"color", increase_parry[0] == '-' ? "#f00" : "#0f0"}}));
633  }
634 
635  if(!set_movement.empty()) {
636  desc.emplace_back(VNGETTEXT(
637  // TRANSLATORS: Current value for WML code set_movement_used, documented in https://wiki.wesnoth.org/EffectWML
638  "$number movement point",
639  "$number movement points",
640  std::stoi(set_movement),
641  {{"number", set_movement}}));
642  }
643 
644  if(!increase_movement.empty()) {
645  desc.emplace_back(VNGETTEXT(
646  // TRANSLATORS: Current value for WML code increase_movement_used, documented in https://wiki.wesnoth.org/EffectWML
647  "<span color=\"$color\">$number_or_percent</span> movement point",
648  "<span color=\"$color\">$number_or_percent</span> movement points",
649  std::stoi(increase_movement),
650  {{"number_or_percent", utils::print_modifier(increase_movement)}, {"color", increase_movement[0] == '-' ? "#f00" : "#0f0"}}));
651  }
652 
653  if(!set_attacks_used.empty()) {
654  desc.emplace_back(VNGETTEXT(
655  // TRANSLATORS: Current value for WML code set_attacks_used, documented in https://wiki.wesnoth.org/EffectWML
656  "$number attack used",
657  "$number attacks used",
659  {{"number", set_attacks_used}}));
660  }
661 
662  if(!increase_attacks_used.empty()) {
663  desc.emplace_back(VNGETTEXT(
664  // TRANSLATORS: Current value for WML code increase_attacks_used, documented in https://wiki.wesnoth.org/EffectWML
665  "<span color=\"$color\">$number_or_percent</span> attack used",
666  "<span color=\"$color\">$number_or_percent</span> attacks used",
667  std::stoi(increase_attacks_used),
668  {{"number_or_percent", utils::print_modifier(increase_attacks_used)}, {"color", increase_attacks_used[0] == '-' ? "#f00" : "#0f0"}}));
669  }
670 
671  *description = utils::format_conjunct_list("", desc);
672  }
673 
674  return true;
675 }
676 
678 {
679  if(utils::contains(open_queries_, &special)) {
680  return recursion_guard();
681  }
682  return recursion_guard(*this, special);
683 }
684 
686 
688  : parent(weapon.shared_from_this())
689 {
690  parent->open_queries_.emplace_back(&special);
691 }
692 
694 {
695  std::swap(parent, other.parent);
696 }
697 
698 attack_type::recursion_guard::operator bool() const {
699  return bool(parent);
700 }
701 
703 {
704  // This is only intended to move ownership to a longer-living variable. Assigning to an instance that
705  // already has a parent implies that the caller is going to recurse and needs a recursion allocation,
706  // but is accidentally dropping one of the allocations that it already has; hence the asserts.
707  assert(this != &other);
708  assert(!parent);
709  std::swap(parent, other.parent);
710  return *this;
711 }
712 
714 {
715  if(parent) {
716  // As this only expects nested recursion, simply pop the top of the open_queries_ stack
717  // without checking that the top of the stack matches the filter passed to the constructor.
718  assert(!parent->open_queries_.empty());
719  parent->open_queries_.pop_back();
720  }
721 }
722 
723 void attack_type::write(config& cfg) const
724 {
725  cfg["description"] = description_;
726  cfg["name"] = id_;
727  cfg["type"] = type_;
728  cfg["icon"] = icon_;
729  cfg["range"] = range_;
730  cfg["min_range"] = min_range_;
731  cfg["max_range"] = max_range_;
732  cfg["alignment"] = alignment_str();
733  cfg["damage"] = damage_;
734  cfg["number"] = num_attacks_;
735  cfg["attack_weight"] = attack_weight_;
736  cfg["defense_weight"] = defense_weight_;
737  cfg["accuracy"] = accuracy_;
738  cfg["movement_used"] = movement_used_;
739  cfg["attacks_used"] = attacks_used_;
740  cfg["parry"] = parry_;
741  cfg.add_child("specials", specials_);
742 }
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...
recursion_guard & operator=(recursion_guard &&)
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...
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:63
bool has_special(const std::string &special, bool simple_check=false, bool special_id=true, bool special_tags=true) const
Returns whether or not *this has a special with a tag or id equal to special.
Definition: abilities.cpp:838
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
Definition: abilities.cpp:2191
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...
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:2100
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
bool has_special_or_ability(const std::string &special, bool special_id=true, bool special_tags=true) const
used for abilities used like weapon and true specials
Definition: abilities.cpp:1893
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:1323
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:195
static variant evaluate(const const_formula_ptr &f, const formula_callable &variables, formula_debugger *fdb=nullptr, variant default_res=variant(0))
Definition: formula.hpp:40
bool as_bool() const
Returns a boolean state of the variant value.
Definition: variant.cpp: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:1029
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:520
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