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