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