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