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