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