The Battle for Wesnoth  1.19.4+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 
50 namespace {
51 /**
52  * Value of attack_type::num_recursion_ at which allocations of further recursion_guards fail. This
53  * value is used per weapon, so if two weapon specials are depending on each other being active then
54  * with ATTACK_RECURSION_LIMIT = 3 the recursion could go 6 levels deep (and then return false on
55  * the 7th call to matches_simple_filter).
56  *
57  * The counter is checked at the start of matches_simple_filter, and even the first level needs an
58  * allocation; setting the limit to zero would make matches_simple_filter always return false.
59  *
60  * With the recursion limit set to 1, the following tests fail; they just need a reasonable depth.
61  * event_test_filter_attack_specials
62  * event_test_filter_attack_opponent_weapon_condition
63  * event_test_filter_attack_student_weapon_condition
64  *
65  * With the limit set to 2, all tests pass, but as the limit only affects cases that would otherwise
66  * lead to a crash, it seems reasonable to leave a little headroom for more complex logic.
67  */
68 constexpr unsigned int ATTACK_RECURSION_LIMIT = 4;
69 };
70 
72  self_loc_(),
73  other_loc_(),
74  is_attacker_(false),
75  other_attack_(nullptr),
76  description_(cfg["description"].t_str()),
77  id_(cfg["name"]),
78  type_(cfg["type"]),
79  icon_(cfg["icon"]),
80  range_(cfg["range"]),
81  min_range_(cfg["min_range"].to_int(1)),
82  max_range_(cfg["max_range"].to_int(1)),
83  alignment_str_(),
84  damage_(cfg["damage"]),
85  num_attacks_(cfg["number"]),
86  attack_weight_(cfg["attack_weight"].to_double(1.0)),
87  defense_weight_(cfg["defense_weight"].to_double(1.0)),
88  accuracy_(cfg["accuracy"]),
89  movement_used_(cfg["movement_used"].to_int(100000)),
90  attacks_used_(cfg["attacks_used"].to_int(1)),
91  parry_(cfg["parry"]),
92  specials_(cfg.child_or_empty("specials")),
93  changed_(true)
94 {
95  if (description_.empty())
97 
98  if(icon_.empty()){
99  if (!id_.empty())
100  icon_ = "attacks/" + id_ + ".png";
101  else
102  icon_ = "attacks/blank-attack.png";
103  }
104  if(cfg.has_attribute("alignment") && (cfg["alignment"] == "neutral" || cfg["alignment"] == "lawful" || cfg["alignment"] == "chaotic" || cfg["alignment"] == "liminal")){
105  alignment_str_ = cfg["alignment"].str();
106  } else if(self_){
108  }
109 }
110 
112 {
113  // pick attack alignment or fall back to unit alignment
114  return (unit_alignments::get_enum(alignment_str_).value_or(self_ ? self_->alignment() : unit_alignments::type::neutral));
115 }
116 
118 {
119  if(accuracy_ == 0 && parry_ == 0) {
120  return "";
121  }
122 
123  std::ostringstream s;
125 
126  if(parry_ != 0) {
127  s << "/" << utils::signed_percent(parry_);
128  }
129 
130  return s.str();
131 }
132 
133 namespace {
134 /**
135  * Print "Recursion limit reached" log messages, including deduplication if the same problem has
136  * already been logged.
137  */
138 void show_recursion_warning(const attack_type& attack, const config& filter) {
139  // This function is only called when the recursion limit has already been reached, meaning the
140  // filter has already been parsed multiple times, so I'm not trying to optimize the performance
141  // of this; it's merely to prevent the logs getting spammed. For example, each of
142  // four_cycle_recursion_branching and event_test_filter_attack_student_weapon_condition only log
143  // 3 unique messages, but without deduplication they'd log 1280 and 392 respectively.
144  static std::vector<std::tuple<std::string, std::string>> already_shown;
145 
146  auto identifier = std::tuple<std::string, std::string>{attack.id(), filter.debug()};
147  if(utils::contains(already_shown, identifier)) {
148  return;
149  }
150 
151  std::string_view filter_text_view = std::get<1>(identifier);
152  utils::trim(filter_text_view);
153  ERR_UT << "Recursion limit reached for weapon '" << attack.id()
154  << "' while checking filter '" << filter_text_view << "'";
155 
156  // Arbitrary limit, just ensuring that having a huge number of specials causing recursion
157  // warnings can't lead to unbounded memory consumption here.
158  if(already_shown.size() > 100) {
159  already_shown.clear();
160  }
161  already_shown.push_back(std::move(identifier));
162 }
163 
164 /**
165  * Returns whether or not *this matches the given @a filter, ignoring the
166  * complexities introduced by [and], [or], and [not].
167  */
168 bool matches_simple_filter(const attack_type& attack, const config& filter, const std::string& check_if_recursion)
169 {
170  //update and check variable_recursion for prevent check special_id/type_active in case of infinite recursion.
172  if(!filter_lock) {
173  show_recursion_warning(attack, filter);
174  return false;
175  }
176 
177  const std::set<std::string> filter_range = utils::split_set(filter["range"].str());
178  const std::string& filter_min_range = filter["min_range"];
179  const std::string& filter_max_range = filter["max_range"];
180  const std::string& filter_damage = filter["damage"];
181  const std::string& filter_attacks = filter["number"];
182  const std::string& filter_accuracy = filter["accuracy"];
183  const std::string& filter_parry = filter["parry"];
184  const std::string& filter_movement = filter["movement_used"];
185  const std::string& filter_attacks_used = filter["attacks_used"];
186  const std::set<std::string> filter_alignment = utils::split_set(filter["alignment"].str());
187  const std::set<std::string> filter_name = utils::split_set(filter["name"].str());
188  const std::set<std::string> filter_type = utils::split_set(filter["type"].str());
189  const std::vector<std::string> filter_special = utils::split(filter["special"]);
190  const std::vector<std::string> filter_special_id = utils::split(filter["special_id"]);
191  const std::vector<std::string> filter_special_type = utils::split(filter["special_type"]);
192  const std::vector<std::string> filter_special_active = utils::split(filter["special_active"]);
193  const std::vector<std::string> filter_special_id_active = utils::split(filter["special_id_active"]);
194  const std::vector<std::string> filter_special_type_active = utils::split(filter["special_type_active"]);
195  const std::string filter_formula = filter["formula"];
196 
197  if (!filter_min_range.empty() && !in_ranges(attack.min_range(), utils::parse_ranges_int(filter_min_range)))
198  return false;
199 
200  if (!filter_max_range.empty() && !in_ranges(attack.max_range(), utils::parse_ranges_int(filter_max_range)))
201  return false;
202 
203  if ( !filter_range.empty() && filter_range.count(attack.range()) == 0 )
204  return false;
205 
206  if ( !filter_damage.empty() && !in_ranges(attack.damage(), utils::parse_ranges_unsigned(filter_damage)) )
207  return false;
208 
209  if (!filter_attacks.empty() && !in_ranges(attack.num_attacks(), utils::parse_ranges_unsigned(filter_attacks)))
210  return false;
211 
212  if (!filter_accuracy.empty() && !in_ranges(attack.accuracy(), utils::parse_ranges_int(filter_accuracy)))
213  return false;
214 
215  if (!filter_parry.empty() && !in_ranges(attack.parry(), utils::parse_ranges_int(filter_parry)))
216  return false;
217 
218  if (!filter_movement.empty() && !in_ranges(attack.movement_used(), utils::parse_ranges_unsigned(filter_movement)))
219  return false;
220 
221  if (!filter_attacks_used.empty() && !in_ranges(attack.attacks_used(), utils::parse_ranges_unsigned(filter_attacks_used)))
222  return false;
223 
224  if(!filter_alignment.empty() && filter_alignment.count(attack.alignment_str()) == 0)
225  return false;
226 
227  if ( !filter_name.empty() && filter_name.count(attack.id()) == 0)
228  return false;
229 
230  if (!filter_type.empty()){
231  // Although there's a general guard against infinite recursion, the "damage_type" special
232  // should always use the base type of the weapon. Otherwise it will flip-flop between the
233  // special being active or inactive based on whether ATTACK_RECURSION_LIMIT is even or odd;
234  // without this it will also behave differently when calculating resistance_against.
235  if(check_if_recursion == "damage_type"){
236  if (filter_type.count(attack.type()) == 0){
237  return false;
238  }
239  } else {
240  //if the type is different from "damage_type" then damage_type() can be called for safe checking.
241  std::pair<std::string, std::string> damage_type = attack.damage_type();
242  if (filter_type.count(damage_type.first) == 0 && filter_type.count(damage_type.second) == 0){
243  return false;
244  }
245  }
246  }
247 
248  if(!filter_special.empty()) {
249  deprecated_message("special=", DEP_LEVEL::PREEMPTIVE, {1, 17, 0}, "Please use special_id or special_type instead");
250  bool found = false;
251  for(auto& special : filter_special) {
252  if(attack.has_special(special, true)) {
253  found = true;
254  break;
255  }
256  }
257  if(!found) {
258  return false;
259  }
260  }
261  if(!filter_special_id.empty()) {
262  bool found = false;
263  for(auto& special : filter_special_id) {
264  if(attack.has_special(special, true, true, false)) {
265  found = true;
266  break;
267  }
268  }
269  if(!found) {
270  return false;
271  }
272  }
273 
274  if(!filter_special_active.empty()) {
275  deprecated_message("special_active=", DEP_LEVEL::PREEMPTIVE, {1, 17, 0}, "Please use special_id_active or special_type_active instead");
276  bool found = false;
277  for(auto& special : filter_special_active) {
278  if(attack.has_special(special, false)) {
279  found = true;
280  break;
281  }
282  }
283  if(!found) {
284  return false;
285  }
286  }
287  if(!filter_special_id_active.empty()) {
288  bool found = false;
289  for(auto& special : filter_special_id_active) {
290  if(attack.has_special_or_ability(special, true, false)) {
291  found = true;
292  break;
293  }
294  }
295  if(!found) {
296  return false;
297  }
298  }
299  if(!filter_special_type.empty()) {
300  bool found = false;
301  for(auto& special : filter_special_type) {
302  if(attack.has_special(special, true, false)) {
303  found = true;
304  break;
305  }
306  }
307  if(!found) {
308  return false;
309  }
310  }
311  if(!filter_special_type_active.empty()) {
312  bool found = false;
313  for(auto& special : filter_special_type_active) {
314  if(attack.has_special_or_ability(special, false)) {
315  found = true;
316  break;
317  }
318  }
319  if(!found) {
320  return false;
321  }
322  }
323 
324  if (!filter_formula.empty()) {
325  try {
326  const wfl::attack_type_callable callable(attack);
327  const wfl::formula form(filter_formula, new wfl::gamestate_function_symbol_table);
328  if(!form.evaluate(callable).as_bool()) {
329  return false;
330  }
331  } catch(const wfl::formula_error& e) {
332  lg::log_to_chat() << "Formula error in weapon filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
333  ERR_WML << "Formula error in weapon filter: " << e.type << " at " << e.filename << ':' << e.line << ")";
334  // Formulae with syntax errors match nothing
335  return false;
336  }
337  }
338 
339  // Passed all tests.
340  return true;
341 }
342 } // anonymous namespace
343 
344 /**
345  * Returns whether or not *this matches the given @a filter.
346  */
347 bool attack_type::matches_filter(const config& filter, const std::string& check_if_recursion) const
348 {
349  // Handle the basic filter.
350  bool matches = matches_simple_filter(*this, filter, check_if_recursion);
351 
352  // Handle [and], [or], and [not] with in-order precedence
353  for(const auto [key, condition_cfg] : filter.all_children_range() )
354  {
355  // Handle [and]
356  if ( key == "and" )
357  matches = matches && matches_filter(condition_cfg, check_if_recursion);
358 
359  // Handle [or]
360  else if ( key == "or" )
361  matches = matches || matches_filter(condition_cfg, check_if_recursion);
362 
363  // Handle [not]
364  else if ( key == "not" )
365  matches = matches && !matches_filter(condition_cfg, check_if_recursion);
366  }
367 
368  return matches;
369 }
370 
371 /**
372  * Modifies *this using the specifications in @a cfg, but only if *this matches
373  * @a cfg viewed as a filter.
374  *
375  * @returns whether or not @c this matched the @a cfg as a filter.
376  */
378 {
379  if( !matches_filter(cfg) )
380  return false;
381 
382  set_changed(true);
383  const std::string& set_name = cfg["set_name"];
384  const t_string& set_desc = cfg["set_description"];
385  const std::string& set_type = cfg["set_type"];
386  const std::string& set_range = cfg["set_range"];
387  const std::string& set_attack_alignment = cfg["set_alignment"];
388  const std::string& set_icon = cfg["set_icon"];
389  const std::string& del_specials = cfg["remove_specials"];
390  auto set_specials = cfg.optional_child("set_specials");
391  const std::string& increase_min_range = cfg["increase_min_range"];
392  const std::string& set_min_range = cfg["set_min_range"];
393  const std::string& increase_max_range = cfg["increase_max_range"];
394  const std::string& set_max_range = cfg["set_max_range"];
395  const std::string& increase_damage = cfg["increase_damage"];
396  const std::string& set_damage = cfg["set_damage"];
397  const std::string& increase_attacks = cfg["increase_attacks"];
398  const std::string& set_attacks = cfg["set_attacks"];
399  const std::string& set_attack_weight = cfg["attack_weight"];
400  const std::string& set_defense_weight = cfg["defense_weight"];
401  const std::string& increase_accuracy = cfg["increase_accuracy"];
402  const std::string& set_accuracy = cfg["set_accuracy"];
403  const std::string& increase_parry = cfg["increase_parry"];
404  const std::string& set_parry = cfg["set_parry"];
405  const std::string& increase_movement = cfg["increase_movement_used"];
406  const std::string& set_movement = cfg["set_movement_used"];
407  const std::string& increase_attacks_used = cfg["increase_attacks_used"];
408  const std::string& set_attacks_used = cfg["set_attacks_used"];
409  // NB: If you add something here that requires a description,
410  // it needs to be added to describe_modification as well.
411 
412  if(set_name.empty() == false) {
413  id_ = set_name;
414  }
415 
416  if(set_desc.empty() == false) {
417  description_ = set_desc;
418  }
419 
420  if(set_type.empty() == false) {
421  type_ = set_type;
422  }
423 
424  if(set_range.empty() == false) {
425  range_ = set_range;
426  }
427 
428  if(set_attack_alignment.empty() == false) {
430  }
431 
432  if(set_icon.empty() == false) {
433  icon_ = set_icon;
434  }
435 
436  if(del_specials.empty() == false) {
437  const std::vector<std::string>& dsl = utils::split(del_specials);
438  config new_specials;
439  for(const auto [key, cfg] : specials_.all_children_range()) {
440  std::vector<std::string>::const_iterator found_id =
441  std::find(dsl.begin(), dsl.end(), cfg["id"].str());
442  if (found_id == dsl.end()) {
443  new_specials.add_child(key, cfg);
444  }
445  }
446  specials_ = new_specials;
447  }
448 
449  if(set_specials) {
450  const std::string &mode = set_specials["mode"];
451  if(mode.empty()){
452  deprecated_message("[set_specials]mode=<unset>", DEP_LEVEL::INDEFINITE, "",
453  "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.");
454  // fall through to mode != "append"
455  }
456  if(mode != "append") {
457  specials_.clear();
458  }
459  for(const auto [key, cfg] : set_specials->all_children_range()) {
460  specials_.add_child(key, cfg);
461  }
462  }
463 
464  if(set_min_range.empty() == false) {
465  min_range_ = std::stoi(set_min_range);
466  }
467 
468  if(increase_min_range.empty() == false) {
469  min_range_ = utils::apply_modifier(min_range_, increase_min_range);
470  }
471 
472  if(set_max_range.empty() == false) {
473  max_range_ = std::stoi(set_max_range);
474  }
475 
476  if(increase_max_range.empty() == false) {
477  max_range_ = utils::apply_modifier(max_range_, increase_max_range);
478  }
479 
480  if(set_damage.empty() == false) {
481  damage_ = std::stoi(set_damage);
482  if (damage_ < 0) {
483  damage_ = 0;
484  }
485  }
486 
487  if(increase_damage.empty() == false) {
488  damage_ = utils::apply_modifier(damage_, increase_damage);
489  if(damage_ < 0) {
490  damage_ = 0;
491  }
492  }
493 
494  if(set_attacks.empty() == false) {
495  num_attacks_ = std::stoi(set_attacks);
496  if (num_attacks_ < 0) {
497  num_attacks_ = 0;
498  }
499 
500  }
501 
502  if(increase_attacks.empty() == false) {
503  num_attacks_ = utils::apply_modifier(num_attacks_, increase_attacks, 1);
504  }
505 
506  if(set_accuracy.empty() == false) {
507  accuracy_ = std::stoi(set_accuracy);
508  }
509 
510  if(increase_accuracy.empty() == false) {
511  accuracy_ = utils::apply_modifier(accuracy_, increase_accuracy);
512  }
513 
514  if(set_parry.empty() == false) {
515  parry_ = std::stoi(set_parry);
516  }
517 
518  if(increase_parry.empty() == false) {
519  parry_ = utils::apply_modifier(parry_, increase_parry);
520  }
521 
522  if(set_movement.empty() == false) {
523  movement_used_ = std::stoi(set_movement);
524  }
525 
526  if(increase_movement.empty() == false) {
527  movement_used_ = utils::apply_modifier(movement_used_, increase_movement, 1);
528  }
529 
530  if(set_attacks_used.empty() == false) {
531  attacks_used_ = std::stoi(set_attacks_used);
532  }
533 
534  if(increase_attacks_used.empty() == false) {
535  attacks_used_ = utils::apply_modifier(attacks_used_, increase_attacks_used, 1);
536  }
537 
538  if(set_attack_weight.empty() == false) {
539  attack_weight_ = lexical_cast_default<double>(set_attack_weight,1.0);
540  }
541 
542  if(set_defense_weight.empty() == false) {
543  defense_weight_ = lexical_cast_default<double>(set_defense_weight,1.0);
544  }
545 
546  return true;
547 }
548 
549 /**
550  * Trimmed down version of apply_modification(), with no modifications actually
551  * made. This can be used to get a description of the modification(s) specified
552  * by @a cfg (if *this matches cfg as a filter).
553  *
554  * If *description is provided, it will be set to a (translated) description
555  * of the modification(s) applied (currently only changes to the number of
556  * strikes, damage, accuracy, and parry are included in this description).
557  *
558  * @returns whether or not @c this matched the @a cfg as a filter.
559  */
560 bool attack_type::describe_modification(const config& cfg,std::string* description)
561 {
562  if( !matches_filter(cfg) )
563  return false;
564 
565  // Did the caller want the description?
566  if(description != nullptr) {
567  const std::string& increase_min_range = cfg["increase_min_range"];
568  const std::string& set_min_range = cfg["set_min_range"];
569  const std::string& increase_max_range = cfg["increase_max_range"];
570  const std::string& set_max_range = cfg["set_max_range"];
571  const std::string& increase_damage = cfg["increase_damage"];
572  const std::string& set_damage = cfg["set_damage"];
573  const std::string& increase_attacks = cfg["increase_attacks"];
574  const std::string& set_attacks = cfg["set_attacks"];
575  const std::string& increase_accuracy = cfg["increase_accuracy"];
576  const std::string& set_accuracy = cfg["set_accuracy"];
577  const std::string& increase_parry = cfg["increase_parry"];
578  const std::string& set_parry = cfg["set_parry"];
579  const std::string& increase_movement = cfg["increase_movement_used"];
580  const std::string& set_movement = cfg["set_movement_used"];
581  const std::string& increase_attacks_used = cfg["increase_attacks_used"];
582  const std::string& set_attacks_used = cfg["set_attacks_used"];
583 
584  std::vector<t_string> desc;
585 
586  if(!set_min_range.empty()) {
587  desc.emplace_back(VGETTEXT(
588  // TRANSLATORS: Current value for WML code set_min_range, documented in https://wiki.wesnoth.org/EffectWML
589  "$number min range",
590  {{"number", set_min_range}}));
591  }
592 
593  if(!increase_min_range.empty()) {
594  desc.emplace_back(VGETTEXT(
595  // TRANSLATORS: Current value for WML code increase_min_range, documented in https://wiki.wesnoth.org/EffectWML
596  "<span color=\"$color\">$number_or_percent</span> min range",
597  {{"number_or_percent", utils::print_modifier(increase_min_range)}, {"color", increase_min_range[0] == '-' ? "#f00" : "#0f0"}}));
598  }
599 
600  if(!set_max_range.empty()) {
601  desc.emplace_back(VGETTEXT(
602  // TRANSLATORS: Current value for WML code set_max_range, documented in https://wiki.wesnoth.org/EffectWML
603  "$number max range",
604  {{"number", set_max_range}}));
605  }
606 
607  if(!increase_max_range.empty()) {
608  desc.emplace_back(VGETTEXT(
609  // TRANSLATORS: Current value for WML code increase_max_range, documented in https://wiki.wesnoth.org/EffectWML
610  "<span color=\"$color\">$number_or_percent</span> max range",
611  {{"number_or_percent", utils::print_modifier(increase_max_range)}, {"color", increase_max_range[0] == '-' ? "#f00" : "#0f0"}}));
612  }
613 
614  if(!increase_damage.empty()) {
615  desc.emplace_back(VNGETTEXT(
616  // TRANSLATORS: Current value for WML code increase_damage, documented in https://wiki.wesnoth.org/EffectWML
617  "<span color=\"$color\">$number_or_percent</span> damage",
618  "<span color=\"$color\">$number_or_percent</span> damage",
619  std::stoi(increase_damage),
620  {{"number_or_percent", utils::print_modifier(increase_damage)}, {"color", increase_damage[0] == '-' ? "#f00" : "#0f0"}}));
621  }
622 
623  if(!set_damage.empty()) {
624  // TRANSLATORS: Current value for WML code set_damage, documented in https://wiki.wesnoth.org/EffectWML
625  desc.emplace_back(VNGETTEXT(
626  "$number damage",
627  "$number damage",
628  std::stoi(set_damage),
629  {{"number", set_damage}}));
630  }
631 
632  if(!increase_attacks.empty()) {
633  desc.emplace_back(VNGETTEXT(
634  // TRANSLATORS: Current value for WML code increase_attacks, documented in https://wiki.wesnoth.org/EffectWML
635  "<span color=\"$color\">$number_or_percent</span> strike",
636  "<span color=\"$color\">$number_or_percent</span> strikes",
637  std::stoi(increase_attacks),
638  {{"number_or_percent", utils::print_modifier(increase_attacks)}, {"color", increase_attacks[0] == '-' ? "#f00" : "#0f0"}}));
639  }
640 
641  if(!set_attacks.empty()) {
642  desc.emplace_back(VNGETTEXT(
643  // TRANSLATORS: Current value for WML code set_attacks, documented in https://wiki.wesnoth.org/EffectWML
644  "$number strike",
645  "$number strikes",
646  std::stoi(set_attacks),
647  {{"number", set_attacks}}));
648  }
649 
650  if(!set_accuracy.empty()) {
651  desc.emplace_back(VGETTEXT(
652  // TRANSLATORS: Current value for WML code set_accuracy, documented in https://wiki.wesnoth.org/EffectWML
653  "$number| accuracy",
654  {{"number", set_accuracy}}));
655  }
656 
657  if(!increase_accuracy.empty()) {
658  desc.emplace_back(VGETTEXT(
659  // TRANSLATORS: Current value for WML code increase_accuracy, documented in https://wiki.wesnoth.org/EffectWML
660  "<span color=\"$color\">$number_or_percent|%</span> accuracy",
661  {{"number_or_percent", utils::print_modifier(increase_accuracy)}, {"color", increase_accuracy[0] == '-' ? "#f00" : "#0f0"}}));
662  }
663 
664  if(!set_parry.empty()) {
665  desc.emplace_back(VGETTEXT(
666  // TRANSLATORS: Current value for WML code set_parry, documented in https://wiki.wesnoth.org/EffectWML
667  "$number parry",
668  {{"number", set_parry}}));
669  }
670 
671  if(!increase_parry.empty()) {
672  desc.emplace_back(VGETTEXT(
673  // TRANSLATORS: Current value for WML code increase_parry, documented in https://wiki.wesnoth.org/EffectWML
674  "<span color=\"$color\">$number_or_percent</span> parry",
675  {{"number_or_percent", utils::print_modifier(increase_parry)}, {"color", increase_parry[0] == '-' ? "#f00" : "#0f0"}}));
676  }
677 
678  if(!set_movement.empty()) {
679  desc.emplace_back(VNGETTEXT(
680  // TRANSLATORS: Current value for WML code set_movement_used, documented in https://wiki.wesnoth.org/EffectWML
681  "$number movement point",
682  "$number movement points",
683  std::stoi(set_movement),
684  {{"number", set_movement}}));
685  }
686 
687  if(!increase_movement.empty()) {
688  desc.emplace_back(VNGETTEXT(
689  // TRANSLATORS: Current value for WML code increase_movement_used, documented in https://wiki.wesnoth.org/EffectWML
690  "<span color=\"$color\">$number_or_percent</span> movement point",
691  "<span color=\"$color\">$number_or_percent</span> movement points",
692  std::stoi(increase_movement),
693  {{"number_or_percent", utils::print_modifier(increase_movement)}, {"color", increase_movement[0] == '-' ? "#f00" : "#0f0"}}));
694  }
695 
696  if(!set_attacks_used.empty()) {
697  desc.emplace_back(VNGETTEXT(
698  // TRANSLATORS: Current value for WML code set_attacks_used, documented in https://wiki.wesnoth.org/EffectWML
699  "$number attack used",
700  "$number attacks used",
701  std::stoi(set_attacks_used),
702  {{"number", set_attacks_used}}));
703  }
704 
705  if(!increase_attacks_used.empty()) {
706  desc.emplace_back(VNGETTEXT(
707  // TRANSLATORS: Current value for WML code increase_attacks_used, documented in https://wiki.wesnoth.org/EffectWML
708  "<span color=\"$color\">$number_or_percent</span> attack used",
709  "<span color=\"$color\">$number_or_percent</span> attacks used",
710  std::stoi(increase_attacks_used),
711  {{"number_or_percent", utils::print_modifier(increase_attacks_used)}, {"color", increase_attacks_used[0] == '-' ? "#f00" : "#0f0"}}));
712  }
713 
714  *description = utils::format_conjunct_list("", desc);
715  }
716 
717  return true;
718 }
719 
721 {
722  if(num_recursion_ < ATTACK_RECURSION_LIMIT) {
723  return recursion_guard(*this);
724  }
725  return recursion_guard();
726 }
727 
729 
731  : parent(weapon.shared_from_this())
732 {
733  weapon.num_recursion_++;
734 }
735 
737 {
738  std::swap(parent, other.parent);
739 }
740 
741 attack_type::recursion_guard::operator bool() const {
742  return bool(parent);
743 }
744 
746 {
747  // This is only intended to move ownership to a longer-living variable. Assigning to an instance that
748  // already has a parent implies that the caller is going to recurse and needs a recursion allocation,
749  // but is accidentally dropping one of the allocations that it already has; hence the asserts.
750  assert(this != &other);
751  assert(!parent);
752  std::swap(parent, other.parent);
753  return *this;
754 }
755 
757 {
758  if(parent) {
759  assert(parent->num_recursion_ > 0);
760  parent->num_recursion_--;
761  }
762 }
763 
764 void attack_type::write(config& cfg) const
765 {
766  cfg["description"] = description_;
767  cfg["name"] = id_;
768  cfg["type"] = type_;
769  cfg["icon"] = icon_;
770  cfg["range"] = range_;
771  cfg["min_range"] = min_range_;
772  cfg["max_range"] = max_range_;
773  cfg["alignment"] = alignment_str_;
774  cfg["damage"] = damage_;
775  cfg["number"] = num_attacks_;
776  cfg["attack_weight"] = attack_weight_;
777  cfg["defense_weight"] = defense_weight_;
778  cfg["accuracy"] = accuracy_;
779  cfg["movement_used"] = movement_used_;
780  cfg["attacks_used"] = attacks_used_;
781  cfg["parry"] = parry_;
782  cfg.add_child("specials", specials_);
783 }
static lg::log_domain log_unit("unit")
#define ERR_WML
Definition: attack_type.cpp:48
#define ERR_UT
Definition: attack_type.cpp:45
static lg::log_domain log_wml("wml")
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 &&)
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.
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:860
int min_range() const
Definition: attack_type.hpp:47
recursion_guard update_variables_recursion() const
Tests which might otherwise cause infinite recursion should call this, check that the returned object...
const std::string & range() const
Definition: attack_type.hpp:46
void set_attacks_used(int value)
int movement_used() const
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
unit_alignments::type alignment() const
Returns alignment specified by alignment_str_ variable If empty or not valid returns the unit's align...
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.
std::string alignment_str_
void set_specials(config value)
Definition: attack_type.hpp:72
unit_const_ptr self_
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)
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
unsigned int num_recursion_
Number of instances of recursion_guard that are currently allocated permission to recurse.
double defense_weight_
std::string id_
double attack_weight_
std::string range_
void set_max_range(int value)
Definition: attack_type.hpp:64
attack_type(const config &cfg)
Definition: attack_type.cpp:71
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:1839
t_string description_
void set_name(const t_string &value)
Definition: attack_type.hpp:58
std::pair< std::string, std::string > damage_type() const
return a modified damage type and/or add a secondary_type for hybrid use if special is active.
Definition: abilities.cpp:1349
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:163
bool has_attribute(config_key_type key) const
Definition: config.cpp:156
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:888
std::string debug() const
Definition: config.cpp:1244
void clear()
Definition: config.cpp:832
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:383
config & add_child(config_key_type key)
Definition: config.cpp:439
bool empty() const
Definition: tstring.hpp:186
void swap(config &lhs, config &rhs)
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1347
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,...)
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:551
std::string egettext(char const *msgid)
Definition: gettext.cpp:429
void trim(std::string_view &s)
std::set< std::string > split_set(std::string_view s, char sep, const int flags)
std::vector< std::pair< int, int > > parse_ranges_int(const std::string &str)
Handles a comma-separated list of inputs to parse_range.
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:83
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)
std::string print_modifier(const std::string &mod)
Add a "+" or replace the "-" par Unicode minus.
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46
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