The Battle for Wesnoth  1.19.17+dev
attack_type.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Handle unit-type specific attributes, animations, advancement.
19  */
20 
21 #include "units/attack_type.hpp"
22 #include "units/types.hpp"
23 #include "units/unit.hpp"
25 #include "formula/formula.hpp"
26 #include "formula/string_utils.hpp"
28 #include "deprecation.hpp"
29 #include "game_version.hpp"
30 
31 #include "log.hpp"
33 #include "serialization/markup.hpp"
34 #include "gettext.hpp"
35 #include "utils/math.hpp"
36 
37 
38 static lg::log_domain log_config("config");
39 #define ERR_CF LOG_STREAM(err, log_config)
40 #define WRN_CF LOG_STREAM(warn, log_config)
41 #define LOG_CONFIG LOG_STREAM(info, log_config)
42 #define DBG_CF LOG_STREAM(debug, log_config)
43 
44 static lg::log_domain log_unit("unit");
45 #define DBG_UT LOG_STREAM(debug, log_unit)
46 #define ERR_UT LOG_STREAM(err, log_unit)
47 
48 static lg::log_domain log_wml("wml");
49 #define ERR_WML LOG_STREAM(err, log_wml)
50 
51 
52 unit_ability_t::unit_ability_t(std::string tag, config cfg, bool inside_attack)
53  : tag_(std::move(tag))
54  , id_(cfg["id"].str())
55  , cfg_(std::move(cfg))
56 {
57  do_compat_fixes(cfg_, inside_attack);
58 }
59 
60 void unit_ability_t::do_compat_fixes(config& cfg, bool inside_attack)
61 {
62  if (!cfg["backstab"].blank()) {
63  deprecated_message("backstab= in weapon specials", DEP_LEVEL::INDEFINITE, "", "Use [filter_opponent] with a formula instead; the code can be found in data/core/macros/ in the WEAPON_SPECIAL_BACKSTAB macro.");
64  }
65  if (cfg["backstab"].to_bool()) {
66  const std::string& backstab_formula = "enemy_of(self, flanker) and not flanker.petrified where flanker = unit_at(direction_from(loc, other.facing))";
67  config& filter_opponent = cfg.child_or_add("filter_opponent");
68  config& filter_opponent2 = filter_opponent.empty() ? filter_opponent : filter_opponent.add_child("and");
69  filter_opponent2["formula"] = backstab_formula;
70  }
71  cfg.remove_attribute("backstab");
72 
73  std::string filter_teacher = inside_attack ? "filter_self" : "filter";
74 
75  if (cfg.has_child("filter_adjacent")) {
76  if (inside_attack) {
77  deprecated_message("[filter_adjacent]in weapon specials in [specials] tags", DEP_LEVEL::INDEFINITE, "", "Use [filter_self][filter_adjacent] instead.");
78  } else {
79  deprecated_message("[filter_adjacent] in abilities", DEP_LEVEL::INDEFINITE, "", "Use [filter][filter_adjacent] instead or other unit filter.");
80  }
81  }
82  if (cfg.has_child("filter_adjacent_location")) {
83  if (inside_attack) {
84  deprecated_message("[filter_adjacent_location]in weapon specials in [specials] tags", DEP_LEVEL::INDEFINITE, "", "Use [filter_self][filter_location][filter_adjacent_location] instead.");
85  } else {
86  deprecated_message("[filter_adjacent_location] in abilities", DEP_LEVEL::INDEFINITE, "", "Use [filter][filter_location][filter_adjacent_location] instead.");
87  }
88  }
89 
90  //These tags are were never supported inside [specials] according to the wiki.
91  for (config& filter_adjacent : cfg.child_range("filter_adjacent")) {
92  if (filter_adjacent["count"].empty()) {
93  //Previously count= behaved differenty in abilities.cpp and in filter.cpp according to the wiki
94  deprecated_message("omitting count= in [filter_adjacent] in abilities", DEP_LEVEL::FOR_REMOVAL, version_info("1.21"), "specify count explicitly");
95  filter_adjacent["count"] = map_location::parse_directions(filter_adjacent["adjacent"]).size();
96  }
97  cfg.child_or_add(filter_teacher).add_child("filter_adjacent", filter_adjacent);
98  }
99  cfg.remove_children("filter_adjacent");
100  for (config& filter_adjacent : cfg.child_range("filter_adjacent_location")) {
101  if (filter_adjacent["count"].empty()) {
102  //Previously count= bahves differenty in abilities.cpp and in filter.cpp according to the wiki
103  deprecated_message("omitting count= in [filter_adjacent_location] in abilities", DEP_LEVEL::FOR_REMOVAL, version_info("1.21"), "specify count explicitly");
104  filter_adjacent["count"] = map_location::parse_directions(filter_adjacent["adjacent"]).size();
105  }
106  cfg.child_or_add(filter_teacher).add_child("filter_location").add_child("filter_adjacent_location", filter_adjacent);
107  }
108  cfg.remove_children("filter_adjacent_location");
109 }
110 
111 void unit_ability_t::parse_vector(const config& abilities_cfg, ability_vector& res, bool inside_attack)
112 {
113  for (auto item : abilities_cfg.all_children_range()) {
114  res.push_back(unit_ability_t::create(item.key, item.cfg, inside_attack));
115  }
116 }
117 
118 ability_vector unit_ability_t::cfg_to_vector(const config& abilities_cfg, bool inside_attack)
119 {
120  ability_vector res;
121  parse_vector(abilities_cfg, res, inside_attack);
122  return res;
123 }
124 
126 {
127  ability_vector res;
128  for (const ability_ptr& p_ab : abs) {
129  if (p_ab->tag() == tag) {
130  res.push_back(p_ab);
131  }
132  }
133  return res;
134 }
135 
137 {
138  ability_vector res;
139  for (const ability_ptr& p_ab : abs) {
140  res.push_back(std::make_shared<unit_ability_t>(*p_ab));
141  }
142  return res;
143 }
144 
146 {
147  config abilities_cfg;
148  for (const auto& item : abilities) {
149  item->write(abilities_cfg);
150  }
151  return abilities_cfg;
152 }
153 
154 
155 void unit_ability_t::write(config& abilities_cfg)
156 {
157  abilities_cfg.add_child(tag(), cfg());
158 }
159 
161  : self_loc_()
162  , other_loc_()
163  , is_attacker_(false)
164  , other_attack_(nullptr)
165  , description_(cfg["description"].t_str())
166  , id_(cfg["name"])
167  , type_(cfg["type"])
168  , icon_(cfg["icon"])
169  , range_(cfg["range"])
170  , min_range_(cfg["min_range"].to_int(1))
171  , max_range_(cfg["max_range"].to_int(1))
172  , alignment_(unit_alignments::get_enum(cfg["alignment"].str()))
173  , damage_(cfg["damage"].to_int())
174  , num_attacks_(cfg["number"].to_int())
175  , attack_weight_(cfg["attack_weight"].to_double(1.0))
176  , defense_weight_(cfg["defense_weight"].to_double(1.0))
177  , accuracy_(cfg["accuracy"].to_int())
178  , movement_used_(cfg["movement_used"].to_int(100000))
179  , attacks_used_(cfg["attacks_used"].to_int(1))
180  , parry_(cfg["parry"].to_int())
181  , specials_()
182  , changed_(true)
183 {
186 
187  if (description_.empty())
189 
190  if(icon_.empty()){
191  if (!id_.empty())
192  icon_ = "attacks/" + id_ + ".png";
193  else
194  icon_ = "attacks/blank-attack.png";
195  }
196 }
197 
199 {
200  if(accuracy_ == 0 && parry_ == 0) {
201  return "";
202  }
203 
204  std::ostringstream s;
206 
207  if(parry_ != 0) {
208  s << "/" << utils::signed_percent(parry_);
209  }
210 
211  return s.str();
212 }
213 
215 {
216  if(accuracy_ == 0 && parry_ == 0) {
217  return "";
218  }
219 
220  std::stringstream tooltip;
221  if (accuracy_) {
222  tooltip << _("Accuracy:") << " " << markup::bold(utils::signed_percent(accuracy_)) << "\n";
223  }
224  if (parry_) {
225  tooltip << _("Parry:") << " " << markup::bold(utils::signed_percent(parry_));
226  }
227 
228  return tooltip.str();
229 }
230 
231 /**
232  * Returns whether or not *this matches the given @a filter, ignoring the
233  * complexities introduced by [and], [or], and [not].
234  */
235 static bool matches_simple_filter(const attack_type & attack, const config & filter, const std::string& check_if_recursion)
236 {
237  const std::set<std::string> filter_range = utils::split_set(filter["range"].str());
238  const std::string& filter_min_range = filter["min_range"];
239  const std::string& filter_max_range = filter["max_range"];
240  const std::string& filter_damage = filter["damage"];
241  const std::string& filter_attacks = filter["number"];
242  const std::string& filter_accuracy = filter["accuracy"];
243  const std::string& filter_parry = filter["parry"];
244  const std::string& filter_movement = filter["movement_used"];
245  const std::string& filter_attacks_used = filter["attacks_used"];
246  const std::set<std::string> filter_alignment = utils::split_set(filter["alignment"].str());
247  const std::set<std::string> filter_name = utils::split_set(filter["name"].str());
248  const std::set<std::string> filter_type = utils::split_set(filter["type"].str());
249  const std::set<std::string> filter_base_type = utils::split_set(filter["base_type"].str());
250  const std::string filter_formula = filter["formula"];
251 
252  if (!filter_min_range.empty() && !in_ranges(attack.min_range(), utils::parse_ranges_int(filter_min_range)))
253  return false;
254 
255  if (!filter_max_range.empty() && !in_ranges(attack.max_range(), utils::parse_ranges_int(filter_max_range)))
256  return false;
257 
258  if ( !filter_range.empty() && filter_range.count(attack.range()) == 0 )
259  return false;
260 
261  if ( !filter_damage.empty() && !in_ranges(attack.damage(), utils::parse_ranges_unsigned(filter_damage)) )
262  return false;
263 
264  if (!filter_attacks.empty() && !in_ranges(attack.num_attacks(), utils::parse_ranges_unsigned(filter_attacks)))
265  return false;
266 
267  if (!filter_accuracy.empty() && !in_ranges(attack.accuracy(), utils::parse_ranges_int(filter_accuracy)))
268  return false;
269 
270  if (!filter_parry.empty() && !in_ranges(attack.parry(), utils::parse_ranges_int(filter_parry)))
271  return false;
272 
273  if (!filter_movement.empty() && !in_ranges(attack.movement_used(), utils::parse_ranges_unsigned(filter_movement)))
274  return false;
275 
276  if (!filter_attacks_used.empty() && !in_ranges(attack.attacks_used(), utils::parse_ranges_unsigned(filter_attacks_used)))
277  return false;
278 
279  if(!filter_alignment.empty() && filter_alignment.count(attack.alignment_str()) == 0)
280  return false;
281 
282  if ( !filter_name.empty() && filter_name.count(attack.id()) == 0)
283  return false;
284 
285  if (!filter_type.empty()){
286  // Although there's a general guard against infinite recursion, the "damage_type" special
287  // should always use the base type of the weapon. Otherwise it will flip-flop between the
288  // special being active or inactive based on whether ATTACK_RECURSION_LIMIT is even or odd;
289  // without this it will also behave differently when calculating resistance_against.
290  if(check_if_recursion == "damage_type"){
291  if (filter_type.count(attack.type()) == 0){
292  return false;
293  }
294  } else {
295  //if the type is different from "damage_type" then damage_type() can be called for safe checking.
296  if (filter_type.count(attack.effective_damage_type().first) == 0){
297  return false;
298  }
299  }
300  }
301 
302  if ( !filter_base_type.empty() && filter_base_type.count(attack.type()) == 0 )
303  return false;
304 
305  if(filter.has_attribute("special")) {
306  deprecated_message("special=", DEP_LEVEL::PREEMPTIVE, {1, 17, 0}, "Please use special_id or special_type instead");
307  }
308 
309  if(filter.has_attribute("special") || filter.has_attribute("special_id") || filter.has_attribute("special_type")) {
310  if(!attack.has_filter_special_or_ability(filter, true)) {
311  return false;
312  }
313  }
314 
315  if(filter.has_attribute("special_active")) {
316  deprecated_message("special_active=", DEP_LEVEL::PREEMPTIVE, {1, 17, 0}, "Please use special_id_active or special_type_active instead");
317  }
318 
319  if(filter.has_attribute("special_active") || filter.has_attribute("special_id_active") || filter.has_attribute("special_type_active")) {
321  return false;
322  }
323  }
324 
325  //children filter_special are checked later,
326  //but only when the function doesn't return earlier
327  if(auto sub_filter_special = filter.optional_child("filter_special")) {
328  if(!attack.has_special_or_ability_with_filter(*sub_filter_special)) {
329  return false;
330  }
331  }
332 
333  if (!filter_formula.empty()) {
334  try {
335  const wfl::attack_type_callable callable(attack);
337  const wfl::formula form(filter_formula, &symbols);
338  if(!form.evaluate(callable).as_bool()) {
339  return false;
340  }
341  } catch(const wfl::formula_error& e) {
342  lg::log_to_chat() << "Formula error in weapon filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
343  ERR_WML << "Formula error in weapon filter: " << e.type << " at " << e.filename << ':' << e.line << ")";
344  // Formulae with syntax errors match nothing
345  return false;
346  }
347  }
348 
349  // Passed all tests.
350  return true;
351 }
352 
353 /**
354  * Returns whether or not *this matches the given @a filter.
355  */
356 bool attack_type::matches_filter(const config& filter, const std::string& check_if_recursion) const
357 {
358  // Handle the basic filter.
359  bool matches = matches_simple_filter(*this, filter, check_if_recursion);
360 
361  // Handle [and], [or], and [not] with in-order precedence
362  for(const auto [key, condition_cfg] : filter.all_children_view() )
363  {
364  // Handle [and]
365  if ( key == "and" )
366  matches = matches && matches_filter(condition_cfg, check_if_recursion);
367 
368  // Handle [or]
369  else if ( key == "or" )
370  matches = matches || matches_filter(condition_cfg, check_if_recursion);
371 
372  // Handle [not]
373  else if ( key == "not" )
374  matches = matches && !matches_filter(condition_cfg, check_if_recursion);
375  }
376 
377  return matches;
378 }
379 
381 {
382  auto i = specials_.begin();
383  while (i != specials_.end()) {
385  i = specials_.erase(i);
386  } else {
387  ++i;
388  }
389  }
390 }
391 
393 {
394  set_changed(true);
395  const config::attribute_value& set_name = cfg["set_name"];
396  const t_string& set_desc = cfg["set_description"].t_str();
397  const config::attribute_value& set_type = cfg["set_type"];
398  const config::attribute_value& set_range = cfg["set_range"];
399  const config::attribute_value& set_attack_alignment = cfg["set_alignment"];
400  const config::attribute_value& set_icon = cfg["set_icon"];
401  const config::attribute_value& del_specials = cfg["remove_specials"];
402  auto set_specials = cfg.optional_child("set_specials");
403  const config::attribute_value& increase_min_range = cfg["increase_min_range"];
404  const config::attribute_value& set_min_range = cfg["set_min_range"];
405  const config::attribute_value& increase_max_range = cfg["increase_max_range"];
406  const config::attribute_value& set_max_range = cfg["set_max_range"];
407  auto remove_specials = cfg.optional_child("remove_specials");
408  const config::attribute_value& increase_damage = cfg["increase_damage"];
409  const config::attribute_value& set_damage = cfg["set_damage"];
410  const config::attribute_value& increase_attacks = cfg["increase_attacks"];
411  const config::attribute_value& set_attacks = cfg["set_attacks"];
412  const config::attribute_value& set_attack_weight = cfg["attack_weight"];
413  const config::attribute_value& set_defense_weight = cfg["defense_weight"];
414  const config::attribute_value& increase_accuracy = cfg["increase_accuracy"];
415  const config::attribute_value& set_accuracy = cfg["set_accuracy"];
416  const config::attribute_value& increase_parry = cfg["increase_parry"];
417  const config::attribute_value& set_parry = cfg["set_parry"];
418  const config::attribute_value& increase_movement = cfg["increase_movement_used"];
419  const config::attribute_value& set_movement = cfg["set_movement_used"];
420  const config::attribute_value& increase_attacks_used = cfg["increase_attacks_used"];
421  const config::attribute_value& set_attacks_used = cfg["set_attacks_used"];
422  // NB: If you add something here that requires a description,
423  // it needs to be added to describe_effect as well.
424 
425  if(set_name.empty() == false) {
426  id_ = set_name;
427  }
428 
429  if(set_desc.empty() == false) {
430  description_ = set_desc;
431  }
432 
433  if(set_type.empty() == false) {
434  type_ = set_type;
435  }
436 
437  if(set_range.empty() == false) {
438  range_ = set_range;
439  }
440 
441  if(set_attack_alignment.empty() == false) {
443  }
444 
445  if(set_icon.empty() == false) {
446  icon_ = set_icon;
447  }
448 
449  if(del_specials.empty() == false) {
450  const std::vector<std::string>& dsl = utils::split(del_specials);
451  ability_vector new_specials;
452  for(ability_ptr& p_ab : specials_) {
453  if(!utils::contains(dsl, p_ab->id())) {
454  new_specials.emplace_back(std::move(p_ab));
455  }
456  }
457  specials_ = new_specials;
458  }
459 
460  if(set_specials) {
461  const std::string &mode = set_specials["mode"];
462  if(mode.empty()){
463  deprecated_message("[set_specials]mode=<unset>", DEP_LEVEL::INDEFINITE, "",
464  "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.");
465  // fall through to mode != "append"
466  }
467  if(mode != "append") {
468  specials_.clear();
469  }
470  // expand and add registry weapon specials
471  config registry_specials = unit_type_data::add_registry_entries(
472  config{"specials_list", set_specials["specials_list"]}, "specials", unit_types.specials());
473  for(const auto [key, cfg] : registry_specials.all_children_view()) {
474  specials_.push_back(unit_ability_t::create(key, cfg, true));
475  }
476 
477  for(const auto [key, cfg] : set_specials->all_children_view()) {
478  specials_.push_back(unit_ability_t::create(key, cfg, true));
479  }
480  }
481 
482  if(set_min_range.empty() == false) {
483  min_range_ = set_min_range.to_int();
484  }
485 
486  if(increase_min_range.empty() == false) {
487  min_range_ = utils::apply_modifier(min_range_, increase_min_range);
488  }
489 
490  if(set_max_range.empty() == false) {
491  max_range_ = set_max_range.to_int();
492  }
493 
494  if(increase_max_range.empty() == false) {
495  max_range_ = utils::apply_modifier(max_range_, increase_max_range);
496  }
497 
498  if(remove_specials) {
499  remove_special_by_filter(*remove_specials);
500  }
501 
502  if(set_damage.empty() == false) {
503  damage_ = set_damage.to_int();
504  if (damage_ < 0) {
505  damage_ = 0;
506  }
507  }
508 
509  if(increase_damage.empty() == false) {
510  damage_ = utils::apply_modifier(damage_, increase_damage);
511  if(damage_ < 0) {
512  damage_ = 0;
513  }
514  }
515 
516  if(set_attacks.empty() == false) {
517  num_attacks_ = set_attacks.to_int();
518  if (num_attacks_ < 0) {
519  num_attacks_ = 0;
520  }
521 
522  }
523 
524  if(increase_attacks.empty() == false) {
525  num_attacks_ = utils::apply_modifier(num_attacks_, increase_attacks, 1);
526  }
527 
528  if(set_accuracy.empty() == false) {
529  accuracy_ = set_accuracy.to_int();
530  }
531 
532  if(increase_accuracy.empty() == false) {
533  accuracy_ = utils::apply_modifier(accuracy_, increase_accuracy);
534  }
535 
536  if(set_parry.empty() == false) {
537  parry_ = set_parry.to_int();
538  }
539 
540  if(increase_parry.empty() == false) {
541  parry_ = utils::apply_modifier(parry_, increase_parry);
542  }
543 
544  if(set_movement.empty() == false) {
545  movement_used_ = set_movement.to_int();
546  }
547 
548  if(increase_movement.empty() == false) {
549  movement_used_ = utils::apply_modifier(movement_used_, increase_movement, 1);
550  }
551 
552  if(set_attacks_used.empty() == false) {
553  attacks_used_ = set_attacks_used.to_int();
554  }
555 
556  if(increase_attacks_used.empty() == false) {
557  attacks_used_ = utils::apply_modifier(attacks_used_, increase_attacks_used, 1);
558  }
559 
560  if(set_attack_weight.empty() == false) {
561  attack_weight_ = set_attack_weight.to_double(1.0);
562  }
563 
564  if(set_defense_weight.empty() == false) {
565  defense_weight_ = set_defense_weight.to_double(1.0);
566  }
567 }
568 
570 {
571  const config::attribute_value& increase_min_range = cfg["increase_min_range"];
572  const config::attribute_value& set_min_range = cfg["set_min_range"];
573  const config::attribute_value& increase_max_range = cfg["increase_max_range"];
574  const config::attribute_value& set_max_range = cfg["set_max_range"];
575  const config::attribute_value& increase_damage = cfg["increase_damage"];
576  const config::attribute_value& set_damage = cfg["set_damage"];
577  const config::attribute_value& increase_attacks = cfg["increase_attacks"];
578  const config::attribute_value& set_attacks = cfg["set_attacks"];
579  const config::attribute_value& increase_accuracy = cfg["increase_accuracy"];
580  const config::attribute_value& set_accuracy = cfg["set_accuracy"];
581  const config::attribute_value& increase_parry = cfg["increase_parry"];
582  const config::attribute_value& set_parry = cfg["set_parry"];
583  const config::attribute_value& increase_movement = cfg["increase_movement_used"];
584  const config::attribute_value& set_movement = cfg["set_movement_used"];
585  const config::attribute_value& increase_attacks_used = cfg["increase_attacks_used"];
586  const config::attribute_value& set_attacks_used = cfg["set_attacks_used"];
587 
588  const auto format_modifier = [](const config::attribute_value& attr) -> utils::string_map {
589  return {{"number_or_percent", utils::print_modifier(attr)}, {"color", attr.to_int() < 0 ? "#f00" : "#0f0"}};
590  };
591 
592  std::vector<t_string> desc;
593 
594  if(!set_min_range.empty()) {
595  desc.emplace_back(VGETTEXT(
596  // TRANSLATORS: Current value for WML code set_min_range, documented in https://wiki.wesnoth.org/EffectWML
597  "$number min range",
598  {{"number", set_min_range.str()}}));
599  }
600 
601  if(!increase_min_range.empty()) {
602  desc.emplace_back(VGETTEXT(
603  // TRANSLATORS: Current value for WML code increase_min_range, documented in https://wiki.wesnoth.org/EffectWML
604  "<span color=\"$color\">$number_or_percent</span> min range",
605  format_modifier(increase_min_range)));
606  }
607 
608  if(!set_max_range.empty()) {
609  desc.emplace_back(VGETTEXT(
610  // TRANSLATORS: Current value for WML code set_max_range, documented in https://wiki.wesnoth.org/EffectWML
611  "$number max range",
612  {{"number", set_max_range.str()}}));
613  }
614 
615  if(!increase_max_range.empty()) {
616  desc.emplace_back(VGETTEXT(
617  // TRANSLATORS: Current value for WML code increase_max_range, documented in https://wiki.wesnoth.org/EffectWML
618  "<span color=\"$color\">$number_or_percent</span> max range",
619  format_modifier(increase_max_range)));
620  }
621 
622  if(!increase_damage.empty()) {
623  desc.emplace_back(VNGETTEXT(
624  // TRANSLATORS: Current value for WML code increase_damage, documented in https://wiki.wesnoth.org/EffectWML
625  "<span color=\"$color\">$number_or_percent</span> damage",
626  "<span color=\"$color\">$number_or_percent</span> damage",
627  increase_damage.to_int(),
628  format_modifier(increase_damage)));
629  }
630 
631  if(!set_damage.empty()) {
632  // TRANSLATORS: Current value for WML code set_damage, documented in https://wiki.wesnoth.org/EffectWML
633  desc.emplace_back(VNGETTEXT(
634  "$number damage",
635  "$number damage",
636  set_damage.to_int(),
637  {{"number", set_damage.str()}}));
638  }
639 
640  if(!increase_attacks.empty()) {
641  desc.emplace_back(VNGETTEXT(
642  // TRANSLATORS: Current value for WML code increase_attacks, documented in https://wiki.wesnoth.org/EffectWML
643  "<span color=\"$color\">$number_or_percent</span> strike",
644  "<span color=\"$color\">$number_or_percent</span> strikes",
645  increase_attacks.to_int(),
646  format_modifier(increase_attacks)));
647  }
648 
649  if(!set_attacks.empty()) {
650  desc.emplace_back(VNGETTEXT(
651  // TRANSLATORS: Current value for WML code set_attacks, documented in https://wiki.wesnoth.org/EffectWML
652  "$number strike",
653  "$number strikes",
654  set_attacks.to_int(),
655  {{"number", set_attacks.str()}}));
656  }
657 
658  if(!set_accuracy.empty()) {
659  desc.emplace_back(VGETTEXT(
660  // TRANSLATORS: Current value for WML code set_accuracy, documented in https://wiki.wesnoth.org/EffectWML
661  "$number| accuracy",
662  {{"number", set_accuracy.str()}}));
663  }
664 
665  if(!increase_accuracy.empty()) {
666  desc.emplace_back(VGETTEXT(
667  // TRANSLATORS: Current value for WML code increase_accuracy, documented in https://wiki.wesnoth.org/EffectWML
668  "<span color=\"$color\">$number_or_percent|%</span> accuracy",
669  format_modifier(increase_accuracy)));
670  }
671 
672  if(!set_parry.empty()) {
673  desc.emplace_back(VGETTEXT(
674  // TRANSLATORS: Current value for WML code set_parry, documented in https://wiki.wesnoth.org/EffectWML
675  "$number parry",
676  {{"number", set_parry.str()}}));
677  }
678 
679  if(!increase_parry.empty()) {
680  desc.emplace_back(VGETTEXT(
681  // TRANSLATORS: Current value for WML code increase_parry, documented in https://wiki.wesnoth.org/EffectWML
682  "<span color=\"$color\">$number_or_percent</span> parry",
683  format_modifier(increase_parry)));
684  }
685 
686  if(!set_movement.empty()) {
687  desc.emplace_back(VNGETTEXT(
688  // TRANSLATORS: Current value for WML code set_movement_used, documented in https://wiki.wesnoth.org/EffectWML
689  "$number movement point",
690  "$number movement points",
691  set_movement.to_int(),
692  {{"number", set_movement.str()}}));
693  }
694 
695  if(!increase_movement.empty()) {
696  desc.emplace_back(VNGETTEXT(
697  // TRANSLATORS: Current value for WML code increase_movement_used, documented in https://wiki.wesnoth.org/EffectWML
698  "<span color=\"$color\">$number_or_percent</span> movement point",
699  "<span color=\"$color\">$number_or_percent</span> movement points",
700  increase_movement.to_int(),
701  format_modifier(increase_movement)));
702  }
703 
704  if(!set_attacks_used.empty()) {
705  desc.emplace_back(VNGETTEXT(
706  // TRANSLATORS: Current value for WML code set_attacks_used, documented in https://wiki.wesnoth.org/EffectWML
707  "$number attack used",
708  "$number attacks used",
709  set_attacks_used.to_int(),
710  {{"number", set_attacks_used.str()}}));
711  }
712 
713  if(!increase_attacks_used.empty()) {
714  desc.emplace_back(VNGETTEXT(
715  // TRANSLATORS: Current value for WML code increase_attacks_used, documented in https://wiki.wesnoth.org/EffectWML
716  "<span color=\"$color\">$number_or_percent</span> attack used",
717  "<span color=\"$color\">$number_or_percent</span> attacks used",
718  increase_attacks_used.to_int(),
719  format_modifier(increase_attacks_used)));
720  }
721 
722  return utils::format_conjunct_list("", desc);
723 }
724 
726 {
727  if(utils::contains(open_queries_, &special)) {
728  return recursion_guard();
729  }
730  return recursion_guard(*this, special);
731 }
732 
734 
736  : parent(weapon.shared_from_this())
737 {
738  parent->open_queries_.emplace_back(&special);
739 }
740 
742 {
743  std::swap(parent, other.parent);
744 }
745 
746 attack_type::recursion_guard::operator bool() const {
747  return bool(parent);
748 }
749 
751 {
752  // This is only intended to move ownership to a longer-living variable. Assigning to an instance that
753  // already has a parent implies that the caller is going to recurse and needs a recursion allocation,
754  // but is accidentally dropping one of the allocations that it already has; hence the asserts.
755  assert(this != &other);
756  assert(!parent);
757  std::swap(parent, other.parent);
758  return *this;
759 }
760 
762 {
763  if(parent) {
764  // As this only expects nested recursion, simply pop the top of the open_queries_ stack
765  // without checking that the top of the stack matches the filter passed to the constructor.
766  assert(!parent->open_queries_.empty());
767  parent->open_queries_.pop_back();
768  }
769 }
770 
772 {
773  cfg["description"] = description_;
774  cfg["name"] = id_;
775  cfg["type"] = type_;
776  cfg["icon"] = icon_;
777  cfg["range"] = range_;
778  cfg["min_range"] = min_range_;
779  cfg["max_range"] = max_range_;
780  cfg["alignment"] = alignment_str();
781  cfg["damage"] = damage_;
782  cfg["number"] = num_attacks_;
783  cfg["attack_weight"] = attack_weight_;
784  cfg["defense_weight"] = defense_weight_;
785  cfg["accuracy"] = accuracy_;
786  cfg["movement_used"] = movement_used_;
787  cfg["attacks_used"] = attacks_used_;
788  cfg["parry"] = parry_;
789  cfg.add_child("specials", specials_cfg());
790 }
static lg::log_domain log_unit("unit")
#define ERR_WML
Definition: attack_type.cpp:49
static lg::log_domain log_wml("wml")
static bool matches_simple_filter(const attack_type &attack, const config &filter, const std::string &check_if_recursion)
Returns whether or not *this matches the given filter, ignoring the complexities introduced by [and],...
static lg::log_domain log_config("config")
std::vector< ability_ptr > ability_vector
Definition: attack_type.hpp:37
Helper similar to std::unique_lock for detecting when calculations such as has_special have entered i...
std::shared_ptr< const attack_type > parent
recursion_guard()
Construct an empty instance, only useful for extending the lifetime of a recursion_guard returned fro...
recursion_guard & operator=(recursion_guard &&) noexcept
std::string alignment_str() const
Returns alignment specified by alignment() for filtering when exist.
void set_min_range(int value)
int min_range() const
Definition: attack_type.hpp:94
const std::string & range() const
Definition: attack_type.hpp:93
void set_attacks_used(int value)
int movement_used() const
bool has_special_or_ability_with_filter(const config &filter) const
check if special matche
Definition: abilities.cpp:2014
void set_accuracy(int value)
const std::string & type() const
Definition: attack_type.hpp:91
int parry() const
Definition: attack_type.hpp:99
std::string accuracy_parry_tooltip() const
std::string accuracy_parry_description() const
bool special_matches_filter(const unit_ability_t &ab, const config &filter) const
Filter a list of abilities or weapon specials.
Definition: abilities.cpp:2009
void apply_effect(const config &cfg)
Applies effect modifications described by cfg.
bool matches_filter(const config &filter, const std::string &check_if_recursion="") const
Returns whether or not *this matches the given filter.
config specials_cfg() const
void set_defense_weight(double value)
int num_attacks() const
void set_changed(bool value)
recursion_guard update_variables_recursion(const config &special) const
Tests which might otherwise cause infinite recursion should call this, check that the returned object...
bool has_filter_special_or_ability(const config &filter, bool simple_check=false) const
check if special matche
Definition: abilities.cpp:1769
std::string type_
std::string icon_
void set_parry(int value)
void set_attack_weight(double value)
void set_damage(int value)
ability_vector specials_
int attacks_used() const
const std::string & id() const
Definition: attack_type.hpp:90
void set_icon(const std::string &value)
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_
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)
utils::optional< unit_alignments::type > alignment_
attack_type(const config &cfg)
void set_type(const std::string &value)
void write(config &cfg) const
int accuracy() const
Definition: attack_type.hpp:98
int max_range() const
Definition: attack_type.hpp:95
void set_range(const std::string &value)
static std::string describe_effect(const config &cfg)
Generates a description of the effect specified by cfg, if applicable.
int damage() const
void set_attack_alignment(const std::string &value)
t_string description_
void set_name(const t_string &value)
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:1225
Variant for storing WML attributes.
bool empty() const
Tests for an attribute that either was never set or was set to "".
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
void remove_attribute(std::string_view key)
Definition: config.cpp:161
config & add_child(std::string_view key)
Definition: config.cpp:435
optional_config_impl< config > optional_child(std::string_view key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:379
config & child_or_add(std::string_view key)
Returns a reference to the first child with the given key.
Definition: config.cpp:400
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:795
child_itors child_range(std::string_view key)
Definition: config.cpp:267
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:857
bool has_child(std::string_view key) const
Determine whether a config has a child or not.
Definition: config.cpp:311
bool empty() const
Definition: config.cpp:822
void remove_children(std::string_view key, const std::function< bool(const config &)> &p={})
Removes all children with tag key for which p returns true.
Definition: config.cpp:633
bool empty() const
Definition: tstring.hpp:199
const std::string & tag() const
Definition: attack_type.hpp:50
static config vector_to_cfg(const ability_vector &abilities)
const config & cfg() const
Definition: attack_type.hpp:52
static void parse_vector(const config &abilities_cfg, ability_vector &res, bool inside_attack)
static ability_vector filter_tag(const ability_vector &vec, const std::string &tag)
unit_ability_t(std::string tag, config cfg, bool inside_attack)
Definition: attack_type.cpp:52
static ability_ptr create(std::string tag, config cfg, bool inside_attack)
Definition: attack_type.hpp:44
static void do_compat_fixes(config &cfg, bool inside_attack)
Definition: attack_type.cpp:60
static ability_vector clone(const ability_vector &vec)
void write(config &abilities_cfg)
static ability_vector cfg_to_vector(const config &abilities_cfg, bool inside_attack)
static config add_registry_entries(const config &base_cfg, const std::string &registry_name, const std::map< std::string, config > &registry)
Definition: types.cpp:1060
const std::map< std::string, config > & specials() const
Definition: types.hpp:409
Represents version numbers.
static variant evaluate(const const_formula_ptr &f, const formula_callable &variables, formula_debugger *fdb=nullptr, variant default_res=variant(0))
Definition: formula.hpp:48
bool as_bool() const
Returns a boolean state of the variant value.
Definition: variant.cpp:313
void swap(config &lhs, config &rhs) noexcept
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1316
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:29
const config * cfg
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
#define VNGETTEXT(msgid, msgid_plural, count,...)
std::size_t i
Definition: function.cpp:1032
Interfaces for manipulating version numbers of engine, add-ons, etc.
static std::string _(const char *str)
Definition: gettext.hpp:97
std::string tooltip
Shown when hovering over an entry in the filter's drop-down list.
Definition: manager.cpp:203
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:85
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:550
std::string bold(Args &&... data)
Applies bold Pango markup to the input.
Definition: markup.hpp:161
std::string tag(std::string_view tag, Args &&... data)
Wraps the given data in the specified tag.
Definition: markup.hpp:45
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)
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:87
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::map< std::string, t_string > string_map
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.
std::shared_ptr< unit_ability_t > ability_ptr
Definition: ptr.hpp:38
static std::vector< direction > parse_directions(const std::string &str)
Parse_directions takes a comma-separated list, and filters out any invalid directions.
Definition: location.cpp:138
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
unit_type_data unit_types
Definition: types.cpp:1513
#define e