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