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