The Battle for Wesnoth  1.19.7+dev
abilities.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2024
3  by Dominic Bolin <dominic.bolin@exong.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  * Manage unit-abilities, like heal, cure, and weapon_specials.
19  */
20 
21 #include "display.hpp"
22 #include "display_context.hpp"
23 #include "serialization/markup.hpp"
24 #include "game_board.hpp"
25 #include "game_version.hpp" // for version_info
26 #include "gettext.hpp"
27 #include "lexical_cast.hpp"
28 #include "log.hpp"
29 #include "map/map.hpp"
30 #include "resources.hpp"
31 #include "team.hpp"
32 #include "terrain/filter.hpp"
33 #include "units/unit.hpp"
34 #include "units/abilities.hpp"
35 #include "units/ability_tags.hpp"
36 #include "units/filter.hpp"
37 #include "units/map.hpp"
38 #include "utils/config_filters.hpp"
39 #include "filter_context.hpp"
40 #include "formula/callable_objects.hpp"
41 #include "formula/formula.hpp"
43 #include "units/filter.hpp"
44 #include "deprecation.hpp"
45 #include <utility>
46 
47 
48 
49 static lg::log_domain log_engine("engine");
50 #define ERR_NG LOG_STREAM(err, log_engine)
51 
52 static lg::log_domain log_wml("wml");
53 #define ERR_WML LOG_STREAM(err, log_wml)
54 
55 namespace {
56  class temporary_facing
57  {
58  map_location::direction save_dir_;
59  unit_const_ptr u_;
60  public:
61  temporary_facing(const unit_const_ptr& u, map_location::direction new_dir)
62  : save_dir_(u ? u->facing() : map_location::direction::indeterminate)
63  , u_(u)
64  {
65  if (u_) {
66  u_->set_facing(new_dir);
67  }
68  }
69  ~temporary_facing()
70  {
71  if (u_) {
72  u_->set_facing(save_dir_);
73  }
74  }
75  };
76 }
77 
78 /*
79  *
80  * [abilities]
81  * ...
82  *
83  * [heals]
84  * value=4
85  * max_value=8
86  * cumulative=no
87  * affect_allies=yes
88  * name= _ "heals"
89  * female_name= _ "female^heals"
90  * name_inactive=null
91  * female_name_inactive=null
92  * description= _ "Heals:
93 Allows the unit to heal adjacent friendly units at the beginning of each turn.
94 
95 A unit cared for by a healer may heal up to 4 HP per turn.
96 A poisoned unit cannot be cured of its poison by a healer, and must seek the care of a village or a unit that can cure."
97  * description_inactive=null
98  *
99  * affect_self=yes
100  * [filter] // SUF
101  * ...
102  * [/filter]
103  * [filter_self] // SUF
104  * ...
105  * [/filter_self]
106  * [filter_adjacent] // SUF
107  * adjacent=n,ne,nw
108  * ...
109  * [/filter_adjacent]
110  * [filter_adjacent_location]
111  * adjacent=n,ne,nw
112  * ...
113  * [/filter_adjacent]
114  * [affect_adjacent]
115  * adjacent=n,ne,nw
116  * [filter] // SUF
117  * ...
118  * [/filter]
119  * [/affect_adjacent]
120  * [affect_adjacent]
121  * adjacent=s,se,sw
122  * [filter] // SUF
123  * ...
124  * [/filter]
125  * [/affect_adjacent]
126  *
127  * [/heals]
128  *
129  * ...
130  * [/abilities]
131  *
132  */
133 
134 
135 namespace {
136 
137 const unit_map& get_unit_map()
138 {
139  // Used if we're in the game, including during the construction of the display_context
141  return resources::gameboard->units();
142  }
143 
144  // If we get here, we're in the scenario editor
145  assert(display::get_singleton());
146  return display::get_singleton()->context().units();
147 }
148 
149 const team& get_team(std::size_t side)
150 {
151  // Used if we're in the game, including during the construction of the display_context
153  return resources::gameboard->get_team(side);
154  }
155 
156  // If we get here, we're in the scenario editor
157  assert(display::get_singleton());
158  return display::get_singleton()->context().get_team(side);
159 }
160 
161 /**
162  * Common code for the question "some other unit has an ability, can that ability affect this
163  * unit" - it's not the full answer to that question, just a part of it.
164  *
165  * Although this is called while checking which units' "hides" abilities are active, that's only
166  * for the question "is this unit next to an ally that has a 'camoflages adjacent allies' ability";
167  * not the question "is this unit next to an enemy, therefore visible".
168  */
169 bool affects_side(const config& cfg, std::size_t side, std::size_t other_side)
170 {
171  const team& side_team = get_team(side);
172 
173  if(side == other_side)
174  return cfg["affect_allies"].to_bool(true);
175  if(side_team.is_enemy(other_side))
176  return cfg["affect_enemies"].to_bool();
177  else
178  return cfg["affect_allies"].to_bool();
179 }
180 
181 }
182 
183 bool unit::get_ability_bool(const std::string& tag_name, const map_location& loc) const
184 {
185  for (const config &i : this->abilities_.child_range(tag_name)) {
186  if (get_self_ability_bool(i, tag_name, loc))
187  {
188  return true;
189  }
190  }
191 
192  const unit_map& units = get_unit_map();
193 
194  const auto adjacent = get_adjacent_tiles(loc);
195  for(unsigned i = 0; i < adjacent.size(); ++i) {
196  const unit_map::const_iterator it = units.find(adjacent[i]);
197  if (it == units.end() || it->incapacitated())
198  continue;
199  // Abilities may be tested at locations other than the unit's current
200  // location. This is intentional to allow for less messing with the unit
201  // map during calculations, particularly with regards to movement.
202  // Thus, we need to make sure the adjacent unit (*it) is not actually
203  // ourself.
204  if ( &*it == this )
205  continue;
206  for (const config &j : it->abilities_.child_range(tag_name)) {
207  if (get_adj_ability_bool(j, tag_name, i, loc,*it))
208  {
209  return true;
210  }
211  }
212  }
213 
214 
215  return false;
216 }
217 
218 unit_ability_list unit::get_abilities(const std::string& tag_name, const map_location& loc) const
219 {
220  unit_ability_list res(loc_);
221 
222  for(const config& i : this->abilities_.child_range(tag_name)) {
223  if (get_self_ability_bool(i, tag_name, loc))
224  {
225  res.emplace_back(&i, loc, loc);
226  }
227  }
228 
229  const unit_map& units = get_unit_map();
230 
231  const auto adjacent = get_adjacent_tiles(loc);
232  for(unsigned i = 0; i < adjacent.size(); ++i) {
233  const unit_map::const_iterator it = units.find(adjacent[i]);
234  if (it == units.end() || it->incapacitated())
235  continue;
236  // Abilities may be tested at locations other than the unit's current
237  // location. This is intentional to allow for less messing with the unit
238  // map during calculations, particularly with regards to movement.
239  // Thus, we need to make sure the adjacent unit (*it) is not actually
240  // ourself.
241  if ( &*it == this )
242  continue;
243  for(const config& j : it->abilities_.child_range(tag_name)) {
244  if(get_adj_ability_bool(j, tag_name, i, loc,*it))
245  {
246  res.emplace_back(&j, loc, adjacent[i]);
247  }
248  }
249  }
250 
251 
252  return res;
253 }
254 
255 unit_ability_list unit::get_abilities_weapons(const std::string& tag_name, const map_location& loc, const_attack_ptr weapon, const_attack_ptr opp_weapon) const
256 {
257  unit_ability_list res = get_abilities(tag_name, loc);
258  utils::erase_if(res, [&](const unit_ability& i) {
259  return !ability_affects_weapon(*i.ability_cfg, weapon, false) || !ability_affects_weapon(*i.ability_cfg, opp_weapon, true);
260  });
261  return res;
262 }
263 
264 std::vector<std::string> unit::get_ability_list() const
265 {
266  std::vector<std::string> res;
267 
268  for(const auto [key, cfg] : this->abilities_.all_children_view()) {
269  std::string id = cfg["id"];
270  if (!id.empty())
271  res.push_back(std::move(id));
272  }
273  return res;
274 }
275 
276 
277 namespace {
278  /**
279  * Adds a quadruple consisting of (in order) id, base name,
280  * male or female name as appropriate for the unit, and description.
281  *
282  * @returns Whether name was resolved and quadruple added.
283  */
284  bool add_ability_tooltip(const config& ab, unit_race::GENDER gender, std::vector<std::tuple<std::string, t_string,t_string,t_string>>& res, bool active)
285  {
286  if (active) {
287  const t_string& name = gender_value(ab, gender, "name", "female_name", "name").t_str();
288 
289  if (!name.empty()) {
290  res.emplace_back(
291  ab["id"],
292  ab["name"].t_str(),
293  name,
294  ab["description"].t_str() );
295  return true;
296  }
297  }
298  else
299  {
300  // See if an inactive name was specified.
301  const config::attribute_value& inactive_value =
302  gender_value(ab, gender, "name_inactive",
303  "female_name_inactive", "name_inactive");
304  const t_string& name = !inactive_value.blank() ? inactive_value.t_str() :
305  gender_value(ab, gender, "name", "female_name", "name").t_str();
306 
307  if (!name.empty()) {
308  res.emplace_back(
309  ab["id"],
310  ab.get_or("name_inactive", "name").t_str(),
311  name,
312  ab.get_or("description_inactive", "description").t_str() );
313  return true;
314  }
315  }
316 
317  return false;
318  }
319 }
320 
321 std::vector<std::tuple<std::string, t_string, t_string, t_string>> unit::ability_tooltips() const
322 {
323  std::vector<std::tuple<std::string, t_string,t_string,t_string>> res;
324 
325  for(const auto [_, cfg] : this->abilities_.all_children_view())
326  {
327  add_ability_tooltip(cfg, gender_, res, true);
328  }
329 
330  return res;
331 }
332 
333 std::vector<std::tuple<std::string, t_string, t_string, t_string>> unit::ability_tooltips(boost::dynamic_bitset<>& active_list, const map_location& loc) const
334 {
335  std::vector<std::tuple<std::string, t_string,t_string,t_string>> res;
336  active_list.clear();
337 
338  for(const auto [key, cfg] : this->abilities_.all_children_view())
339  {
340  bool active = ability_active(key, cfg, loc);
341  if (add_ability_tooltip(cfg, gender_, res, active))
342  {
343  active_list.push_back(active);
344  }
345  }
346  return res;
347 }
348 
349 namespace {
350  /**
351  * Print "Recursion limit reached" log messages, including deduplication if the same problem has
352  * already been logged.
353  */
354  void show_recursion_warning(const unit& unit, const config& filter) {
355  // This function is only called when an ability is checked for the second time
356  // filter has already been parsed multiple times, so I'm not trying to optimize the performance
357  // of this; it's merely to prevent the logs getting spammed. For example, each of
358  // four_cycle_recursion_branching and event_test_filter_attack_student_weapon_condition only log
359  // 3 unique messages, but without deduplication they'd log 1280 and 392 respectively.
360  static std::vector<std::tuple<std::string, std::string>> already_shown;
361 
362  auto identifier = std::tuple<std::string, std::string>{unit.id(), filter.debug()};
363  if(utils::contains(already_shown, identifier)) {
364  return;
365  }
366 
367  std::string_view filter_text_view = std::get<1>(identifier);
368  utils::trim(filter_text_view);
369  ERR_NG << "Looped recursion error for unit '" << unit.id()
370  << "' while checking ability '" << filter_text_view << "'";
371 
372  // Arbitrary limit, just ensuring that having a huge number of specials causing recursion
373  // warnings can't lead to unbounded memory consumption here.
374  if(already_shown.size() > 100) {
375  already_shown.clear();
376  }
377  already_shown.push_back(std::move(identifier));
378  }
379 }//anonymous namespace
380 
382 {
383  if(utils::contains(open_queries_, &ability)) {
384  return recursion_guard();
385  }
386  return recursion_guard(*this, ability);
387 }
388 
390 
392  : parent(u.shared_from_this())
393 {
394  u.open_queries_.emplace_back(&ability);
395 }
396 
398 {
399  std::swap(parent, other.parent);
400 }
401 
402 unit::recursion_guard::operator bool() const {
403  return bool(parent);
404 }
405 
407 {
408  assert(this != &other);
409  assert(!parent);
410  std::swap(parent, other.parent);
411  return *this;
412 }
413 
415 {
416  if(parent) {
417  assert(!parent->open_queries_.empty());
418  parent->open_queries_.pop_back();
419  }
420 }
421 
422 bool unit::ability_active(const std::string& ability,const config& cfg,const map_location& loc) const
423 {
424  auto filter_lock = update_variables_recursion(cfg);
425  if(!filter_lock) {
426  show_recursion_warning(*this, cfg);
427  return false;
428  }
429  return ability_active_impl(ability, cfg, loc);
430 }
431 
432 bool unit::ability_active_impl(const std::string& ability,const config& cfg,const map_location& loc) const
433 {
434  bool illuminates = ability == "illuminates";
435 
436  if (auto afilter = cfg.optional_child("filter"))
437  if ( !unit_filter(vconfig(*afilter)).set_use_flat_tod(illuminates).matches(*this, loc) )
438  return false;
439 
440  const auto adjacent = get_adjacent_tiles(loc);
441 
442  const unit_map& units = get_unit_map();
443 
444  for (const config &i : cfg.child_range("filter_adjacent"))
445  {
446  std::size_t count = 0;
447  unit_filter ufilt{ vconfig(i) };
448  ufilt.set_use_flat_tod(illuminates);
449  std::vector<map_location::direction> dirs = map_location::parse_directions(i["adjacent"]);
450  for (const map_location::direction index : dirs)
451  {
453  continue;
454  unit_map::const_iterator unit = units.find(adjacent[static_cast<int>(index)]);
455  if (unit == units.end())
456  return false;
457  if (!ufilt(*unit, *this))
458  return false;
459  if((*this).id() == (*unit).id())
460  return false;
461  if (i.has_attribute("is_enemy")) {
463  if (i["is_enemy"].to_bool() != dc.get_team(unit->side()).is_enemy(side_)) {
464  continue;
465  }
466  }
467  count++;
468  }
469  if (i["count"].empty() && count != dirs.size()) {
470  return false;
471  }
472  if (!in_ranges<int>(count, utils::parse_ranges_unsigned(i["count"].str()))) {
473  return false;
474  }
475  }
476 
477  for (const config &i : cfg.child_range("filter_adjacent_location"))
478  {
479  std::size_t count = 0;
480  terrain_filter adj_filter(vconfig(i), resources::filter_con, false);
481  adj_filter.flatten(illuminates);
482 
483  std::vector<map_location::direction> dirs = map_location::parse_directions(i["adjacent"]);
484  for (const map_location::direction index : dirs)
485  {
487  continue;
488  }
489  if(!adj_filter.match(adjacent[static_cast<int>(index)])) {
490  return false;
491  }
492  count++;
493  }
494  if (i["count"].empty() && count != dirs.size()) {
495  return false;
496  }
497  if (!in_ranges<int>(count, utils::parse_ranges_unsigned(i["count"].str()))) {
498  return false;
499  }
500  }
501  return true;
502 }
503 
504 bool unit::ability_affects_adjacent(const std::string& ability, const config& cfg,int dir,const map_location& loc,const unit& from) const
505 {
506  bool illuminates = ability == "illuminates";
507 
508  assert(dir >=0 && dir <= 5);
509  map_location::direction direction{ dir };
510 
511  for (const config &i : cfg.child_range("affect_adjacent"))
512  {
513  if (i.has_attribute("adjacent")) { //key adjacent defined
514  std::vector<map_location::direction> dirs = map_location::parse_directions(i["adjacent"]);
515  if (std::find(dirs.begin(), dirs.end(), direction) == dirs.end()) {
516  continue;
517  }
518  }
519  if((*this).id() == from.id()){
520  return false;
521  }
522  auto filter = i.optional_child("filter");
523  if (!filter || //filter tag given
524  unit_filter(vconfig(*filter)).set_use_flat_tod(illuminates).matches(*this, loc, from) ) {
525  return true;
526  }
527  }
528  return false;
529 }
530 
531 bool unit::ability_affects_self(const std::string& ability,const config& cfg,const map_location& loc) const
532 {
533  auto filter = cfg.optional_child("filter_self");
534  bool affect_self = cfg["affect_self"].to_bool(true);
535  if (!filter || !affect_self) return affect_self;
536  return unit_filter(vconfig(*filter)).set_use_flat_tod(ability == "illuminates").matches(*this, loc);
537 }
538 
539 bool unit::ability_affects_weapon(const config& cfg, const const_attack_ptr& weapon, bool is_opp) const
540 {
541  const std::string filter_tag_name = is_opp ? "filter_second_weapon" : "filter_weapon";
542  if(!cfg.has_child(filter_tag_name)) {
543  return true;
544  }
545  const config& filter = cfg.mandatory_child(filter_tag_name);
546  if(!weapon) {
547  return false;
548  }
549  attack_type::recursion_guard filter_lock;
550  filter_lock = weapon->update_variables_recursion(cfg);
551  if(!filter_lock) {
552  show_recursion_warning(*this, cfg);
553  return false;
554  }
555  return weapon->matches_filter(filter);
556 }
557 
558 bool unit::has_ability_type(const std::string& ability) const
559 {
560  return !abilities_.child_range(ability).empty();
561 }
562 
563 //these two functions below are used in order to add to the unit
564 //a second set of halo encoded in the abilities (like illuminates halo in [illuminates] ability for example)
565 static void add_string_to_vector(std::vector<std::string>& image_list, const config& cfg, const std::string& attribute_name)
566 {
567  auto ret = std::find(image_list.begin(), image_list.end(), cfg[attribute_name].str());
568  if(ret == image_list.end()){
569  image_list.push_back(cfg[attribute_name].str());
570  }
571 }
572 
573 std::vector<std::string> unit::halo_or_icon_abilities(const std::string& image_type) const
574 {
575  std::vector<std::string> image_list;
576  for(const auto [key, cfg] : abilities_.all_children_view()){
577  bool is_active = ability_active(key, cfg, loc_);
578  //Add halo/overlay to owner of ability if active and affect_self is true.
579  if( !cfg[image_type + "_image"].str().empty() && is_active && ability_affects_self(key, cfg, loc_)){
580  add_string_to_vector(image_list, cfg,image_type + "_image");
581  }
582  //Add halo/overlay to owner of ability who affect adjacent only if active.
583  if(!cfg[image_type + "_image_self"].str().empty() && is_active){
584  add_string_to_vector(image_list, cfg, image_type + "_image_self");
585  }
586  }
587 
588  const unit_map& units = get_unit_map();
589 
590  //Add halo/overlay to unit under abilities owned by adjacent who has [affect_adjacent]
591  //if condition matched
592  const auto adjacent = get_adjacent_tiles(loc_);
593  for(unsigned i = 0; i < adjacent.size(); ++i) {
594  const unit_map::const_iterator it = units.find(adjacent[i]);
595  if (it == units.end() || it->incapacitated())
596  continue;
597  if ( &*it == this )
598  continue;
599  for(const auto [key, cfg] : it->abilities_.all_children_view()) {
600  if(!cfg[image_type + "_image"].str().empty() && affects_side(cfg, side(), it->side()) && it->ability_active(key, cfg, adjacent[i]) && ability_affects_adjacent(key, cfg, i, loc_, *it))
601  {
602  add_string_to_vector(image_list, cfg, image_type + "_image");
603  }
604  }
605  }
606  //rearranges vector alphabetically when its size equals or exceeds two.
607  if(image_list.size() >= 2){
608  std::sort(image_list.begin(), image_list.end());
609  }
610  return image_list;
611 }
612 
614 {
615  if(unit_const_ptr & att = is_attacker_ ? self_ : other_) {
616  callable.add("attacker", wfl::variant(std::make_shared<wfl::unit_callable>(*att)));
617  }
618  if(unit_const_ptr & def = is_attacker_ ? other_ : self_) {
619  callable.add("defender", wfl::variant(std::make_shared<wfl::unit_callable>(*def)));
620  }
621 }
622 
623 namespace {
624 
625 
626 template<typename T, typename TFuncFormula>
627 class get_ability_value_visitor
628 #ifdef USING_BOOST_VARIANT
629  : public boost::static_visitor<T>
630 #endif
631 {
632 public:
633  // Constructor stores the default value.
634  get_ability_value_visitor(T def, const TFuncFormula& formula_handler) : def_(def), formula_handler_(formula_handler) {}
635 
636  T operator()(const utils::monostate&) const { return def_; }
637  T operator()(bool) const { return def_; }
638  T operator()(int i) const { return static_cast<T>(i); }
639  T operator()(unsigned long long u) const { return static_cast<T>(u); }
640  T operator()(double d) const { return static_cast<T>(d); }
641  T operator()(const t_string&) const { return def_; }
642  T operator()(const std::string& s) const
643  {
644  if(s.size() >= 2 && s[0] == '(') {
645  return formula_handler_(s);
646  }
647  return lexical_cast_default<T>(s, def_);
648  }
649 
650 private:
651  const T def_;
652  const TFuncFormula& formula_handler_;
653 };
654 
655 template<typename T, typename TFuncFormula>
656 T get_single_ability_value(const config::attribute_value& v, T def, const unit_ability& ability_info, const map_location& receiver_loc, const const_attack_ptr& att, const TFuncFormula& formula_handler)
657 {
658  return v.apply_visitor(get_ability_value_visitor(def, [&](const std::string& s) {
659 
660  try {
661  const unit_map& units = get_unit_map();
662 
663  auto u_itor = units.find(ability_info.teacher_loc);
664 
665  if(u_itor == units.end()) {
666  return def;
667  }
668  wfl::map_formula_callable callable(std::make_shared<wfl::unit_callable>(*u_itor));
669  if(att) {
670  att->add_formula_context(callable);
671  }
672  if (auto uptr = units.find_unit_ptr(ability_info.student_loc)) {
673  callable.add("student", wfl::variant(std::make_shared<wfl::unit_callable>(*uptr)));
674  }
675  if (auto uptr = units.find_unit_ptr(receiver_loc)) {
676  callable.add("other", wfl::variant(std::make_shared<wfl::unit_callable>(*uptr)));
677  }
678  return formula_handler(wfl::formula(s, new wfl::gamestate_function_symbol_table), callable);
679  } catch(const wfl::formula_error& e) {
680  lg::log_to_chat() << "Formula error in ability or weapon special: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
681  ERR_WML << "Formula error in ability or weapon special: " << e.type << " at " << e.filename << ':' << e.line << ")";
682  return def;
683  }
684  }));
685 }
686 }
687 
688 template<typename TComp>
689 std::pair<int,map_location> unit_ability_list::get_extremum(const std::string& key, int def, const TComp& comp) const
690 {
691  if ( cfgs_.empty() ) {
692  return std::pair(def, map_location());
693  }
694  // The returned location is the best non-cumulative one, if any,
695  // the best absolute cumulative one otherwise.
696  map_location best_loc;
697  bool only_cumulative = true;
698  int abs_max = 0;
699  int flat = 0;
700  int stack = 0;
701  for (const unit_ability& p : cfgs_)
702  {
703  int value = get_single_ability_value((*p.ability_cfg)[key], def, p, loc(), const_attack_ptr(), [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
704  return formula.evaluate(callable).as_int();
705  });
706 
707  if ((*p.ability_cfg)["cumulative"].to_bool()) {
708  stack += value;
709  if (value < 0) value = -value;
710  if (only_cumulative && !comp(value, abs_max)) {
711  abs_max = value;
712  best_loc = p.teacher_loc;
713  }
714  } else if (only_cumulative || comp(flat, value)) {
715  only_cumulative = false;
716  flat = value;
717  best_loc = p.teacher_loc;
718  }
719  }
720  return std::pair(flat + stack, best_loc);
721 }
722 
723 template std::pair<int, map_location> unit_ability_list::get_extremum<std::less<int>>(const std::string& key, int def, const std::less<int>& comp) const;
724 template std::pair<int, map_location> unit_ability_list::get_extremum<std::greater<int>>(const std::string& key, int def, const std::greater<int>& comp) const;
725 
726 /*
727  *
728  * [special]
729  * [swarm]
730  * name= _ "swarm"
731  * name_inactive= _ ""
732  * description= _ ""
733  * description_inactive= _ ""
734  * cumulative=no
735  * apply_to=self #self,opponent,defender,attacker,both
736  * #active_on=defense # or offense; omitting this means "both"
737  *
738  * swarm_attacks_max=4
739  * swarm_attacks_min=2
740  *
741  * [filter_self] // SUF
742  * ...
743  * [/filter_self]
744  * [filter_opponent] // SUF
745  * [filter_attacker] // SUF
746  * [filter_defender] // SUF
747  * [filter_adjacent] // SAUF
748  * [filter_adjacent_location] // SAUF + locs
749  * [/swarm]
750  * [/special]
751  *
752  */
753 
754 namespace {
755 
756  struct special_match
757  {
758  std::string tag_name;
759  const config* cfg;
760  };
761 
762  /**
763  * Gets the children of @parent (which should be the specials for an
764  * attack_type) and places the ones whose tag or id= matches @a id into
765  * @a tag_result and @a id_result.
766  *
767  * If @a just_peeking is set to true, then @a tag_result and @a id_result
768  * are not touched; instead the return value is used to indicate if any
769  * matching children were found.
770  *
771  * @returns true if @a just_peeking is true and a match was found;
772  * false otherwise.
773  */
774  bool get_special_children(std::vector<special_match>& tag_result,
775  std::vector<special_match>& id_result,
776  const config& parent, const std::string& id,
777  bool just_peeking=false) {
778  for(const auto [key, cfg] : parent.all_children_view())
779  {
780  if (just_peeking && (key == id || cfg["id"] == id)) {
781  return true; // peek succeeded; done
782  }
783 
784  if(key == id) {
785  special_match special = { key, &cfg };
786  tag_result.push_back(special);
787  }
788  if(cfg["id"] == id) {
789  special_match special = { key, &cfg };
790  id_result.push_back(special);
791  }
792  }
793  return false;
794  }
795 
796  bool get_special_children_id(std::vector<special_match>& id_result,
797  const config& parent, const std::string& id,
798  bool just_peeking=false) {
799  for(const auto [key, cfg] : parent.all_children_view())
800  {
801  if (just_peeking && (cfg["id"] == id)) {
802  return true; // peek succeeded; done
803  }
804 
805  if(cfg["id"] == id) {
806  special_match special = { key, &cfg };
807  id_result.push_back(special);
808  }
809  }
810  return false;
811  }
812 
813  bool get_special_children_tags(std::vector<special_match>& tag_result,
814  const config& parent, const std::string& id,
815  bool just_peeking=false) {
816  for(const auto [key, cfg] : parent.all_children_view())
817  {
818  if (just_peeking && (key == id)) {
819  return true; // peek succeeded; done
820  }
821 
822  if(key == id) {
823  special_match special = { key, &cfg };
824  tag_result.push_back(special);
825  }
826  }
827  return false;
828  }
829 }
830 
831 /**
832  * Returns whether or not @a *this has a special with a tag or id equal to
833  * @a special. If @a simple_check is set to true, then the check is merely
834  * for being present. Otherwise (the default), the check is for a special
835  * active in the current context (see set_specials_context), including
836  * specials obtained from the opponent's attack.
837  */
838 bool attack_type::has_special(const std::string& special, bool simple_check, bool special_id, bool special_tags) const
839 {
840  {
841  std::vector<special_match> special_tag_matches;
842  std::vector<special_match> special_id_matches;
843  if(special_id && special_tags){
844  if ( get_special_children(special_tag_matches, special_id_matches, specials_, special, simple_check) ) {
845  return true;
846  }
847  } else if(special_id && !special_tags){
848  if ( get_special_children_id(special_id_matches, specials_, special, simple_check) ) {
849  return true;
850  }
851  } else if(!special_id && special_tags){
852  if ( get_special_children_tags(special_tag_matches, specials_, special, simple_check) ) {
853  return true;
854  }
855  }
856  // If we make it to here, then either list.empty() or !simple_check.
857  // So if the list is not empty, then this is not a simple check and
858  // we need to check each special in the list to see if any are active.
859  if(special_tags){
860  for(const special_match& entry : special_tag_matches) {
861  if ( special_active(*entry.cfg, AFFECT_SELF, entry.tag_name) ) {
862  return true;
863  }
864  }
865  }
866  if(special_id){
867  for(const special_match& entry : special_id_matches) {
868  if ( special_active(*entry.cfg, AFFECT_SELF, entry.tag_name) ) {
869  return true;
870  }
871  }
872  }
873  }
874 
875  // Skip checking the opponent's attack?
876  if ( simple_check || !other_attack_ ) {
877  return false;
878  }
879 
880  std::vector<special_match> special_tag_matches;
881  std::vector<special_match> special_id_matches;
882  if(special_id && special_tags){
883  get_special_children(special_tag_matches, special_id_matches, other_attack_->specials_, special);
884  } else if(special_id && !special_tags){
885  get_special_children_id(special_id_matches, other_attack_->specials_, special);
886  } else if(!special_id && special_tags){
887  get_special_children_tags(special_tag_matches, other_attack_->specials_, special);
888  }
889  if(special_tags){
890  for(const special_match& entry : special_tag_matches) {
891  if ( other_attack_->special_active(*entry.cfg, AFFECT_OTHER, entry.tag_name) ) {
892  return true;
893  }
894  }
895  }
896  if(special_id){
897  for(const special_match& entry : special_id_matches) {
898  if ( other_attack_->special_active(*entry.cfg, AFFECT_OTHER, entry.tag_name) ) {
899  return true;
900  }
901  }
902  }
903  return false;
904 }
905 
906 /**
907  * Returns the currently active specials as an ability list, given the current
908  * context (see set_specials_context).
909  */
910 unit_ability_list attack_type::get_specials(const std::string& special) const
911 {
912  //log_scope("get_specials");
913  const map_location loc = self_ ? self_->get_location() : self_loc_;
914  unit_ability_list res(loc);
915 
916  for(const config& i : specials_.child_range(special)) {
917  if(special_active(i, AFFECT_SELF, special)) {
918  res.emplace_back(&i, loc, loc);
919  }
920  }
921 
922  if(!other_attack_) {
923  return res;
924  }
925 
926  for(const config& i : other_attack_->specials_.child_range(special)) {
927  if(other_attack_->special_active(i, AFFECT_OTHER, special)) {
928  res.emplace_back(&i, other_loc_, other_loc_);
929  }
930  }
931  return res;
932 }
933 
934 /**
935  * Returns a vector of names and descriptions for the specials of *this.
936  * Each std::pair in the vector has first = name and second = description.
937  *
938  * This uses either the active or inactive name/description for each special,
939  * based on the current context (see set_specials_context), provided
940  * @a active_list is not nullptr. Otherwise specials are assumed active.
941  * If the appropriate name is empty, the special is skipped.
942  */
943 std::vector<std::pair<t_string, t_string>> attack_type::special_tooltips(
944  boost::dynamic_bitset<>* active_list) const
945 {
946  //log_scope("special_tooltips");
947  std::vector<std::pair<t_string, t_string>> res;
948  if ( active_list )
949  active_list->clear();
950 
951  for(const auto [key, cfg] : specials_.all_children_view())
952  {
953  if ( !active_list || special_active(cfg, AFFECT_EITHER, key) ) {
954  const t_string &name = cfg["name"];
955  if (!name.empty()) {
956  res.emplace_back(name, cfg["description"].t_str() );
957  if ( active_list )
958  active_list->push_back(true);
959  }
960  } else {
961  const t_string& name = cfg.get_or("name_inactive", "name").t_str();
962  if (!name.empty()) {
963  res.emplace_back(name, cfg.get_or("description_inactive", "description").t_str() );
964  active_list->push_back(false);
965  }
966  }
967  }
968  return res;
969 }
970 
971 /**
972  * static used in weapon_specials (bool only_active) and
973  * @return a string and a set_string for the weapon_specials function below.
974  * @param[in,out] temp_string the string modified and returned
975  * @param[in] active the boolean for determine if @name can be added or not
976  * @param[in] name string who must be or not added
977  * @param[in,out] checking_name the reference for checking if @name already added
978  */
979 static void add_name(std::string& temp_string, bool active, const std::string& name, std::set<std::string>& checking_name)
980 {
981  if (active) {
982  if (!name.empty() && checking_name.count(name) == 0) {
983  checking_name.insert(name);
984  if (!temp_string.empty()) temp_string += ", ";
985  temp_string += markup::span_color(font::BUTTON_COLOR, name);
986  }
987  }
988 }
989 
990 /**
991  * Returns a comma-separated string of active names for the specials of *this.
992  * Empty names are skipped.
993  *
994  * Whether or not a special is active depends
995  * on the current context (see set_specials_context)
996  */
997 std::string attack_type::weapon_specials() const
998 {
999  //log_scope("weapon_specials");
1000  std::string res;
1001  for(const auto [key, cfg] : specials_.all_children_view())
1002  {
1003  const bool active = special_active(cfg, AFFECT_EITHER, key);
1004 
1005  const std::string& name =
1006  active
1007  ? cfg["name"].str()
1008  : cfg.get_or("name_inactive", "name").str();
1009  if (!name.empty()) {
1010  if (!res.empty()) {
1011  res += ", ";
1012  }
1013 
1014  if (!active) {
1016  } else {
1017  res += name;
1018  }
1019  }
1020  }
1021  std::string temp_string;
1022  std::set<std::string> checking_name;
1023  weapon_specials_impl_self(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name);
1024  weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, {}, "affect_allies");
1025  if(!temp_string.empty() && !res.empty()) {
1026  temp_string = ", \n" + temp_string;
1027  res += temp_string;
1028  } else if (!temp_string.empty()){
1029  res = temp_string;
1030  }
1031  return res;
1032 }
1033 
1034 static void add_name_list(std::string& temp_string, std::string& weapon_abilities, std::set<std::string>& checking_name, const std::string& from_str)
1035 {
1036  if(!temp_string.empty()){
1037  temp_string = from_str.c_str() + temp_string;
1038  weapon_abilities += (!weapon_abilities.empty() && !temp_string.empty()) ? "\n" : "";
1039  weapon_abilities += temp_string;
1040  temp_string.clear();
1041  checking_name.clear();
1042  }
1043 }
1044 
1045 std::string attack_type::weapon_specials_value(const std::set<std::string>& checking_tags) const
1046 {
1047  //log_scope("weapon_specials_value");
1048  std::string temp_string, weapon_abilities;
1049  std::set<std::string> checking_name;
1050  for(const auto [key, cfg] : specials_.all_children_view()) {
1051  if((checking_tags.count(key) != 0)){
1052  const bool active = special_active(cfg, AFFECT_SELF, key);
1053  add_name(temp_string, active, cfg["name"].str(), checking_name);
1054  }
1055  }
1056  add_name_list(temp_string, weapon_abilities, checking_name, "");
1057 
1058  weapon_specials_impl_self(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, checking_tags, true);
1059  add_name_list(temp_string, weapon_abilities, checking_name, _("Owned: "));
1060 
1061  weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, checking_tags, "affect_allies", true);
1062  // TRANSLATORS: Past-participle of "teach", used for an ability similar to leadership
1063  add_name_list(temp_string, weapon_abilities, checking_name, _("Taught: "));
1064 
1065  weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, checking_tags, "affect_enemies", true);
1066  // TRANSLATORS: Past-participle of "teach", used for an ability similar to leadership
1067  add_name_list(temp_string, weapon_abilities, checking_name, _("Taught: (by an enemy): "));
1068 
1069 
1070  if(other_attack_) {
1071  for(const auto [key, cfg] : other_attack_->specials_.all_children_view()) {
1072  if((checking_tags.count(key) != 0)){
1073  const bool active = other_attack_->special_active(cfg, AFFECT_OTHER, key);
1074  add_name(temp_string, active, cfg["name"].str(), checking_name);
1075  }
1076  }
1077  }
1078  weapon_specials_impl_self(temp_string, other_, other_attack_, shared_from_this(), other_loc_, AFFECT_OTHER, checking_name, checking_tags);
1079  weapon_specials_impl_adj(temp_string, other_, other_attack_, shared_from_this(), other_loc_, AFFECT_OTHER, checking_name, checking_tags);
1080  add_name_list(temp_string, weapon_abilities, checking_name, _("Used by opponent: "));
1081 
1082  return weapon_abilities;
1083 }
1084 
1086  std::string& temp_string,
1087  const unit_const_ptr& self,
1088  const const_attack_ptr& self_attack,
1089  const const_attack_ptr& other_attack,
1090  const map_location& self_loc,
1091  AFFECTS whom,
1092  std::set<std::string>& checking_name,
1093  const std::set<std::string>& checking_tags,
1094  bool leader_bool)
1095 {
1096  if(self){
1097  for(const auto [key, cfg] : self->abilities().all_children_view()){
1098  bool tag_checked = (!checking_tags.empty()) ? (checking_tags.count(key) != 0) : true;
1099  const bool active = tag_checked && check_self_abilities_impl(self_attack, other_attack, cfg, self, self_loc, whom, key, leader_bool);
1100  add_name(temp_string, active, cfg["name"].str(), checking_name);
1101  }
1102  }
1103 }
1104 
1106  std::string& temp_string,
1107  const unit_const_ptr& self,
1108  const const_attack_ptr& self_attack,
1109  const const_attack_ptr& other_attack,
1110  const map_location& self_loc,
1111  AFFECTS whom,
1112  std::set<std::string>& checking_name,
1113  const std::set<std::string>& checking_tags,
1114  const std::string& affect_adjacents,
1115  bool leader_bool)
1116 {
1117  const unit_map& units = get_unit_map();
1118  if(self){
1119  const auto adjacent = get_adjacent_tiles(self_loc);
1120  for(unsigned i = 0; i < adjacent.size(); ++i) {
1121  const unit_map::const_iterator it = units.find(adjacent[i]);
1122  if (it == units.end() || it->incapacitated())
1123  continue;
1124  if(&*it == self.get())
1125  continue;
1126  for(const auto [key, cfg] : it->abilities().all_children_view()) {
1127  bool tag_checked = (!checking_tags.empty()) ? (checking_tags.count(key) != 0) : true;
1128  bool default_bool = (affect_adjacents == "affect_allies") ? true : false;
1129  bool affect_allies = (!affect_adjacents.empty()) ? cfg[affect_adjacents].to_bool(default_bool) : true;
1130  const bool active = tag_checked && check_adj_abilities_impl(self_attack, other_attack, cfg, self, *it, i, self_loc, whom, key, leader_bool) && affect_allies;
1131  add_name(temp_string, active, cfg["name"].str(), checking_name);
1132  }
1133  }
1134  }
1135 }
1136 
1137 
1138 /**
1139  * Sets the context under which specials will be checked for being active.
1140  * This version is appropriate if both units in a combat are known.
1141  * @param[in] weapon The weapon being considered.
1142  * @param[in] self A reference to the unit with this weapon.
1143  * @param[in] other A reference to the other unit in the combat.
1144  * @param[in] unit_loc The location of the unit with this weapon.
1145  * @param[in] other_loc The location of the other unit in the combat.
1146  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
1147  * @param[in] other_attack The attack used by the other unit.
1148  */
1150  const attack_type& weapon,
1151  const_attack_ptr other_attack,
1152  unit_const_ptr self,
1153  unit_const_ptr other,
1154  const map_location& unit_loc,
1155  const map_location& other_loc,
1156  bool attacking)
1157  : parent(weapon.shared_from_this())
1158 {
1159  weapon.self_ = std::move(self);
1160  weapon.other_ = std::move(other);
1161  weapon.self_loc_ = unit_loc;
1162  weapon.other_loc_ = other_loc;
1163  weapon.is_attacker_ = attacking;
1164  weapon.other_attack_ = std::move(other_attack);
1165  weapon.is_for_listing_ = false;
1166 }
1167 
1168 /**
1169  * Sets the context under which specials will be checked for being active.
1170  * This version is appropriate if there is no specific combat being considered.
1171  * @param[in] weapon The weapon being considered.
1172  * @param[in] self A reference to the unit with this weapon.
1173  * @param[in] loc The location of the unit with this weapon.
1174  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
1175  */
1177  : parent(weapon.shared_from_this())
1178 {
1179  weapon.self_ = std::move(self);
1180  weapon.other_ = unit_ptr();
1181  weapon.self_loc_ = loc;
1183  weapon.is_attacker_ = attacking;
1184  weapon.other_attack_ = nullptr;
1185  weapon.is_for_listing_ = false;
1186 }
1187 
1188 /**
1189  * Sets the context under which specials will be checked for being active.
1190  * This version is appropriate for theoretical units of a particular type.
1191  * @param[in] weapon The weapon being considered.
1192  * @param[in] self_type A reference to the type of the unit with this weapon.
1193  * @param[in] loc The location of the unit with this weapon.
1194  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
1195  */
1196 attack_type::specials_context_t::specials_context_t(const attack_type& weapon, const unit_type& /*self_type*/, const map_location& loc, bool attacking)
1197  : parent(weapon.shared_from_this())
1198 {
1199  weapon.self_ = unit_ptr();
1200  weapon.other_ = unit_ptr();
1201  weapon.self_loc_ = loc;
1203  weapon.is_attacker_ = attacking;
1204  weapon.other_attack_ = nullptr;
1205  weapon.is_for_listing_ = false;
1206 }
1207 
1209  : parent(weapon.shared_from_this())
1210 {
1211  weapon.is_for_listing_ = true;
1212  weapon.is_attacker_ = attacking;
1213 }
1214 
1216 {
1217  if(was_moved) return;
1218  parent->self_ = unit_ptr();
1219  parent->other_ = unit_ptr();
1220  parent->self_loc_ = map_location::null_location();
1221  parent->other_loc_ = map_location::null_location();
1222  parent->is_attacker_ = false;
1223  parent->other_attack_ = nullptr;
1224  parent->is_for_listing_ = false;
1225 }
1226 
1228  : parent(other.parent)
1229 {
1230  other.was_moved = true;
1231 }
1232 
1233 /**
1234  * Calculates the number of attacks this weapon has, considering specials.
1235  * This returns two numbers because of the swarm special. The actual number of
1236  * attacks depends on the unit's health and should be:
1237  * min_attacks + (max_attacks - min_attacks) * (current hp) / (max hp)
1238  * c.f. swarm_blows()
1239  */
1240 void attack_type::modified_attacks(unsigned & min_attacks,
1241  unsigned & max_attacks) const
1242 {
1243  // Apply [attacks].
1244  int attacks_value = composite_value(get_specials_and_abilities("attacks"), num_attacks());
1245 
1246  if ( attacks_value < 0 ) {
1247  attacks_value = 0;
1248  ERR_NG << "negative number of strikes after applying weapon specials";
1249  }
1250 
1251  // Apply [swarm].
1252  unit_ability_list swarm_specials = get_specials_and_abilities("swarm");
1253  if ( !swarm_specials.empty() ) {
1254  min_attacks = std::max<int>(0, swarm_specials.highest("swarm_attacks_min").first);
1255  max_attacks = std::max<int>(0, swarm_specials.highest("swarm_attacks_max", attacks_value).first);
1256  } else {
1257  min_attacks = max_attacks = attacks_value;
1258  }
1259 }
1260 
1261 static std::string select_replacement_type(const unit_ability_list& damage_type_list)
1262 {
1263  std::map<std::string, unsigned int> type_count;
1264  unsigned int max = 0;
1265  for(auto& i : damage_type_list) {
1266  const config& c = *i.ability_cfg;
1267  if(c.has_attribute("replacement_type")) {
1268  std::string type = c["replacement_type"].str();
1269  unsigned int count = ++type_count[type];
1270  if((count > max)) {
1271  max = count;
1272  }
1273  }
1274  }
1275 
1276  if (type_count.empty()) return "";
1277 
1278  std::vector<std::string> type_list;
1279  for(auto& i : type_count){
1280  if(i.second == max){
1281  type_list.push_back(i.first);
1282  }
1283  }
1284 
1285  if(type_list.empty()) return "";
1286 
1287  return type_list.front();
1288 }
1289 
1290 static std::string select_alternative_type(const unit_ability_list& damage_type_list, const unit_ability_list& resistance_list, const unit& u)
1291 {
1292  std::map<std::string, int> type_res;
1293  int max_res = std::numeric_limits<int>::min();
1294  for(auto& i : damage_type_list) {
1295  const config& c = *i.ability_cfg;
1296  if(c.has_attribute("alternative_type")) {
1297  std::string type = c["alternative_type"].str();
1298  if(type_res.count(type) == 0){
1299  type_res[type] = u.resistance_value(resistance_list, type);
1300  max_res = std::max(max_res, type_res[type]);
1301  }
1302  }
1303  }
1304 
1305  if (type_res.empty()) return "";
1306 
1307  std::vector<std::string> type_list;
1308  for(auto& i : type_res){
1309  if(i.second == max_res){
1310  type_list.push_back(i.first);
1311  }
1312  }
1313  if(type_list.empty()) return "";
1314 
1315  return type_list.front();
1316 }
1317 
1318 std::string attack_type::select_damage_type(const unit_ability_list& damage_type_list, const std::string& key_name, const unit_ability_list& resistance_list) const
1319 {
1320  bool is_alternative = (key_name == "alternative_type");
1321  if(is_alternative && other_){
1322  return select_alternative_type(damage_type_list, resistance_list, (*other_));
1323  } else if(!is_alternative){
1324  return select_replacement_type(damage_type_list);
1325  }
1326  return "";
1327 }
1328 
1329 /**
1330  * Returns the type of damage inflicted.
1331  */
1332 std::pair<std::string, std::string> attack_type::damage_type() const
1333 {
1334  if(attack_empty()){
1335  return {"", ""};
1336  }
1337  unit_ability_list damage_type_list = get_specials_and_abilities("damage_type");
1338  if(damage_type_list.empty()){
1339  return {type(), ""};
1340  }
1341 
1342  unit_ability_list resistance_list;
1343  if(other_){
1344  resistance_list = (*other_).get_abilities_weapons("resistance", other_loc_, other_attack_, shared_from_this());
1345  }
1346  std::string replacement_type = select_damage_type(damage_type_list, "replacement_type", resistance_list);
1347  std::string alternative_type = select_damage_type(damage_type_list, "alternative_type", resistance_list);
1348  std::string type_damage = replacement_type.empty() ? type() : replacement_type;
1349  if(!alternative_type.empty() && type_damage != alternative_type){
1350  return {type_damage, alternative_type};
1351  }
1352  return {type_damage, ""};
1353 }
1354 
1355 std::set<std::string> attack_type::alternative_damage_types() const
1356 {
1357  unit_ability_list damage_type_list = get_specials_and_abilities("damage_type");
1358  if(damage_type_list.empty()){
1359  return {};
1360  }
1361  std::set<std::string> damage_types;
1362  for(auto& i : damage_type_list) {
1363  const config& c = *i.ability_cfg;
1364  if(c.has_attribute("alternative_type")){
1365  damage_types.insert(c["alternative_type"].str());
1366  }
1367  }
1368 
1369  return damage_types;
1370 }
1371 
1372 
1373 /**
1374  * Returns the damage per attack of this weapon, considering specials.
1375  */
1377 {
1378  int damage_value = composite_value(get_specials_and_abilities("damage"), damage());
1379  return damage_value;
1380 }
1381 
1382 
1383 namespace { // Helpers for attack_type::special_active()
1384 
1385  /**
1386  * Returns whether or not the given special affects the opponent of the unit
1387  * with the special.
1388  * @param[in] special a weapon special WML structure
1389  * @param[in] is_attacker whether or not the unit with the special is the attacker
1390  */
1391  bool special_affects_opponent(const config& special, bool is_attacker)
1392  {
1393  //log_scope("special_affects_opponent");
1394  const std::string& apply_to = special["apply_to"];
1395  if ( apply_to.empty() )
1396  return false;
1397  if ( apply_to == "both" )
1398  return true;
1399  if ( apply_to == "opponent" )
1400  return true;
1401  if ( is_attacker && apply_to == "defender" )
1402  return true;
1403  if ( !is_attacker && apply_to == "attacker" )
1404  return true;
1405  return false;
1406  }
1407 
1408  /**
1409  * Returns whether or not the given special affects the unit with the special.
1410  * @param[in] special a weapon special WML structure
1411  * @param[in] is_attacker whether or not the unit with the special is the attacker
1412  */
1413  bool special_affects_self(const config& special, bool is_attacker)
1414  {
1415  //log_scope("special_affects_self");
1416  const std::string& apply_to = special["apply_to"];
1417  if ( apply_to.empty() )
1418  return true;
1419  if ( apply_to == "both" )
1420  return true;
1421  if ( apply_to == "self" )
1422  return true;
1423  if ( is_attacker && apply_to == "attacker" )
1424  return true;
1425  if ( !is_attacker && apply_to == "defender")
1426  return true;
1427  return false;
1428  }
1429 
1430  /**
1431  * Print "Recursion limit reached" log messages, including deduplication if the same problem has
1432  * already been logged.
1433  */
1434  void show_recursion_warning(const const_attack_ptr& attack, const config& filter) {
1435  // This function is only called when a special is checked for the second time
1436  // filter has already been parsed multiple times, so I'm not trying to optimize the performance
1437  // of this; it's merely to prevent the logs getting spammed. For example, each of
1438  // four_cycle_recursion_branching and event_test_filter_attack_student_weapon_condition only log
1439  // 3 unique messages, but without deduplication they'd log 1280 and 392 respectively.
1440  static std::vector<std::tuple<std::string, std::string>> already_shown;
1441 
1442  auto identifier = std::tuple<std::string, std::string>{attack->id(), filter.debug()};
1443  if(utils::contains(already_shown, identifier)) {
1444  return;
1445  }
1446 
1447  std::string_view filter_text_view = std::get<1>(identifier);
1448  utils::trim(filter_text_view);
1449  ERR_NG << "Looped recursion error for weapon '" << attack->id()
1450  << "' while checking weapon special '" << filter_text_view << "'";
1451 
1452  // Arbitrary limit, just ensuring that having a huge number of specials causing recursion
1453  // warnings can't lead to unbounded memory consumption here.
1454  if(already_shown.size() > 100) {
1455  already_shown.clear();
1456  }
1457  already_shown.push_back(std::move(identifier));
1458  }
1459 
1460  /**
1461  * Determines if a unit/weapon combination matches the specified child
1462  * (normally a [filter_*] child) of the provided filter.
1463  * @param[in] u A unit to filter.
1464  * @param[in] u2 Another unit to filter.
1465  * @param[in] loc The presumed location of @a unit.
1466  * @param[in] weapon The attack_type to filter.
1467  * @param[in] filter The filter containing the child filter to use.
1468  * @param[in] for_listing
1469  * @param[in] child_tag The tag of the child filter to use.
1470  * @param[in] check_if_recursion Parameter used for don't have infinite recusion for some filter attribute.
1471  */
1472  static bool special_unit_matches(unit_const_ptr & u,
1473  unit_const_ptr & u2,
1474  const map_location & loc,
1475  const const_attack_ptr& weapon,
1476  const config & filter,
1477  const bool for_listing,
1478  const std::string & child_tag, const std::string& check_if_recursion)
1479  {
1480  if (for_listing && !loc.valid())
1481  // The special's context was set to ignore this unit, so assume we pass.
1482  // (This is used by reports.cpp to show active specials when the
1483  // opponent is not known. From a player's perspective, the special
1484  // is active, in that it can be used, even though the player might
1485  // need to select an appropriate opponent.)
1486  return true;
1487 
1488  //Add wml filter if "backstab" attribute used.
1489  if (!filter["backstab"].blank() && child_tag == "filter_opponent") {
1490  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.");
1491  }
1492  config cfg = filter;
1493  if(filter["backstab"].to_bool() && child_tag == "filter_opponent"){
1494  const std::string& backstab_formula = "enemy_of(self, flanker) and not flanker.petrified where flanker = unit_at(direction_from(loc, other.facing))";
1495  config& filter_child = cfg.child_or_add("filter_opponent");
1496  if(!filter.has_child("filter_opponent")){
1497  filter_child["formula"] = backstab_formula;
1498  } else {
1499  config filter_opponent;
1500  filter_opponent["formula"] = backstab_formula;
1501  filter_child.add_child("and", filter_opponent);
1502  }
1503  }
1504  const config& filter_backstab = filter["backstab"].to_bool() ? cfg : filter;
1505 
1506  auto filter_child = filter_backstab.optional_child(child_tag);
1507  if ( !filter_child )
1508  // The special does not filter on this unit, so we pass.
1509  return true;
1510 
1511  // If the primary unit doesn't exist, there's nothing to match
1512  if (!u) {
1513  return false;
1514  }
1515 
1516  unit_filter ufilt{vconfig(*filter_child)};
1517 
1518  // If the other unit doesn't exist, try matching without it
1519 
1520 
1521  attack_type::recursion_guard filter_lock;
1522  if (weapon && (filter_child->optional_child("has_attack") || filter_child->optional_child("filter_weapon"))) {
1523  filter_lock = weapon->update_variables_recursion(filter);
1524  if(!filter_lock) {
1525  show_recursion_warning(weapon, filter);
1526  return false;
1527  }
1528  }
1529  // Check for a weapon match.
1530  if (auto filter_weapon = filter_child->optional_child("filter_weapon") ) {
1531  if ( !weapon || !weapon->matches_filter(*filter_weapon, check_if_recursion) )
1532  return false;
1533  }
1534 
1535  // Passed.
1536  // If the other unit doesn't exist, try matching without it
1537  if (!u2) {
1538  return ufilt.matches(*u, loc);
1539  }
1540  return ufilt.matches(*u, loc, *u2);
1541  }
1542 
1543 }//anonymous namespace
1544 
1545 
1546 //The following functions are intended to allow the use in combat of capacities
1547 //identical to special weapons and therefore to be able to use them on adjacent
1548 //units (abilities of type 'aura') or else on all types of weapons even if the
1549 //beneficiary unit does not have a corresponding weapon
1550 //(defense against ranged weapons abilities for a unit that only has melee attacks)
1551 
1552 unit_ability_list attack_type::get_weapon_ability(const std::string& ability) const
1553 {
1554  const map_location loc = self_ ? self_->get_location() : self_loc_;
1555  unit_ability_list abil_list(loc);
1556  if(self_) {
1557  abil_list.append_if((*self_).get_abilities(ability, self_loc_), [&](const unit_ability& i) {
1558  return special_active(*i.ability_cfg, AFFECT_SELF, ability, "filter_student");
1559  });
1560  }
1561 
1562  if(other_) {
1563  abil_list.append_if((*other_).get_abilities(ability, other_loc_), [&](const unit_ability& i) {
1564  return special_active_impl(other_attack_, shared_from_this(), *i.ability_cfg, AFFECT_OTHER, ability, "filter_student");
1565  });
1566  }
1567 
1568  return abil_list;
1569 }
1570 
1572 {
1573  // get all weapon specials of the provided type
1574  unit_ability_list abil_list = get_specials(special);
1575  // append all such weapon specials as abilities as well
1576  abil_list.append(get_weapon_ability(special));
1577  // get a list of specials/"specials as abilities" that may potentially overwrite others
1578  unit_ability_list overwriters = overwrite_special_overwriter(abil_list, special);
1579  if(!abil_list.empty() && !overwriters.empty()){
1580  // remove all abilities that would be overwritten
1581  utils::erase_if(abil_list, [&](const unit_ability& j) {
1582  return (overwrite_special_checking(overwriters, *j.ability_cfg, special));
1583  });
1584  }
1585  return abil_list;
1586 }
1587 
1588 int attack_type::composite_value(const unit_ability_list& abil_list, int base_value) const
1589 {
1590  return unit_abilities::effect(abil_list, base_value, shared_from_this()).get_composite_value();
1591 }
1592 
1593 static bool overwrite_special_affects(const config& special)
1594 {
1595  const std::string& apply_to = special["overwrite_specials"];
1596  return (apply_to == "one_side" || apply_to == "both_sides");
1597 }
1598 
1600 {
1601  //remove element without overwrite_specials key, if list empty after check return empty list.
1602  utils::erase_if(overwriters, [&](const unit_ability& i) {
1603  return (!overwrite_special_affects(*i.ability_cfg));
1604  });
1605 
1606  // if empty, nothing is doing any overwriting
1607  if(overwriters.empty()){
1608  return overwriters;
1609  }
1610 
1611  // if there are specials/"specials as abilities" that could potentially overwrite each other
1612  if(overwriters.size() >= 2){
1613  // sort them by overwrite priority from highest to lowest (default priority is 0)
1614  utils::sort_if(overwriters,[](const unit_ability& i, const unit_ability& j){
1615  auto oi = (*i.ability_cfg).optional_child("overwrite");
1616  double l = 0;
1617  if(oi && !oi["priority"].empty()){
1618  l = oi["priority"].to_double(0);
1619  }
1620  auto oj = (*j.ability_cfg).optional_child("overwrite");
1621  double r = 0;
1622  if(oj && !oj["priority"].empty()){
1623  r = oj["priority"].to_double(0);
1624  }
1625  return l > r;
1626  });
1627  // remove any that need to be overwritten
1628  utils::erase_if(overwriters, [&](const unit_ability& i) {
1629  return (overwrite_special_checking(overwriters, *i.ability_cfg, tag_name));
1630  });
1631  }
1632  return overwriters;
1633 }
1634 
1635 bool attack_type::overwrite_special_checking(unit_ability_list& overwriters, const config& cfg, const std::string& tag_name) const
1636 {
1637  if(overwriters.empty()){
1638  return false;
1639  }
1640 
1641  for(const auto& j : overwriters) {
1642  // whether the overwriter affects a single side
1643  bool affect_side = ((*j.ability_cfg)["overwrite_specials"] == "one_side");
1644  // the overwriter's priority, default of 0
1645  auto overwrite_specials = (*j.ability_cfg).optional_child("overwrite");
1646  double priority = overwrite_specials ? overwrite_specials["priority"].to_double(0) : 0.00;
1647  // the cfg being checked for whether it will be overwritten
1648  auto has_overwrite_specials = cfg.optional_child("overwrite");
1649  // if the overwriter's priority is greater than 0, then true if the cfg being checked has a higher priority
1650  // else true
1651  bool prior = (priority > 0) ? (has_overwrite_specials && has_overwrite_specials["priority"].to_double(0) >= priority) : true;
1652  // true if the cfg being checked affects one or both sides and doesn't have a higher priority, or if it doesn't affect one or both sides
1653  // aka whether the cfg being checked can potentially be overwritten by the current overwriter
1654  bool is_overwritable = (overwrite_special_affects(cfg) && !prior) || !overwrite_special_affects(cfg);
1655  bool one_side_overwritable = true;
1656 
1657  // if the current overwriter affects one side and the cfg being checked can be overwritten by this overwriter
1658  // then check that the current overwriter and the cfg being checked both affect either this unit or its opponent
1659  if(affect_side && is_overwritable){
1660  if(special_affects_self(*j.ability_cfg, is_attacker_)){
1661  one_side_overwritable = special_affects_self(cfg, is_attacker_);
1662  }
1663  else if(special_affects_opponent(*j.ability_cfg, !is_attacker_)){
1664  one_side_overwritable = special_affects_opponent(cfg, !is_attacker_);
1665  }
1666  }
1667 
1668  // check whether the current overwriter is disabled due to a filter
1669  bool special_matches = true;
1670  if(overwrite_specials){
1671  auto overwrite_filter = (*overwrite_specials).optional_child("filter_specials");
1672  if(!overwrite_filter){
1673  overwrite_filter = (*overwrite_specials).optional_child("experimental_filter_specials");
1674  if(overwrite_filter){
1675  deprecated_message("experimental_filter_specials", DEP_LEVEL::INDEFINITE, "", "Use filter_specials instead.");
1676  }
1677  }
1678  if(overwrite_filter && is_overwritable && one_side_overwritable){
1679  special_matches = special_matches_filter(cfg, tag_name, *overwrite_filter);
1680  }
1681  }
1682 
1683  // if the cfg being checked should be overwritten
1684  // and either this unit or its opponent are affected
1685  // and the current overwriter is not disabled due to a filter
1686  if(is_overwritable && one_side_overwritable && special_matches){
1687  return true;
1688  }
1689  }
1690  return false;
1691 }
1692 
1693  /**
1694  * Gets the children of parent (which should be the abilities for an
1695  * attack_type) and places the ones whose tag or id= matches @a id into
1696  * @a tag_result and @a id_result.
1697  * @param tag_result receive the children whose tag matches @a id
1698  * @param id_result receive the children whose id matches @a id
1699  * @param parent the tags whose contain children (abilities here)
1700  * @param id tag or id of child tested
1701  * @param special_id if true, children check by id
1702  * @param special_tags if true, children check by tags
1703  */
1704 static void get_ability_children(std::vector<special_match>& tag_result,
1705  std::vector<special_match>& id_result,
1706  const config& parent, const std::string& id,
1707  bool special_id=true, bool special_tags=true) {
1708  if(special_id && special_tags){
1709  get_special_children(tag_result, id_result, parent, id);
1710  } else if(special_id && !special_tags){
1711  get_special_children_id(id_result, parent, id);
1712  } else if(!special_id && special_tags){
1713  get_special_children_tags(tag_result, parent, id);
1714  }
1715 }
1716 
1717 bool unit::get_self_ability_bool(const config& cfg, const std::string& ability, const map_location& loc) const
1718 {
1719  auto filter_lock = update_variables_recursion(cfg);
1720  if(!filter_lock) {
1721  show_recursion_warning(*this, cfg);
1722  return false;
1723  }
1724  return (ability_active_impl(ability, cfg, loc) && ability_affects_self(ability, cfg, loc));
1725 }
1726 
1727 bool unit::get_adj_ability_bool(const config& cfg, const std::string& ability, int dir, const map_location& loc, const unit& from) const
1728 {
1729  auto filter_lock = from.update_variables_recursion(cfg);
1730  if(!filter_lock) {
1731  show_recursion_warning(from, cfg);
1732  return false;
1733  }
1734  const auto adjacent = get_adjacent_tiles(loc);
1735  return (affects_side(cfg, side(), from.side()) && from.ability_active_impl(ability, cfg, adjacent[dir]) && ability_affects_adjacent(ability, cfg, dir, loc, from));
1736 }
1737 
1738 bool unit::get_self_ability_bool_weapon(const config& special, const std::string& tag_name, const map_location& loc, const const_attack_ptr& weapon, const const_attack_ptr& opp_weapon) const
1739 {
1740  return (get_self_ability_bool(special, tag_name, loc) && ability_affects_weapon(special, weapon, false) && ability_affects_weapon(special, opp_weapon, true));
1741 }
1742 
1743 bool unit::get_adj_ability_bool_weapon(const config& special, const std::string& tag_name, int dir, const map_location& loc, const unit& from, const const_attack_ptr& weapon, const const_attack_ptr& opp_weapon) const
1744 {
1745  return (get_adj_ability_bool(special, tag_name, dir, loc, from) && ability_affects_weapon(special, weapon, false) && ability_affects_weapon(special, opp_weapon, true));
1746 }
1747 
1748 bool attack_type::check_self_abilities(const config& cfg, const std::string& special) const
1749 {
1750  return check_self_abilities_impl(shared_from_this(), other_attack_, cfg, self_, self_loc_, AFFECT_SELF, special, true);
1751 }
1752 
1753 bool attack_type::check_self_abilities_impl(const const_attack_ptr& self_attack, const const_attack_ptr& other_attack, const config& special, const unit_const_ptr& u, const map_location& loc, AFFECTS whom, const std::string& tag_name, bool leader_bool)
1754 {
1755  if(tag_name == "leadership" && leader_bool){
1756  if((*u).get_self_ability_bool_weapon(special, tag_name, loc, self_attack, other_attack)) {
1757  return true;
1758  }
1759  }
1760  if((*u).checking_tags().count(tag_name) != 0){
1761  if((*u).get_self_ability_bool(special, tag_name, loc) && special_active_impl(self_attack, other_attack, special, whom, tag_name, "filter_student")) {
1762  return true;
1763  }
1764  }
1765  return false;
1766 }
1767 
1768 bool attack_type::check_adj_abilities(const config& cfg, const std::string& special, int dir, const unit& from) const
1769 {
1770  return check_adj_abilities_impl(shared_from_this(), other_attack_, cfg, self_, from, dir, self_loc_, AFFECT_SELF, special, true);
1771 }
1772 
1773 bool attack_type::check_adj_abilities_impl(const const_attack_ptr& self_attack, const const_attack_ptr& other_attack, const config& special, const unit_const_ptr& u, const unit& from, int dir, const map_location& loc, AFFECTS whom, const std::string& tag_name, bool leader_bool)
1774 {
1775  if(tag_name == "leadership" && leader_bool){
1776  if((*u).get_adj_ability_bool_weapon(special, tag_name, dir, loc, from, self_attack, other_attack)) {
1777  return true;
1778  }
1779  }
1780  if((*u).checking_tags().count(tag_name) != 0){
1781  if((*u).get_adj_ability_bool(special, tag_name, dir, loc, from) && special_active_impl(self_attack, other_attack, special, whom, tag_name, "filter_student")) {
1782  return true;
1783  }
1784  }
1785  return false;
1786 }
1787 /**
1788  * Returns whether or not @a *this has a special ability with a tag or id equal to
1789  * @a special. the Check is for a special ability
1790  * active in the current context (see set_specials_context), including
1791  * specials obtained from the opponent's attack.
1792  */
1793 bool attack_type::has_weapon_ability(const std::string& special, bool special_id, bool special_tags) const
1794 {
1795  const unit_map& units = get_unit_map();
1796  if(self_){
1797  std::vector<special_match> special_tag_matches_self;
1798  std::vector<special_match> special_id_matches_self;
1799  get_ability_children(special_tag_matches_self, special_id_matches_self, (*self_).abilities(), special, special_id , special_tags);
1800  if(special_tags){
1801  for(const special_match& entry : special_tag_matches_self) {
1802  if(check_self_abilities(*entry.cfg, entry.tag_name)){
1803  return true;
1804  }
1805  }
1806  }
1807  if(special_id){
1808  for(const special_match& entry : special_id_matches_self) {
1809  if(check_self_abilities(*entry.cfg, entry.tag_name)){
1810  return true;
1811  }
1812  }
1813  }
1814 
1815  const auto adjacent = get_adjacent_tiles(self_loc_);
1816  for(unsigned i = 0; i < adjacent.size(); ++i) {
1817  const unit_map::const_iterator it = units.find(adjacent[i]);
1818  if (it == units.end() || it->incapacitated())
1819  continue;
1820  if ( &*it == self_.get() )
1821  continue;
1822 
1823  std::vector<special_match> special_tag_matches_adj;
1824  std::vector<special_match> special_id_matches_adj;
1825  get_ability_children(special_tag_matches_adj, special_id_matches_adj, it->abilities(), special, special_id , special_tags);
1826  if(special_tags){
1827  for(const special_match& entry : special_tag_matches_adj) {
1828  if(check_adj_abilities(*entry.cfg, entry.tag_name, i , *it)){
1829  return true;
1830  }
1831  }
1832  }
1833  if(special_id){
1834  for(const special_match& entry : special_id_matches_adj) {
1835  if(check_adj_abilities(*entry.cfg, entry.tag_name, i , *it)){
1836  return true;
1837  }
1838  }
1839  }
1840  }
1841  }
1842 
1843  if(other_){
1844  std::vector<special_match> special_tag_matches_other;
1845  std::vector<special_match> special_id_matches_other;
1846  get_ability_children(special_tag_matches_other, special_id_matches_other, (*other_).abilities(), special, special_id , special_tags);
1847  if(special_tags){
1848  for(const special_match& entry : special_tag_matches_other) {
1849  if(check_self_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, other_loc_, AFFECT_OTHER, entry.tag_name)){
1850  return true;
1851  }
1852  }
1853  }
1854 
1855  if(special_id){
1856  for(const special_match& entry : special_id_matches_other) {
1857  if(check_self_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, other_loc_, AFFECT_OTHER, entry.tag_name)){
1858  return true;
1859  }
1860  }
1861  }
1862 
1863  const auto adjacent = get_adjacent_tiles(other_loc_);
1864  for(unsigned i = 0; i < adjacent.size(); ++i) {
1865  const unit_map::const_iterator it = units.find(adjacent[i]);
1866  if (it == units.end() || it->incapacitated())
1867  continue;
1868  if ( &*it == other_.get() )
1869  continue;
1870 
1871  std::vector<special_match> special_tag_matches_oadj;
1872  std::vector<special_match> special_id_matches_oadj;
1873  get_ability_children(special_tag_matches_oadj, special_id_matches_oadj, it->abilities(), special, special_id , special_tags);
1874  if(special_tags){
1875  for(const special_match& entry : special_tag_matches_oadj) {
1876  if(check_adj_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, *it, i, other_loc_, AFFECT_OTHER, entry.tag_name)){
1877  return true;
1878  }
1879  }
1880  }
1881 
1882  if(special_id){
1883  for(const special_match& entry : special_id_matches_oadj) {
1884  if(check_adj_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, *it, i, other_loc_, AFFECT_OTHER, entry.tag_name)){
1885  return true;
1886  }
1887  }
1888  }
1889  }
1890  }
1891  return false;
1892 }
1893 
1894 bool attack_type::has_special_or_ability(const std::string& special, bool special_id, bool special_tags) const
1895 {
1896  //Now that filter_(second)attack in event supports special_id/type_active, including abilities used as weapons,
1897  //these can be detected even in placeholder attacks generated to compensate for the lack of attack in defense against an attacker using a range attack not possessed by the defender.
1898  //It is therefore necessary to check if the range is not empty (proof that the weapon is not a placeholder) to decide if has_weapon_ability can be returned or not.
1899  if(range().empty()){
1900  return false;
1901  }
1902  return (has_special(special, false, special_id, special_tags) || has_weapon_ability(special, special_id, special_tags));
1903 }
1904 //end of emulate weapon special functions.
1905 
1906 namespace
1907 {
1908  bool exclude_ability_attributes(const std::string& tag_name, const config & filter)
1909  {
1910  ///check what filter attributes used can be used in type of ability checked.
1911  bool abilities_check = abilities_list::ability_value_tags().count(tag_name) != 0 || abilities_list::ability_no_value_tags().count(tag_name) != 0;
1912  if(filter.has_attribute("active_on") && tag_name != "resistance" && abilities_check)
1913  return false;
1914  if(filter.has_attribute("apply_to") && tag_name != "resistance" && abilities_check)
1915  return false;
1916 
1917  if(filter.has_attribute("overwrite_specials") && abilities_list::weapon_number_tags().count(tag_name) == 0)
1918  return false;
1919 
1920  bool no_value_weapon_abilities_check = abilities_list::no_weapon_number_tags().count(tag_name) != 0 || abilities_list::ability_no_value_tags().count(tag_name) != 0;
1921  if(filter.has_attribute("value") && no_value_weapon_abilities_check)
1922  return false;
1923  if(filter.has_attribute("add") && no_value_weapon_abilities_check)
1924  return false;
1925  if(filter.has_attribute("sub") && no_value_weapon_abilities_check)
1926  return false;
1927  if(filter.has_attribute("multiply") && no_value_weapon_abilities_check)
1928  return false;
1929  if(filter.has_attribute("divide") && no_value_weapon_abilities_check)
1930  return false;
1931 
1932  bool all_engine = abilities_list::no_weapon_number_tags().count(tag_name) != 0 || abilities_list::weapon_number_tags().count(tag_name) != 0 || abilities_list::ability_value_tags().count(tag_name) != 0 || abilities_list::ability_no_value_tags().count(tag_name) != 0;
1933  if(filter.has_attribute("replacement_type") && tag_name != "damage_type" && all_engine)
1934  return false;
1935  if(filter.has_attribute("alternative_type") && tag_name != "damage_type" && all_engine)
1936  return false;
1937  if(filter.has_attribute("type") && tag_name != "plague" && all_engine)
1938  return false;
1939 
1940  return true;
1941  }
1942 
1943  bool matches_ability_filter(const config & cfg, const std::string& tag_name, const config & filter)
1944  {
1945  using namespace utils::config_filters;
1946 
1947  //check if attributes have right to be in type of ability checked
1948  if(!exclude_ability_attributes(tag_name, filter))
1949  return false;
1950 
1951  // tag_name and id are equivalent of ability ability_type and ability_id/type_active filters
1952  //can be extent to special_id/type_active. If tag_name or id matche if present in list.
1953  const std::vector<std::string> filter_type = utils::split(filter["tag_name"]);
1954  if ( !filter_type.empty() && std::find(filter_type.begin(), filter_type.end(), tag_name) == filter_type.end() )
1955  return false;
1956 
1957  if(!string_matches_if_present(filter, cfg, "id", ""))
1958  return false;
1959 
1960  //when affect_adjacent=yes detect presence of [affect_adjacent] in abilities, if no
1961  //then matches when tag not present.
1962  if(!filter["affect_adjacent"].empty()){
1963  bool adjacent = cfg.has_child("affect_adjacent");
1964  if(filter["affect_adjacent"].to_bool() != adjacent){
1965  return false;
1966  }
1967  }
1968 
1969  //these attributs below filter attribute used in all engine abilities.
1970  //matches if filter attribute have same boolean value what attribute
1971  if(!bool_matches_if_present(filter, cfg, "affect_self", true))
1972  return false;
1973 
1974  //here if value of affect_allies but also his presence who is checked because
1975  //when affect_allies not specified, ability affect unit of same side what owner only.
1976  if(!bool_or_empty(filter, cfg, "affect_allies"))
1977  return false;
1978 
1979  if(!bool_matches_if_present(filter, cfg, "affect_enemies", false))
1980  return false;
1981 
1982 
1983  //cumulative, overwrite_specials and active_on check attributes used in all abilities
1984  //who return a numerical value.
1985  if(!bool_matches_if_present(filter, cfg, "cumulative", false))
1986  return false;
1987 
1988  if(!string_matches_if_present(filter, cfg, "overwrite_specials", "none"))
1989  return false;
1990 
1991  if(!string_matches_if_present(filter, cfg, "active_on", "both"))
1992  return false;
1993 
1994  //value, add, sub multiply and divide check values of attribute used in engines abilities(default value of 'value' can be checked when not specified)
1995  //who return numericals value but can also check in non-engine abilities(in last case if 'value' not specified none value can matches)
1996  if(!filter["value"].empty()){
1997  if(tag_name == "drains"){
1998  if(!int_matches_if_present(filter, cfg, "value", 50)){
1999  return false;
2000  }
2001  } else if(tag_name == "berserk"){
2002  if(!int_matches_if_present(filter, cfg, "value", 1)){
2003  return false;
2004  }
2005  } else if(tag_name == "heal_on_hit" || tag_name == "heals" || tag_name == "regenerate" || tag_name == "leadership"){
2006  if(!int_matches_if_present(filter, cfg, "value" , 0)){
2007  return false;
2008  }
2009  } else {
2010  if(!int_matches_if_present(filter, cfg, "value")){
2011  return false;
2012  }
2013  }
2014  }
2015 
2016  if(!int_matches_if_present_or_negative(filter, cfg, "add", "sub"))
2017  return false;
2018 
2019  if(!int_matches_if_present_or_negative(filter, cfg, "sub", "add"))
2020  return false;
2021 
2022  if(!double_matches_if_present(filter, cfg, "multiply"))
2023  return false;
2024 
2025  if(!double_matches_if_present(filter, cfg, "divide"))
2026  return false;
2027 
2028 
2029  //apply_to is a special case, in resistance ability, it check a list of damage type used by [resistance]
2030  //but in weapon specials, check identity of unit affected by special(self, opponent tc...)
2031  if(tag_name == "resistance"){
2032  if(!set_includes_if_present(filter, cfg, "apply_to")){
2033  return false;
2034  }
2035  } else {
2036  if(!string_matches_if_present(filter, cfg, "apply_to", "self")){
2037  return false;
2038  }
2039  }
2040 
2041  //the three attribute below are used for check in specifics abilitie:
2042  //replacement_type and alternative_type are present in [damage_type] only for engine abilities
2043  //and type for [plague], but if someone want use this in non-engine abilities, these attribute can be checked outside type mentioned.
2044  //
2045 
2046  //for damage_type only(in engine cases)
2047  if(!string_matches_if_present(filter, cfg, "replacement_type", ""))
2048  return false;
2049 
2050  if(!string_matches_if_present(filter, cfg, "alternative_type", ""))
2051  return false;
2052 
2053  //for plague only(in engine cases)
2054  if(!string_matches_if_present(filter, cfg, "type", ""))
2055  return false;
2056 
2057  //the wml_filter is used in cases where the attribute we are looking for is not
2058  //previously listed or to check the contents of the sub_tags ([filter_adjacent],[filter_self],[filter_opponent] etc.
2059  //If the checked set does not exactly match the content of the capability, the function returns a false response.
2060  auto fwml = filter.optional_child("filter_wml");
2061  if (fwml){
2062  if(!cfg.matches(*fwml)){
2063  return false;
2064  }
2065  }
2066 
2067  // Passed all tests.
2068  return true;
2069  }
2070 
2071  static bool common_matches_filter(const config & cfg, const std::string& tag_name, const config & filter)
2072  {
2073  // Handle the basic filter.
2074  bool matches = matches_ability_filter(cfg, tag_name, filter);
2075 
2076  // Handle [and], [or], and [not] with in-order precedence
2077  for(const auto [key, condition_cfg] : filter.all_children_view() )
2078  {
2079  // Handle [and]
2080  if ( key == "and" )
2081  matches = matches && common_matches_filter(cfg, tag_name, condition_cfg);
2082 
2083  // Handle [or]
2084  else if ( key == "or" )
2085  matches = matches || common_matches_filter(cfg, tag_name, condition_cfg);
2086 
2087  // Handle [not]
2088  else if ( key == "not" )
2089  matches = matches && !common_matches_filter(cfg, tag_name, condition_cfg);
2090  }
2091 
2092  return matches;
2093  }
2094 }
2095 
2096 bool unit::ability_matches_filter(const config & cfg, const std::string& tag_name, const config & filter) const
2097 {
2098  return common_matches_filter(cfg, tag_name, filter);
2099 }
2100 
2101 bool attack_type::special_matches_filter(const config & cfg, const std::string& tag_name, const config & filter) const
2102 {
2103  return common_matches_filter(cfg, tag_name, filter);
2104 }
2105 
2107 {
2108  using namespace utils::config_filters;
2109  bool check_if_active = filter["active"].to_bool();
2110  for(const auto [key, cfg] : specials().all_children_view()) {
2111  if(special_matches_filter(cfg, key, filter)){
2112  if(!check_if_active){
2113  return true;
2114  }
2115  if ( special_active(cfg, AFFECT_SELF, key) ) {
2116  return true;
2117  }
2118  }
2119  }
2120 
2121  if(!check_if_active || !other_attack_){
2122  return false;
2123  }
2124 
2125  for(const auto [key, cfg] : other_attack_->specials().all_children_view()) {
2126  if(other_attack_->special_matches_filter(cfg, key, filter)){
2127  if ( other_attack_->special_active(cfg, AFFECT_OTHER, key) ) {
2128  return true;
2129  }
2130  }
2131  }
2132 
2133  return false;
2134 }
2135 
2137 {
2138  if(!filter["active"].to_bool()){
2139  return false;
2140  }
2141  const unit_map& units = get_unit_map();
2142  if(self_){
2143  for(const auto [key, cfg] : (*self_).abilities().all_children_view()) {
2144  if(self_->ability_matches_filter(cfg, key, filter)){
2145  if(check_self_abilities(cfg, key)){
2146  return true;
2147  }
2148  }
2149  }
2150 
2151  const auto adjacent = get_adjacent_tiles(self_loc_);
2152  for(unsigned i = 0; i < adjacent.size(); ++i) {
2153  const unit_map::const_iterator it = units.find(adjacent[i]);
2154  if (it == units.end() || it->incapacitated())
2155  continue;
2156  if ( &*it == self_.get() )
2157  continue;
2158 
2159  for(const auto [key, cfg] : it->abilities().all_children_view()) {
2160  if(it->ability_matches_filter(cfg, key, filter) && check_adj_abilities(cfg, key, i , *it)){
2161  return true;
2162  }
2163  }
2164  }
2165  }
2166 
2167  if(other_){
2168  for(const auto [key, cfg] : (*other_).abilities().all_children_view()) {
2169  if(other_->ability_matches_filter(cfg, key, filter) && check_self_abilities_impl(other_attack_, shared_from_this(), cfg, other_, other_loc_, AFFECT_OTHER, key)){
2170  return true;
2171  }
2172  }
2173 
2174  const auto adjacent = get_adjacent_tiles(other_loc_);
2175  for(unsigned i = 0; i < adjacent.size(); ++i) {
2176  const unit_map::const_iterator it = units.find(adjacent[i]);
2177  if (it == units.end() || it->incapacitated())
2178  continue;
2179  if ( &*it == other_.get() )
2180  continue;
2181 
2182  for(const auto [key, cfg] : it->abilities().all_children_view()) {
2183  if(it->ability_matches_filter(cfg, key, filter) && check_adj_abilities_impl(other_attack_, shared_from_this(), cfg, other_, *it, i, other_loc_, AFFECT_OTHER, key)){
2184  return true;
2185  }
2186  }
2187  }
2188  }
2189  return false;
2190 }
2191 
2193 {
2194  if(range().empty()){
2195  return false;
2196  }
2197  return (has_special_with_filter(filter) || has_ability_with_filter(filter));
2198 }
2199 
2200 bool attack_type::special_active(const config& special, AFFECTS whom, const std::string& tag_name,
2201  const std::string& filter_self) const
2202 {
2203  return special_active_impl(shared_from_this(), other_attack_, special, whom, tag_name, filter_self);
2204 }
2205 
2206 /**
2207  * Returns whether or not the given special is active for the specified unit,
2208  * based on the current context (see set_specials_context).
2209  * @param self_attack this unit's attack
2210  * @param other_attack the other unit's attack
2211  * @param special a weapon special WML structure
2212  * @param whom specifies which combatant we care about
2213  * @param tag_name tag name of the special config
2214  * @param filter_self the filter to use
2215  */
2217  const const_attack_ptr& self_attack,
2218  const const_attack_ptr& other_attack,
2219  const config& special,
2220  AFFECTS whom,
2221  const std::string& tag_name,
2222  const std::string& filter_self)
2223 {
2224  assert(self_attack || other_attack);
2225  bool is_attacker = self_attack ? self_attack->is_attacker_ : !other_attack->is_attacker_;
2226  bool is_for_listing = self_attack ? self_attack->is_for_listing_ : other_attack->is_for_listing_;
2227  //log_scope("special_active");
2228 
2229 
2230  // Does this affect the specified unit?
2231  if ( whom == AFFECT_SELF ) {
2232  if ( !special_affects_self(special, is_attacker) )
2233  return false;
2234  }
2235  if ( whom == AFFECT_OTHER ) {
2236  if ( !special_affects_opponent(special, is_attacker) )
2237  return false;
2238  }
2239 
2240  // Is this active on attack/defense?
2241  const std::string & active_on = special["active_on"];
2242  if ( !active_on.empty() ) {
2243  if ( is_attacker && active_on != "offense" )
2244  return false;
2245  if ( !is_attacker && active_on != "defense" )
2246  return false;
2247  }
2248 
2249  // Get the units involved.
2250  const unit_map& units = get_unit_map();
2251 
2252  unit_const_ptr self = self_attack ? self_attack->self_ : other_attack->other_;
2253  unit_const_ptr other = self_attack ? self_attack->other_ : other_attack->self_;
2254  map_location self_loc = self_attack ? self_attack->self_loc_ : other_attack->other_loc_;
2255  map_location other_loc = self_attack ? self_attack->other_loc_ : other_attack->self_loc_;
2256  //TODO: why is this needed?
2257  if(self == nullptr) {
2258  unit_map::const_iterator it = units.find(self_loc);
2259  if(it.valid()) {
2260  self = it.get_shared_ptr();
2261  }
2262  }
2263  if(other == nullptr) {
2264  unit_map::const_iterator it = units.find(other_loc);
2265  if(it.valid()) {
2266  other = it.get_shared_ptr();
2267  }
2268  }
2269 
2270  // Make sure they're facing each other.
2271  temporary_facing self_facing(self, self_loc.get_relative_dir(other_loc));
2272  temporary_facing other_facing(other, other_loc.get_relative_dir(self_loc));
2273 
2274  // Filter poison, plague, drain, slow, petrifies
2275  // True if "whom" corresponds to "self", false if "whom" is "other"
2276  bool whom_is_self = ((whom == AFFECT_SELF) || ((whom == AFFECT_EITHER) && special_affects_self(special, is_attacker)));
2277  unit_const_ptr them = whom_is_self ? other : self;
2278  map_location their_loc = whom_is_self ? other_loc : self_loc;
2279 
2280  if (tag_name == "drains" && them && them->get_state("undrainable")) {
2281  return false;
2282  }
2283  if (tag_name == "plague" && them &&
2284  (them->get_state("unplagueable") ||
2285  resources::gameboard->map().is_village(their_loc))) {
2286  return false;
2287  }
2288  if (tag_name == "poison" && them &&
2289  (them->get_state("unpoisonable") || them->get_state(unit::STATE_POISONED))) {
2290  return false;
2291  }
2292  if (tag_name == "slow" && them &&
2293  (them->get_state("unslowable") || them->get_state(unit::STATE_SLOWED))) {
2294  return false;
2295  }
2296  if (tag_name == "petrifies" && them &&
2297  them->get_state("unpetrifiable")) {
2298  return false;
2299  }
2300 
2301 
2302  // Translate our context into terms of "attacker" and "defender".
2303  unit_const_ptr & att = is_attacker ? self : other;
2304  unit_const_ptr & def = is_attacker ? other : self;
2305  const map_location & att_loc = is_attacker ? self_loc : other_loc;
2306  const map_location & def_loc = is_attacker ? other_loc : self_loc;
2307  const const_attack_ptr& att_weapon = is_attacker ? self_attack : other_attack;
2308  const const_attack_ptr& def_weapon = is_attacker ? other_attack : self_attack;
2309 
2310  // Filter firststrike here, if both units have first strike then the effects cancel out. Only check
2311  // the opponent if "whom" is the defender, otherwise this leads to infinite recursion.
2312  if (tag_name == "firststrike") {
2313  bool whom_is_defender = whom_is_self ? !is_attacker : is_attacker;
2314  if (whom_is_defender && att_weapon && att_weapon->has_special_or_ability("firststrike"))
2315  return false;
2316  }
2317 
2318  // Filter the units involved.
2319  //If filter concerns the unit on which special is applied,
2320  //then the type of special must be entered to avoid calling
2321  //the function of this special in matches_filter()
2322  //In apply_to=both case, tag_name must be checked in all filter because special applied to both self and opponent.
2323  bool applied_both = special["apply_to"] == "both";
2324  std::string self_check_if_recursion = (applied_both || whom_is_self) ? tag_name : "";
2325  if (!special_unit_matches(self, other, self_loc, self_attack, special, is_for_listing, filter_self, self_check_if_recursion))
2326  return false;
2327  std::string opp_check_if_recursion = (applied_both || !whom_is_self) ? tag_name : "";
2328  if (!special_unit_matches(other, self, other_loc, other_attack, special, is_for_listing, "filter_opponent", opp_check_if_recursion))
2329  return false;
2330  //in case of apply_to=attacker|defender, if both [filter_attacker] and [filter_defender] are used,
2331  //check what is_attacker is true(or false for (filter_defender]) in affect self case only is necessary for what unit affected by special has a tag_name check.
2332  bool applied_to_attacker = applied_both || (whom_is_self && is_attacker) || (!whom_is_self && !is_attacker);
2333  std::string att_check_if_recursion = applied_to_attacker ? tag_name : "";
2334  if (!special_unit_matches(att, def, att_loc, att_weapon, special, is_for_listing, "filter_attacker", att_check_if_recursion))
2335  return false;
2336  bool applied_to_defender = applied_both || (whom_is_self && !is_attacker) || (!whom_is_self && is_attacker);
2337  std::string def_check_if_recursion= applied_to_defender ? tag_name : "";
2338  if (!special_unit_matches(def, att, def_loc, def_weapon, special, is_for_listing, "filter_defender", def_check_if_recursion))
2339  return false;
2340 
2341  const auto adjacent = get_adjacent_tiles(self_loc);
2342 
2343  // Filter the adjacent units.
2344  for (const config &i : special.child_range("filter_adjacent"))
2345  {
2346  std::size_t count = 0;
2347  std::vector<map_location::direction> dirs = map_location::parse_directions(i["adjacent"]);
2348  unit_filter filter{ vconfig(i) };
2349  for (const map_location::direction index : dirs)
2350  {
2352  continue;
2353  unit_map::const_iterator unit = units.find(adjacent[static_cast<int>(index)]);
2354  if (unit == units.end() || !filter.matches(*unit, adjacent[static_cast<int>(index)], *self))
2355  return false;
2356  if (i.has_attribute("is_enemy")) {
2358  if (i["is_enemy"].to_bool() != dc.get_team(unit->side()).is_enemy(self->side())) {
2359  continue;
2360  }
2361  }
2362  count++;
2363  }
2364  if (i["count"].empty() && count != dirs.size()) {
2365  return false;
2366  }
2367  if (!in_ranges<int>(count, utils::parse_ranges_unsigned(i["count"].str()))) {
2368  return false;
2369  }
2370  }
2371 
2372  // Filter the adjacent locations.
2373  for (const config &i : special.child_range("filter_adjacent_location"))
2374  {
2375  std::size_t count = 0;
2376  std::vector<map_location::direction> dirs = map_location::parse_directions(i["adjacent"]);
2377  terrain_filter adj_filter(vconfig(i), resources::filter_con, false);
2378  for (const map_location::direction index : dirs)
2379  {
2381  continue;
2382  if(!adj_filter.match(adjacent[static_cast<int>(index)])) {
2383  return false;
2384  }
2385  count++;
2386  }
2387  if (i["count"].empty() && count != dirs.size()) {
2388  return false;
2389  }
2390  if (!in_ranges<int>(count, utils::parse_ranges_unsigned(i["count"].str()))) {
2391  return false;
2392  }
2393  }
2394 
2395  return true;
2396 }
2397 
2398 
2399 
2401 {
2402 
2403 void individual_effect::set(value_modifier t, int val, const config *abil, const map_location &l)
2404 {
2405  type=t;
2406  value=val;
2407  ability=abil;
2408  loc=l;
2409 }
2410 
2411 bool filter_base_matches(const config& cfg, int def)
2412 {
2413  if (auto apply_filter = cfg.optional_child("filter_base_value")) {
2414  config::attribute_value cond_eq = apply_filter["equals"];
2415  config::attribute_value cond_ne = apply_filter["not_equals"];
2416  config::attribute_value cond_lt = apply_filter["less_than"];
2417  config::attribute_value cond_gt = apply_filter["greater_than"];
2418  config::attribute_value cond_ge = apply_filter["greater_than_equal_to"];
2419  config::attribute_value cond_le = apply_filter["less_than_equal_to"];
2420  return (cond_eq.empty() || def == cond_eq.to_int()) &&
2421  (cond_ne.empty() || def != cond_ne.to_int()) &&
2422  (cond_lt.empty() || def < cond_lt.to_int()) &&
2423  (cond_gt.empty() || def > cond_gt.to_int()) &&
2424  (cond_ge.empty() || def >= cond_ge.to_int()) &&
2425  (cond_le.empty() || def <= cond_le.to_int());
2426  }
2427  return true;
2428 }
2429 
2430 effect::effect(const unit_ability_list& list, int def, const const_attack_ptr& att, EFFECTS wham) :
2431  effect_list_(),
2432  composite_value_(0)
2433 {
2434 
2435  int value_set = (wham == EFFECT_CUMULABLE) ? std::max(list.highest("value").first, 0) + std::min(list.lowest("value").first, 0) : def;
2436  std::map<std::string,individual_effect> values_add;
2437  std::map<std::string,individual_effect> values_sub;
2438  std::map<std::string,individual_effect> values_mul;
2439  std::map<std::string,individual_effect> values_div;
2440 
2441  individual_effect set_effect_max;
2442  individual_effect set_effect_min;
2443  utils::optional<int> max_value = utils::nullopt;
2444  utils::optional<int> min_value = utils::nullopt;
2445 
2446  for (const unit_ability & ability : list) {
2447  const config& cfg = *ability.ability_cfg;
2448  const std::string& effect_id = cfg[cfg["id"].empty() ? "name" : "id"];
2449 
2450  if (!filter_base_matches(cfg, def))
2451  continue;
2452 
2453  if(wham != EFFECT_CUMULABLE){
2454  if (const config::attribute_value *v = cfg.get("value")) {
2455  int value = get_single_ability_value(*v, def, ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2456  callable.add("base_value", wfl::variant(def));
2457  return formula.evaluate(callable).as_int();
2458  });
2459 
2460  int value_cum = cfg["cumulative"].to_bool() ? std::max(def, value) : value;
2461  assert((set_effect_min.type != NOT_USED) == (set_effect_max.type != NOT_USED));
2462  if(set_effect_min.type == NOT_USED) {
2463  set_effect_min.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2464  set_effect_max.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2465  }
2466  else {
2467  if(value_cum > set_effect_max.value) {
2468  set_effect_max.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2469  }
2470  if(value_cum < set_effect_min.value) {
2471  set_effect_min.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2472  }
2473  }
2474  }
2475  }
2476 
2477  if(wham == EFFECT_DEFAULT || wham == EFFECT_CUMULABLE){
2478  if(cfg.has_attribute("max_value")){
2479  max_value = max_value ? std::min(*max_value, cfg["max_value"].to_int()) : cfg["max_value"].to_int();
2480  }
2481  if(cfg.has_attribute("min_value")){
2482  min_value = min_value ? std::max(*min_value, cfg["min_value"].to_int()) : cfg["min_value"].to_int();
2483  }
2484  }
2485 
2486  if (const config::attribute_value *v = cfg.get("add")) {
2487  int add = get_single_ability_value(*v, def, ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2488  callable.add("base_value", wfl::variant(def));
2489  return formula.evaluate(callable).as_int();
2490  });
2491  std::map<std::string,individual_effect>::iterator add_effect = values_add.find(effect_id);
2492  if(add_effect == values_add.end() || add > add_effect->second.value) {
2493  values_add[effect_id].set(ADD, add, ability.ability_cfg, ability.teacher_loc);
2494  }
2495  }
2496  if (const config::attribute_value *v = cfg.get("sub")) {
2497  int sub = - get_single_ability_value(*v, def, ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2498  callable.add("base_value", wfl::variant(def));
2499  return formula.evaluate(callable).as_int();
2500  });
2501  std::map<std::string,individual_effect>::iterator sub_effect = values_sub.find(effect_id);
2502  if(sub_effect == values_sub.end() || sub < sub_effect->second.value) {
2503  values_sub[effect_id].set(ADD, sub, ability.ability_cfg, ability.teacher_loc);
2504  }
2505  }
2506  if (const config::attribute_value *v = cfg.get("multiply")) {
2507  int multiply = static_cast<int>(get_single_ability_value(*v, static_cast<double>(def), ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2508  callable.add("base_value", wfl::variant(def));
2509  return formula.evaluate(callable).as_decimal() / 1000.0 ;
2510  }) * 100);
2511  std::map<std::string,individual_effect>::iterator mul_effect = values_mul.find(effect_id);
2512  if(mul_effect == values_mul.end() || multiply > mul_effect->second.value) {
2513  values_mul[effect_id].set(MUL, multiply, ability.ability_cfg, ability.teacher_loc);
2514  }
2515  }
2516  if (const config::attribute_value *v = cfg.get("divide")) {
2517  int divide = static_cast<int>(get_single_ability_value(*v, static_cast<double>(def), ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2518  callable.add("base_value", wfl::variant(def));
2519  return formula.evaluate(callable).as_decimal() / 1000.0 ;
2520  }) * 100);
2521 
2522  if (divide == 0) {
2523  ERR_NG << "division by zero with divide= in ability/weapon special " << effect_id;
2524  }
2525  else {
2526  std::map<std::string,individual_effect>::iterator div_effect = values_div.find(effect_id);
2527  if(div_effect == values_div.end() || divide > div_effect->second.value) {
2528  values_div[effect_id].set(DIV, divide, ability.ability_cfg, ability.teacher_loc);
2529  }
2530  }
2531  }
2532  }
2533 
2534  if((wham != EFFECT_CUMULABLE) && set_effect_max.type != NOT_USED) {
2535  value_set = std::max(set_effect_max.value, 0) + std::min(set_effect_min.value, 0);
2536  if(set_effect_max.value > def) {
2537  effect_list_.push_back(set_effect_max);
2538  }
2539  if(set_effect_min.value < def) {
2540  effect_list_.push_back(set_effect_min);
2541  }
2542  }
2543 
2544  /* Do multiplication with floating point values rather than integers
2545  * We want two places of precision for each multiplier
2546  * Using integers multiplied by 100 to keep precision causes overflow
2547  * after 3-4 abilities for 32-bit values and ~8 for 64-bit
2548  * Avoiding the overflow by dividing after each step introduces rounding errors
2549  * that may vary depending on the order effects are applied
2550  * As the final values are likely <1000 (always true for mainline), loss of less significant digits is not an issue
2551  */
2552  double multiplier = 1.0;
2553  double divisor = 1.0;
2554 
2555  for(const auto& val : values_mul) {
2556  multiplier *= val.second.value/100.0;
2557  effect_list_.push_back(val.second);
2558  }
2559 
2560  for(const auto& val : values_div) {
2561  divisor *= val.second.value/100.0;
2562  effect_list_.push_back(val.second);
2563  }
2564 
2565  int addition = 0;
2566  for(const auto& val : values_add) {
2567  addition += val.second.value;
2568  effect_list_.push_back(val.second);
2569  }
2570 
2571  /* Additional and subtraction are independent since Wesnoth 1.19.4. Prior to that, they affected each other.
2572  */
2573  int substraction = 0;
2574  for(const auto& val : values_sub) {
2575  substraction += val.second.value;
2576  effect_list_.push_back(val.second);
2577  }
2578 
2579  composite_value_ = static_cast<int>((value_set + addition + substraction) * multiplier / divisor);
2580  //clamp what if min_value < max_value or one attribute only used.
2581  if(max_value && min_value && *min_value < *max_value) {
2582  composite_value_ = std::clamp(*min_value, *max_value, composite_value_);
2583  } else if(max_value && !min_value) {
2584  composite_value_ = std::min(*max_value, composite_value_);
2585  } else if(min_value && !max_value) {
2586  composite_value_ = std::max(*min_value, composite_value_);
2587  }
2588 }
2589 
2590 } // end namespace unit_abilities
static std::string select_replacement_type(const unit_ability_list &damage_type_list)
Definition: abilities.cpp:1261
static void add_name(std::string &temp_string, bool active, const std::string &name, std::set< std::string > &checking_name)
Definition: abilities.cpp:979
static lg::log_domain log_engine("engine")
#define ERR_NG
Definition: abilities.cpp:50
static void add_string_to_vector(std::vector< std::string > &image_list, const config &cfg, const std::string &attribute_name)
Definition: abilities.cpp:565
#define ERR_WML
Definition: abilities.cpp:53
static void add_name_list(std::string &temp_string, std::string &weapon_abilities, std::set< std::string > &checking_name, const std::string &from_str)
Definition: abilities.cpp:1034
static std::string select_alternative_type(const unit_ability_list &damage_type_list, const unit_ability_list &resistance_list, const unit &u)
Definition: abilities.cpp:1290
static bool overwrite_special_affects(const config &special)
Definition: abilities.cpp:1593
static lg::log_domain log_wml("wml")
static void get_ability_children(std::vector< special_match > &tag_result, std::vector< special_match > &id_result, const config &parent, const std::string &id, bool special_id=true, bool special_tags=true)
Gets the children of parent (which should be the abilities for an attack_type) and places the ones wh...
Definition: abilities.cpp:1704
map_location loc
Definition: move.cpp:172
double t
Definition: astarsearch.cpp:63
Helper similar to std::unique_lock for detecting when calculations such as has_special have entered i...
specials_context_t(const attack_type &weapon, bool attacking)
Initialize weapon specials context for listing.
Definition: abilities.cpp:1208
const config & specials() const
Definition: attack_type.hpp:56
map_location other_loc_
std::string weapon_specials() const
Returns a comma-separated string of active names for the specials of *this.
Definition: abilities.cpp:997
bool check_self_abilities(const config &cfg, const std::string &special) const
check_self_abilities : return an boolean value for checking of activities of abilities used like weap...
Definition: abilities.cpp:1748
bool has_special(const std::string &special, bool simple_check=false, bool special_id=true, bool special_tags=true) const
Returns whether or not *this has a special with a tag or id equal to special.
Definition: abilities.cpp:838
std::string weapon_specials_value(const std::set< std::string > &checking_tags) const
Definition: abilities.cpp:1045
int composite_value(const unit_ability_list &abil_list, int base_value) const
Return the special weapon value, considering specials.
Definition: abilities.cpp:1588
const_attack_ptr other_attack_
void add_formula_context(wfl::map_formula_callable &) const
Definition: abilities.cpp:613
const std::string & range() const
Definition: attack_type.hpp:46
map_location self_loc_
bool has_special_or_ability_with_filter(const config &filter) const
Definition: abilities.cpp:2192
const std::string & type() const
Definition: attack_type.hpp:44
static bool special_active_impl(const const_attack_ptr &self_attack, const const_attack_ptr &other_attack, const config &special, AFFECTS whom, const std::string &tag_name, const std::string &filter_self="filter_self")
Returns whether or not the given special is active for the specified unit, based on the current conte...
Definition: abilities.cpp:2216
unit_ability_list get_weapon_ability(const std::string &ability) const
Returns list for weapon like abilities for each ability type.
Definition: abilities.cpp:1552
std::string select_damage_type(const unit_ability_list &damage_type_list, const std::string &key_name, const unit_ability_list &resistance_list) const
Select best damage type based on frequency count for replacement_type and based on highest damage for...
Definition: abilities.cpp:1318
bool has_ability_with_filter(const config &filter) const
Definition: abilities.cpp:2136
unit_ability_list get_specials_and_abilities(const std::string &special) const
Definition: abilities.cpp:1571
bool overwrite_special_checking(unit_ability_list &overwriters, const config &cfg, const std::string &tag_name) const
Check whether cfg would be overwritten by any element of overwriters.
Definition: abilities.cpp:1635
unit_const_ptr self_
bool check_adj_abilities(const config &cfg, const std::string &special, int dir, const unit &from) const
check_adj_abilities : return an boolean value for checking of activities of abilities used like weapo...
Definition: abilities.cpp:1768
int num_attacks() const
Definition: attack_type.hpp:53
static void weapon_specials_impl_adj(std::string &temp_string, const unit_const_ptr &self, const const_attack_ptr &self_attack, const const_attack_ptr &other_attack, const map_location &self_loc, AFFECTS whom, std::set< std::string > &checking_name, const std::set< std::string > &checking_tags={}, const std::string &affect_adjacents="", bool leader_bool=false)
Definition: abilities.cpp:1105
unit_ability_list overwrite_special_overwriter(unit_ability_list overwriters, const std::string &tag_name) const
Filter a list of abilities or weapon specials, removing any entries that don't own the overwrite_spec...
Definition: abilities.cpp:1599
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::vector< std::pair< t_string, t_string > > special_tooltips(boost::dynamic_bitset<> *active_list=nullptr) const
Returns a vector of names and descriptions for the specials of *this.
Definition: abilities.cpp:943
static void weapon_specials_impl_self(std::string &temp_string, const unit_const_ptr &self, const const_attack_ptr &self_attack, const const_attack_ptr &other_attack, const map_location &self_loc, AFFECTS whom, std::set< std::string > &checking_name, const std::set< std::string > &checking_tags={}, bool leader_bool=false)
weapon_specials_impl_self and weapon_specials_impl_adj : check if special name can be added.
Definition: abilities.cpp:1085
std::set< std::string > alternative_damage_types() const
Definition: abilities.cpp:1355
static bool check_self_abilities_impl(const const_attack_ptr &self_attack, const const_attack_ptr &other_attack, const config &special, const unit_const_ptr &u, const map_location &loc, AFFECTS whom, const std::string &tag_name, bool leader_bool=false)
check_self_abilities_impl : return an boolean value for checking of activities of abilities used like...
Definition: abilities.cpp:1753
bool attack_empty() const
Returns true if this is a dummy attack_type, for example the placeholder that the unit_attack dialog ...
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:2101
int modified_damage() const
Returns the damage per attack of this weapon, considering specials.
Definition: abilities.cpp:1376
void modified_attacks(unsigned &min_attacks, unsigned &max_attacks) const
Calculates the number of attacks this weapon has, considering specials.
Definition: abilities.cpp:1240
unit_const_ptr other_
static bool check_adj_abilities_impl(const const_attack_ptr &self_attack, const const_attack_ptr &other_attack, const config &special, const unit_const_ptr &u, const unit &from, int dir, const map_location &loc, AFFECTS whom, const std::string &tag_name, bool leader_bool=false)
check_adj_abilities_impl : return an boolean value for checking of activities of abilities used like ...
Definition: abilities.cpp:1773
bool has_special_with_filter(const config &filter) const
check if special matche
Definition: abilities.cpp:2106
bool special_active(const config &special, AFFECTS whom, const std::string &tag_name, const std::string &filter_self="filter_self") const
Definition: abilities.cpp:2200
unit_ability_list get_specials(const std::string &special) const
Returns the currently active specials as an ability list, given the current context (see set_specials...
Definition: abilities.cpp:910
int damage() const
Definition: attack_type.hpp:52
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:1894
bool has_weapon_ability(const std::string &special, bool special_id=true, bool special_tags=true) const
used for abilities used like weapon
Definition: abilities.cpp:1793
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:1332
bool is_for_listing_
Variant for storing WML attributes.
std::string str(const std::string &fallback="") const
auto apply_visitor(const V &visitor) const
Visitor support: Applies a visitor to the underlying variant.
bool blank() const
Tests for an attribute that was never set.
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:172
const attribute_value & get_or(const config_key_type key, const config_key_type default_key) const
Chooses a value.
Definition: config.cpp:691
config & mandatory_child(config_key_type key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition: config.cpp:366
bool matches(const config &filter) const
Definition: config.cpp:1194
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:810
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:316
bool has_attribute(config_key_type key) const
Definition: config.cpp:157
child_itors child_range(config_key_type key)
Definition: config.cpp:272
config & child_or_add(config_key_type key)
Returns a reference to the first child with the given key.
Definition: config.cpp:405
std::string debug() const
Definition: config.cpp:1240
bool empty() const
Definition: config.cpp:849
const attribute_value * get(config_key_type key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
Definition: config.cpp:685
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
Abstract class for exposing game data that doesn't depend on the GUI, however which for historical re...
const team & get_team(int side) const
This getter takes a 1-based side number, not a 0-based team number.
virtual const unit_map & units() const =0
const display_context & context() const
Definition: display.hpp:193
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:111
virtual const display_context & get_disp_context() const =0
team & get_team(int i)
Definition: game_board.hpp:92
virtual const unit_map & units() const override
Definition: game_board.hpp:107
virtual const gamemap & map() const override
Definition: game_board.hpp:97
bool is_village(const map_location &loc) const
Definition: map.cpp:66
bool empty() const
Definition: tstring.hpp:194
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:75
bool is_enemy(int n) const
Definition: team.hpp:229
bool match(const map_location &loc) const
Definition: filter.hpp:43
Helper similar to std::unique_lock for detecting when calculations such as abilities have entered inf...
Definition: unit.hpp:1882
int get_composite_value() const
Definition: abilities.hpp:49
std::vector< individual_effect > effect_list_
Definition: abilities.hpp:56
void append(const unit_ability_list &other)
Appends the abilities from other to this, ignores other.loc()
Definition: unit.hpp:106
std::pair< int, map_location > lowest(const std::string &key, int def=0) const
Definition: unit.hpp:72
const map_location & loc() const
Definition: unit.hpp:103
void emplace_back(T &&... args)
Definition: unit.hpp:101
std::pair< int, map_location > highest(const std::string &key, int def=0) const
Definition: unit.hpp:68
std::size_t size()
Definition: unit.hpp:95
bool empty() const
Definition: unit.hpp:90
void append_if(const unit_ability_list &other, const Predicate &predicate)
Appends any abilities from other for which the given condition returns true to this,...
Definition: unit.hpp:118
std::pair< int, map_location > get_extremum(const std::string &key, int def, const TComp &comp) const
Definition: abilities.cpp:689
bool matches(const unit &u, const map_location &loc) const
Determine if *this matches filter at a specified location.
Definition: filter.hpp:123
unit_filter & set_use_flat_tod(bool value)
Definition: filter.hpp:113
Container associating units to locations.
Definition: map.hpp:98
unit_iterator end()
Definition: map.hpp:428
unit_ptr find_unit_ptr(const T &val)
Definition: map.hpp:387
unit_iterator find(std::size_t id)
Definition: map.cpp:302
A single unit type that the player may recruit.
Definition: types.hpp:43
This class represents a single unit of a specific type.
Definition: unit.hpp:133
A variable-expanding proxy for the config class.
Definition: variable.hpp:45
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
map_formula_callable & add(const std::string &key, const variant &value)
Definition: callable.hpp:253
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
map_display and display: classes which take care of displaying the map and game-data on the screen.
std::size_t i
Definition: function.cpp:1029
Interfaces for manipulating version numbers of engine, add-ons, etc.
static std::string _(const char *str)
Definition: gettext.hpp:93
bool ability_active(const std::string &ability, const config &cfg, const map_location &loc) const
Check if an ability is active.
Definition: abilities.cpp:422
bool ability_affects_self(const std::string &ability, const config &cfg, const map_location &loc) const
Check if an ability affects the owning unit.
Definition: abilities.cpp:531
std::vector< const config * > open_queries_
While processing a recursive match, all the filters that are currently being checked,...
Definition: unit.hpp:2073
bool get_ability_bool(const std::string &tag_name, const map_location &loc) const
Checks whether this unit currently possesses or is affected by a given ability.
Definition: abilities.cpp:183
bool ability_affects_adjacent(const std::string &ability, const config &cfg, int dir, const map_location &loc, const unit &from) const
Check if an ability affects adjacent units.
Definition: abilities.cpp:504
std::vector< std::tuple< std::string, t_string, t_string, t_string > > ability_tooltips() const
Gets the names and descriptions of this unit's abilities.
Definition: abilities.cpp:321
config abilities_
Definition: unit.hpp:2101
unit_ability_list get_abilities_weapons(const std::string &tag_name, const map_location &loc, const_attack_ptr weapon=nullptr, const_attack_ptr opp_weapon=nullptr) const
Definition: abilities.cpp:255
int side_
Definition: unit.hpp:2031
bool has_ability_type(const std::string &ability) const
Check if the unit has an ability of a specific type.
Definition: abilities.cpp:558
unit_ability_list get_abilities(const std::string &tag_name, const map_location &loc) const
Gets the unit's active abilities of a particular type if it were on a specified location.
Definition: abilities.cpp:218
unit_race::GENDER gender_
Definition: unit.hpp:2033
bool get_self_ability_bool_weapon(const config &special, const std::string &tag_name, const map_location &loc, const const_attack_ptr &weapon=nullptr, const const_attack_ptr &opp_weapon=nullptr) const
Checks whether this unit currently possesses a given ability of leadership type.
Definition: abilities.cpp:1738
recursion_guard()
Construct an empty instance, only useful for extending the lifetime of a recursion_guard returned fro...
bool get_self_ability_bool(const config &cfg, const std::string &ability, const map_location &loc) const
Checks whether this unit currently possesses a given ability, and that that ability is active.
Definition: abilities.cpp:1717
std::vector< std::string > get_ability_list() const
Get a list of all abilities by ID.
Definition: abilities.cpp:264
bool ability_active_impl(const std::string &ability, const config &cfg, const map_location &loc) const
Check if an ability is active.
Definition: abilities.cpp:432
bool get_adj_ability_bool_weapon(const config &special, const std::string &tag_name, int dir, const map_location &loc, const unit &from, const const_attack_ptr &weapon=nullptr, const const_attack_ptr &opp_weapon=nullptr) const
Checks whether this unit is affected by a given ability of leadership type.
Definition: abilities.cpp:1743
map_location loc_
Definition: unit.hpp:1994
recursion_guard update_variables_recursion(const config &ability) const
Definition: abilities.cpp:381
recursion_guard & operator=(recursion_guard &&)
Definition: abilities.cpp:406
const std::set< std::string > & checking_tags() const
Definition: unit.hpp:1809
bool get_adj_ability_bool(const config &cfg, const std::string &ability, int dir, const map_location &loc, const unit &from) const
Checks whether this unit is affected by a given ability, and that that ability is active.
Definition: abilities.cpp:1727
bool ability_matches_filter(const config &cfg, const std::string &tag_name, const config &filter) const
Verify what abilities attributes match with filter.
Definition: abilities.cpp:2096
bool ability_affects_weapon(const config &cfg, const const_attack_ptr &weapon, bool is_opp) const
filters the weapons that condition the use of abilities for combat ([resistance],[leadership] or abil...
Definition: abilities.cpp:539
const std::string & id() const
Gets this unit's id.
Definition: unit.hpp:380
int side() const
The side this unit belongs to.
Definition: unit.hpp:343
const t_string & name() const
Gets this unit's translatable display name.
Definition: unit.hpp:403
@ STATE_SLOWED
Definition: unit.hpp:860
@ STATE_POISONED
The unit is slowed - it moves slower and does less damage.
Definition: unit.hpp:861
int resistance_value(unit_ability_list resistance_list, const std::string &damage_name) const
For the provided list of resistance abilities, determine the damage resistance based on which are act...
Definition: unit.cpp:1756
std::vector< std::string > halo_or_icon_abilities(const std::string &image_type) const
Definition: abilities.cpp:573
New lexcical_cast header.
void get_adjacent_tiles(const map_location &a, map_location *res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:479
Standard logging facilities (interface).
CURSOR_TYPE get()
Definition: cursor.cpp:216
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
Definition: cursor.cpp:176
const color_t inactive_details_color
const color_t BUTTON_COLOR
static bool is_active(const widget *wgt)
Definition: window.cpp:1267
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:520
std::string span_color(const color_t &color, Args &&... data)
Definition: markup.hpp:68
game_board * gameboard
Definition: resources.cpp:20
filter_context * filter_con
Definition: resources.cpp:23
bool filter_base_matches(const config &cfg, int def)
Definition: abilities.cpp:2411
std::size_t index(std::string_view str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
Utility functions for implementing [filter], [filter_ability], [filter_weapon], etc.
bool int_matches_if_present(const config &filter, const config &cfg, const std::string &attribute, utils::optional< int > def=utils::nullopt)
bool set_includes_if_present(const config &filter, const config &cfg, const std::string &attribute)
filter[attribute] and cfg[attribute] are assumed to be comma-separated lists.
bool double_matches_if_present(const config &filter, const config &cfg, const std::string &attribute, utils::optional< double > def=utils::nullopt)
Checks whether the filter matches the value of cfg[attribute].
bool int_matches_if_present_or_negative(const config &filter, const config &cfg, const std::string &attribute, const std::string &opposite, utils::optional< int > def=utils::nullopt)
Supports filters using "add" and "sub" attributes, for example a filter add=1 matching a cfg containi...
bool string_matches_if_present(const config &filter, const config &cfg, const std::string &attribute, const std::string &def)
bool bool_or_empty(const config &filter, const config &cfg, const std::string &attribute)
bool bool_matches_if_present(const config &filter, const config &cfg, const std::string &attribute, bool def)
Checks whether the filter matches the value of cfg[attribute].
void trim(std::string_view &s)
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:86
std::vector< std::pair< int, int > > parse_ranges_unsigned(const std::string &str)
Handles a comma-separated list of inputs to parse_range, in a context that does not expect negative v...
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition: general.hpp:106
void sort_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::sort on a container.
Definition: general.hpp:130
std::vector< std::string > split(const config_attribute_value &val)
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:140
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
std::shared_ptr< const unit > unit_const_ptr
Definition: ptr.hpp:27
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:34
std::shared_ptr< unit > unit_ptr
Definition: ptr.hpp:26
const config::attribute_value & gender_value(const config &cfg, unit_race::GENDER gender, const std::string &male_key, const std::string &female_key, const std::string &default_key)
Chooses a value from the given config based on gender.
Definition: race.cpp:158
Encapsulates the map of the game.
Definition: location.hpp:45
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
bool valid() const
Definition: location.hpp:110
direction
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:47
direction get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition: location.cpp:240
static const map_location & null_location()
Definition: location.hpp:102
void set(value_modifier t, int val, const config *abil, const map_location &l)
Definition: abilities.cpp:2403
Data typedef for unit_ability_list.
Definition: unit.hpp:38
map_location student_loc
Used by the formula in the ability.
Definition: unit.hpp:52
const config * ability_cfg
The contents of the ability tag, never nullptr.
Definition: unit.hpp:59
map_location teacher_loc
The location of the teacher, that is the unit who owns the ability tags (different from student becau...
Definition: unit.hpp:57
bool valid() const
Definition: map.hpp:273
pointer get_shared_ptr() const
This is exactly the same as operator-> but it's slightly more readable, and can replace &*iter syntax...
Definition: map.hpp:217
mock_char c
mock_party p
static map_location::direction s
#define d
#define e