The Battle for Wesnoth  1.19.13+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  std::vector<map_location::direction> dirs = map_location::parse_directions(i["adjacent"]);
583  if (std::find(dirs.begin(), dirs.end(), direction) == dirs.end()) {
584  continue;
585  }
586  }
587  auto filter = i.optional_child("filter");
588  if (!filter || //filter tag given
589  unit_filter(vconfig(*filter)).set_use_flat_tod(illuminates).matches(*this, loc, from) ) {
590  return true;
591  }
592  }
593  return false;
594 }
595 
596 bool unit::ability_affects_self(const std::string& ability,const config& cfg,const map_location& loc) const
597 {
598  auto filter = cfg.optional_child("filter_self");
599  bool affect_self = cfg["affect_self"].to_bool(true);
600  if (!filter || !affect_self) return affect_self;
601  return unit_filter(vconfig(*filter)).set_use_flat_tod(ability == "illuminates").matches(*this, loc);
602 }
603 
604 bool unit::ability_affects_weapon(const config& cfg, const const_attack_ptr& weapon, bool is_opp) const
605 {
606  const std::string filter_tag_name = is_opp ? "filter_second_weapon" : "filter_weapon";
607  if(!cfg.has_child(filter_tag_name)) {
608  return true;
609  }
610  const config& filter = cfg.mandatory_child(filter_tag_name);
611  if(!weapon) {
612  return false;
613  }
614  attack_type::recursion_guard filter_lock;
615  filter_lock = weapon->update_variables_recursion(cfg);
616  if(!filter_lock) {
617  show_recursion_warning(*this, cfg);
618  return false;
619  }
620  return weapon->matches_filter(filter);
621 }
622 
623 bool unit::has_ability_type(const std::string& ability) const
624 {
625  return !abilities_.child_range(ability).empty();
626 }
627 
628 //these two functions below are used in order to add to the unit
629 //a second set of halo encoded in the abilities (like illuminates halo in [illuminates] ability for example)
630 static void add_string_to_vector(std::vector<std::string>& image_list, const config& cfg, const std::string& attribute_name)
631 {
632  auto ret = std::find(image_list.begin(), image_list.end(), cfg[attribute_name].str());
633  if(ret == image_list.end()){
634  image_list.push_back(cfg[attribute_name].str());
635  }
636 }
637 
638 std::vector<std::string> unit::halo_or_icon_abilities(const std::string& image_type) const
639 {
640  std::vector<std::string> image_list;
641  for(const auto [key, cfg] : abilities_.all_children_view()){
642  bool is_active = ability_active(key, cfg, loc_);
643  //Add halo/overlay to owner of ability if active and affect_self is true.
644  if( !cfg[image_type + "_image"].str().empty() && is_active && ability_affects_self(key, cfg, loc_)){
645  add_string_to_vector(image_list, cfg,image_type + "_image");
646  }
647  //Add halo/overlay to owner of ability who affect adjacent only if active.
648  if(!cfg[image_type + "_image_self"].str().empty() && is_active){
649  add_string_to_vector(image_list, cfg, image_type + "_image_self");
650  }
651  }
652 
653  const unit_map& units = get_unit_map();
654 
655  for(const unit& u : units) {
656  if(!u.has_ability_distant_image() || u.incapacitated() || same_unit(u, *this)) {
657  continue;
658  }
659  const map_location& from_loc = u.get_location();
660  std::size_t distance = distance_between(from_loc, loc_);
661  if(distance > *u.has_ability_distant_image()) {
662  continue;
663  }
664  int dir = find_direction(loc_, from_loc, distance);
665  for(const auto [key, cfg] : u.abilities_.all_children_view()) {
666  if(!cfg[image_type + "_image"].str().empty() && get_adj_ability_bool(cfg, key, distance, dir, loc_, u, from_loc))
667  {
668  add_string_to_vector(image_list, cfg, image_type + "_image");
669  }
670  }
671  }
672  //rearranges vector alphabetically when its size equals or exceeds two.
673  if(image_list.size() >= 2){
674  std::sort(image_list.begin(), image_list.end());
675  }
676  return image_list;
677 }
678 
680 {
681  if(unit_const_ptr & att = is_attacker_ ? self_ : other_) {
682  callable.add("attacker", wfl::variant(std::make_shared<wfl::unit_callable>(*att)));
683  }
684  if(unit_const_ptr & def = is_attacker_ ? other_ : self_) {
685  callable.add("defender", wfl::variant(std::make_shared<wfl::unit_callable>(*def)));
686  }
687 }
688 
689 namespace {
690 
691 
692 template<typename T, typename TFuncFormula>
693 class get_ability_value_visitor
694 #ifdef USING_BOOST_VARIANT
695  : public boost::static_visitor<T>
696 #endif
697 {
698 public:
699  // Constructor stores the default value.
700  get_ability_value_visitor(T def, const TFuncFormula& formula_handler) : def_(def), formula_handler_(formula_handler) {}
701 
702  T operator()(const utils::monostate&) const { return def_; }
703  T operator()(bool) const { return def_; }
704  T operator()(int i) const { return static_cast<T>(i); }
705  T operator()(unsigned long long u) const { return static_cast<T>(u); }
706  T operator()(double d) const { return static_cast<T>(d); }
707  T operator()(const t_string&) const { return def_; }
708  T operator()(const std::string& s) const
709  {
710  if(s.size() >= 2 && s[0] == '(') {
711  return formula_handler_(s);
712  }
713  return lexical_cast_default<T>(s, def_);
714  }
715 
716 private:
717  const T def_;
718  const TFuncFormula& formula_handler_;
719 };
720 
721 template<typename T, typename TFuncFormula>
722 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)
723 {
724  return v.apply_visitor(get_ability_value_visitor(def, [&](const std::string& s) {
725 
726  try {
727  const unit_map& units = get_unit_map();
728 
729  auto u_itor = units.find(ability_info.teacher_loc);
730 
731  if(u_itor == units.end()) {
732  return def;
733  }
734  wfl::map_formula_callable callable(std::make_shared<wfl::unit_callable>(*u_itor));
735  if(att) {
736  att->add_formula_context(callable);
737  }
738  if (auto uptr = units.find_unit_ptr(ability_info.student_loc)) {
739  callable.add("student", wfl::variant(std::make_shared<wfl::unit_callable>(*uptr)));
740  }
741  if (auto uptr = units.find_unit_ptr(receiver_loc)) {
742  callable.add("other", wfl::variant(std::make_shared<wfl::unit_callable>(*uptr)));
743  }
744  return formula_handler(wfl::formula(s, new wfl::gamestate_function_symbol_table, true), callable);
745  } catch(const wfl::formula_error& e) {
746  lg::log_to_chat() << "Formula error in ability or weapon special: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
747  ERR_WML << "Formula error in ability or weapon special: " << e.type << " at " << e.filename << ':' << e.line << ")";
748  return def;
749  }
750  }));
751 }
752 }
753 
754 template<typename TComp>
755 std::pair<int,map_location> unit_ability_list::get_extremum(const std::string& key, int def, const TComp& comp) const
756 {
757  if ( cfgs_.empty() ) {
758  return std::pair(def, map_location());
759  }
760  // The returned location is the best non-cumulative one, if any,
761  // the best absolute cumulative one otherwise.
762  map_location best_loc;
763  bool only_cumulative = true;
764  int abs_max = 0;
765  int flat = 0;
766  int stack = 0;
767  for (const unit_ability& p : cfgs_)
768  {
769  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) {
770  return std::round(formula.evaluate(callable).as_int());
771  }));
772 
773  if ((*p.ability_cfg)["cumulative"].to_bool()) {
774  stack += value;
775  if (value < 0) value = -value;
776  if (only_cumulative && !comp(value, abs_max)) {
777  abs_max = value;
778  best_loc = p.teacher_loc;
779  }
780  } else if (only_cumulative || comp(flat, value)) {
781  only_cumulative = false;
782  flat = value;
783  best_loc = p.teacher_loc;
784  }
785  }
786  return std::pair(flat + stack, best_loc);
787 }
788 
789 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;
790 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;
791 
792 /*
793  *
794  * [special]
795  * [swarm]
796  * name= _ "swarm"
797  * name_inactive= _ ""
798  * description= _ ""
799  * description_inactive= _ ""
800  * cumulative=no
801  * apply_to=self #self,opponent,defender,attacker,both
802  * #active_on=defense # or offense; omitting this means "both"
803  *
804  * swarm_attacks_max=4
805  * swarm_attacks_min=2
806  *
807  * [filter_self] // SUF
808  * ...
809  * [/filter_self]
810  * [filter_opponent] // SUF
811  * [filter_attacker] // SUF
812  * [filter_defender] // SUF
813  * [filter_adjacent] // SAUF
814  * [filter_adjacent_location] // SAUF + locs
815  * [/swarm]
816  * [/special]
817  *
818  */
819 
820 /**
821  * Returns whether or not @a *this has a special with a tag or id equal to
822  * @a special. If @a simple_check is set to true, then the check is merely
823  * for being present. Otherwise (the default), the check is for a special
824  * active in the current context (see set_specials_context), including
825  * specials obtained from the opponent's attack.
826  */
827 bool attack_type::has_special(const std::string& special, bool simple_check) const
828 {
829  if(simple_check && specials().has_child(special)) {
830  return true;
831  }
832 
833  for(const config &i : specials().child_range(special)) {
834  if(special_active(i, AFFECT_SELF, special)) {
835  return true;
836  }
837  }
838 
839  // Skip checking the opponent's attack?
840  if ( simple_check || !other_attack_ ) {
841  return false;
842  }
843 
844  for(const config &i : other_attack_->specials().child_range(special)) {
845  if(other_attack_->special_active(i, AFFECT_OTHER, special)) {
846  return true;
847  }
848  }
849  return false;
850 }
851 
852 /**
853  * Returns the currently active specials as an ability list, given the current
854  * context (see set_specials_context).
855  */
856 unit_ability_list attack_type::get_specials(const std::string& special) const
857 {
858  //log_scope("get_specials");
859  const map_location loc = self_ ? self_->get_location() : self_loc_;
860  unit_ability_list res(loc);
861 
862  for(const config& i : specials_.child_range(special)) {
863  if(special_active(i, AFFECT_SELF, special)) {
864  res.emplace_back(&i, loc, loc);
865  }
866  }
867 
868  if(!other_attack_) {
869  return res;
870  }
871 
872  for(const config& i : other_attack_->specials_.child_range(special)) {
873  if(other_attack_->special_active(i, AFFECT_OTHER, special)) {
874  res.emplace_back(&i, other_loc_, other_loc_);
875  }
876  }
877  return res;
878 }
879 
880 /**
881  * Returns a vector of names and descriptions for the specials of *this.
882  * Each std::pair in the vector has first = name and second = description.
883  *
884  * This uses either the active or inactive name/description for each special,
885  * based on the current context (see set_specials_context), provided
886  * @a active_list is not nullptr. Otherwise specials are assumed active.
887  * If the appropriate name is empty, the special is skipped.
888  */
889 std::vector<std::pair<t_string, t_string>> attack_type::special_tooltips(
890  boost::dynamic_bitset<>* active_list) const
891 {
892  //log_scope("special_tooltips");
893  std::vector<std::pair<t_string, t_string>> res;
894  if(active_list) {
895  active_list->clear();
896  }
897 
898  for(const auto [key, cfg] : specials_.all_children_view()) {
899  bool active = !active_list || special_active(cfg, AFFECT_EITHER, key);
900 
901  std::string name = active
902  ? cfg["name"].str()
903  : cfg.get_or("name_inactive", "name").str();
904 
905  if(name.empty()) {
906  continue;
907  }
908 
909  std::string desc = active
910  ? cfg["description"].str()
911  : cfg.get_or("description_inactive", "description").str();
912 
913  res.emplace_back(
916  );
917 
918  if(active_list) {
919  active_list->push_back(active);
920  }
921  }
922  return res;
923 }
924 
925 std::vector<std::pair<t_string, t_string>> attack_type::abilities_special_tooltips(
926  boost::dynamic_bitset<>* active_list) const
927 {
928  std::vector<std::pair<t_string, t_string>> res;
929  if(active_list) {
930  active_list->clear();
931  }
932  std::set<std::string> checking_name;
933  if(!self_) {
934  return res;
935  }
936  for(const auto [key, cfg] : self_->abilities().all_children_view()) {
937  if(!active_list || check_self_abilities_impl(shared_from_this(), other_attack_, cfg, self_, self_loc_, AFFECT_SELF, key, false)) {
938  const std::string name = cfg["name_affected"];
939  const std::string desc = cfg["description_affected"];
940 
941  if(name.empty() || checking_name.count(name) != 0) {
942  continue;
943  }
944  res.emplace_back(name, desc);
945  checking_name.insert(name);
946  if(active_list) {
947  active_list->push_back(true);
948  }
949  }
950  }
951  for(const unit& u : get_unit_map()) {
952  if(!u.has_ability_distant() || u.incapacitated() || same_unit(u, *self_)) {
953  continue;
954  }
955  const map_location& from_loc = u.get_location();
956  std::size_t distance = distance_between(from_loc, self_loc_);
957  if(distance > *u.has_ability_distant()) {
958  continue;
959  }
960  int dir = find_direction(self_loc_, from_loc, distance);
961  for(const auto [key, cfg] : u.abilities().all_children_view()) {
962  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)) {
963  const std::string name = cfg["name_affected"];
964  const std::string desc = cfg["description_affected"];
965 
966  if(name.empty() || checking_name.count(name) != 0) {
967  continue;
968  }
969  res.emplace_back(name, desc);
970  checking_name.insert(name);
971  if(active_list) {
972  active_list->push_back(true);
973  }
974  }
975  }
976  }
977  return res;
978 }
979 
980 /**
981  * static used in weapon_specials (bool only_active) and
982  * @return a string and a set_string for the weapon_specials function below.
983  * @param[in,out] temp_string the string modified and returned
984  * @param[in] active the boolean for determine if @name can be added or not
985  * @param[in] name string who must be or not added
986  * @param[in,out] checking_name the reference for checking if @name already added
987  */
988 static void add_name(std::string& temp_string, bool active, const std::string& name, std::set<std::string>& checking_name)
989 {
990  if (active && !name.empty() && checking_name.count(name) == 0) {
991  checking_name.insert(name);
992  if (!temp_string.empty()) temp_string += ", ";
993  temp_string += markup::span_color(font::TITLE_COLOR, name);
994  }
995 }
996 
997 /**
998  * Returns a comma-separated string of active names for the specials of *this.
999  * Empty names are skipped.
1000  *
1001  * Whether or not a special is active depends
1002  * on the current context (see set_specials_context)
1003  */
1004 std::string attack_type::weapon_specials() const
1005 {
1006  //log_scope("weapon_specials");
1007  std::vector<std::string> specials;
1008 
1009  for(const auto [key, cfg] : specials_.all_children_view()) {
1010  const bool active = special_active(cfg, AFFECT_EITHER, key);
1011 
1012  std::string name = active
1013  ? cfg["name"].str()
1014  : cfg.get_or("name_inactive", "name").str();
1015 
1016  if(name.empty()) {
1017  continue;
1018  }
1019 
1021 
1022  specials.push_back(active ? std::move(name) : markup::span_color(font::INACTIVE_COLOR, name));
1023  }
1024 
1025  // FIXME: clean this up...
1026  std::string temp_string;
1027  std::set<std::string> checking_name;
1028  weapon_specials_impl_self(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name);
1029  weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, {}, "affect_allies");
1030 
1031  if(!temp_string.empty()) {
1032  specials.push_back("\n" + std::move(temp_string));
1033  }
1034 
1035  return utils::join(specials, ", ");
1036 }
1037 
1038 static void add_name_list(std::string& temp_string, std::string& weapon_abilities, std::set<std::string>& checking_name, const std::string& from_str)
1039 {
1040  if(!temp_string.empty()){
1041  temp_string = from_str.c_str() + temp_string;
1042  weapon_abilities += (!weapon_abilities.empty() && !temp_string.empty()) ? "\n" : "";
1043  weapon_abilities += temp_string;
1044  temp_string.clear();
1045  checking_name.clear();
1046  }
1047 }
1048 
1049 std::string attack_type::weapon_specials_value(const std::set<std::string>& checking_tags) const
1050 {
1051  //log_scope("weapon_specials_value");
1052  std::string temp_string, weapon_abilities;
1053  std::set<std::string> checking_name;
1054  for(const auto [key, cfg] : specials_.all_children_view()) {
1055  if(checking_tags.count(key) != 0) {
1056  const bool active = special_active(cfg, AFFECT_SELF, key);
1057  add_name(temp_string, active, cfg["name"].str(), checking_name);
1058  }
1059  }
1060  add_name_list(temp_string, weapon_abilities, checking_name, "");
1061 
1062  weapon_specials_impl_self(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, checking_tags, true);
1063  add_name_list(temp_string, weapon_abilities, checking_name, _("Owned: "));
1064 
1065  weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, checking_tags, "affect_allies", true);
1066  // TRANSLATORS: Past-participle of "teach", used for an ability similar to leadership
1067  add_name_list(temp_string, weapon_abilities, checking_name, _("Taught: "));
1068 
1069  weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, checking_tags, "affect_enemies", true);
1070  // TRANSLATORS: Past-participle of "teach", used for an ability similar to leadership
1071  add_name_list(temp_string, weapon_abilities, checking_name, _("Taught: (by an enemy): "));
1072 
1073 
1074  if(other_attack_) {
1075  for(const auto [key, cfg] : other_attack_->specials_.all_children_view()) {
1076  if((checking_tags.count(key) != 0)){
1077  const bool active = other_attack_->special_active(cfg, AFFECT_OTHER, key);
1078  add_name(temp_string, active, cfg["name"].str(), checking_name);
1079  }
1080  }
1081  }
1082  weapon_specials_impl_self(temp_string, other_, other_attack_, shared_from_this(), other_loc_, AFFECT_OTHER, checking_name, checking_tags);
1083  weapon_specials_impl_adj(temp_string, other_, other_attack_, shared_from_this(), other_loc_, AFFECT_OTHER, checking_name, checking_tags);
1084  add_name_list(temp_string, weapon_abilities, checking_name, _("Used by opponent: "));
1085 
1086  return weapon_abilities;
1087 }
1088 
1090  std::string& temp_string,
1091  const unit_const_ptr& self,
1092  const const_attack_ptr& self_attack,
1093  const const_attack_ptr& other_attack,
1094  const map_location& self_loc,
1095  AFFECTS whom,
1096  std::set<std::string>& checking_name,
1097  const std::set<std::string>& checking_tags,
1098  bool leader_bool)
1099 {
1100  if(self){
1101  for(const auto [key, cfg] : self->abilities().all_children_view()){
1102  bool tag_checked = (!checking_tags.empty()) ? (checking_tags.count(key) != 0) : true;
1103  const bool active = tag_checked && check_self_abilities_impl(self_attack, other_attack, cfg, self, self_loc, whom, key, leader_bool);
1104  add_name(temp_string, active, cfg.get_or("name_affected", "name").str(), checking_name);
1105  }
1106  }
1107 }
1108 
1110  std::string& temp_string,
1111  const unit_const_ptr& self,
1112  const const_attack_ptr& self_attack,
1113  const const_attack_ptr& other_attack,
1114  const map_location& self_loc,
1115  AFFECTS whom,
1116  std::set<std::string>& checking_name,
1117  const std::set<std::string>& checking_tags,
1118  const std::string& affect_adjacents,
1119  bool leader_bool)
1120 {
1121  const unit_map& units = get_unit_map();
1122  if(self){
1123  for(const unit& u : units) {
1124  if(!u.has_ability_distant() || u.incapacitated() || same_unit(u, *self)) {
1125  continue;
1126  }
1127  const map_location& from_loc = u.get_location();
1128  std::size_t distance = distance_between(from_loc, self_loc);
1129  if(distance > *u.has_ability_distant()) {
1130  continue;
1131  }
1132  int dir = find_direction(self_loc, from_loc, distance);
1133  for(const auto [key, cfg] : u.abilities().all_children_view()) {
1134  bool tag_checked = !checking_tags.empty() ? checking_tags.count(key) != 0 : true;
1135  bool default_bool = affect_adjacents == "affect_allies" ? true : false;
1136  bool affect_allies = !affect_adjacents.empty() ? cfg[affect_adjacents].to_bool(default_bool) : true;
1137  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;
1138  add_name(temp_string, active, cfg.get_or("name_affected", "name").str(), checking_name);
1139  }
1140  }
1141  }
1142 }
1143 
1144 
1145 /**
1146  * Sets the context under which specials will be checked for being active.
1147  * This version is appropriate if both units in a combat are known.
1148  * @param[in] weapon The weapon being considered.
1149  * @param[in] self A reference to the unit with this weapon.
1150  * @param[in] other A reference to the other unit in the combat.
1151  * @param[in] unit_loc The location of the unit with this weapon.
1152  * @param[in] other_loc The location of the other unit in the combat.
1153  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
1154  * @param[in] other_attack The attack used by the other unit.
1155  */
1157  const attack_type& weapon,
1158  const_attack_ptr other_attack,
1159  unit_const_ptr self,
1160  unit_const_ptr other,
1161  const map_location& unit_loc,
1162  const map_location& other_loc,
1163  bool attacking)
1164  : parent(weapon.shared_from_this())
1165 {
1166  weapon.self_ = std::move(self);
1167  weapon.other_ = std::move(other);
1168  weapon.self_loc_ = unit_loc;
1169  weapon.other_loc_ = other_loc;
1170  weapon.is_attacker_ = attacking;
1171  weapon.other_attack_ = std::move(other_attack);
1172  weapon.is_for_listing_ = false;
1173 }
1174 
1175 /**
1176  * Sets the context under which specials will be checked for being active.
1177  * This version is appropriate if there is no specific combat being considered.
1178  * @param[in] weapon The weapon being considered.
1179  * @param[in] self A reference to the unit with this weapon.
1180  * @param[in] loc The location of the unit with this weapon.
1181  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
1182  */
1184  : parent(weapon.shared_from_this())
1185 {
1186  weapon.self_ = std::move(self);
1187  weapon.other_ = unit_ptr();
1188  weapon.self_loc_ = loc;
1190  weapon.is_attacker_ = attacking;
1191  weapon.other_attack_ = nullptr;
1192  weapon.is_for_listing_ = false;
1193 }
1194 
1195 /**
1196  * Sets the context under which specials will be checked for being active.
1197  * This version is appropriate for theoretical units of a particular type.
1198  * @param[in] weapon The weapon being considered.
1199  * @param[in] self_type A reference to the type of the unit with this weapon.
1200  * @param[in] loc The location of the unit with this weapon.
1201  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
1202  */
1203 attack_type::specials_context_t::specials_context_t(const attack_type& weapon, const unit_type& /*self_type*/, const map_location& loc, bool attacking)
1204  : parent(weapon.shared_from_this())
1205 {
1206  weapon.self_ = unit_ptr();
1207  weapon.other_ = unit_ptr();
1208  weapon.self_loc_ = loc;
1210  weapon.is_attacker_ = attacking;
1211  weapon.other_attack_ = nullptr;
1212  weapon.is_for_listing_ = false;
1213 }
1214 
1216  : parent(weapon.shared_from_this())
1217 {
1218  weapon.is_for_listing_ = true;
1219  weapon.is_attacker_ = attacking;
1220 }
1221 
1223 {
1224  if(was_moved) return;
1225  parent->self_ = unit_ptr();
1226  parent->other_ = unit_ptr();
1227  parent->self_loc_ = map_location::null_location();
1228  parent->other_loc_ = map_location::null_location();
1229  parent->is_attacker_ = false;
1230  parent->other_attack_ = nullptr;
1231  parent->is_for_listing_ = false;
1232 }
1233 
1235  : parent(std::move(other.parent))
1236 {
1237  other.was_moved = true;
1238 }
1239 
1240 /**
1241  * Calculates the number of attacks this weapon has, considering specials.
1242  * This returns two numbers because of the swarm special. The actual number of
1243  * attacks depends on the unit's health and should be:
1244  * min_attacks + (max_attacks - min_attacks) * (current hp) / (max hp)
1245  * c.f. swarm_blows()
1246  */
1247 void attack_type::modified_attacks(unsigned & min_attacks,
1248  unsigned & max_attacks) const
1249 {
1250  // Apply [attacks].
1251  int attacks_value = composite_value(get_specials_and_abilities("attacks"), num_attacks());
1252 
1253  if ( attacks_value < 0 ) {
1254  attacks_value = 0;
1255  ERR_NG << "negative number of strikes after applying weapon specials";
1256  }
1257 
1258  // Apply [swarm].
1259  unit_ability_list swarm_specials = get_specials_and_abilities("swarm");
1260  if ( !swarm_specials.empty() ) {
1261  min_attacks = std::max<int>(0, swarm_specials.highest("swarm_attacks_min").first);
1262  max_attacks = std::max<int>(0, swarm_specials.highest("swarm_attacks_max", attacks_value).first);
1263  } else {
1264  min_attacks = max_attacks = attacks_value;
1265  }
1266 }
1267 
1268 std::string attack_type::select_replacement_type(const unit_ability_list& damage_type_list) const
1269 {
1270  std::map<std::string, unsigned int> type_count;
1271  unsigned int max = 0;
1272  for(auto& i : damage_type_list) {
1273  const config& c = *i.ability_cfg;
1274  if(c.has_attribute("replacement_type")) {
1275  std::string type = c["replacement_type"].str();
1276  unsigned int count = ++type_count[type];
1277  if((count > max)) {
1278  max = count;
1279  }
1280  }
1281  }
1282 
1283  if (type_count.empty()) return type();
1284 
1285  std::vector<std::string> type_list;
1286  for(auto& i : type_count){
1287  if(i.second == max){
1288  type_list.push_back(i.first);
1289  }
1290  }
1291 
1292  if(type_list.empty()) return type();
1293 
1294  return type_list.front();
1295 }
1296 
1297 std::pair<std::string, int> attack_type::select_alternative_type(const unit_ability_list& damage_type_list, const unit_ability_list& resistance_list) const
1298 {
1299  std::map<std::string, int> type_res;
1300  int max_res = INT_MIN;
1301  if(other_){
1302  for(auto& i : damage_type_list) {
1303  const config& c = *i.ability_cfg;
1304  if(c.has_attribute("alternative_type")) {
1305  std::string type = c["alternative_type"].str();
1306  if(type_res.count(type) == 0){
1307  type_res[type] = (*other_).resistance_value(resistance_list, type);
1308  max_res = std::max(max_res, type_res[type]);
1309  }
1310  }
1311  }
1312  }
1313 
1314  if (type_res.empty()) return {"", INT_MIN};
1315 
1316  std::vector<std::string> type_list;
1317  for(auto& i : type_res){
1318  if(i.second == max_res){
1319  type_list.push_back(i.first);
1320  }
1321  }
1322  if(type_list.empty()) return {"", INT_MIN};
1323 
1324  return {type_list.front(), max_res};
1325 }
1326 
1327 /**
1328  * The type of attack used and the resistance value that does the most damage.
1329  */
1330 std::pair<std::string, int> attack_type::effective_damage_type() const
1331 {
1332  if(attack_empty()){
1333  return {"", 100};
1334  }
1335  unit_ability_list resistance_list;
1336  if(other_){
1337  resistance_list = (*other_).get_abilities_weapons("resistance", other_loc_, other_attack_, shared_from_this());
1338  utils::erase_if(resistance_list, [&](const unit_ability& i) {
1339  return (!((*i.ability_cfg)["active_on"].empty() || (!is_attacker_ && (*i.ability_cfg)["active_on"] == "offense") || (is_attacker_ && (*i.ability_cfg)["active_on"] == "defense")));
1340  });
1341  }
1342  unit_ability_list damage_type_list = get_specials_and_abilities("damage_type");
1343  int res = other_ ? (*other_).resistance_value(resistance_list, type()) : 100;
1344  if(damage_type_list.empty()){
1345  return {type(), res};
1346  }
1347  std::string replacement_type = select_replacement_type(damage_type_list);
1348  std::pair<std::string, int> alternative_type = select_alternative_type(damage_type_list, resistance_list);
1349 
1350  if(other_){
1351  res = replacement_type != type() ? (*other_).resistance_value(resistance_list, replacement_type) : res;
1352  replacement_type = alternative_type.second > res ? alternative_type.first : replacement_type;
1353  res = std::max(res, alternative_type.second);
1354  }
1355  return {replacement_type, res};
1356 }
1357 
1358 /**
1359  * Return a type()/replacement_type and a list of alternative_types that should be displayed in the selected unit's report.
1360  */
1361 std::pair<std::string, std::set<std::string>> attack_type::damage_types() const
1362 {
1363  unit_ability_list damage_type_list = get_specials_and_abilities("damage_type");
1364  std::set<std::string> alternative_damage_types;
1365  if(damage_type_list.empty()){
1366  return {type(), alternative_damage_types};
1367  }
1368  std::string replacement_type = select_replacement_type(damage_type_list);
1369  for(auto& i : damage_type_list) {
1370  const config& c = *i.ability_cfg;
1371  if(c.has_attribute("alternative_type")){
1372  alternative_damage_types.insert(c["alternative_type"].str());
1373  }
1374  }
1375 
1376  return {replacement_type, alternative_damage_types};
1377 }
1378 
1379 /**
1380  * Returns the damage per attack of this weapon, considering specials.
1381  */
1383 {
1384  double damage_value = unit_abilities::effect(get_specials_and_abilities("damage"), damage(), shared_from_this()).get_composite_double_value();
1385  return damage_value;
1386 }
1387 
1388 
1389 namespace { // Helpers for attack_type::special_active()
1390 
1391  /**
1392  * Returns whether or not the given special affects the opponent of the unit
1393  * with the special.
1394  * @param[in] special a weapon special WML structure
1395  * @param[in] is_attacker whether or not the unit with the special is the attacker
1396  */
1397  bool special_affects_opponent(const config& special, bool is_attacker)
1398  {
1399  //log_scope("special_affects_opponent");
1400  const std::string& apply_to = special["apply_to"];
1401  if ( apply_to.empty() )
1402  return false;
1403  if ( apply_to == "both" )
1404  return true;
1405  if ( apply_to == "opponent" )
1406  return true;
1407  if ( is_attacker && apply_to == "defender" )
1408  return true;
1409  if ( !is_attacker && apply_to == "attacker" )
1410  return true;
1411  return false;
1412  }
1413 
1414  /**
1415  * Returns whether or not the given special affects the unit with the special.
1416  * @param[in] special a weapon special WML structure
1417  * @param[in] is_attacker whether or not the unit with the special is the attacker
1418  */
1419  bool special_affects_self(const config& special, bool is_attacker)
1420  {
1421  //log_scope("special_affects_self");
1422  const std::string& apply_to = special["apply_to"];
1423  if ( apply_to.empty() )
1424  return true;
1425  if ( apply_to == "both" )
1426  return true;
1427  if ( apply_to == "self" )
1428  return true;
1429  if ( is_attacker && apply_to == "attacker" )
1430  return true;
1431  if ( !is_attacker && apply_to == "defender")
1432  return true;
1433  return false;
1434  }
1435 
1436  /**
1437  * Print "Recursion limit reached" log messages, including deduplication if the same problem has
1438  * already been logged.
1439  */
1440  void show_recursion_warning(const const_attack_ptr& attack, const config& filter) {
1441  // This function is only called when a special is checked for the second time
1442  // filter has already been parsed multiple times, so I'm not trying to optimize the performance
1443  // of this; it's merely to prevent the logs getting spammed. For example, each of
1444  // four_cycle_recursion_branching and event_test_filter_attack_student_weapon_condition only log
1445  // 3 unique messages, but without deduplication they'd log 1280 and 392 respectively.
1446  static std::vector<std::tuple<std::string, std::string>> already_shown;
1447 
1448  auto identifier = std::tuple<std::string, std::string>{attack->id(), filter.debug()};
1449  if(utils::contains(already_shown, identifier)) {
1450  return;
1451  }
1452 
1453  std::string_view filter_text_view = std::get<1>(identifier);
1454  utils::trim(filter_text_view);
1455  ERR_NG << "Looped recursion error for weapon '" << attack->id()
1456  << "' while checking weapon special '" << filter_text_view << "'";
1457 
1458  // Arbitrary limit, just ensuring that having a huge number of specials causing recursion
1459  // warnings can't lead to unbounded memory consumption here.
1460  if(already_shown.size() > 100) {
1461  already_shown.clear();
1462  }
1463  already_shown.push_back(std::move(identifier));
1464  }
1465 
1466  /**
1467  * Determines if a unit/weapon combination matches the specified child
1468  * (normally a [filter_*] child) of the provided filter.
1469  * @param[in] u A unit to filter.
1470  * @param[in] u2 Another unit to filter.
1471  * @param[in] loc The presumed location of @a unit.
1472  * @param[in] weapon The attack_type to filter.
1473  * @param[in] filter The filter containing the child filter to use.
1474  * @param[in] for_listing
1475  * @param[in] child_tag The tag of the child filter to use.
1476  * @param[in] check_if_recursion Parameter used for don't have infinite recusion for some filter attribute.
1477  */
1478  static bool special_unit_matches(unit_const_ptr & u,
1479  unit_const_ptr & u2,
1480  const map_location & loc,
1481  const const_attack_ptr& weapon,
1482  const config & filter,
1483  const bool for_listing,
1484  const std::string & child_tag, const std::string& check_if_recursion)
1485  {
1486  if (for_listing && !loc.valid())
1487  // The special's context was set to ignore this unit, so assume we pass.
1488  // (This is used by reports.cpp to show active specials when the
1489  // opponent is not known. From a player's perspective, the special
1490  // is active, in that it can be used, even though the player might
1491  // need to select an appropriate opponent.)
1492  return true;
1493 
1494  //Add wml filter if "backstab" attribute used.
1495  if (!filter["backstab"].blank() && child_tag == "filter_opponent") {
1496  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.");
1497  }
1498  config cfg = filter;
1499  if(filter["backstab"].to_bool() && child_tag == "filter_opponent"){
1500  const std::string& backstab_formula = "enemy_of(self, flanker) and not flanker.petrified where flanker = unit_at(direction_from(loc, other.facing))";
1501  config& filter_child = cfg.child_or_add("filter_opponent");
1502  if(!filter.has_child("filter_opponent")){
1503  filter_child["formula"] = backstab_formula;
1504  } else {
1505  config filter_opponent;
1506  filter_opponent["formula"] = backstab_formula;
1507  filter_child.add_child("and", filter_opponent);
1508  }
1509  }
1510  const config& filter_backstab = filter["backstab"].to_bool() ? cfg : filter;
1511 
1512  auto filter_child = filter_backstab.optional_child(child_tag);
1513  if ( !filter_child )
1514  // The special does not filter on this unit, so we pass.
1515  return true;
1516 
1517  // If the primary unit doesn't exist, there's nothing to match
1518  if (!u) {
1519  return false;
1520  }
1521 
1522  unit_filter ufilt{vconfig(*filter_child)};
1523 
1524  // If the other unit doesn't exist, try matching without it
1525 
1526 
1527  attack_type::recursion_guard filter_lock;
1528  if (weapon && (filter_child->optional_child("has_attack") || filter_child->optional_child("filter_weapon"))) {
1529  filter_lock = weapon->update_variables_recursion(filter);
1530  if(!filter_lock) {
1531  show_recursion_warning(weapon, filter);
1532  return false;
1533  }
1534  }
1535  // Check for a weapon match.
1536  if (auto filter_weapon = filter_child->optional_child("filter_weapon") ) {
1537  if ( !weapon || !weapon->matches_filter(*filter_weapon, check_if_recursion) )
1538  return false;
1539  }
1540 
1541  // Passed.
1542  // If the other unit doesn't exist, try matching without it
1543  if (!u2) {
1544  return ufilt.matches(*u, loc);
1545  }
1546  return ufilt.matches(*u, loc, *u2);
1547  }
1548 
1549 }//anonymous namespace
1550 
1551 
1552 //The following functions are intended to allow the use in combat of capacities
1553 //identical to special weapons and therefore to be able to use them on adjacent
1554 //units (abilities of type 'aura') or else on all types of weapons even if the
1555 //beneficiary unit does not have a corresponding weapon
1556 //(defense against ranged weapons abilities for a unit that only has melee attacks)
1557 
1558 unit_ability_list attack_type::get_weapon_ability(const std::string& ability) const
1559 {
1560  const map_location loc = self_ ? self_->get_location() : self_loc_;
1561  unit_ability_list abil_list(loc);
1562  if(self_) {
1563  abil_list.append_if((*self_).get_abilities(ability, self_loc_), [&](const unit_ability& i) {
1564  return special_active(*i.ability_cfg, AFFECT_SELF, ability, true);
1565  });
1566  }
1567 
1568  if(other_) {
1569  abil_list.append_if((*other_).get_abilities(ability, other_loc_), [&](const unit_ability& i) {
1570  return special_active_impl(other_attack_, shared_from_this(), *i.ability_cfg, AFFECT_OTHER, ability, true);
1571  });
1572  }
1573 
1574  return abil_list;
1575 }
1576 
1578 {
1579  // get all weapon specials of the provided type
1580  unit_ability_list abil_list = get_specials(special);
1581  // append all such weapon specials as abilities as well
1582  abil_list.append(get_weapon_ability(special));
1583  // get a list of specials/"specials as abilities" that may potentially overwrite others
1584  unit_ability_list overwriters = overwrite_special_overwriter(abil_list, special);
1585  if(!abil_list.empty() && !overwriters.empty()){
1586  // remove all abilities that would be overwritten
1587  utils::erase_if(abil_list, [&](const unit_ability& j) {
1588  return (overwrite_special_checking(overwriters, *j.ability_cfg, special));
1589  });
1590  }
1591  return abil_list;
1592 }
1593 
1594 int attack_type::composite_value(const unit_ability_list& abil_list, int base_value) const
1595 {
1596  return unit_abilities::effect(abil_list, base_value, shared_from_this()).get_composite_value();
1597 }
1598 
1599 static bool overwrite_special_affects(const config& special)
1600 {
1601  const std::string& apply_to = special["overwrite_specials"];
1602  return (apply_to == "one_side" || apply_to == "both_sides");
1603 }
1604 
1606 {
1607  //remove element without overwrite_specials key, if list empty after check return empty list.
1608  utils::erase_if(overwriters, [&](const unit_ability& i) {
1609  return (!overwrite_special_affects(*i.ability_cfg));
1610  });
1611 
1612  // if empty, nothing is doing any overwriting
1613  if(overwriters.empty()){
1614  return overwriters;
1615  }
1616 
1617  // if there are specials/"specials as abilities" that could potentially overwrite each other
1618  if(overwriters.size() >= 2){
1619  // sort them by overwrite priority from highest to lowest (default priority is 0)
1620  utils::sort_if(overwriters,[](const unit_ability& i, const unit_ability& j){
1621  auto oi = (*i.ability_cfg).optional_child("overwrite");
1622  double l = 0;
1623  if(oi && !oi["priority"].empty()){
1624  l = oi["priority"].to_double(0);
1625  }
1626  auto oj = (*j.ability_cfg).optional_child("overwrite");
1627  double r = 0;
1628  if(oj && !oj["priority"].empty()){
1629  r = oj["priority"].to_double(0);
1630  }
1631  return l > r;
1632  });
1633  // remove any that need to be overwritten
1634  utils::erase_if(overwriters, [&](const unit_ability& i) {
1635  return (overwrite_special_checking(overwriters, *i.ability_cfg, tag_name));
1636  });
1637  }
1638  return overwriters;
1639 }
1640 
1641 bool attack_type::overwrite_special_checking(unit_ability_list& overwriters, const config& cfg, const std::string& tag_name) const
1642 {
1643  if(overwriters.empty()){
1644  return false;
1645  }
1646 
1647  for(const auto& j : overwriters) {
1648  // whether the overwriter affects a single side
1649  bool affect_side = ((*j.ability_cfg)["overwrite_specials"] == "one_side");
1650  // the overwriter's priority, default of 0
1651  auto overwrite_specials = (*j.ability_cfg).optional_child("overwrite");
1652  double priority = overwrite_specials ? overwrite_specials["priority"].to_double(0) : 0.00;
1653  // the cfg being checked for whether it will be overwritten
1654  auto has_overwrite_specials = cfg.optional_child("overwrite");
1655  // if the overwriter's priority is greater than 0, then true if the cfg being checked has a higher priority
1656  // else true
1657  bool prior = (priority > 0) ? (has_overwrite_specials && has_overwrite_specials["priority"].to_double(0) >= priority) : true;
1658  // 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
1659  // aka whether the cfg being checked can potentially be overwritten by the current overwriter
1660  bool is_overwritable = (overwrite_special_affects(cfg) && !prior) || !overwrite_special_affects(cfg);
1661  bool one_side_overwritable = true;
1662 
1663  // if the current overwriter affects one side and the cfg being checked can be overwritten by this overwriter
1664  // then check that the current overwriter and the cfg being checked both affect either this unit or its opponent
1665  if(affect_side && is_overwritable){
1666  if(special_affects_self(*j.ability_cfg, is_attacker_)){
1667  one_side_overwritable = special_affects_self(cfg, is_attacker_);
1668  }
1669  else if(special_affects_opponent(*j.ability_cfg, !is_attacker_)){
1670  one_side_overwritable = special_affects_opponent(cfg, !is_attacker_);
1671  }
1672  }
1673 
1674  // check whether the current overwriter is disabled due to a filter
1675  bool special_matches = true;
1676  if(overwrite_specials){
1677  auto overwrite_filter = (*overwrite_specials).optional_child("filter_specials");
1678  if(!overwrite_filter){
1679  overwrite_filter = (*overwrite_specials).optional_child("experimental_filter_specials");
1680  if(overwrite_filter){
1681  deprecated_message("experimental_filter_specials", DEP_LEVEL::INDEFINITE, "", "Use filter_specials instead.");
1682  }
1683  }
1684  if(overwrite_filter && is_overwritable && one_side_overwritable){
1685  special_matches = special_matches_filter(cfg, tag_name, *overwrite_filter);
1686  }
1687  }
1688 
1689  // if the cfg being checked should be overwritten
1690  // and either this unit or its opponent are affected
1691  // and the current overwriter is not disabled due to a filter
1692  if(is_overwritable && one_side_overwritable && special_matches){
1693  return true;
1694  }
1695  }
1696  return false;
1697 }
1698 
1699 bool unit::get_self_ability_bool(const config& cfg, const std::string& ability, const map_location& loc) const
1700 {
1701  auto filter_lock = update_variables_recursion(cfg);
1702  if(!filter_lock) {
1703  show_recursion_warning(*this, cfg);
1704  return false;
1705  }
1706  return (ability_active_impl(ability, cfg, loc) && ability_affects_self(ability, cfg, loc));
1707 }
1708 
1709 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
1710 {
1711  auto filter_lock = from.update_variables_recursion(cfg);
1712  if(!filter_lock) {
1713  show_recursion_warning(from, cfg);
1714  return false;
1715  }
1716  return (affects_side(cfg, side(), from.side()) && from.ability_active_impl(ability, cfg, from_loc) && ability_affects_adjacent(ability, cfg, dist, dir, loc, from));
1717 }
1718 
1719 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
1720 {
1721  return (get_self_ability_bool(special, tag_name, loc) && ability_affects_weapon(special, weapon, false) && ability_affects_weapon(special, opp_weapon, true));
1722 }
1723 
1724 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
1725 {
1726  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));
1727 }
1728 
1729 bool attack_type::check_self_abilities(const config& cfg, const std::string& special) const
1730 {
1731  return check_self_abilities_impl(shared_from_this(), other_attack_, cfg, self_, self_loc_, AFFECT_SELF, special, true);
1732 }
1733 
1734 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)
1735 {
1736  if(tag_name == "leadership" && leader_bool){
1737  if(u->get_self_ability_bool_weapon(special, tag_name, loc, self_attack, other_attack)) {
1738  return true;
1739  }
1740  }
1741  if(u->checking_tags().count(tag_name) != 0){
1742  if(u->get_self_ability_bool(special, tag_name, loc) && special_active_impl(self_attack, other_attack, special, whom, tag_name, true)) {
1743  return true;
1744  }
1745  }
1746  return false;
1747 }
1748 
1749 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
1750 {
1751  return check_adj_abilities_impl(shared_from_this(), other_attack_, cfg, self_, from, dist, dir, self_loc_, from_loc, AFFECT_SELF, special, true);
1752 }
1753 
1754 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)
1755 {
1756  if(tag_name == "leadership" && leader_bool) {
1757  if(u->get_adj_ability_bool_weapon(special, tag_name, dist, dir, loc, from, from_loc, self_attack, other_attack)) {
1758  return true;
1759  }
1760  }
1761  if(u->checking_tags().count(tag_name) != 0) {
1762  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)) {
1763  return true;
1764  }
1765  }
1766  return false;
1767 }
1768 
1769 /**
1770  * Returns whether or not @a *this has a special ability with a tag or id equal to
1771  * @a special. the Check is for a special ability
1772  * active in the current context (see set_specials_context), including
1773  * specials obtained from the opponent's attack.
1774  */
1775 bool attack_type::has_special_or_ability(const std::string& special) const
1776 {
1777  //Now that filter_(second)attack in event supports special_id/type_active, including abilities used as weapons,
1778  //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.
1779  //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.
1780  if(range().empty()){
1781  return false;
1782  }
1783  if(has_special(special, false)) {
1784  return true;
1785  }
1786 
1787  const unit_map& units = get_unit_map();
1788  if(self_) {
1789  for(const config &i : self_->abilities().child_range(special)) {
1790  if(check_self_abilities(i, special)) {
1791  return true;
1792  }
1793  }
1794 
1795  for(const unit& u : units) {
1796  if(!u.affect_distant(special) || u.incapacitated() || same_unit(u, *self_)) {
1797  continue;
1798  }
1799  const map_location& from_loc = u.get_location();
1800  std::size_t distance = distance_between(from_loc, self_loc_);
1801  if(distance > *u.affect_distant(special)) {
1802  continue;
1803  }
1804  int dir = find_direction(self_loc_, from_loc, distance);
1805  for(const config &i : u.abilities().child_range(special)) {
1806  if(check_adj_abilities(i, special, distance, dir, u, from_loc)) {
1807  return true;
1808  }
1809  }
1810  }
1811  }
1812 
1813  if(other_) {
1814  for(const config &i : other_->abilities().child_range(special)) {
1815  if(check_self_abilities_impl(other_attack_, shared_from_this(), i, other_, other_loc_, AFFECT_OTHER, special)) {
1816  return true;
1817  }
1818  }
1819 
1820  for(const unit& u : units) {
1821  if(!u.affect_distant(special) || u.incapacitated() || same_unit(u, *other_)) {
1822  continue;
1823  }
1824  const map_location& from_loc = u.get_location();
1825  std::size_t distance = distance_between(from_loc, other_loc_);
1826  if(distance > *u.affect_distant(special)) {
1827  continue;
1828  }
1829  int dir = find_direction(other_loc_, from_loc, distance);
1830  for(const config &i : u.abilities().child_range(special)) {
1831  if(check_adj_abilities_impl(other_attack_, shared_from_this(), i, other_, u, distance, dir, other_loc_, from_loc, AFFECT_OTHER, special)) {
1832  return true;
1833  }
1834  }
1835  }
1836  }
1837  return false;
1838 }
1839 //end of emulate weapon special functions.
1840 
1841 namespace
1842 {
1843  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)
1844  {
1845  if(!filter_special.empty() && filter_special.count(special_id) == 0 && filter_special.count(tag_name) == 0)
1846  return false;
1847 
1848  if(!filter_special_id.empty() && filter_special_id.count(special_id) == 0)
1849  return false;
1850 
1851  if(!filter_special_type.empty() && filter_special_type.count(tag_name) == 0)
1852  return false;
1853 
1854  return true;
1855  }
1856 }
1857 
1858 bool attack_type::has_filter_special_or_ability(const config& filter, bool simple_check) const
1859 {
1860  if(range().empty()){
1861  return false;
1862  }
1863  const std::set<std::string> filter_special = simple_check ? utils::split_set(filter["special"].str()) : utils::split_set(filter["special_active"].str());
1864  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());
1865  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());
1866  using namespace utils::config_filters;
1867  for(const auto [key, cfg] : specials().all_children_view()) {
1868  if(special_checking(cfg["id"].str(), key, filter_special, filter_special_id, filter_special_type)) {
1869  if(simple_check) {
1870  return true;
1871  } else if(special_active(cfg, AFFECT_SELF, key)) {
1872  return true;
1873  }
1874  }
1875  }
1876 
1877  if(!simple_check && other_attack_) {
1878  for(const auto [key, cfg] : other_attack_->specials().all_children_view()) {
1879  if(special_checking(cfg["id"].str(), key, filter_special, filter_special_id, filter_special_type)) {
1880  if(other_attack_->special_active(cfg, AFFECT_OTHER, key)) {
1881  return true;
1882  }
1883  }
1884  }
1885  }
1886 
1887  if(simple_check) {
1888  return false;
1889  }
1890 
1891  const unit_map& units = get_unit_map();
1892  if(self_) {
1893  for(const auto [key, cfg] : self_->abilities().all_children_view()) {
1894  if(special_checking(cfg["id"].str(), key, filter_special, filter_special_id, filter_special_type)) {
1895  if(check_self_abilities(cfg, key)){
1896  return true;
1897  }
1898  }
1899  }
1900  for(const unit& u : units) {
1901  if(!u.has_ability_distant() || u.incapacitated() || same_unit(u, *self_)) {
1902  continue;
1903  }
1904  const map_location& from_loc = u.get_location();
1905  std::size_t distance = distance_between(from_loc, self_loc_);
1906  if(distance > *u.has_ability_distant()) {
1907  continue;
1908  }
1909  int dir = find_direction(self_loc_, from_loc, distance);
1910 
1911  for(const auto [key, cfg] : u.abilities().all_children_view()) {
1912  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)) {
1913  return true;
1914  }
1915  }
1916  }
1917  }
1918 
1919  if(other_) {
1920  for(const auto [key, cfg] : other_->abilities().all_children_view()) {
1921  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)){
1922  return true;
1923  }
1924  }
1925 
1926  for(const unit& u : units) {
1927  if(!u.has_ability_distant() || u.incapacitated() || same_unit(u, *other_)) {
1928  continue;
1929  }
1930  const map_location& from_loc = u.get_location();
1931  std::size_t distance = distance_between(from_loc, other_loc_);
1932  if(distance > *u.has_ability_distant()) {
1933  continue;
1934  }
1935  int dir = find_direction(other_loc_, from_loc, distance);
1936 
1937  for(const auto [key, cfg] : u.abilities().all_children_view()) {
1938  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)) {
1939  return true;
1940  }
1941  }
1942  }
1943  }
1944  return false;
1945 }
1946 
1947 namespace
1948 {
1949  bool exclude_ability_attributes(const std::string& tag_name, const config & filter)
1950  {
1951  ///check what filter attributes used can be used in type of ability checked.
1952  bool abilities_check = abilities_list::ability_value_tags().count(tag_name) != 0 || abilities_list::ability_no_value_tags().count(tag_name) != 0;
1953  if(filter.has_attribute("active_on") && tag_name != "resistance" && abilities_check)
1954  return false;
1955  if(filter.has_attribute("apply_to") && tag_name != "resistance" && abilities_check)
1956  return false;
1957 
1958  if(filter.has_attribute("overwrite_specials") && abilities_list::weapon_number_tags().count(tag_name) == 0)
1959  return false;
1960 
1961  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;
1962  if(filter.has_attribute("value") && no_value_weapon_abilities_check)
1963  return false;
1964  if(filter.has_attribute("add") && no_value_weapon_abilities_check)
1965  return false;
1966  if(filter.has_attribute("sub") && no_value_weapon_abilities_check)
1967  return false;
1968  if(filter.has_attribute("multiply") && no_value_weapon_abilities_check)
1969  return false;
1970  if(filter.has_attribute("divide") && no_value_weapon_abilities_check)
1971  return false;
1972 
1973  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;
1974  if(filter.has_attribute("replacement_type") && tag_name != "damage_type" && all_engine)
1975  return false;
1976  if(filter.has_attribute("alternative_type") && tag_name != "damage_type" && all_engine)
1977  return false;
1978  if(filter.has_attribute("type") && tag_name != "plague" && all_engine)
1979  return false;
1980 
1981  return true;
1982  }
1983 
1984  bool matches_ability_filter(const config & cfg, const std::string& tag_name, const config & filter)
1985  {
1986  using namespace utils::config_filters;
1987 
1988  //check if attributes have right to be in type of ability checked
1989  if(!exclude_ability_attributes(tag_name, filter))
1990  return false;
1991 
1992  // tag_name and id are equivalent of ability ability_type and ability_id/type_active filters
1993  //can be extent to special_id/type_active. If tag_name or id matche if present in list.
1994  const std::vector<std::string> filter_type = utils::split(filter["tag_name"]);
1995  if ( !filter_type.empty() && std::find(filter_type.begin(), filter_type.end(), tag_name) == filter_type.end() )
1996  return false;
1997 
1998  if(!string_matches_if_present(filter, cfg, "id", ""))
1999  return false;
2000 
2001  //when affect_adjacent=yes detect presence of [affect_adjacent] in abilities, if no
2002  //then matches when tag not present.
2003  if(!filter["affect_adjacent"].empty()){
2004  bool adjacent = cfg.has_child("affect_adjacent");
2005  if(filter["affect_adjacent"].to_bool() != adjacent){
2006  return false;
2007  }
2008  }
2009 
2010  //these attributs below filter attribute used in all engine abilities.
2011  //matches if filter attribute have same boolean value what attribute
2012  if(!bool_matches_if_present(filter, cfg, "affect_self", true))
2013  return false;
2014 
2015  //here if value of affect_allies but also his presence who is checked because
2016  //when affect_allies not specified, ability affect unit of same side what owner only.
2017  if(!bool_or_empty(filter, cfg, "affect_allies"))
2018  return false;
2019 
2020  if(!bool_matches_if_present(filter, cfg, "affect_enemies", false))
2021  return false;
2022 
2023 
2024  //cumulative, overwrite_specials and active_on check attributes used in all abilities
2025  //who return a numerical value.
2026  if(!bool_matches_if_present(filter, cfg, "cumulative", false))
2027  return false;
2028 
2029  if(!string_matches_if_present(filter, cfg, "overwrite_specials", "none"))
2030  return false;
2031 
2032  if(!string_matches_if_present(filter, cfg, "active_on", "both"))
2033  return false;
2034 
2035  //value, add, sub multiply and divide check values of attribute used in engines abilities(default value of 'value' can be checked when not specified)
2036  //who return numericals value but can also check in non-engine abilities(in last case if 'value' not specified none value can matches)
2037  if(!filter["value"].empty()){
2038  if(tag_name == "drains"){
2039  if(!int_matches_if_present(filter, cfg, "value", 50)){
2040  return false;
2041  }
2042  } else if(tag_name == "berserk"){
2043  if(!int_matches_if_present(filter, cfg, "value", 1)){
2044  return false;
2045  }
2046  } else if(tag_name == "heal_on_hit" || tag_name == "heals" || tag_name == "regenerate" || tag_name == "leadership"){
2047  if(!int_matches_if_present(filter, cfg, "value" , 0)){
2048  return false;
2049  }
2050  } else {
2051  if(!int_matches_if_present(filter, cfg, "value")){
2052  return false;
2053  }
2054  }
2055  }
2056 
2057  if(!int_matches_if_present_or_negative(filter, cfg, "add", "sub"))
2058  return false;
2059 
2060  if(!int_matches_if_present_or_negative(filter, cfg, "sub", "add"))
2061  return false;
2062 
2063  if(!double_matches_if_present(filter, cfg, "multiply"))
2064  return false;
2065 
2066  if(!double_matches_if_present(filter, cfg, "divide"))
2067  return false;
2068 
2069 
2070  //apply_to is a special case, in resistance ability, it check a list of damage type used by [resistance]
2071  //but in weapon specials, check identity of unit affected by special(self, opponent tc...)
2072  if(tag_name == "resistance"){
2073  if(!set_includes_if_present(filter, cfg, "apply_to")){
2074  return false;
2075  }
2076  } else {
2077  if(!string_matches_if_present(filter, cfg, "apply_to", "self")){
2078  return false;
2079  }
2080  }
2081 
2082  //the three attribute below are used for check in specifics abilitie:
2083  //replacement_type and alternative_type are present in [damage_type] only for engine abilities
2084  //and type for [plague], but if someone want use this in non-engine abilities, these attribute can be checked outside type mentioned.
2085  //
2086 
2087  //for damage_type only(in engine cases)
2088  if(!string_matches_if_present(filter, cfg, "replacement_type", ""))
2089  return false;
2090 
2091  if(!string_matches_if_present(filter, cfg, "alternative_type", ""))
2092  return false;
2093 
2094  //for plague only(in engine cases)
2095  if(!string_matches_if_present(filter, cfg, "type", ""))
2096  return false;
2097 
2098  //the wml_filter is used in cases where the attribute we are looking for is not
2099  //previously listed or to check the contents of the sub_tags ([filter_adjacent],[filter_self],[filter_opponent] etc.
2100  //If the checked set does not exactly match the content of the capability, the function returns a false response.
2101  auto fwml = filter.optional_child("filter_wml");
2102  if (fwml){
2103  if(!cfg.matches(*fwml)){
2104  return false;
2105  }
2106  }
2107 
2108  // Passed all tests.
2109  return true;
2110  }
2111 
2112  static bool common_matches_filter(const config & cfg, const std::string& tag_name, const config & filter)
2113  {
2114  // Handle the basic filter.
2115  bool matches = matches_ability_filter(cfg, tag_name, filter);
2116 
2117  // Handle [and], [or], and [not] with in-order precedence
2118  for(const auto [key, condition_cfg] : filter.all_children_view() )
2119  {
2120  // Handle [and]
2121  if ( key == "and" )
2122  matches = matches && common_matches_filter(cfg, tag_name, condition_cfg);
2123 
2124  // Handle [or]
2125  else if ( key == "or" )
2126  matches = matches || common_matches_filter(cfg, tag_name, condition_cfg);
2127 
2128  // Handle [not]
2129  else if ( key == "not" )
2130  matches = matches && !common_matches_filter(cfg, tag_name, condition_cfg);
2131  }
2132 
2133  return matches;
2134  }
2135 }
2136 
2137 bool unit::ability_matches_filter(const config & cfg, const std::string& tag_name, const config & filter) const
2138 {
2139  return common_matches_filter(cfg, tag_name, filter);
2140 }
2141 
2142 bool attack_type::special_matches_filter(const config & cfg, const std::string& tag_name, const config & filter) const
2143 {
2144  return common_matches_filter(cfg, tag_name, filter);
2145 }
2146 
2148 {
2149  if(range().empty()){
2150  return false;
2151  }
2152  using namespace utils::config_filters;
2153  bool check_if_active = filter["active"].to_bool();
2154  for(const auto [key, cfg] : specials().all_children_view()) {
2155  if(special_matches_filter(cfg, key, filter)) {
2156  if(!check_if_active) {
2157  return true;
2158  }
2159  if(special_active(cfg, AFFECT_SELF, key)) {
2160  return true;
2161  }
2162  }
2163  }
2164 
2165  if(check_if_active && other_attack_) {
2166  for(const auto [key, cfg] : other_attack_->specials().all_children_view()) {
2167  if(other_attack_->special_matches_filter(cfg, key, filter)) {
2168  if(other_attack_->special_active(cfg, AFFECT_OTHER, key)) {
2169  return true;
2170  }
2171  }
2172  }
2173  }
2174  if(!check_if_active){
2175  return false;
2176  }
2177 
2178  const unit_map& units = get_unit_map();
2179  bool check_adjacent = filter["affect_adjacent"].to_bool(true);
2180  if(self_) {
2181  for(const auto [key, cfg] : self_->abilities().all_children_view()) {
2182  if(self_->ability_matches_filter(cfg, key, filter)) {
2183  if(check_self_abilities(cfg, key)) {
2184  return true;
2185  }
2186  }
2187  }
2188  if(check_adjacent) {
2189  for(const unit& u : units) {
2190  if(!u.has_ability_distant() || u.incapacitated() || same_unit(u, *self_)) {
2191  continue;
2192  }
2193  const map_location& from_loc = u.get_location();
2194  std::size_t distance = distance_between(from_loc, self_loc_);
2195  if(distance > *u.has_ability_distant()) {
2196  continue;
2197  }
2198  int dir = find_direction(self_loc_, from_loc, distance);
2199 
2200  for(const auto [key, cfg] : u.abilities().all_children_view()) {
2201  if(u.ability_matches_filter(cfg, key, filter) && check_adj_abilities(cfg, key, distance, dir, u, from_loc)) {
2202  return true;
2203  }
2204  }
2205  }
2206  }
2207  }
2208 
2209  if(other_) {
2210  for(const auto [key, cfg] : other_->abilities().all_children_view()) {
2211  if(other_->ability_matches_filter(cfg, key, filter) && check_self_abilities_impl(other_attack_, shared_from_this(), cfg, other_, other_loc_, AFFECT_OTHER, key)){
2212  return true;
2213  }
2214  }
2215 
2216  if(check_adjacent) {
2217  for(const unit& u : units) {
2218  if(!u.has_ability_distant() || u.incapacitated() || same_unit(u, *other_)) {
2219  continue;
2220  }
2221  const map_location& from_loc = u.get_location();
2222  std::size_t distance = distance_between(from_loc, other_loc_);
2223  if(distance > *u.has_ability_distant()) {
2224  continue;
2225  }
2226  int dir = find_direction(other_loc_, from_loc, distance);
2227 
2228  for(const auto [key, cfg] : u.abilities().all_children_view()) {
2229  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)) {
2230  return true;
2231  }
2232  }
2233  }
2234  }
2235  }
2236  return false;
2237 }
2238 
2239 bool attack_type::special_active(const config& special, AFFECTS whom, const std::string& tag_name,
2240  bool in_abilities_tag) const
2241 {
2242  return special_active_impl(shared_from_this(), other_attack_, special, whom, tag_name, in_abilities_tag);
2243 }
2244 
2245 /**
2246  * Returns whether or not the given special is active for the specified unit,
2247  * based on the current context (see set_specials_context).
2248  * @param self_attack this unit's attack
2249  * @param other_attack the other unit's attack
2250  * @param special a weapon special WML structure
2251  * @param whom specifies which combatant we care about
2252  * @param tag_name tag name of the special config
2253  * @param in_abilities_tag if special coded in [specials] or [abilities] tags
2254  */
2256  const const_attack_ptr& self_attack,
2257  const const_attack_ptr& other_attack,
2258  const config& special,
2259  AFFECTS whom,
2260  const std::string& tag_name,
2261  bool in_abilities_tag)
2262 {
2263  assert(self_attack || other_attack);
2264  bool is_attacker = self_attack ? self_attack->is_attacker_ : !other_attack->is_attacker_;
2265  bool is_for_listing = self_attack ? self_attack->is_for_listing_ : other_attack->is_for_listing_;
2266  //log_scope("special_active");
2267 
2268 
2269  // Does this affect the specified unit?
2270  if ( whom == AFFECT_SELF ) {
2271  if ( !special_affects_self(special, is_attacker) )
2272  return false;
2273  }
2274  if ( whom == AFFECT_OTHER ) {
2275  if ( !special_affects_opponent(special, is_attacker) )
2276  return false;
2277  }
2278 
2279  // Is this active on attack/defense?
2280  const std::string & active_on = special["active_on"];
2281  if ( !active_on.empty() ) {
2282  if ( is_attacker && active_on != "offense" )
2283  return false;
2284  if ( !is_attacker && active_on != "defense" )
2285  return false;
2286  }
2287 
2288  // Get the units involved.
2289  const unit_map& units = get_unit_map();
2290 
2291  unit_const_ptr self = self_attack ? self_attack->self_ : other_attack->other_;
2292  unit_const_ptr other = self_attack ? self_attack->other_ : other_attack->self_;
2293  map_location self_loc = self_attack ? self_attack->self_loc_ : other_attack->other_loc_;
2294  map_location other_loc = self_attack ? self_attack->other_loc_ : other_attack->self_loc_;
2295  //TODO: why is this needed?
2296  if(self == nullptr) {
2297  unit_map::const_iterator it = units.find(self_loc);
2298  if(it.valid()) {
2299  self = it.get_shared_ptr();
2300  }
2301  }
2302  if(other == nullptr) {
2303  unit_map::const_iterator it = units.find(other_loc);
2304  if(it.valid()) {
2305  other = it.get_shared_ptr();
2306  }
2307  }
2308 
2309  // Make sure they're facing each other.
2310  temporary_facing self_facing(self, self_loc.get_relative_dir(other_loc));
2311  temporary_facing other_facing(other, other_loc.get_relative_dir(self_loc));
2312 
2313  // Filter poison, plague, drain, slow, petrifies
2314  // True if "whom" corresponds to "self", false if "whom" is "other"
2315  bool whom_is_self = ((whom == AFFECT_SELF) || ((whom == AFFECT_EITHER) && special_affects_self(special, is_attacker)));
2316  unit_const_ptr them = whom_is_self ? other : self;
2317  map_location their_loc = whom_is_self ? other_loc : self_loc;
2318 
2319  if (tag_name == "drains" && them && them->get_state("undrainable")) {
2320  return false;
2321  }
2322  if (tag_name == "plague" && them &&
2323  (them->get_state("unplagueable") ||
2324  resources::gameboard->map().is_village(their_loc))) {
2325  return false;
2326  }
2327  if (tag_name == "poison" && them &&
2328  (them->get_state("unpoisonable") || them->get_state(unit::STATE_POISONED))) {
2329  return false;
2330  }
2331  if (tag_name == "slow" && them &&
2332  (them->get_state("unslowable") || them->get_state(unit::STATE_SLOWED))) {
2333  return false;
2334  }
2335  if (tag_name == "petrifies" && them &&
2336  them->get_state("unpetrifiable")) {
2337  return false;
2338  }
2339 
2340 
2341  // Translate our context into terms of "attacker" and "defender".
2342  unit_const_ptr & att = is_attacker ? self : other;
2343  unit_const_ptr & def = is_attacker ? other : self;
2344  const map_location & att_loc = is_attacker ? self_loc : other_loc;
2345  const map_location & def_loc = is_attacker ? other_loc : self_loc;
2346  const const_attack_ptr& att_weapon = is_attacker ? self_attack : other_attack;
2347  const const_attack_ptr& def_weapon = is_attacker ? other_attack : self_attack;
2348 
2349  // Filter firststrike here, if both units have first strike then the effects cancel out. Only check
2350  // the opponent if "whom" is the defender, otherwise this leads to infinite recursion.
2351  if (tag_name == "firststrike") {
2352  bool whom_is_defender = whom_is_self ? !is_attacker : is_attacker;
2353  if (whom_is_defender && att_weapon && att_weapon->has_special_or_ability("firststrike"))
2354  return false;
2355  }
2356 
2357  // Filter the units involved.
2358  //If filter concerns the unit on which special is applied,
2359  //then the type of special must be entered to avoid calling
2360  //the function of this special in matches_filter()
2361  //In apply_to=both case, tag_name must be checked in all filter because special applied to both self and opponent.
2362  bool applied_both = special["apply_to"] == "both";
2363  const std::string& filter_self = in_abilities_tag ? "filter_student" : "filter_self";
2364  std::string self_check_if_recursion = (applied_both || whom_is_self) ? tag_name : "";
2365  if (!special_unit_matches(self, other, self_loc, self_attack, special, is_for_listing, filter_self, self_check_if_recursion))
2366  return false;
2367  std::string opp_check_if_recursion = (applied_both || !whom_is_self) ? tag_name : "";
2368  if (!special_unit_matches(other, self, other_loc, other_attack, special, is_for_listing, "filter_opponent", opp_check_if_recursion))
2369  return false;
2370  //in case of apply_to=attacker|defender, if both [filter_attacker] and [filter_defender] are used,
2371  //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.
2372  bool applied_to_attacker = applied_both || (whom_is_self && is_attacker) || (!whom_is_self && !is_attacker);
2373  std::string att_check_if_recursion = applied_to_attacker ? tag_name : "";
2374  if (!special_unit_matches(att, def, att_loc, att_weapon, special, is_for_listing, "filter_attacker", att_check_if_recursion))
2375  return false;
2376  bool applied_to_defender = applied_both || (whom_is_self && !is_attacker) || (!whom_is_self && is_attacker);
2377  std::string def_check_if_recursion= applied_to_defender ? tag_name : "";
2378  if (!special_unit_matches(def, att, def_loc, def_weapon, special, is_for_listing, "filter_defender", def_check_if_recursion))
2379  return false;
2380 
2381  // filter adjacent units or adjacent locations
2382  if(self && !ability_active_adjacent_helper(*self, false, special, self_loc, in_abilities_tag))
2383  return false;
2384 
2385 
2386  return true;
2387 }
2388 
2389 
2390 
2392 {
2393 
2394 void individual_effect::set(value_modifier t, int val, const config *abil, const map_location &l)
2395 {
2396  type=t;
2397  value=val;
2398  ability=abil;
2399  loc=l;
2400 }
2401 
2402 bool filter_base_matches(const config& cfg, int def)
2403 {
2404  if (auto apply_filter = cfg.optional_child("filter_base_value")) {
2405  config::attribute_value cond_eq = apply_filter["equals"];
2406  config::attribute_value cond_ne = apply_filter["not_equals"];
2407  config::attribute_value cond_lt = apply_filter["less_than"];
2408  config::attribute_value cond_gt = apply_filter["greater_than"];
2409  config::attribute_value cond_ge = apply_filter["greater_than_equal_to"];
2410  config::attribute_value cond_le = apply_filter["less_than_equal_to"];
2411  return (cond_eq.empty() || def == cond_eq.to_int()) &&
2412  (cond_ne.empty() || def != cond_ne.to_int()) &&
2413  (cond_lt.empty() || def < cond_lt.to_int()) &&
2414  (cond_gt.empty() || def > cond_gt.to_int()) &&
2415  (cond_ge.empty() || def >= cond_ge.to_int()) &&
2416  (cond_le.empty() || def <= cond_le.to_int());
2417  }
2418  return true;
2419 }
2420 
2421 // TODO add more [specials] keys
2422 // Currently supports only [plague]type= -> $type
2423 std::string substitute_variables(const std::string& str, const std::string& tag_name, const config& ability_or_special) {
2424  if(tag_name == "plague") {
2425  // Substitute [plague]type= as $type
2426  const auto iter = unit_types.types().find(ability_or_special["type"]);
2427 
2428  // TODO: warn if an invalid type is specified?
2429  if(iter == unit_types.types().end()) {
2430  return str;
2431  }
2432 
2433  const unit_type& type = iter->second;
2434  utils::string_map symbols{{ "type", type.type_name() }};
2435  return utils::interpolate_variables_into_string(str, &symbols);
2436  }
2437 
2438  return str;
2439 }
2440 
2441 effect::effect(const unit_ability_list& list, int def, const const_attack_ptr& att, EFFECTS wham) :
2442  effect_list_(),
2443  composite_value_(0)
2444 {
2445 
2446  int value_set = (wham == EFFECT_CUMULABLE) ? std::max(list.highest("value").first, 0) + std::min(list.lowest("value").first, 0) : def;
2447  std::map<std::string,individual_effect> values_add;
2448  std::map<std::string,individual_effect> values_sub;
2449  std::map<std::string,individual_effect> values_mul;
2450  std::map<std::string,individual_effect> values_div;
2451 
2452  individual_effect set_effect_max;
2453  individual_effect set_effect_min;
2454  utils::optional<int> max_value = utils::nullopt;
2455  utils::optional<int> min_value = utils::nullopt;
2456 
2457  for (const unit_ability & ability : list) {
2458  const config& cfg = *ability.ability_cfg;
2459  const std::string& effect_id = cfg[cfg["id"].empty() ? "name" : "id"];
2460 
2461  if (!filter_base_matches(cfg, def))
2462  continue;
2463 
2464  if(wham != EFFECT_CUMULABLE){
2465  if (const config::attribute_value *v = cfg.get("value")) {
2466  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) {
2467  callable.add("base_value", wfl::variant(def));
2468  return std::round(formula.evaluate(callable).as_int());
2469  }));
2470 
2471  int value_cum = cfg["cumulative"].to_bool() ? std::max(def, value) : value;
2472  assert((set_effect_min.type != NOT_USED) == (set_effect_max.type != NOT_USED));
2473  if(set_effect_min.type == NOT_USED) {
2474  set_effect_min.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2475  set_effect_max.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2476  }
2477  else {
2478  if(value_cum > set_effect_max.value) {
2479  set_effect_max.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2480  }
2481  if(value_cum < set_effect_min.value) {
2482  set_effect_min.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2483  }
2484  }
2485  }
2486  }
2487 
2488  if(wham == EFFECT_DEFAULT || wham == EFFECT_CUMULABLE){
2489  if(cfg.has_attribute("max_value")){
2490  max_value = max_value ? std::min(*max_value, cfg["max_value"].to_int()) : cfg["max_value"].to_int();
2491  }
2492  if(cfg.has_attribute("min_value")){
2493  min_value = min_value ? std::max(*min_value, cfg["min_value"].to_int()) : cfg["min_value"].to_int();
2494  }
2495  }
2496 
2497  if (const config::attribute_value *v = cfg.get("add")) {
2498  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) {
2499  callable.add("base_value", wfl::variant(def));
2500  return std::round(formula.evaluate(callable).as_int());
2501  }));
2502  std::map<std::string,individual_effect>::iterator add_effect = values_add.find(effect_id);
2503  if(add_effect == values_add.end() || add > add_effect->second.value) {
2504  values_add[effect_id].set(ADD, add, ability.ability_cfg, ability.teacher_loc);
2505  }
2506  }
2507  if (const config::attribute_value *v = cfg.get("sub")) {
2508  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) {
2509  callable.add("base_value", wfl::variant(def));
2510  return std::round(formula.evaluate(callable).as_int());
2511  }));
2512  std::map<std::string,individual_effect>::iterator sub_effect = values_sub.find(effect_id);
2513  if(sub_effect == values_sub.end() || sub < sub_effect->second.value) {
2514  values_sub[effect_id].set(ADD, sub, ability.ability_cfg, ability.teacher_loc);
2515  }
2516  }
2517  if (const config::attribute_value *v = cfg.get("multiply")) {
2518  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) {
2519  callable.add("base_value", wfl::variant(def));
2520  return formula.evaluate(callable).as_decimal() / 1000.0 ;
2521  }) * 100);
2522  std::map<std::string,individual_effect>::iterator mul_effect = values_mul.find(effect_id);
2523  if(mul_effect == values_mul.end() || multiply > mul_effect->second.value) {
2524  values_mul[effect_id].set(MUL, multiply, ability.ability_cfg, ability.teacher_loc);
2525  }
2526  }
2527  if (const config::attribute_value *v = cfg.get("divide")) {
2528  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) {
2529  callable.add("base_value", wfl::variant(def));
2530  return formula.evaluate(callable).as_decimal() / 1000.0 ;
2531  }) * 100);
2532 
2533  if (divide == 0) {
2534  ERR_NG << "division by zero with divide= in ability/weapon special " << effect_id;
2535  }
2536  else {
2537  std::map<std::string,individual_effect>::iterator div_effect = values_div.find(effect_id);
2538  if(div_effect == values_div.end() || divide > div_effect->second.value) {
2539  values_div[effect_id].set(DIV, divide, ability.ability_cfg, ability.teacher_loc);
2540  }
2541  }
2542  }
2543  }
2544 
2545  if((wham != EFFECT_CUMULABLE) && set_effect_max.type != NOT_USED) {
2546  value_set = std::max(set_effect_max.value, 0) + std::min(set_effect_min.value, 0);
2547  if(set_effect_max.value > def) {
2548  effect_list_.push_back(set_effect_max);
2549  }
2550  if(set_effect_min.value < def) {
2551  effect_list_.push_back(set_effect_min);
2552  }
2553  }
2554 
2555  /* Do multiplication with floating point values rather than integers
2556  * We want two places of precision for each multiplier
2557  * Using integers multiplied by 100 to keep precision causes overflow
2558  * after 3-4 abilities for 32-bit values and ~8 for 64-bit
2559  * Avoiding the overflow by dividing after each step introduces rounding errors
2560  * that may vary depending on the order effects are applied
2561  * As the final values are likely <1000 (always true for mainline), loss of less significant digits is not an issue
2562  */
2563  double multiplier = 1.0;
2564  double divisor = 1.0;
2565 
2566  for(const auto& val : values_mul) {
2567  multiplier *= val.second.value/100.0;
2568  effect_list_.push_back(val.second);
2569  }
2570 
2571  for(const auto& val : values_div) {
2572  divisor *= val.second.value/100.0;
2573  effect_list_.push_back(val.second);
2574  }
2575 
2576  int addition = 0;
2577  for(const auto& val : values_add) {
2578  addition += val.second.value;
2579  effect_list_.push_back(val.second);
2580  }
2581 
2582  /* Additional and subtraction are independent since Wesnoth 1.19.4. Prior to that, they affected each other.
2583  */
2584  int substraction = 0;
2585  for(const auto& val : values_sub) {
2586  substraction += val.second.value;
2587  effect_list_.push_back(val.second);
2588  }
2589 
2590  composite_double_value_ = (value_set + addition + substraction) * multiplier / divisor;
2591  //clamp what if min_value < max_value or one attribute only used.
2592  if(max_value && min_value && *min_value < *max_value) {
2593  composite_double_value_ = std::clamp(static_cast<double>(*min_value), static_cast<double>(*max_value), composite_double_value_);
2594  } else if(max_value && !min_value) {
2595  composite_double_value_ = std::min(static_cast<double>(*max_value), composite_double_value_);
2596  } else if(min_value && !max_value) {
2597  composite_double_value_ = std::max(static_cast<double>(*min_value), composite_double_value_);
2598  }
2600 }
2601 
2602 } // 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:988
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:630
#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:1038
static bool overwrite_special_affects(const config &special)
Definition: abilities.cpp:1599
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:1215
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:2239
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:1004
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:1729
std::string weapon_specials_value(const std::set< std::string > &checking_tags) const
Definition: abilities.cpp:1049
int composite_value(const unit_ability_list &abil_list, int base_value) const
Return the special weapon value, considering specials.
Definition: abilities.cpp:1594
const_attack_ptr other_attack_
void add_formula_context(wfl::map_formula_callable &) const
Definition: abilities.cpp:679
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:2147
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:1268
bool has_special_or_ability(const std::string &special) const
used for abilities used like weapon and true specials
Definition: abilities.cpp:1775
unit_ability_list get_weapon_ability(const std::string &ability) const
Returns list for weapon like abilities for each ability type.
Definition: abilities.cpp:1558
double modified_damage() const
Returns the damage per attack of this weapon, considering specials.
Definition: abilities.cpp:1382
unit_ability_list get_specials_and_abilities(const std::string &special) const
Definition: abilities.cpp:1577
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:2255
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:1641
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:1109
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:1605
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:1297
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:889
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:1089
bool has_filter_special_or_ability(const config &filter, bool simple_check=false) const
check if special matche
Definition: abilities.cpp:1858
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:1361
std::vector< std::pair< t_string, t_string > > abilities_special_tooltips(boost::dynamic_bitset<> *active_list=nullptr) const
Definition: abilities.cpp:925
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:1754
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:1734
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:2142
void modified_attacks(unsigned &min_attacks, unsigned &max_attacks) const
Calculates the number of attacks this weapon has, considering specials.
Definition: abilities.cpp:1247
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:1749
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:856
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:827
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:1330
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:1190
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:845
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:1913
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:755
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:396
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:1339
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:1709
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:596
std::vector< const config * > open_queries_
While processing a recursive match, all the filters that are currently being checked,...
Definition: unit.hpp:2098
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:1724
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:2135
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:623
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:2060
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:1719
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:1699
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:2019
recursion_guard update_variables_recursion(const config &ability) const
Definition: abilities.cpp:418
const std::set< std::string > & checking_tags() const
Definition: unit.hpp:1840
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:2137
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:604
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:638
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1431
New lexcical_cast header.
void get_adjacent_tiles(const map_location &a, map_location *res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:512
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
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:1269
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:2423
bool filter_base_matches(const config &cfg, int def)
Definition: abilities.cpp:2402
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:86
std::vector< std::pair< int, int > > parse_ranges_unsigned(const std::string &str)
Handles a comma-separated list of inputs to parse_range, in a context that does not expect negative v...
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition: general.hpp:106
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:130
std::vector< std::string > split(const config_attribute_value &val)
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:140
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
std::shared_ptr< const unit > unit_const_ptr
Definition: ptr.hpp:27
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:34
std::shared_ptr< unit > unit_ptr
Definition: ptr.hpp:26
const config::attribute_value & gender_value(const config &cfg, unit_race::GENDER gender, const std::string &male_key, const std::string &female_key, const std::string &default_key)
Chooses a value from the given config based on gender.
Definition: race.cpp:156
Encapsulates the map of the game.
Definition: location.hpp:45
static std::vector< direction > parse_directions(const std::string &str)
Parse_directions takes a comma-separated list, and filters out any invalid directions.
Definition: location.cpp:138
static std::vector< direction > all_directions()
Definition: location.cpp:61
bool valid() const
Definition: location.hpp:110
direction
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:47
direction get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition: location.cpp:238
static const map_location & null_location()
Definition: location.hpp:102
void set(value_modifier t, int val, const config *abil, const map_location &l)
Definition: abilities.cpp:2394
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