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