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