The Battle for Wesnoth  1.19.28+dev
abilities.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2025
3  by Dominic Bolin <dominic.bolin@exong.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Manage unit-abilities, like heal, cure, and weapon_specials.
19  */
20 
21 #include "deprecation.hpp"
22 #include "display.hpp"
23 #include "display_context.hpp"
24 #include "filter_context.hpp"
25 #include "font/standard_colors.hpp"
27 #include "formula/formula.hpp"
29 #include "formula/string_utils.hpp"
30 #include "game_board.hpp"
31 #include "game_version.hpp" // for version_info
32 #include "gettext.hpp"
33 #include "language.hpp"
34 #include "lexical_cast.hpp"
35 #include "log.hpp"
36 #include "map/map.hpp"
37 #include "preferences/preferences.hpp" // for prefs::get_show_deprecation
38 #include "resources.hpp"
39 #include "serialization/markup.hpp"
41 #include "team.hpp"
42 #include "terrain/filter.hpp"
43 #include "units/types.hpp"
44 #include "units/abilities.hpp"
45 #include "units/ability_tags.hpp"
46 #include "units/filter.hpp"
47 #include "units/map.hpp"
48 #include "utils/config_filters.hpp"
49 #include "units/unit.hpp"
50 #include "utils/general.hpp"
51 
52 #include <utility>
53 
54 static lg::log_domain log_engine("engine");
55 #define ERR_NG LOG_STREAM(err, log_engine)
56 
57 static lg::log_domain log_wml("wml");
58 #define ERR_WML LOG_STREAM(err, log_wml)
59 #define DEBUG_WML LOG_STREAM(debug, log_wml)
60 
61 namespace
62 {
63  using namespace std::string_literals;
64  const std::array numeric_keys{
65  "value"s, "add"s, "sub"s, "multiply"s, "divide"s, "max_value"s, "min_value"s
66  };
67 }
68 
69 
70 /*
71  *
72  * [abilities]
73  * ...
74  *
75  * [heals]
76  * value=4
77  * max_value=8
78  * cumulative=no
79  * affect_allies=yes
80  * name= _ "heals"
81  * female_name= _ "female^heals"
82  * name_inactive=null
83  * female_name_inactive=null
84  * description= _ "Heals:
85 Allows the unit to heal adjacent friendly units at the beginning of each turn.
86 
87 A unit cared for by a healer may heal up to 4 HP per turn.
88 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."
89  * description_inactive=null
90  *
91  * affect_self=yes
92  * [filter] // SUF
93  * ...
94  * [/filter]
95  * [filter_self] // SUF
96  * ...
97  * [/filter_self]
98  * [filter_adjacent] // SUF
99  * adjacent=n,ne,nw
100  * ...
101  * [/filter_adjacent]
102  * [filter_adjacent_location]
103  * adjacent=n,ne,nw
104  * ...
105  * [/filter_adjacent]
106  * [affect_adjacent]
107  * adjacent=n,ne,nw
108  * [filter] // SUF
109  * ...
110  * [/filter]
111  * [/affect_adjacent]
112  * [affect_adjacent]
113  * adjacent=s,se,sw
114  * [filter] // SUF
115  * ...
116  * [/filter]
117  * [/affect_adjacent]
118  *
119  * [/heals]
120  *
121  * ...
122  * [/abilities]
123  *
124  */
125 
126 unit_ability_t::unit_ability_t(std::string tag, config cfg, bool inside_attack)
127  : tag_(std::move(tag))
128  , id_(cfg["id"].str())
129  , in_specials_tag_(inside_attack)
130  , active_on_(active_on_t::both)
131  , apply_to_(apply_to_t::self)
132  , affects_allies_(affects_allies_t::same_side_only)
133  , affects_self_(true)
134  , affects_enemies_(false)
135  , priority_(cfg["priority"].to_double(0.00))
136  , suppress_special_priority_(-100000.00)
137  , suppress_ability_priority_(-100000.00)
138  , cfg_(std::move(cfg))
139  , currently_checked_(false)
140 {
141  do_compat_fixes(cfg_, tag_, inside_attack);
142 
143  if (tag_ != "resistance" && tag_ != "leadership") {
144  std::string apply_to = cfg_["apply_to"].str();
145  apply_to_ = apply_to == "attacker" ? apply_to_t::attacker :
146  apply_to == "defender" ? apply_to_t::defender :
147  apply_to == "self" ? apply_to_t::self :
148  apply_to == "opponent" ? apply_to_t::opponent :
149  apply_to == "both" ? apply_to_t::both :
151 
152  }
153  if (tag_ != "leadership") {
154  std::string active_on = cfg_["active_on"].str();
155  active_on_ = active_on == "defense" ? active_on_t::defense :
156  active_on == "offense" ? active_on_t::offense :
158  }
159  if (!cfg_.has_child("affect_adjacent")) {
160  //optimisation
162  }
163  if (cfg_["affect_allies"].to_bool(false)) {
165  }
166  if (!cfg_["affect_allies"].to_bool(true)) {
168  }
169  affects_self_ = cfg_["affect_self"].to_bool(true);
170  affects_enemies_ = cfg_["affect_enemies"].to_bool(false);
171 
172  if(auto overwrite_specials = cfg_.optional_child("overwrite_specials")) {
173  suppress_special_priority_ = overwrite_specials["priority"].to_double(0.00);
174  }
175  else if(cfg_["overwrite_specials"] == "one_side" || cfg_["overwrite_specials"] == "both_sides") {
176  if(auto overwrite = cfg_.optional_child("overwrite")) {
177  suppress_special_priority_ = overwrite["priority"].to_double(0.00);
178  } else {
180  }
181  }
182  if(auto overwrite_abilities = cfg_.optional_child("overwrite_abilities")) {
183  suppress_ability_priority_ = overwrite_abilities["priority"].to_double(0.00);
184  }
185 }
186 
187 void unit_ability_t::do_compat_fixes(config& cfg, const std::string& tag, bool inside_attack)
188 {
189  // Some behavior changed without deprecation when moving to using the common implementations of
190  // SUF and SLF instead of ability-specific reimplementations. For filters that, in 1.18 and
191  // earlier, could never match anything, it seems reasonable to treat those edge cases as bugs in
192  // the WML and warn about them, rather than to add bug-for-bug compatibility.
193 
194  // The warnings about changed behavior are similar to deprecation warnings, so show them in the
195  // chat log if and only if the player has enabled deprecation messages. Most of the message
196  // isn't translated, but the hint about which preference controls it can use an existing string.
197  auto note_bugfix = [](const std::string& message) {
198  DEBUG_WML << message;
200  lg::log_to_chat() << message << " (“" << _("Show deprecation messages in-game") << "” is on)\n";
201  }
202  };
203 
204  // replace deprecated backstab with formula
205  if (!cfg["backstab"].blank()) {
206  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.");
207  }
208  if (cfg["backstab"].to_bool()) {
209  const std::string& backstab_formula = "enemy_of(self, flanker) and not flanker.petrified where flanker = unit_at(direction_from(loc, other.facing))";
210  config& filter_opponent = cfg.child_or_add("filter_opponent");
211  config& filter_opponent2 = filter_opponent.empty() ? filter_opponent : filter_opponent.add_child("and");
212  filter_opponent2["formula"] = backstab_formula;
213  }
214  cfg.remove_attribute("backstab");
215 
216  // replace deprecated filter_adjacent/filter_adjacent_location with formula
217  std::string filter_teacher = inside_attack ? "filter_self" : "filter";
218  if (cfg.has_child("filter_adjacent")) {
219  if (inside_attack) {
220  deprecated_message("[filter_adjacent] in weapon specials in [specials] tags", DEP_LEVEL::FOR_REMOVAL, version_info("1.23"), "Use [filter_self][filter_adjacent] instead.");
221  }
222  else {
223  deprecated_message("[filter_adjacent] in abilities", DEP_LEVEL::FOR_REMOVAL, version_info("1.23"), "Use [filter][filter_adjacent] instead or other unit filter.");
224  }
225  }
226  if (cfg.has_child("filter_adjacent_location")) {
227  if (inside_attack) {
228  deprecated_message("[filter_adjacent_location] in weapon specials in [specials] tags", DEP_LEVEL::FOR_REMOVAL, version_info("1.23"), "Use [filter_self][filter_location][filter_adjacent_location] instead.");
229  }
230  else {
231  deprecated_message("[filter_adjacent_location] in abilities", DEP_LEVEL::FOR_REMOVAL, version_info("1.23"), "Use [filter][filter_location][filter_adjacent_location] instead.");
232  }
233  }
234 
235  //These tags are were never supported inside [specials] according to the wiki.
236  for (config& filter_adjacent : cfg.child_range("filter_adjacent")) {
237  // For SUF, omitting adjacent= or count= triggers defaults. Contrary to SLF's behavior, an
238  // empty attribute is treated the same as an omitted one, so this could just use
239  // config::operator[] without checking that the attribute already exists, but one of SUF or
240  // SLF is likely to change behavior to match the other.
241  const auto has_dirs = filter_adjacent.has_attribute("adjacent");
242  const auto has_count = filter_adjacent.has_attribute("count");
243  if (!has_dirs) {
244  std::ostringstream ss;
245  ss << "Filter [" << tag << "][filter_adjacent] changed behavior in 1.19.26. Previously this would never match because adjacent= is omitted.";
246  note_bugfix(ss.str());
247  }
248  if (!has_count) {
249  // It was documented on the Wiki that omitting count meant that every given direction
250  // must match, equivalent to a count that matches the number of directions given.
251  //
252  // However, a bug introduced in 1.13.2 meant filters without count= would never match.
253  //
254  // For 1.20 this simply passes the data through to the common SUF implementation.
255  std::ostringstream ss;
256  ss << "Filter [" << tag << "][filter_adjacent] changed behavior in 1.19.26. Previously this would never match because count= is omitted.";
257  note_bugfix(ss.str());
258  }
259  if (has_dirs && has_count && !in_ranges<int>(map_location::parse_directions(filter_adjacent["adjacent"]).size(), utils::parse_ranges_unsigned(filter_adjacent["count"].str()))) {
260  // In 1.18, all directions had to match, for example count=1 with adjacent=n,s would never match.
261  // This was a bug, it returned false on the first non-match instead of counting.
262  std::ostringstream ss;
263  ss << "Filter [" << tag << "][filter_adjacent] changed behavior in 1.19.26. In 1.18 this combination of adjacent= and count= would never match.";
264  note_bugfix(ss.str());
265  }
266  cfg.child_or_add(filter_teacher).add_child("filter_adjacent", filter_adjacent);
267  }
268  cfg.remove_children("filter_adjacent");
269  for (config& filter_adjacent : cfg.child_range("filter_adjacent_location")) {
270  // For the common implementation of SLF, omitting adjacent or count triggers defaults, but
271  // creating an empty attribute prevents that behavior. Therefore we can't use
272  // config::operator[] without first checking that the attribute already exists.
273  //
274  // The warnings refer to the behavior of [filter_adjacent_location] in 1.18, which means the
275  // now-removed ability-specific implementation rather than the common implementation.
276  const auto has_dirs = filter_adjacent.has_attribute("adjacent");
277  const auto has_count = filter_adjacent.has_attribute("count");
278  if (!has_dirs) {
279  std::ostringstream ss;
280  ss << "Filter [" << tag << "][filter_adjacent_location] changed behavior in 1.19.26. Previously this would never match because adjacent= is omitted.";
281  note_bugfix(ss.str());
282  }
283  if (!has_count) {
284  std::ostringstream ss;
285  ss << "Filter [" << tag << "][filter_adjacent_location] changed behavior in 1.19.26. Previously this would never match because count= is omitted.";
286  note_bugfix(ss.str());
287  }
288  if (has_dirs && has_count && !in_ranges<int>(map_location::parse_directions(filter_adjacent["adjacent"]).size(), utils::parse_ranges_unsigned(filter_adjacent["count"].str()))) {
289  // In 1.18, all directions had to match, for example count=1 with adjacent=n,s would never match
290  std::ostringstream ss;
291  ss << "Filter [" << tag << "][filter_adjacent_location] changed behavior in 1.19.26. In 1.18 this combination of adjacent= and count= would never match.";
292  note_bugfix(ss.str());
293  }
294  cfg.child_or_add(filter_teacher).add_child("filter_location").add_child("filter_adjacent_location", filter_adjacent);
295  }
296  cfg.remove_children("filter_adjacent_location");
297 
298  if (tag == "resistance" || tag == "leadership") {
299  if (auto child = cfg.optional_child("filter_second_weapon")) {
300  cfg.add_child("filter_opponent").add_child("filter_weapon", *child);
301  }
302  if (auto child = cfg.optional_child("filter_weapon")) {
303  cfg.add_child("filter_student").add_child("filter_weapon", *child);
304  }
305  cfg.remove_children("filter_second_weapon");
306  cfg.remove_children("filter_weapon");
307  }
308 
309  if (tag == "drains" && cfg["value"].empty()) {
310  deprecated_message("the default value of 50 of [drains]value= is deprecated", DEP_LEVEL::FOR_REMOVAL, version_info("1.21"), "Specify value=50 directly.");
311  cfg["value"] = 50;
312  }
313 
314  if(!cfg["overwrite_specials"].blank() || cfg.optional_child("overwrite")) {
315  deprecated_message("overwrite_specials= or [overwrite] in weapon specials", DEP_LEVEL::INDEFINITE, "", "Use [overwrite_specials] instead.");
316  }
317 }
318 
319 
321 {
322  return cfg["unique_id"].str(cfg["id"].str());
323 }
324 
326 {
327  return get_help_topic_id(cfg());
328 }
329 
330 
331 void unit_ability_t::parse_vector(const config& abilities_cfg, ability_vector& res, bool inside_attack)
332 {
333  for (auto item : abilities_cfg.all_children_range()) {
334  res.push_back(unit_ability_t::create(item.key, item.cfg, inside_attack));
335  }
336 }
337 
338 ability_vector unit_ability_t::cfg_to_vector(const config& abilities_cfg, bool inside_attack)
339 {
340  ability_vector res;
341  parse_vector(abilities_cfg, res, inside_attack);
342  return res;
343 }
344 
346 {
347  ability_vector res;
348  for (const ability_ptr& p_ab : abs) {
349  if (p_ab->tag() == tag) {
350  res.push_back(p_ab);
351  }
352  }
353  return res;
354 }
355 
357 {
358  ability_vector res;
359  for (const ability_ptr& p_ab : abs) {
360  res.push_back(std::make_shared<unit_ability_t>(*p_ab));
361  }
362  return res;
363 }
364 
366 {
367  config abilities_cfg;
368  for (const auto& item : abilities) {
369  item->write(abilities_cfg);
370  }
371  return abilities_cfg;
372 }
373 
374 
375 void unit_ability_t::write(config& abilities_cfg)
376 {
377  abilities_cfg.add_child(tag(), cfg());
378 }
379 
380 std::string unit_ability_t::substitute_variables(const std::string& str) const {
381  // TODO add more [specials] keys
382 
383  utils::string_map symbols;
384 
385  // [plague]type= -> $type
386  if(tag() == "plague") {
387  // Substitute [plague]type= as $type
388  const auto iter = unit_types.types().find(cfg()["type"]);
389 
390  // TODO: warn if an invalid type is specified?
391  if (iter == unit_types.types().end()) {
392  return str;
393  }
394 
395  const unit_type& type = iter->second;
396  symbols.emplace("type", type.type_name());
397  }
398 
399  // weapon specials with value keys, like value, add, sub etc.
400  // i.e., [heals]value= -> $value, [regenerates]add= -> $add etc.
401  for(const auto& vkey : numeric_keys) {
402  if(cfg().has_attribute(vkey)) {
403  if(vkey == "multiply" || vkey == "divide") {
404  const std::string lang_locale = get_language().localename;
405  std::stringstream formatter_str;
406  try {
407  formatter_str.imbue(std::locale{lang_locale});
408  } catch(const std::runtime_error&) {}
409  formatter_str << cfg()[vkey].to_double();
410  symbols.emplace(vkey, formatter_str.str());
411  } else {
412  symbols.emplace(vkey, std::to_string(cfg()[vkey].to_int()));
413  }
414  }
415  }
416 
417  return symbols.empty() ? str : utils::interpolate_variables_into_string(str, &symbols);
418 }
419 
420 
421 namespace {
422  const config_attribute_value& get_attr_four_fallback(const config& cfg, bool b1, bool b2, std::string_view s_yes_yes, std::string_view s_yes_no, std::string_view s_no_yes, std::string_view s_no_no)
423  {
424  if (b1 && b2) {
425  if (auto* attr = cfg.get(s_yes_yes)) { return *attr; }
426  }
427  if (b1) {
428  if (auto* attr = cfg.get(s_yes_no)) { return *attr; }
429  }
430  if (b2) {
431  if (auto* attr = cfg.get(s_no_yes)) { return *attr; }
432  }
433  return cfg[s_no_no];
434  }
435 }
436 
437 std::string unit_ability_t::get_name(bool is_inactive, unit_race::GENDER gender) const
438 {
439  bool is_female = gender == unit_race::FEMALE;
440  std::string res = get_attr_four_fallback(cfg_, is_inactive, is_female, "female_name_inactive", "name_inactive", "female_name", "name").str();
441  return substitute_variables(res);
442 }
443 
444 std::string unit_ability_t::get_description(bool is_inactive, unit_race::GENDER gender) const
445 {
446  bool is_female = gender == unit_race::FEMALE;
447  std::string res = get_attr_four_fallback(cfg_, is_inactive, is_female, "female_description_inactive", "description_inactive", "female_description", "description").str();
448  return substitute_variables(res);
449 }
450 
451 bool unit_ability_t::active_on_matches(bool student_is_attacker) const
452 {
454  return true;
455  }
456  if (active_on() == unit_ability_t::active_on_t::offense && student_is_attacker) {
457  return true;
458  }
459  if (active_on() == unit_ability_t::active_on_t::defense && !student_is_attacker) {
460  return true;
461  }
462  return false;
463 }
464 
465 
467  : parent()
468 {
469  if (!p.currently_checked_) {
470  p.currently_checked_ = true;
471  parent = &p;
472  }
473 }
474 
476 {
477  if (parent) {
478  parent->currently_checked_ = false;
479  }
480 }
481 
482 unit_ability_t::recursion_guard::operator bool() const {
483  return bool(parent);
484 }
485 
487 {
488  if (currently_checked_) {
489  static std::vector<std::tuple<std::string, std::string>> already_shown;
490 
491  auto identifier = std::tuple<std::string, std::string>{ u.id(), cfg().debug()};
492  if (!utils::contains(already_shown, identifier)) {
493 
494  std::string_view filter_text_view = std::get<1>(identifier);
495  utils::trim(filter_text_view);
496  ERR_NG << "Looped recursion error for unit '" << u.id()
497  << "' while checking ability '" << filter_text_view << "'";
498 
499  // Arbitrary limit, just ensuring that having a huge number of specials causing recursion
500  // warnings can't lead to unbounded memory consumption here.
501  if (already_shown.size() > 100) {
502  already_shown.clear();
503  }
504  already_shown.push_back(std::move(identifier));
505  }
506  }
507  return recursion_guard(*this);
508 }
509 
510 
511 
512 
513 namespace {
514 
515 const unit_map& get_unit_map()
516 {
517  // Used if we're in the game, including during the construction of the display_context
519  return resources::gameboard->units();
520  }
521 
522  // If we get here, we're in the scenario editor
523  assert(display::get_singleton());
524  return display::get_singleton()->context().units();
525 }
526 
527 const team& get_team(std::size_t side)
528 {
529  // Used if we're in the game, including during the construction of the display_context
531  return resources::gameboard->get_team(side);
532  }
533 
534  // If we get here, we're in the scenario editor
535  assert(display::get_singleton());
536  return display::get_singleton()->context().get_team(side);
537 }
538 
539 /**
540  * Common code for the question "some other unit has an ability, can that ability affect this
541  * unit" - it's not the full answer to that question, just a part of it.
542  *
543  * Although this is called while checking which units' "hides" abilities are active, that's only
544  * for the question "is this unit next to an ally that has a 'camoflages adjacent allies' ability";
545  * not the question "is this unit next to an enemy, therefore visible".
546  */
547 bool affects_side(const unit_ability_t& ab, std::size_t side, std::size_t other_side)
548 {
549  const team& side_team = get_team(side);
550 
551  if(side == other_side)
553  if(side_team.is_enemy(other_side))
554  return ab.affects_enemies();
555  else
557 }
558 
559 /**
560  * This function defines in which direction loc is relative to from_loc
561  * either by pointing to the hexagon loc is on or by pointing to the hexagon adjacent to from_loc closest to loc.
562  */
563 int find_direction(const map_location& loc, const map_location& from_loc, std::size_t distance)
564 {
565  const auto adjacent = get_adjacent_tiles(from_loc);
566  for(std::size_t j = 0; j < adjacent.size(); ++j) {
567  bool adj_or_dist = distance != 1 ? distance_between(adjacent[j], loc) == (distance - 1) : adjacent[j] == loc;
568  if(adj_or_dist) {
569  return j;
570  }
571  }
572  return 0;
573 }
574 
575 /// Helper function, to turn void retuning function into false retuning functions
576 /// Calls @a f with arguments @args, but if @f returns void this function returns false.
577 template<typename TFunc, typename... TArgs>
578 bool default_false(const TFunc& f, const TArgs&... args) {
579  if constexpr (std::is_same_v<decltype(f(args...)), void>) {
580  f(args...);
581  return false;
582  }
583  else {
584  return f(args...);
585  }
586 }
587 
588 struct tag_check
589 {
590  const std::string& tag;
591 
592  bool operator()(const ability_ptr& p_ab) const { return p_ab->tag() == tag; }
593  size_t get_unit_radius(const unit& u) const { return u.max_ability_radius_type(tag); }
594 };
595 
596 template <typename T>
597 auto radius_helper(const unit&u, T const& check, int) -> decltype(check.get_unit_radius(u))
598 {
599  return check.get_unit_radius(u);
600 }
601 
602 template <typename T>
603 size_t radius_helper(const unit& u, T const&, long)
604 {
605  return u.max_ability_radius();
606 }
607 
608 
609 template<typename TCheck, typename THandler>
610 bool foreach_distant_active_ability(const unit& un, const map_location& loc, TCheck&& quick_check, THandler&& handler)
611 {
612  // If the unit does not have abilities that match the criteria, check if adjacent units or elsewhere on the map have active abilities
613  // with the [affect_adjacent] subtag that could affect the unit.
614  const unit_map& units = get_unit_map();
615 
616  // Check for each unit present on the map that it corresponds to the criteria
617  // (possession of an ability with [affect_adjacent] via a boolean variable, not incapacitated,
618  // different from the central unit, that the ability is of the right type, detailed verification of each ability),
619  // if so return true.
620  for (const unit& u : units) {
621  if (!u.max_ability_radius() || u.incapacitated() || u.underlying_id() == un.underlying_id()) {
622  continue;
623  }
624  size_t u_ability_radius = radius_helper(u, quick_check, 0);
625  if (!u_ability_radius) {
626  continue;
627  }
628  const map_location& from_loc = u.get_location();
629  std::size_t distance = distance_between(from_loc, loc);
630  if (distance > u_ability_radius) {
631  continue;
632  }
633  int dir = find_direction(loc, from_loc, distance);
634  for (const auto& p_ab : u.abilities()) {
635  if (!quick_check(p_ab)) {
636  continue;
637  }
638  if (un.get_adj_ability_bool(*p_ab, distance, dir, loc, u, from_loc)) {
639  if (default_false(handler, p_ab, u)) {
640  return true;
641  }
642  }
643  }
644  }
645  return false;
646 }
647 
648 template<typename TCheck, typename THandler>
649 bool foreach_self_active_ability(const unit& un, map_location loc, const TCheck& quick_check, const THandler& handler)
650 {
651  for (const auto& p_ab : un.abilities()) {
652  if (!quick_check(p_ab)) {
653  continue;
654  }
655  if (un.get_self_ability_bool(*p_ab, loc)) {
656  if (default_false(handler, p_ab, un)) {
657  return true;
658  }
659  }
660  }
661  return false;
662 }
663 
664 // enum class loop_type_t { self_only, distant_only, both };
665 
666 /*
667  * execeutes a given function for each active ability of @a unit, including
668  * abilitied thought by other units
669  * @param un the unit receiving the abilities
670  * @param loc the location we assume the unit to be at.
671  * @param quick_check a quick check that is exceuted before the ability tested
672  * @param handler the function that is called for each acive ability.
673  * if this is a boolean function and returns true the execeution
674  * is aborted, used for "have any active ability"-like checks.
675  * @returns true iff any of the handlers returned true.
676  */
677 template<typename TCheck, typename THandler>
678 bool foreach_active_ability(const unit& un, map_location loc, const TCheck& quick_check, const THandler& handler, bool skip_adjacent = false)
679 {
680  // Check that the unit has an ability of tag_name type which meets the conditions to be active.
681  // If so, return true.
682  if (foreach_self_active_ability(un, loc, quick_check, handler)) {
683  return true;
684  }
685  if (!skip_adjacent && foreach_distant_active_ability(un, loc, quick_check, handler)) {
686  return true;
687  }
688  return false;
689 }
690 
691 auto return_true = [](const auto&...) { return true; };
692 
693 /*
694  * executes the given handler for each active special/ability affecting an attack during combat.
695  * a simple_check parameter can be as a predicate to filter out the abilities/specials that we
696  * are not interested in, simple_check is executed before checking whether a given ability is active.
697  * the @a skip_adjacent parameter can be set to true if we are not intereted in abilities from
698  * adjacent units, used by the [filter_special] code as an optimisation.
699  * @param context the abilities context we are in, describing attacker defender etc.
700  * @param self the combatant whose weapons are affected by the specials
701  * @param quick_check a predicate describing in whiich abilities we are interested in. (usually checking for tag name)
702  * @handler handler a callback that is executed for each active speical that affects @self in the current context, it takes 3 parameters:
703  * - a const ability_t& describing the ability
704  * - a specials_combatant& student
705  * - a 'source', this is either am attack_type& or a unit&. so handler operator() has to be able to handle both cases
706  * handler can return a bool, if it returns true, the seacrch is aborted and this functions returns true withaout checking mroe abilities.
707  * @skip_adjacent whether we should skip looking into adjacent units effecting the weapon via leadership-like abilities. (used by the [filter_special] as a optimisation)
708  */
709 template<typename TCheck, typename THandler>
710 bool foreach_active_special(
711  const specials_context_t& context,
713  const TCheck& quick_check,
714  const THandler& handler,
715  bool skip_adjacent = false)
716 {
717  auto& other = context.other(self);
718  // "const auto&..." because foreach_active_ability calls this with a unit& argument.
719  auto handler_self = [&](const ability_ptr& p_ab, const auto& source) {
720  return context.is_special_active(self, *p_ab, unit_ability_t::affects_t::SELF) && default_false(handler, p_ab, self, source);
721  };
722  auto handler_other = [&](const ability_ptr& p_ab, const auto& source) {
723  return context.is_special_active(other, *p_ab, unit_ability_t::affects_t::OTHER) && default_false(handler, p_ab, other, source);
724  };
725 
726  //search in the attacks [specials]
727  if (self.at) {
728  for (const ability_ptr& p_ab : self.at->specials()) {
729  if (quick_check(p_ab) && handler_self(p_ab, *self.at)) {
730  return true;
731  }
732  }
733  }
734  //search in the opponents attacks [specials]
735  if (other.at) {
736  for (const ability_ptr& p_ab : other.at->specials()) {
737  if (quick_check(p_ab) && handler_other(p_ab, *other.at)) {
738  return true;
739  }
740  }
741  }
742  //search in unit [abilities] including abilities tought via loadship like abilities.
743  if (self.un) {
744  if (foreach_active_ability(*self.un, self.loc, quick_check, handler_self, skip_adjacent)) {
745  return true;
746  }
747  }
748  //search in the opponents [abilities] including abilities tought via loadship like abilities.
749  if (other.un) {
750  if (foreach_active_ability(*other.un, other.loc, quick_check, handler_other, skip_adjacent)) {
751  return true;
752  }
753  }
754  return false;
755 }
756 
757 
758 }
759 
760 bool unit::get_ability_bool(const std::string& tag_name, const map_location& loc) const
761 {
762  return foreach_active_ability(*this, loc, tag_check{ tag_name },
763  [&](const ability_ptr&, const unit&) {
764  return true;
765  });
766 }
767 
768 active_ability_list unit::get_abilities(const std::string& tag_name, const map_location& loc) const
769 {
770  active_ability_list res(loc_);
771  foreach_active_ability(*this, loc, tag_check{ tag_name },
772  [&](const ability_ptr& p_ab, const unit& u2) {
773  res.emplace_back(p_ab, loc, u2.get_location());
774  });
775  return res;
776 }
777 
778 
779 std::vector<std::string> unit::get_ability_id_list() const
780 {
781  std::vector<std::string> res;
782 
783  for(const auto& p_ab : this->abilities()) {
784  std::string id = p_ab->id();
785  if (!id.empty())
786  res.push_back(std::move(id));
787  }
788  return res;
789 }
790 
791 
792 namespace {
793  /**
794  * Adds a quadruple consisting of (in order) id, base name,
795  * male or female name as appropriate for the unit, and description.
796  *
797  * @returns Whether name was resolved and quadruple added.
798  */
799  bool add_ability_tooltip(const unit_ability_t& ab, unit_race::GENDER gender, std::vector<unit_ability_t::tooltip_info>& res, bool active)
800  {
801  auto name = ab.get_name(!active, gender);
802  auto desc = ab.get_description(!active, gender);
803 
804  if (name.empty()) {
805  return false;
806  }
807 
808  res.AGGREGATE_EMPLACE(
809  name,
810  desc,
811  ab.get_help_topic_id()
812  );
813  return true;
814  }
815 }
816 
817 std::vector<unit_ability_t::tooltip_info> unit::ability_tooltips() const
818 {
819  std::vector<unit_ability_t::tooltip_info> res;
820 
821  for(const auto& p_ab : abilities())
822  {
823  add_ability_tooltip(*p_ab, gender_, res, true);
824  }
825 
826  return res;
827 }
828 
829 std::vector<unit_ability_t::tooltip_info> unit::ability_tooltips(boost::dynamic_bitset<>& active_list, const map_location& loc) const
830 {
831  std::vector<unit_ability_t::tooltip_info> res;
832  active_list.clear();
833 
834  for(const auto& p_ab : abilities())
835  {
836  bool active = ability_active(*p_ab, loc);
837  if(add_ability_tooltip(*p_ab, gender_, res, active))
838  {
839  active_list.push_back(active);
840  }
841  }
842  return res;
843 }
844 
845 
847 {
848  auto filter_lock = ab.guard_against_recursion(*this);
849  if(!filter_lock) {
850  return false;
851  }
852  return ability_active_impl(ab, loc);
853 }
854 
856 {
857  bool illuminates = ab.tag() == "illuminates";
858 
859  if(auto afilter = ab.cfg().optional_child("filter")) {
860  if(!unit_filter(vconfig(*afilter)).set_use_flat_tod(illuminates).matches(*this, loc)) {
861  return false;
862  }
863  }
864 
865  return true;
866 }
867 
868 bool unit::ability_affects_adjacent(const unit_ability_t& ab, std::size_t dist, int dir, const map_location& loc, const unit& from) const
869 {
870  if(!ab.cfg().has_child("affect_adjacent")) {
871  return false;
872  }
873  bool illuminates = ab.tag() == "illuminates";
874 
875  assert(dir >=0 && dir <= 5);
876  map_location::direction direction{ dir };
877 
878  for (const config &i : ab.cfg().child_range("affect_adjacent"))
879  {
880  if(i["radius"] != "all_map") {
881  int radius = i["radius"].to_int(1);
882  if(radius <= 0) {
883  continue;
884  }
885  if(dist > size_t(radius)) {
886  continue;
887  }
888  }
889  if (i.has_attribute("adjacent")) { //key adjacent defined
890  if(!utils::contains(map_location::parse_directions(i["adjacent"]), direction)) {
891  continue;
892  }
893  }
894  auto filter = i.optional_child("filter");
895  if (!filter || //filter tag given
896  unit_filter(vconfig(*filter)).set_use_flat_tod(illuminates).matches(*this, loc, from) ) {
897  return true;
898  }
899  }
900  return false;
901 }
902 
904 {
905  auto filter = ab.cfg().optional_child("filter_self");
906  bool affect_self = ab.affects_self();
907  if (!filter || !affect_self) return affect_self;
908  return unit_filter(vconfig(*filter)).set_use_flat_tod(ab.tag() == "illuminates").matches(*this, loc);
909 }
910 
911 bool unit::has_ability_type(const std::string& ability) const
912 {
913  return !abilities(ability).empty();
914 }
915 
916 
917 std::vector<std::string> unit::halo_or_icon_abilities(const std::string& image_type) const
918 {
919  std::string attr_image = image_type + "_image";
920 
921  // i didn't know this syntax existed.
922  struct {
923  bool operator()(const ability_ptr& p_ab) const { return !p_ab->cfg()[attr_image_].empty(); }
924  size_t get_unit_radius(const unit& u) const { return u.max_ability_radius_image(); }
925  std::string& attr_image_;
926  } quick_check { attr_image };
927 
928 
929  //todoc++23: use std::flat_set
930  std::set<std::string> image_list;
931  for(const auto& p_ab : abilities()){
932  bool is_active = ability_active(*p_ab, loc_);
933  //Add halo/overlay to owner of ability if active and affect_self is true.
934  if( !p_ab->cfg()[attr_image].str().empty() && is_active && ability_affects_self(*p_ab, loc_)){
935  image_list.insert(p_ab->cfg()[attr_image].str());
936  }
937  //Add halo/overlay to owner of ability who affect adjacent only if active.
938  if(!p_ab->cfg()[image_type + "_image_self"].str().empty() && is_active){
939  image_list.insert(p_ab->cfg()[image_type + "_image_self"].str());
940  }
941  }
942 
943  foreach_distant_active_ability(*this, loc_, quick_check,
944  [&](const ability_ptr& p_ab, const unit&) {
945  image_list.insert(p_ab->cfg()[attr_image].str());
946  });
947 
948  return std::vector(image_list.begin(), image_list.end());
949 }
950 
952 {
953  //TODO: remove this function and make the caller use specials_context_t::add_formula_context directly.
954  if (context_) {
955  context_->add_formula_context(callable);
956  }
957 }
958 
960 {
961  if(const unit_const_ptr & att = attacker.un) {
962  callable.add("attacker", wfl::make_callable<wfl::unit_callable>(*att));
963  }
964  if(const unit_const_ptr & def = defender.un) {
965  callable.add("defender", wfl::make_callable<wfl::unit_callable>(*def));
966  }
967 }
968 
969 namespace {
970 
971 
972 template<typename T, typename TFuncFormula>
973 class get_ability_value_visitor
974 #ifdef USING_BOOST_VARIANT
975  : public boost::static_visitor<T>
976 #endif
977 {
978 public:
979  // Constructor stores the default value.
980  get_ability_value_visitor(T def, const TFuncFormula& formula_handler) : def_(def), formula_handler_(formula_handler) {}
981 
982  T operator()(const utils::monostate&) const { return def_; }
983  T operator()(bool) const { return def_; }
984  T operator()(int i) const { return static_cast<T>(i); }
985  T operator()(unsigned long long u) const { return static_cast<T>(u); }
986  T operator()(double d) const { return static_cast<T>(d); }
987  T operator()(const t_string&) const { return def_; }
988  T operator()(const std::string& s) const
989  {
990  if(s.size() >= 2 && s[0] == '(') {
991  return formula_handler_(s);
992  }
993  return lexical_cast_default<T>(s, def_);
994  }
995 
996 private:
997  const T def_;
998  const TFuncFormula& formula_handler_;
999 };
1000 
1001 template<typename T, typename TFuncFormula>
1002 T get_single_ability_value(const config::attribute_value& v, T def, const active_ability& ability_info, const map_location& receiver_loc, const specials_context_t* ctx, const TFuncFormula& formula_handler)
1003 {
1004  return v.apply_visitor(get_ability_value_visitor(def, [&](const std::string& s) {
1005 
1006  try {
1007  const unit_map& units = get_unit_map();
1008 
1009  auto u_itor = units.find(ability_info.teacher_loc);
1010 
1011  if(u_itor == units.end()) {
1012  return def;
1013  }
1014  wfl::map_formula_callable callable(std::make_shared<wfl::unit_callable>(*u_itor));
1015  if(ctx) {
1016  ctx->add_formula_context(callable);
1017  }
1018  if (auto uptr = units.find_unit_ptr(ability_info.student_loc)) {
1019  callable.add("student", wfl::make_callable<wfl::unit_callable>(*uptr));
1020  }
1021  if (auto uptr = units.find_unit_ptr(receiver_loc)) {
1022  callable.add("other", wfl::make_callable<wfl::unit_callable>(*uptr));
1023  }
1024  return formula_handler(wfl::formula(s, new wfl::gamestate_function_symbol_table, true), callable);
1025  } catch(const wfl::formula_error& e) {
1026  lg::log_to_chat() << "Formula error in ability or weapon special: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
1027  ERR_WML << "Formula error in ability or weapon special: " << e.type << " at " << e.filename << ':' << e.line << ")";
1028  return def;
1029  }
1030  }));
1031 }
1032 }
1033 
1034 template<typename TComp>
1035 std::pair<int,map_location> active_ability_list::get_extremum(const std::string& key, int def, const TComp& comp) const
1036 {
1037  if ( cfgs_.empty() ) {
1038  return std::pair(def, map_location());
1039  }
1040  // The returned location is the best non-cumulative one, if any,
1041  // the best absolute cumulative one otherwise.
1042  map_location best_loc;
1043  bool only_cumulative = true;
1044  int abs_max = 0;
1045  int flat = 0;
1046  int stack = 0;
1047  for (const active_ability& p : cfgs_)
1048  {
1049  int value = std::round(get_single_ability_value(p.ability_cfg()[key], static_cast<double>(def), p, loc(), nullptr, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
1050  return std::round(formula.evaluate(callable).as_int());
1051  }));
1052 
1053  if (p.ability_cfg()["cumulative"].to_bool()) {
1054  stack += value;
1055  if (value < 0) value = -value;
1056  if (only_cumulative && !comp(value, abs_max)) {
1057  abs_max = value;
1058  best_loc = p.teacher_loc;
1059  }
1060  } else if (only_cumulative || comp(flat, value)) {
1061  only_cumulative = false;
1062  flat = value;
1063  best_loc = p.teacher_loc;
1064  }
1065  }
1066  return std::pair(flat + stack, best_loc);
1067 }
1068 
1069 template std::pair<int, map_location> active_ability_list::get_extremum<std::less<int>>(const std::string& key, int def, const std::less<int>& comp) const;
1070 template std::pair<int, map_location> active_ability_list::get_extremum<std::greater<int>>(const std::string& key, int def, const std::greater<int>& comp) const;
1071 
1072 /*
1073  *
1074  * [special]
1075  * [swarm]
1076  * name= _ "swarm"
1077  * name_inactive= _ ""
1078  * description= _ ""
1079  * description_inactive= _ ""
1080  * cumulative=no
1081  * apply_to=self #self,opponent,defender,attacker,both
1082  * #active_on=defense # or offense; omitting this means "both"
1083  *
1084  * swarm_attacks_max=4
1085  * swarm_attacks_min=2
1086  *
1087  * [filter_self] // SUF
1088  * ...
1089  * [/filter_self]
1090  * [filter_opponent] // SUF
1091  * [filter_attacker] // SUF
1092  * [filter_defender] // SUF
1093  * [filter_adjacent] // SAUF
1094  * [filter_adjacent_location] // SAUF + locs
1095  * [/swarm]
1096  * [/special]
1097  *
1098  */
1099 
1101  : attacker(std::move(att))
1102  , defender(std::move(def))
1103  , is_for_listing(false)
1104 {
1105  if (attacker.at) {
1106  attacker.at->context_ = this;
1107  }
1108  if (defender.at) {
1109  defender.at->context_ = this;
1110  }
1111 }
1112 
1114 {
1115  if (attacker.at) {
1116  attacker.at->context_ = nullptr;
1117  }
1118  if (defender.at) {
1119  defender.at->context_ = nullptr;
1120  }
1121 }
1122 
1123 
1124 static bool is_enemy(std::size_t side, std::size_t other_side)
1125 {
1126  const team& side_team = get_team(side);
1127  return side_team.is_enemy(other_side);
1128 }
1129 
1130 /**
1131  * Returns a comma-separated string of active names for the specials of *this.
1132  * Empty names are skipped.
1133  *
1134  * Whether or not a special is active depends
1135  * on the current context (see set_specials_context)
1136  */
1138 {
1139  auto s_a_o = self_and_other(at);
1140  const auto& [self, other] = s_a_o;
1141 
1142  std::vector<std::string> special_names;
1143  std::set<std::string> ability_names;
1144 
1145  for(const auto& p_ab : at.specials()) {
1146  const bool active = is_special_active(self, *p_ab, unit_ability_t::affects_t::EITHER);
1147  std::string name = p_ab->get_name(!active);
1148  if(!name.empty()) {
1149  special_names.push_back(active ? std::move(name) : markup::span_color(font::INACTIVE_COLOR, name));
1150  }
1151  }
1152 
1153  // FIXME: clean this up...
1154 
1155  if (self.un) {
1156  const std::set<std::string>& checking_tags = abilities_list::all_weapon_tags();
1157  auto quick_check = [&](const ability_ptr& p_ab) {
1158  return checking_tags.count(p_ab->tag()) != 0;
1159  };
1160 
1161  foreach_active_ability(*self.un, self.loc, quick_check,
1162  [&](const ability_ptr& p_ab, const unit& source) {
1163  if (is_enemy(source.side(), s_a_o.self.un->side())) {
1164  return;
1165  }
1166  if (!is_special_active(s_a_o.self, *p_ab, unit_ability_t::affects_t::SELF)) {
1167  return;
1168  }
1169  const std::string& name_affected = p_ab->cfg().get_or("name_affected", "name").str();
1170  ability_names.insert(p_ab->substitute_variables(name_affected));
1171  });
1172  }
1173 
1174  if(!ability_names.empty()) {
1175  special_names.push_back("\n" + utils::join(ability_names, ", "));
1176  }
1177 
1178  return utils::join(special_names, ", ");
1179 }
1180 
1181 std::string specials_context_t::describe_weapon_specials_value(const attack_type& at, const std::set<std::string>& checking_tags) const
1182 {
1183  auto s_a_o = self_and_other(at);
1184  const auto& [self, other] = s_a_o;
1185 
1186  std::string res;
1187 
1188  std::set<std::string> wespon_specials;
1189  std::set<std::string> abilities_self;
1190  std::set<std::string> abilities_allies;
1191  std::set<std::string> abilities_enemies;
1192  std::set<std::string> opponents_abilities;
1193 
1194  auto quick_check = [&](const ability_ptr& p_ab) {
1195  return checking_tags.count(p_ab->tag()) != 0;
1196  };
1197 
1198  auto add_to_list = [&](const ability_ptr& p_ab, const specials_combatant& student, const auto& source) {
1199  if (&student == &s_a_o.other) {
1200  opponents_abilities.insert(p_ab->substitute_variables(p_ab->cfg()["name"].str()));
1201  } else if constexpr (utils::decayed_is_same<decltype(source), attack_type>) {
1202  wespon_specials.insert(p_ab->substitute_variables(p_ab->cfg()["name"].str()));
1203  } else if (&source == s_a_o.self.un.get()) {
1204  const std::string& name_affected = p_ab->cfg().get_or("name_affected", "name").str();
1205  abilities_self.insert(p_ab->substitute_variables(name_affected));
1206  } else if (!is_enemy(source.side(), s_a_o.self.un->side())) {
1207  const std::string& name_affected = p_ab->cfg().get_or("name_affected", "name").str();
1208  abilities_allies.insert(p_ab->substitute_variables(name_affected));
1209  } else {
1210  const std::string& name_affected = p_ab->cfg().get_or("name_affected", "name").str();
1211  abilities_enemies.insert(p_ab->substitute_variables(name_affected));
1212  }
1213  };
1214 
1215  auto add_to_res = [&](std::set<std::string>& to_add, const std::string& category_name) {
1216  to_add.erase("");
1217  if (!to_add.empty()) {
1218  //TODO: markup::span_color(font::TITLE_COLOR) ??
1219  res += (res.empty() ? "\n" : "") + category_name + utils::join(to_add, ", ");
1220  }
1221  };
1222 
1223 
1224  foreach_active_special(*this, self, quick_check, add_to_list);
1225 
1226  add_to_res(wespon_specials, "");
1227  add_to_res(abilities_self, _("Owned: "));
1228  // TRANSLATORS: Past-participle of "teach", used for an ability similar to leadership
1229  add_to_res(abilities_allies, _("Taught: "));
1230  // TRANSLATORS: Past-participle of "teach", used for an ability similar to leadership
1231  add_to_res(abilities_enemies, _("Taught: (by an enemy): "));
1232  add_to_res(opponents_abilities, _("Used by opponent: "));
1233 
1234  return res;
1235 }
1236 
1237 
1238 namespace { // Helpers for attack_type::special_active()
1239 
1240  /**
1241  * Returns whether or not the given special affects the opponent of the unit
1242  * with the special.
1243  * @param ab the ability/special
1244  * @param[in] is_attacker whether or not the unit with the special is the attacker
1245  */
1246  bool special_affects_opponent(const unit_ability_t& ab, bool is_attacker)
1247  {
1248  using apply_to_t = unit_ability_t::apply_to_t;
1249  const auto apply_to = ab.apply_to();
1250  if ( apply_to == apply_to_t::both)
1251  return true;
1252  if ( apply_to == apply_to_t::opponent )
1253  return true;
1254  if ( is_attacker && apply_to == apply_to_t::defender)
1255  return true;
1256  if ( !is_attacker && apply_to == apply_to_t::attacker)
1257  return true;
1258  return false;
1259  }
1260 
1261  /**
1262  * Returns whether or not the given special affects the unit with the special.
1263  * @param ab the ability/special
1264  * @param[in] is_attacker whether or not the unit with the special is the attacker
1265  */
1266  bool special_affects_self(const unit_ability_t& ab, bool is_attacker)
1267  {
1268  using apply_to_t = unit_ability_t::apply_to_t;
1269  const auto apply_to = ab.apply_to();
1270  if ( apply_to == apply_to_t::both )
1271  return true;
1272  if ( apply_to == apply_to_t::self)
1273  return true;
1274  if ( is_attacker && apply_to == apply_to_t::attacker)
1275  return true;
1276  if ( !is_attacker && apply_to == apply_to_t::defender)
1277  return true;
1278  return false;
1279  }
1280 
1281  static bool buildin_is_immune(const unit_ability_t& ab, const unit_const_ptr& them, map_location their_loc)
1282  {
1283  if (ab.tag() == "drains" && them && them->get_state("undrainable")) {
1284  return true;
1285  }
1286  if (ab.tag() == "plague" && them &&
1287  (them->get_state("unplagueable") ||
1288  resources::gameboard->map().is_village(their_loc))) {
1289  return true;
1290  }
1291  if (ab.tag() == "poison" && them &&
1292  (them->get_state("unpoisonable") || them->get_state(unit::STATE_POISONED))) {
1293  return true;
1294  }
1295  if (ab.tag() == "slow" && them &&
1296  (them->get_state("unslowable") || them->get_state(unit::STATE_SLOWED))) {
1297  return true;
1298  }
1299  if (ab.tag() == "petrifies" && them &&
1300  them->get_state("unpetrifiable")) {
1301  return true;
1302  }
1303  return false;
1304  }
1305  /**
1306  * Determines if a unit/weapon combination matches the specified child
1307  * (normally a [filter_*] child) of the provided filter.
1308  * @param[in] u A unit to filter.
1309  * @param[in] u2 Another unit to filter.
1310  * @param[in] loc The presumed location of @a unit.
1311  * @param[in] weapon The attack_type to filter.
1312  * @param[in] filter The filter containing the child filter to use.
1313  * @param[in] for_listing
1314  * @param[in] child_tag The tag of the child filter to use.
1315  * @param[in] applies_to_checked Parameter used for don't have infinite recusion for some filter attribute.
1316  */
1317  static bool special_unit_matches(const unit_const_ptr & u,
1318  const unit_const_ptr & u2,
1319  const map_location & loc,
1320  const const_attack_ptr& weapon,
1321  const unit_ability_t& ab,
1322  const bool for_listing,
1323  const std::string & child_tag, bool applies_to_checked)
1324  {
1325  if (for_listing && !loc.valid())
1326  // The special's context was set to ignore this unit, so assume we pass.
1327  // (This is used by reports.cpp to show active specials when the
1328  // opponent is not known. From a player's perspective, the special
1329  // is active, in that it can be used, even though the player might
1330  // need to select an appropriate opponent.)
1331  return true;
1332 
1333  const config& filter = ab.cfg();
1334  const config& filter_backstab = filter;
1335 
1336  auto filter_child = filter_backstab.optional_child(child_tag);
1337  if ( !filter_child )
1338  // The special does not filter on this unit, so we pass.
1339  return true;
1340 
1341  // If the primary unit doesn't exist, there's nothing to match
1342  if (!u) {
1343  return false;
1344  }
1345 
1346  unit_filter ufilt{vconfig(*filter_child)};
1347 
1348  // If the other unit doesn't exist, try matching without it
1349 
1350 
1351  auto filter_lock = ab.guard_against_recursion(*u);
1352  if(!filter_lock) {
1353  return false;
1354  }
1355  // Check for a weapon match.
1356  if (auto filter_weapon = filter_child->optional_child("filter_weapon") ) {
1357  std::string check_if_recursion = applies_to_checked ? ab.tag() : "";
1358  if ( !weapon || !weapon->matches_filter(*filter_weapon, check_if_recursion) )
1359  return false;
1360  }
1361 
1362  // Passed.
1363  // If the other unit doesn't exist, try matching without it
1364  if (!u2) {
1365  return ufilt.matches(*u, loc);
1366  }
1367  return ufilt.matches(*u, loc, *u2);
1368  }
1369 
1370 }//anonymous namespace
1371 
1372 
1373 /**
1374  * Returns a vector of names and descriptions for the specials of *this.
1375  * Each std::pair in the vector has first = name and second = description.
1376  *
1377  * This uses either the active or inactive name/description for each special,
1378  * based on the current context (see set_specials_context), provided
1379  * @a active_list is not nullptr. Otherwise specials are assumed active.
1380  * If the appropriate name is empty, the special is skipped.
1381  */
1382 std::vector<unit_ability_t::tooltip_info> specials_context_t::special_tooltips(const attack_type& at,
1383  boost::dynamic_bitset<>& active_list) const
1384 {
1385  //log_scope("special_tooltips");
1386  auto [self, other] = self_and_other(at);
1387 
1388  std::vector<unit_ability_t::tooltip_info> res;
1389  active_list.clear();
1390 
1391  for (const auto& p_ab : self.at->specials()) {
1392  bool active = is_special_active(self, *p_ab, unit_ability_t::affects_t::EITHER);
1393  auto name = p_ab->get_name(!active);
1394  auto desc = p_ab->get_description(!active);
1395 
1396  if (name.empty()) {
1397  continue;
1398  }
1399 
1400  res.AGGREGATE_EMPLACE(
1401  name,
1402  desc,
1403  p_ab->get_help_topic_id()
1404  );
1405 
1406  active_list.push_back(active);
1407  }
1408  return res;
1409 }
1410 
1411 namespace {
1412  /**
1413  * Returns whether or not the given special is active for the specified unit disregarding other units,
1414  * based on the current context (see specials_context).
1415  * @param ab the ability/special
1416  */
1417  bool special_tooltip_active(const specials_context_t& context, const specials_context_t::specials_combatant& self, const unit_ability_t& ab)
1418  {
1419  bool is_for_listing = context.is_for_listing;
1420 
1421  auto& other = context.other(self);
1422  bool is_attacker = &self == &context.attacker;
1423  //log_scope("special_tooltip_active");
1424 
1425  //here 'active_on' and checking of opponent weapon shouldn't implemented
1426  //because other_attack_ don't exist in sidebar display.
1427  //'apply_to' and some filters like [filter_student] are checked for know if
1428  //special must be displayed in sidebar.
1429 
1430  //only special who affect self are valid here.
1431  bool whom_is_self = special_affects_self(ab, is_attacker);
1432  if (!whom_is_self)
1433  return false;
1434 
1435  //this part of checking is similar to special_active but not the same.
1436  //"filter_opponent" is not checked here, and "filter_attacker/defender" only
1437  //if attacker/defender is self_.
1438  bool applied_both = ab.apply_to() == unit_ability_t::apply_to_t::both;
1439 
1440  if (!special_unit_matches(self.un, other.un, self.loc, self.at, ab, is_for_listing, "filter_student", applied_both || whom_is_self))
1441  return false;
1442  bool applied_to_attacker = applied_both || (whom_is_self && is_attacker);
1443  if (is_attacker && !special_unit_matches(self.un, other.un, self.loc, self.at, ab, is_for_listing, "filter_attacker", applied_to_attacker))
1444  return false;
1445  bool applied_to_defender = applied_both || (whom_is_self && !is_attacker);
1446  if (!is_attacker && !special_unit_matches(self.un, other.un, self.loc, self.at, ab, is_for_listing, "filter_defender", applied_to_defender))
1447  return false;
1448 
1449  return true;
1450  }
1451 
1452 }
1453 
1454 
1455 std::vector<unit_ability_t::tooltip_info> specials_context_t::abilities_special_tooltips(const attack_type& at,
1456  boost::dynamic_bitset<>& active_list) const
1457 {
1458  auto s_a_o = self_and_other(at);
1459  const auto& [self, other] = s_a_o;
1460 
1461  std::vector<unit_ability_t::tooltip_info> res;
1462  active_list.clear();
1463  std::set<std::string> checking_name;
1464  if (!self.un) {
1465  return res;
1466  }
1467  foreach_active_ability(*self.un, self.loc,
1468  [&](const ability_ptr&) {
1469  return true;
1470  },
1471  [&](const ability_ptr& p_ab, const unit&) {
1472  if (special_tooltip_active(*this, s_a_o.self, *p_ab)) {
1473  bool active = is_special_active(s_a_o.self, *p_ab, unit_ability_t::affects_t::SELF);
1474  const std::string name = p_ab->substitute_variables(p_ab->cfg()["name_affected"]);
1475  const std::string desc = p_ab->substitute_variables(p_ab->cfg()["description_affected"]);
1476 
1477  if (name.empty() || checking_name.count(name) != 0) {
1478  return;
1479  }
1480  res.AGGREGATE_EMPLACE(name, desc, p_ab->get_help_topic_id());
1481  checking_name.insert(name);
1482  active_list.push_back(active);
1483  }
1484  });
1485  return res;
1486 }
1487 
1488 
1489 //The following functions are intended to allow the use in combat of capacities
1490 //identical to special weapons and therefore to be able to use them on adjacent
1491 //units (abilities of type 'aura') or else on all types of weapons even if the
1492 //beneficiary unit does not have a corresponding weapon
1493 //(defense against ranged weapons abilities for a unit that only has melee attacks)
1494 namespace {
1495  bool overwrite_special_affects(const unit_ability_t& ab)
1496  {
1497  const std::string& apply_to = ab.cfg()["overwrite_specials"];
1498  return (apply_to == "one_side" || apply_to == "both_sides");
1499  }
1500 }
1501 
1503 {
1504  auto ctx = fallback_context();
1505  auto [self, other] = context_->self_and_other(*this);
1506  const map_location& loc = overwritten.student_loc;
1507  if(overwriters.empty()) {
1508  return false;
1509  }
1510 
1511  const unit_ability_t& ab = overwritten.ability();
1512  for(const auto& j : overwriters) {
1513  if(j.ability().suppress_special_priority() <= ab.suppress_special_priority()) {
1514  continue;
1515  }
1516 
1517  // code for new feature [overwrite_specials].
1518  auto overwrite_specials = j.ability_cfg().optional_child("overwrite_specials");
1519  if(overwrite_specials) {
1520  // the location of the fighters is used to differentiate the specials applied to 'self' from those applied to 'opponent'
1521  // in all cases including 'apply_to=attacker/defender'.
1522  if((*overwrite_specials)["affect"].str("both") == "self" && loc != self.loc) {
1523  continue;
1524  }
1525  if(other.un && (*overwrite_specials)["affect"].str("both") == "opponent" && loc != other.loc) {
1526  continue;
1527  }
1528  // if ab don't match with [filter_special], continue check with next element of overwriters list.
1529  auto filter_abilities_specials = (*overwrite_specials).optional_child("filter_special");
1530  if(filter_abilities_specials && !ab.matches_filter(*filter_abilities_specials)) {
1531  continue;
1532  }
1533  return true;
1534  }
1535 
1536  // If neither the tag nor the 'overwrite_specials' attribute are valid, we move directly to the next element in the overwriters list.
1537  if(!overwrite_special_affects(j.ability())) {
1538  continue;
1539  }
1540 
1541  //code for old feature 'overwrite_specials' if special don't have new one.
1542 
1543  // the location of the fighters is used to differentiate the specials applied to 'self' from those applied to 'opponent'
1544  // in all cases including 'apply_to=attacker/defender'.
1545  if(j.ability_cfg()["overwrite_specials"].str() == "one_side") {
1546  if(j.student_loc == self.loc && loc != self.loc) {
1547  continue;
1548  }
1549  if(other.un && j.student_loc == other.loc && loc != other.loc) {
1550  continue;
1551  }
1552  }
1553 
1554  auto overwrite_filter = j.ability_cfg().optional_child("overwrite");
1555  if(overwrite_filter) {
1556  auto filter_abilities_specials = (*overwrite_filter).optional_child("filter_specials");
1557  if(!filter_abilities_specials) {
1558  filter_abilities_specials = (*overwrite_filter).optional_child("experimental_filter_specials");
1559  if(filter_abilities_specials) {
1560  deprecated_message("experimental_filter_specials", DEP_LEVEL::FOR_REMOVAL, {1, 21, 0}, "Use filter_specials instead.");
1561  }
1562  }
1563  if(filter_abilities_specials && !ab.matches_filter(*filter_abilities_specials)) {
1564  continue;
1565  }
1566  }
1567  return true;
1568  }
1569  return false;
1570 }
1571 
1573 {
1574  auto filter_lock = ab.guard_against_recursion(*this);
1575  if(!filter_lock) {
1576  return false;
1577  }
1578  return (ability_active_impl(ab, loc) && ability_affects_self(ab, loc));
1579 }
1580 
1581 bool unit::get_adj_ability_bool(const unit_ability_t& ab, std::size_t dist, int dir, const map_location& loc, const unit& from, const map_location& from_loc) const
1582 {
1583  auto filter_lock = ab.guard_against_recursion(from);;
1584  if(!filter_lock) {
1585  return false;
1586  }
1587  return (affects_side(ab, side(), from.side()) && from.ability_active_impl(ab, from_loc) && ability_affects_adjacent(ab, dist, dir, loc, from));
1588 }
1589 
1590 /**
1591  * Returns whether or not @a *this has a special ability with a tag or id equal to
1592  * @a special. the Check is for a special ability
1593  * active in the current context (see set_specials_context), including
1594  * specials obtained from the opponent's attack.
1595  */
1596 bool specials_context_t::has_active_special(const attack_type & at, const std::string & tag_name) const
1597 {
1598 
1599  auto quick_check = [&](const ability_ptr& p_ab) {
1600  return p_ab->tag() == tag_name;
1601  };
1602 
1603  auto [self, other] = self_and_other(at);
1604  return foreach_active_special(*this, self, quick_check, return_true);
1605 }
1606 
1607 bool specials_context_t::has_active_special_id(const attack_type& at, const std::string& special_id) const
1608 {
1609  //Now that filter_(second)attack in event supports special_id/type_active, including abilities used as weapons,
1610  //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.
1611  //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.
1612  if (at.range().empty()) {
1613  return false;
1614  }
1615 
1616 
1617  auto quick_check = [&](const ability_ptr& p_ab) {
1618  return p_ab->id() == special_id;
1619  };
1620 
1621  auto [self, other] = self_and_other(at);
1622  return foreach_active_special(*this, self, quick_check, return_true);
1623 }
1624 
1626 {
1627  auto s_a_o = self_and_other(at);
1628  const auto& [self, other] = s_a_o;
1629 
1630  const map_location loc = self.un ? self.un->get_location() : self.loc;
1631  active_ability_list res(loc);
1632  const std::set<std::string>& checking_tags = abilities_list::all_weapon_tags();
1633  auto quick_check = [&](const ability_ptr& p_ab) {
1634  return checking_tags.count(p_ab->tag()) != 0;
1635  };
1636 
1637  if (self.un) {
1638  foreach_distant_active_ability(*self.un, self.loc, quick_check,
1639  [&](const ability_ptr& p_ab, const unit& u_teacher) {
1640  if (is_special_active(s_a_o.self, *p_ab, unit_ability_t::affects_t::SELF)) {
1641  res.emplace_back(p_ab, s_a_o.self.un->get_location(), u_teacher.get_location());
1642  }
1643  }
1644  );
1645  }
1646  if (other.un) {
1647  foreach_distant_active_ability(*other.un, other.loc, quick_check,
1648  [&](const ability_ptr& p_ab, const unit& u_teacher) {
1649  if (is_special_active(s_a_o.other, *p_ab, unit_ability_t::affects_t::OTHER)) {
1650  res.emplace_back(p_ab, s_a_o.other.un->get_location(), u_teacher.get_location());
1651  }
1652  }
1653  );
1654  }
1655  return res;
1656 }
1657 
1659 {
1660  auto [self, other] = self_and_other(at);
1661  const map_location loc = self.un ? self.un->get_location() : self.loc;
1662  active_ability_list res(loc);
1663 
1664  auto quick_check = [&](const ability_ptr& p_ab) {
1665  return p_ab->tag() == tag_name;
1666  };
1667 
1668  auto add_to_list = utils::overload {
1669  [&](const ability_ptr& p_ab, const specials_combatant& student, const attack_type&) {
1670  res.emplace_back(p_ab, student.loc, student.loc);
1671  },
1672  [&](const ability_ptr& p_ab, const specials_combatant& student, const unit& source) {
1673  res.emplace_back(p_ab, student.un->get_location(), source.get_location());
1674  }
1675  };
1676 
1677 
1678  foreach_active_special(*this, self, quick_check, add_to_list);
1679  return res;
1680 }
1681 
1682 active_ability_list specials_context_t::get_abilities_weapons(const std::string& tag_name, const unit& un) const
1683 {
1684  auto s_a_o = self_and_other(un);
1685  const auto& [self, other] = s_a_o;
1686  //TODO: fall back to un.get_location() ?
1687  active_ability_list res = un.get_abilities(tag_name, self.loc);
1688 
1689  utils::erase_if(res, [&](const active_ability& i) {
1690  //If no weapon is given, assume the ability is active. this is used by ai code.
1691  return !is_special_active(s_a_o.self, i.ability(), unit_ability_t::affects_t::SELF);
1692  });
1693  return res;
1694 
1695 }
1696 
1697 
1698 //end of emulate weapon special functions.
1699 
1700 namespace
1701 {
1702  bool exclude_ability_attributes(const std::string& tag_name, const config & filter)
1703  {
1704  ///check what filter attributes used can be used in type of ability checked.
1705  bool abilities_check = abilities_list::ability_value_tags().count(tag_name) != 0 || abilities_list::ability_no_value_tags().count(tag_name) != 0;
1706  if(filter.has_attribute("active_on") && tag_name != "resistance" && abilities_check)
1707  return false;
1708  if(filter.has_attribute("apply_to") && tag_name != "resistance" && abilities_check)
1709  return false;
1710 
1711  if(filter.has_attribute("overwrite_specials") && abilities_list::weapon_math_tags().count(tag_name) == 0)
1712  return false;
1713 
1714  bool no_value_weapon_abilities_check = abilities_list::no_weapon_math_tags().count(tag_name) != 0 || abilities_list::ability_no_value_tags().count(tag_name) != 0;
1715  if(filter.has_attribute("cumulative") && no_value_weapon_abilities_check && (tag_name != "swarm" || tag_name != "berserk"))
1716  return false;
1717  if(filter.has_attribute("value") && (no_value_weapon_abilities_check && tag_name != "berserk"))
1718  return false;
1719  if(filter.has_attribute("add") && no_value_weapon_abilities_check)
1720  return false;
1721  if(filter.has_attribute("sub") && no_value_weapon_abilities_check)
1722  return false;
1723  if(filter.has_attribute("multiply") && no_value_weapon_abilities_check)
1724  return false;
1725  if(filter.has_attribute("divide") && no_value_weapon_abilities_check)
1726  return false;
1727  if(filter.has_attribute("priority") && no_value_weapon_abilities_check)
1728  return false;
1729 
1730  bool all_engine = abilities_list::no_weapon_math_tags().count(tag_name) != 0 || abilities_list::weapon_math_tags().count(tag_name) != 0 || abilities_list::ability_value_tags().count(tag_name) != 0 || abilities_list::ability_no_value_tags().count(tag_name) != 0;
1731  if(filter.has_attribute("replacement_type") && tag_name != "damage_type" && all_engine)
1732  return false;
1733  if(filter.has_attribute("alternative_type") && tag_name != "damage_type" && all_engine)
1734  return false;
1735  if(filter.has_attribute("type") && tag_name != "plague" && all_engine)
1736  return false;
1737 
1738  return true;
1739  }
1740 
1741  bool matches_ability_filter(const config & cfg, const std::string& tag_name, const config & filter)
1742  {
1743  using namespace utils::config_filters;
1744 
1745  //check if attributes have right to be in type of ability checked
1746  if(!exclude_ability_attributes(tag_name, filter))
1747  return false;
1748 
1749  // tag_name and id are equivalent of ability ability_type and ability_id/type_active filters
1750  //can be extent to special_id/type_active. If tag_name or id matche if present in list.
1751  const std::vector<std::string> filter_type = utils::split(filter["tag_name"]);
1752  if(!filter_type.empty() && !utils::contains(filter_type, tag_name))
1753  return false;
1754 
1755  if(!string_matches_if_present(filter, cfg, "id", ""))
1756  return false;
1757 
1758  //when affect_adjacent=yes detect presence of [affect_adjacent] in abilities, if no
1759  //then matches when tag not present.
1760  if(!filter["affect_adjacent"].empty()){
1761  bool adjacent = cfg.has_child("affect_adjacent");
1762  if(filter["affect_adjacent"].to_bool() != adjacent){
1763  return false;
1764  }
1765  }
1766 
1767  //these attributs below filter attribute used in all engine abilities.
1768  //matches if filter attribute have same boolean value what attribute
1769  if(!bool_matches_if_present(filter, cfg, "affect_self", true))
1770  return false;
1771 
1772  //here if value of affect_allies but also his presence who is checked because
1773  //when affect_allies not specified, ability affect unit of same side what owner only.
1774  if(!bool_or_empty(filter, cfg, "affect_allies"))
1775  return false;
1776 
1777  if(!bool_matches_if_present(filter, cfg, "affect_enemies", false))
1778  return false;
1779 
1780 
1781  //cumulative, overwrite_specials and active_on check attributes used in all abilities
1782  //who return a numerical value.
1783  if(!bool_matches_if_present(filter, cfg, "cumulative", false))
1784  return false;
1785 
1786  if(!cfg["overwrite_specials"].blank() || cfg.optional_child("overwrite")) {
1787  deprecated_message("overwrite_specials= or [overwrite] in weapon specials", DEP_LEVEL::INDEFINITE, "", "Use [overwrite_specials] instead.");
1788  }
1789  if(!string_matches_if_present(filter, cfg, "overwrite_specials", "none"))
1790  return false;
1791 
1792  if(!string_matches_if_present(filter, cfg, "active_on", "both"))
1793  return false;
1794 
1795  if(abilities_list::weapon_math_tags().count(tag_name) != 0 || abilities_list::ability_value_tags().count(tag_name) != 0) {
1796  if(!double_matches_if_present(filter, cfg, "priority", 0.00)) {
1797  return false;
1798  }
1799  } else {
1800  if(!double_matches_if_present(filter, cfg, "priority")) {
1801  return false;
1802  }
1803  }
1804 
1805  //value, add, sub multiply and divide check values of attribute used in engines abilities(default value of 'value' can be checked when not specified)
1806  //who return numericals value but can also check in non-engine abilities(in last case if 'value' not specified none value can matches)
1807  if(!filter["value"].empty()){
1808  if(tag_name == "drains"){
1809  if(!int_matches_if_present(filter, cfg, "value", 50)){
1810  return false;
1811  }
1812  } else if(tag_name == "berserk"){
1813  if(!int_matches_if_present(filter, cfg, "value", 1)){
1814  return false;
1815  }
1816  } else if(tag_name == "heal_on_hit" || tag_name == "heals" || tag_name == "regenerate" || tag_name == "leadership"){
1817  if(!int_matches_if_present(filter, cfg, "value" , 0)){
1818  return false;
1819  }
1820  } else {
1821  if(!int_matches_if_present(filter, cfg, "value")){
1822  return false;
1823  }
1824  }
1825  }
1826 
1827  if(!int_matches_if_present_or_negative(filter, cfg, "add", "sub"))
1828  return false;
1829 
1830  if(!int_matches_if_present_or_negative(filter, cfg, "sub", "add"))
1831  return false;
1832 
1833  if(!double_matches_if_present(filter, cfg, "multiply"))
1834  return false;
1835 
1836  if(!double_matches_if_present(filter, cfg, "divide"))
1837  return false;
1838 
1839 
1840  //apply_to is a special case, in resistance ability, it check a list of damage type used by [resistance]
1841  //but in weapon specials, check identity of unit affected by special(self, opponent tc...)
1842  if(tag_name == "resistance"){
1843  if(!set_includes_if_present(filter, cfg, "apply_to")){
1844  return false;
1845  }
1846  } else {
1847  if(!string_matches_if_present(filter, cfg, "apply_to", "self")){
1848  return false;
1849  }
1850  }
1851 
1852  //the three attribute below are used for check in specifics abilitie:
1853  //replacement_type and alternative_type are present in [damage_type] only for engine abilities
1854  //and type for [plague], but if someone want use this in non-engine abilities, these attribute can be checked outside type mentioned.
1855  //
1856 
1857  //for damage_type only(in engine cases)
1858  if(!string_matches_if_present(filter, cfg, "replacement_type", ""))
1859  return false;
1860 
1861  if(!string_matches_if_present(filter, cfg, "alternative_type", ""))
1862  return false;
1863 
1864  //for plague only(in engine cases)
1865  if(!string_matches_if_present(filter, cfg, "type", ""))
1866  return false;
1867 
1868  //the wml_filter is used in cases where the attribute we are looking for is not
1869  //previously listed or to check the contents of the sub_tags ([filter_adjacent],[filter_self],[filter_opponent] etc.
1870  //If the checked set does not exactly match the content of the capability, the function returns a false response.
1871  auto fwml = filter.optional_child("filter_wml");
1872  if (fwml){
1873  if(!cfg.matches(*fwml)){
1874  return false;
1875  }
1876  }
1877 
1878  // Passed all tests.
1879  return true;
1880  }
1881 
1882  static bool common_matches_filter(const config & cfg, const std::string& tag_name, const config & filter)
1883  {
1884  // Handle the basic filter.
1885  bool matches = matches_ability_filter(cfg, tag_name, filter);
1886 
1887  // Handle [and], [or], and [not] with in-order precedence
1888  for(const auto [key, condition_cfg] : filter.all_children_view() )
1889  {
1890  // Handle [and]
1891  if ( key == "and" )
1892  matches = matches && common_matches_filter(cfg, tag_name, condition_cfg);
1893 
1894  // Handle [or]
1895  else if ( key == "or" )
1896  matches = matches || common_matches_filter(cfg, tag_name, condition_cfg);
1897 
1898  // Handle [not]
1899  else if ( key == "not" )
1900  matches = matches && !common_matches_filter(cfg, tag_name, condition_cfg);
1901  }
1902 
1903  return matches;
1904  }
1905 }
1906 
1908 {
1909  return common_matches_filter(cfg(), tag(), filter);
1910 }
1911 
1913 {
1914  if(at.range().empty()){
1915  return false;
1916  }
1917 
1918  bool skip_adjacent = !filter["affect_adjacent"].to_bool(true);
1919 
1920  auto quick_check = [&](const ability_ptr& p_ab) {
1921  return p_ab->matches_filter(filter);
1922  };
1923 
1924  auto [self, other] = self_and_other(at);
1925  return foreach_active_special(*this, self, quick_check, return_true, skip_adjacent);
1926 }
1927 
1929 {
1930  bool skip_adjacent = !filter["affect_adjacent"].to_bool(true);
1931 
1932  auto quick_check = [&](const ability_ptr& p_ab) {
1933  return p_ab->matches_filter(filter);
1934  };
1935 
1936  return foreach_active_ability(un, loc, quick_check, return_true, skip_adjacent);
1937 }
1938 
1939 bool specials_context_t::has_active_ability_id(const unit& un, map_location loc, const std::string& id)
1940 {
1941  auto quick_check = [&](const ability_ptr& p_ab) {
1942  return p_ab->id() == id;
1943  };
1944 
1945  return foreach_active_ability(un, loc, quick_check, return_true);
1946 }
1947 
1948 namespace {
1949  class temporary_facing
1950  {
1951  map_location::direction save_dir_;
1952  unit_const_ptr u_;
1953  public:
1954  temporary_facing(const unit_const_ptr& u, map_location::direction new_dir)
1955  : save_dir_(u ? u->facing() : map_location::direction::indeterminate)
1956  , u_(u)
1957  {
1958  if (u_) {
1959  u_->set_facing(new_dir);
1960  }
1961  }
1962  ~temporary_facing()
1963  {
1964  if (u_) {
1965  u_->set_facing(save_dir_);
1966  }
1967  }
1968  };
1969 }
1970 /**
1971  * Returns whether or not the given special is active for the specified unit,
1972  * based on the current context (see set_specials_context).
1973  * @param self this combatant
1974  * @param ab the ability
1975  * @param whom specifies which combatant we care about
1976  */
1978 {
1979  bool is_attacker = &self == &attacker;
1980  const auto& other = this->other(self);
1981 
1982  bool is_for_listing = this->is_for_listing;
1983  //log_scope("special_active");
1984 
1985 
1986  // Does this affect the specified unit?
1987  if ( whom == unit_ability_t::affects_t::SELF ) {
1988  if ( !special_affects_self(ab, is_attacker) )
1989  return false;
1990  }
1991  if ( whom == unit_ability_t::affects_t::OTHER ) {
1992  if ( !special_affects_opponent(ab, is_attacker) )
1993  return false;
1994  }
1995 
1996  // Is this active on attack/defense?
1997  if (!ab.active_on_matches(is_attacker)) {
1998  return false;
1999  }
2000 
2001  // Get the units involved.
2002  const unit_map& units = get_unit_map();
2003 
2004  unit_const_ptr self_u = self.un;
2005  unit_const_ptr other_u = other.un;
2006 
2007  // We also set the weapons context during (attack) wml events, in that case we identify the units via locations because wml might change
2008  // the actual unit and usually does so via replacing, in that case self_ is set to nullptr.
2009  // TODO: does this really make sense? if wml replaces the unit it also replaces the attack object, deleting the attack context properties
2010  if(self_u == nullptr) {
2011  unit_map::const_iterator it = units.find(self.loc);
2012  if(it.valid()) {
2013  self_u = it.get_shared_ptr();
2014  }
2015  }
2016  if(other_u == nullptr) {
2017  unit_map::const_iterator it = units.find(other.loc);
2018  if(it.valid()) {
2019  other_u = it.get_shared_ptr();
2020  }
2021  }
2022 
2023  // Make sure they're facing each other.
2024  temporary_facing self_facing(self_u, self.loc.get_relative_dir(other.loc));
2025  temporary_facing other_facing(other_u, other.loc.get_relative_dir(self.loc));
2026 
2027  // Filter poison, plague, drain, slow, petrifies
2028  // True if "whom" corresponds to "self", false if "whom" is "other"
2029  bool whom_is_self = ((whom == unit_ability_t::affects_t::SELF) || ((whom == unit_ability_t::affects_t::EITHER) && special_affects_self(ab, is_attacker)));
2030  unit_const_ptr them = whom_is_self ? other_u : self_u;
2031  map_location their_loc = whom_is_self ? other.loc : self.loc;
2032 
2033  if (buildin_is_immune(ab, them, their_loc)) {
2034  return false;
2035  }
2036 
2037 
2038  // Translate our context into terms of "attacker" and "defender".
2039  unit_const_ptr & att = is_attacker ? self_u : other_u;
2040  unit_const_ptr & def = is_attacker ? other_u : self_u;
2041 
2042  // Filter firststrike here, if both units have first strike then the effects cancel out. Only check
2043  // the opponent if "whom" is the defender, otherwise this leads to infinite recursion.
2044  if (ab.tag() == "firststrike") {
2045  bool whom_is_defender = whom_is_self ? !is_attacker : is_attacker;
2046  if (whom_is_defender && attacker.at && attacker.at->has_special_or_ability("firststrike"))
2047  return false;
2048  }
2049 
2050  // Filter the units involved.
2051  //If filter concerns the unit on which special is applied,
2052  //then the type of special must be entered to avoid calling
2053  //the function of this special in matches_filter()
2054  //In apply_to=both case, ab.tag() must be checked in all filter because special applied to both self and opponent.
2055  bool applied_both = ab.apply_to() == unit_ability_t::apply_to_t::both;
2056  const std::string& filter_self = ab.in_specials_tag() ? "filter_self" : "filter_student";
2057 
2058  bool applied_to_self = (applied_both || whom_is_self);
2059  if (!special_unit_matches(self_u, other_u, self.loc, self.at, ab, is_for_listing, filter_self, applied_to_self))
2060  return false;
2061  bool applied_to_opp = (applied_both || !whom_is_self);
2062  if (!special_unit_matches(other_u, self_u, other.loc, other.at, ab, is_for_listing, "filter_opponent", applied_to_opp))
2063  return false;
2064  //in case of apply_to=attacker|defender, if both [filter_attacker] and [filter_defender] are used,
2065  //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.
2066  bool applied_to_attacker = applied_both || (whom_is_self && is_attacker) || (!whom_is_self && !is_attacker);
2067  if (!special_unit_matches(att, def, attacker.loc, attacker.at, ab, is_for_listing, "filter_attacker", applied_to_attacker))
2068  return false;
2069  bool applied_to_defender = applied_both || (whom_is_self && !is_attacker) || (!whom_is_self && is_attacker);
2070  if (!special_unit_matches(def, att, defender.loc, defender.at, ab, is_for_listing, "filter_defender", applied_to_defender))
2071  return false;
2072 
2073  return true;
2074 }
2075 
2076 namespace
2077 {
2078  bool priority_checking(active_ability_list& overwriters, const active_ability& overwritten)
2079  {
2080  if(overwriters.empty()){
2081  return false;
2082  }
2083  const unit_ability_t& ab = overwritten.ability();
2084 
2085  for(const auto& j : overwriters) {
2086  // If this element of overwriters does not have a priority strictly higher than ab,
2087  // either it does not have [overwrite_abilities] or both have [overwrite_abilities] of the same priority;
2088  // in either case, ab cannot be suppressed by this element of the list
2089  if(j.ability().suppress_ability_priority() <= ab.suppress_ability_priority()) {
2090  continue;
2091  }
2092 
2093  // [overwrite_abilities]priority= having already been checked above, it remains to check if a sub-filter [filter_ability] exists and if so,
2094  // if it corresponds to ab.
2095  auto overwrite_filter = j.ability_cfg().optional_child("overwrite_abilities");
2096  if(overwrite_filter) {
2097  auto filter_abilities = (*overwrite_filter).optional_child("filter_ability");
2098  if(filter_abilities && !common_matches_filter(ab.cfg(), ab.tag(), *filter_abilities)) {
2099  continue;
2100  }
2101  // if all checks match, ab can be suppressed and other elements of the list won't be checked.
2102  return true;
2103  }
2104  }
2105  return false;
2106  }
2107 
2108  void apply_ability_suppression(active_ability_list& abil_list)
2109  {
2110  // If two or more abilities have [overwrite_abilities],
2111  // priority sorting allows the highest priority ability to suppress a lower priority ability before the lower priority ability can,
2112  // in turn, suppress an ability with an even lower priority than the first two.
2113  utils::sort_if(abil_list,[](const active_ability& i, const active_ability& j){
2114  double l = i.ability().suppress_ability_priority();
2115  double r = j.ability().suppress_ability_priority();
2116  return l > r;
2117  });
2118  utils::erase_if(abil_list, [&](const active_ability& i) {
2119  return (priority_checking(abil_list, i));
2120  });
2121  }
2122 }
2123 
2125 {
2126 
2127 void individual_effect::set(value_modifier t, int val, const config& abil, const map_location &l)
2128 {
2129  type = t;
2130  value = val;
2131  ability = &abil;
2132  loc = l;
2133 }
2134 
2135 bool filter_base_matches(const config& cfg, int def)
2136 {
2137  if (auto apply_filter = cfg.optional_child("filter_base_value")) {
2138  config::attribute_value cond_eq = apply_filter["equals"];
2139  config::attribute_value cond_ne = apply_filter["not_equals"];
2140  config::attribute_value cond_lt = apply_filter["less_than"];
2141  config::attribute_value cond_gt = apply_filter["greater_than"];
2142  config::attribute_value cond_ge = apply_filter["greater_than_equal_to"];
2143  config::attribute_value cond_le = apply_filter["less_than_equal_to"];
2144  return (cond_eq.empty() || def == cond_eq.to_int()) &&
2145  (cond_ne.empty() || def != cond_ne.to_int()) &&
2146  (cond_lt.empty() || def < cond_lt.to_int()) &&
2147  (cond_gt.empty() || def > cond_gt.to_int()) &&
2148  (cond_ge.empty() || def >= cond_ge.to_int()) &&
2149  (cond_le.empty() || def <= cond_le.to_int());
2150  }
2151  return true;
2152 }
2153 
2154 static int individual_value_int(const config::attribute_value *v, int def, const active_ability & ability, const map_location& loc, const specials_context_t* ctx) {
2155  int value = std::round(get_single_ability_value(*v, static_cast<double>(def), ability, loc, ctx, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2156  callable.add("base_value", wfl::variant(def));
2157  return std::round(formula.evaluate(callable).as_int());
2158  }));
2159  return value;
2160 }
2161 
2162 static int individual_value_double(const config::attribute_value *v, int def, const active_ability & ability, const map_location& loc, const specials_context_t* ctx) {
2163  int value = std::round(get_single_ability_value(*v, static_cast<double>(def), ability, loc, ctx, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2164  callable.add("base_value", wfl::variant(def));
2165  return formula.evaluate(callable).as_decimal() / 1000.0 ;
2166  }) * 100);
2167  return value;
2168 }
2169 
2171  effect_list_(),
2172  composite_value_(def),
2173  composite_double_value_(def)
2174 {
2175  // If ctx is not empty, then this is a special. The [overwrite_specials] feature is handled in get_specials_and_abilities() and so nothing should be done here in that case.
2176  if(!ctx) {
2177  apply_ability_suppression(list);
2178  }
2179  std::map<double, active_ability_list> base_list;
2180  for(const active_ability& i : list) {
2181  double priority = i.ability().priority();
2182  if(base_list[priority].empty()) {
2183  base_list[priority] = active_ability_list(list.loc());
2184  }
2185  base_list[priority].emplace_back(i);
2186  }
2187  int value = def;
2188  for(auto base : base_list) {
2189  effect::effect_impl(base.second, value, ctx, wham);
2191  }
2192 }
2193 
2194 void effect::effect_impl(const active_ability_list& list, int def, const specials_context_t* ctx, EFFECTS wham )
2195 {
2196  int value_set = def;
2197  std::map<std::string,individual_effect> values_add;
2198  std::map<std::string,individual_effect> values_sub;
2199  std::map<std::string,individual_effect> values_mul;
2200  std::map<std::string,individual_effect> values_div;
2201 
2202  individual_effect set_effect_max;
2203  individual_effect set_effect_min;
2204  individual_effect set_effect_cum;
2205  utils::optional<int> max_value = utils::nullopt;
2206  utils::optional<int> min_value = utils::nullopt;
2207 
2208  for (const active_ability & ability : list) {
2209  const config& cfg = ability.ability_cfg();
2210  const std::string& effect_id = cfg[cfg["id"].empty() ? "name" : "id"];
2211 
2212  if (!filter_base_matches(cfg, def))
2213  continue;
2214 
2215  if (const config::attribute_value *v = cfg.get("value")) {
2216  int value = individual_value_int(v, def, ability, list.loc(), ctx);
2217  int value_cum = wham != EFFECT_CUMULABLE && cfg["cumulative"].to_bool() ? std::max(def, value) : value;
2218  if(set_effect_cum.type != NOT_USED && wham == EFFECT_CUMULABLE && cfg["cumulative"].to_bool()) {
2219  set_effect_cum.set(SET, set_effect_cum.value + value_cum, ability.ability_cfg(), ability.teacher_loc);
2220  } else if(wham == EFFECT_CUMULABLE && cfg["cumulative"].to_bool()) {
2221  set_effect_cum.set(SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2222  } else {
2223  assert((set_effect_min.type != NOT_USED) == (set_effect_max.type != NOT_USED));
2224  if(set_effect_min.type == NOT_USED) {
2225  set_effect_min.set(SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2226  set_effect_max.set(SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2227  }
2228  else {
2229  if(value_cum > set_effect_max.value) {
2230  set_effect_max.set(SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2231  }
2232  if(value_cum < set_effect_min.value) {
2233  set_effect_min.set(SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2234  }
2235  }
2236  }
2237  }
2238 
2239  if(wham != EFFECT_WITHOUT_CLAMP_MIN_MAX) {
2240  if(const config::attribute_value *v = cfg.get("max_value")) {
2241  int value = individual_value_int(v, def, ability, list.loc(), ctx);
2242  max_value = max_value ? std::min(*max_value, value) : value;
2243  }
2244  if(const config::attribute_value *v = cfg.get("min_value")) {
2245  int value = individual_value_int(v, def, ability, list.loc(), ctx);
2246  min_value = min_value ? std::max(*min_value, value) : value;
2247  }
2248  }
2249 
2250  if (const config::attribute_value *v = cfg.get("add")) {
2251  int add = individual_value_int(v, def, ability, list.loc(), ctx);
2252  std::map<std::string,individual_effect>::iterator add_effect = values_add.find(effect_id);
2253  if(add_effect == values_add.end() || add > add_effect->second.value) {
2254  values_add[effect_id].set(ADD, add, ability.ability_cfg(), ability.teacher_loc);
2255  }
2256  }
2257  if (const config::attribute_value *v = cfg.get("sub")) {
2258  int sub = - individual_value_int(v, def, ability, list.loc(), ctx);
2259  std::map<std::string,individual_effect>::iterator sub_effect = values_sub.find(effect_id);
2260  if(sub_effect == values_sub.end() || sub < sub_effect->second.value) {
2261  values_sub[effect_id].set(ADD, sub, ability.ability_cfg(), ability.teacher_loc);
2262  }
2263  }
2264  if (const config::attribute_value *v = cfg.get("multiply")) {
2265  int multiply = individual_value_double(v, def, ability, list.loc(), ctx);
2266  std::map<std::string,individual_effect>::iterator mul_effect = values_mul.find(effect_id);
2267  if(mul_effect == values_mul.end() || multiply > mul_effect->second.value) {
2268  values_mul[effect_id].set(MUL, multiply, ability.ability_cfg(), ability.teacher_loc);
2269  }
2270  }
2271  if (const config::attribute_value *v = cfg.get("divide")) {
2272  int divide = individual_value_double(v, def, ability, list.loc(), ctx);
2273 
2274  if (divide == 0) {
2275  ERR_NG << "division by zero with divide= in ability/weapon special " << effect_id;
2276  }
2277  else {
2278  std::map<std::string,individual_effect>::iterator div_effect = values_div.find(effect_id);
2279  if(div_effect == values_div.end() || divide > div_effect->second.value) {
2280  values_div[effect_id].set(DIV, divide, ability.ability_cfg(), ability.teacher_loc);
2281  }
2282  }
2283  }
2284  }
2285 
2286  if(set_effect_max.type != NOT_USED) {
2287  value_set = std::max(set_effect_max.value, 0) + std::min(set_effect_min.value, 0);
2288  if(set_effect_max.value > def) {
2289  effect_list_.push_back(set_effect_max);
2290  }
2291  if(set_effect_min.value < def) {
2292  effect_list_.push_back(set_effect_min);
2293  }
2294  }
2295 
2296  /* Do multiplication with floating point values rather than integers
2297  * We want two places of precision for each multiplier
2298  * Using integers multiplied by 100 to keep precision causes overflow
2299  * after 3-4 abilities for 32-bit values and ~8 for 64-bit
2300  * Avoiding the overflow by dividing after each step introduces rounding errors
2301  * that may vary depending on the order effects are applied
2302  * As the final values are likely <1000 (always true for mainline), loss of less significant digits is not an issue
2303  */
2304  double multiplier = 1.0;
2305  double divisor = 1.0;
2306 
2307  for(const auto& val : values_mul) {
2308  multiplier *= val.second.value/100.0;
2309  effect_list_.push_back(val.second);
2310  }
2311 
2312  for(const auto& val : values_div) {
2313  divisor *= val.second.value/100.0;
2314  effect_list_.push_back(val.second);
2315  }
2316 
2317  int addition = 0;
2318  for(const auto& val : values_add) {
2319  addition += val.second.value;
2320  effect_list_.push_back(val.second);
2321  }
2322 
2323  /* Additional and subtraction are independent since Wesnoth 1.19.4. Prior to that, they affected each other.
2324  */
2325  int substraction = 0;
2326  for(const auto& val : values_sub) {
2327  substraction += val.second.value;
2328  effect_list_.push_back(val.second);
2329  }
2330 
2331  if(set_effect_cum.type != NOT_USED) {
2332  value_set += set_effect_cum.value;
2333  effect_list_.push_back(set_effect_cum);
2334  }
2335 
2336  composite_double_value_ = (value_set + addition + substraction) * multiplier / divisor;
2337  //clamp what if min_value < max_value or one attribute only used.
2338  if(max_value && min_value && *min_value < *max_value) {
2339  composite_double_value_ = std::clamp(static_cast<double>(*min_value), static_cast<double>(*max_value), composite_double_value_);
2340  } else if(max_value && !min_value) {
2341  composite_double_value_ = std::min(static_cast<double>(*max_value), composite_double_value_);
2342  } else if(min_value && !max_value) {
2343  composite_double_value_ = std::max(static_cast<double>(*min_value), composite_double_value_);
2344  }
2346 }
2347 
2348 } // end namespace unit_abilities
static lg::log_domain log_engine("engine")
#define ERR_NG
Definition: abilities.cpp:55
#define ERR_WML
Definition: abilities.cpp:58
static bool is_enemy(std::size_t side, std::size_t other_side)
Definition: abilities.cpp:1124
#define DEBUG_WML
Definition: abilities.cpp:59
static lg::log_domain log_wml("wml")
std::vector< ability_ptr > ability_vector
Definition: abilities.hpp:33
map_location loc
Definition: move.cpp:172
double t
Definition: astarsearch.cpp:63
const map_location & loc() const
Definition: abilities.hpp:234
std::pair< int, map_location > get_extremum(const std::string &key, int def, const TComp &comp) const
Definition: abilities.cpp:1035
void emplace_back(T &&... args)
Definition: abilities.hpp:232
bool empty() const
Definition: abilities.hpp:221
void add_formula_context(wfl::map_formula_callable &) const
Definition: abilities.cpp:951
std::unique_ptr< specials_context_t > fallback_context(const unit_ptr &self=nullptr) const
bool overwrite_special_checking(active_ability_list &overwriters, const active_ability &overwrited) const
Check whether overwrited would be overwritten by any element of overwriters.
Definition: abilities.cpp:1502
specials_context_t * context_
Variant for storing WML attributes.
auto apply_visitor(const V &visitor) const
Visitor support: Applies a visitor to the underlying variant.
bool empty() const
Tests for an attribute that either was never set or was set to "".
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
void remove_attribute(std::string_view key)
Definition: config.cpp:162
config & add_child(std::string_view key)
Definition: config.cpp:436
optional_config_impl< config > optional_child(std::string_view key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:380
bool matches(const config &filter) const
Definition: config.cpp:1168
config & child_or_add(std::string_view key)
Returns a reference to the first child with the given key.
Definition: config.cpp:401
child_itors child_range(std::string_view key)
Definition: config.cpp:268
const attribute_value * get(std::string_view key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
Definition: config.cpp:665
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:858
std::string debug() const
Definition: config.cpp:1214
bool has_child(std::string_view key) const
Determine whether a config has a child or not.
Definition: config.cpp:312
bool empty() const
Definition: config.cpp:823
void remove_children(std::string_view key, const std::function< bool(const config &)> &p={})
Removes all children with tag key for which p returns true.
Definition: config.cpp:634
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:187
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:102
team & get_team(int i)
Definition: game_board.hpp:91
virtual const unit_map & units() const override
Definition: game_board.hpp:106
virtual const gamemap & map() const override
Definition: game_board.hpp:96
bool is_village(const map_location &loc) const
Definition: map.cpp:60
static prefs & get()
bool get_show_deprecation(bool def)
self_and_other_ref self_and_other(const attack_type &self_att) const
Definition: abilities.hpp:296
bool has_active_special(const attack_type &at, const std::string &tag) const
Returns whether or not *this has a special ability with a tag or id equal to special.
Definition: abilities.cpp:1596
active_ability_list get_abilities_weapons(const std::string &tag, const unit &un) const
Definition: abilities.cpp:1682
bool is_special_active(const specials_combatant &wep, const unit_ability_t &ab, unit_ability_t::affects_t whom) const
Returns whether or not the given special is active for the specified unit, based on the current conte...
Definition: abilities.cpp:1977
specials_combatant defender
Definition: abilities.hpp:311
std::vector< unit_ability_t::tooltip_info > special_tooltips(const attack_type &at, boost::dynamic_bitset<> &active_list) const
Returns a vector of names and descriptions for the specials of *this.
Definition: abilities.cpp:1382
static bool has_active_ability_matching_filter(const unit &un, map_location loc, const config &filter)
Definition: abilities.cpp:1928
std::string describe_weapon_specials_value(const attack_type &at, const std::set< std::string > &checking_tags) const
Definition: abilities.cpp:1181
specials_combatant attacker
Definition: abilities.hpp:310
const specials_combatant & other(const specials_combatant &self) const
Definition: abilities.hpp:305
std::string describe_weapon_specials(const attack_type &at) const
Returns a comma-separated string of active names for the specials of *this.
Definition: abilities.cpp:1137
void add_formula_context(wfl::map_formula_callable &callable) const
Definition: abilities.cpp:959
bool has_active_special_id(const attack_type &at, const std::string &id) const
Definition: abilities.cpp:1607
specials_context_t(specials_context_t &&)=delete
std::vector< unit_ability_t::tooltip_info > abilities_special_tooltips(const attack_type &at, boost::dynamic_bitset<> &active_list) const
Definition: abilities.cpp:1455
bool has_active_special_matching_filter(const attack_type &at, const config &filter) const
Definition: abilities.cpp:1912
active_ability_list get_active_specials(const attack_type &at, const std::string &tag) const
Definition: abilities.cpp:1658
static bool has_active_ability_id(const unit &un, map_location loc, const std::string &id)
Definition: abilities.cpp:1939
active_ability_list get_active_combat_teachers(const attack_type &at) const
Definition: abilities.cpp:1625
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
bool is_enemy(int n) const
Definition: team.hpp:270
std::vector< individual_effect > effect_list_
Definition: abilities.hpp:375
effect(active_ability_list list, int def, const specials_context_t *ctx=nullptr, EFFECTS wham=EFFECT_DEFAULT)
Definition: abilities.cpp:2170
void effect_impl(const active_ability_list &list, int def, const specials_context_t *ctx, EFFECTS wham)
Part of the constructor, calculates for a group of abilities with equal priority.
Definition: abilities.cpp:2194
const unit_ability_t * parent
Definition: abilities.hpp:130
std::string substitute_variables(const std::string &str) const
Substitute gettext variables in name and description of abilities and specials.
Definition: abilities.cpp:380
const std::string & tag() const
Definition: abilities.hpp:53
double suppress_special_priority_
Definition: abilities.hpp:155
static std::string get_help_topic_id(const config &cfg)
Definition: abilities.cpp:320
double suppress_special_priority() const
Definition: abilities.hpp:62
apply_to_t apply_to() const
Definition: abilities.hpp:59
static config vector_to_cfg(const ability_vector &abilities)
Definition: abilities.cpp:365
static void do_compat_fixes(config &cfg, const std::string &tag, bool inside_attack)
Definition: abilities.cpp:187
const config & cfg() const
Definition: abilities.hpp:56
static void parse_vector(const config &abilities_cfg, ability_vector &res, bool inside_attack)
Definition: abilities.cpp:331
bool affects_enemies() const
Definition: abilities.hpp:70
std::string get_description(bool is_inactive=false, unit_race::GENDER=unit_race::MALE) const
Definition: abilities.cpp:444
bool matches_filter(const config &filter) const
Definition: abilities.cpp:1907
active_on_t active_on() const
Definition: abilities.hpp:58
bool in_specials_tag() const
Definition: abilities.hpp:55
active_on_t active_on_
Definition: abilities.hpp:149
static ability_vector filter_tag(const ability_vector &vec, const std::string &tag)
Definition: abilities.cpp:345
unit_ability_t(std::string tag, config cfg, bool inside_attack)
Definition: abilities.cpp:126
double suppress_ability_priority() const
Definition: abilities.hpp:63
static ability_ptr create(std::string tag, config cfg, bool inside_attack)
Definition: abilities.hpp:47
bool currently_checked_
Definition: abilities.hpp:159
affects_allies_t affects_allies_
Definition: abilities.hpp:151
std::string tag_
Definition: abilities.hpp:145
static ability_vector clone(const ability_vector &vec)
Definition: abilities.cpp:356
std::string get_name(bool is_inactive=false, unit_race::GENDER=unit_race::MALE) const
Definition: abilities.cpp:437
std::string get_help_topic_id() const
Definition: abilities.cpp:325
recursion_guard guard_against_recursion(const unit &u) const
Tests which might otherwise cause infinite recursion should call this, check that the returned object...
Definition: abilities.cpp:486
void write(config &abilities_cfg)
Definition: abilities.cpp:375
double suppress_ability_priority_
Definition: abilities.hpp:156
static ability_vector cfg_to_vector(const config &abilities_cfg, bool inside_attack)
Definition: abilities.cpp:338
affects_allies_t affects_allies() const
Definition: abilities.hpp:66
apply_to_t apply_to_
Definition: abilities.hpp:150
bool affects_enemies_
Definition: abilities.hpp:153
bool active_on_matches(bool student_is_attacker) const
Definition: abilities.cpp:451
bool affects_self() const
Definition: abilities.hpp:68
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
@ FEMALE
Definition: race.hpp:28
const unit_type_map & types() const
Definition: types.hpp:398
A single unit type that the player may recruit.
Definition: types.hpp:43
This class represents a single unit of a specific type.
Definition: unit.hpp:39
A variable-expanding proxy for the config class.
Definition: variable.hpp:45
Represents version numbers.
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:249
int as_int(int fallback=0) const
Returns the variant's value as an integer.
Definition: variant.cpp:336
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:29
map_display and display: classes which take care of displaying the map and game-data on the screen.
const config * cfg
std::size_t i
Definition: function.cpp:1031
Interfaces for manipulating version numbers of engine, add-ons, etc.
static std::string _(const char *str)
Definition: gettext.hpp:100
const ability_vector & abilities() const
Definition: unit.hpp:1708
bool ability_active_impl(const unit_ability_t &ab, const map_location &loc) const
Check if an ability is active.
Definition: abilities.cpp:855
bool ability_affects_self(const unit_ability_t &ab, const map_location &loc) const
Check if an ability affects the owning unit.
Definition: abilities.cpp:903
bool ability_active(const unit_ability_t &ab, const map_location &loc) const
Check if an ability is active.
Definition: abilities.cpp:846
active_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:768
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:760
bool ability_affects_adjacent(const unit_ability_t &ab, std::size_t dist, int dir, const map_location &loc, const unit &from) const
Check if an ability affects distant units.
Definition: abilities.cpp:868
bool has_ability_type(const std::string &ability) const
Check if the unit has an ability of a specific type.
Definition: abilities.cpp:911
std::vector< std::string > get_ability_id_list() const
Get a list of all abilities by ID.
Definition: abilities.cpp:779
bool get_self_ability_bool(const unit_ability_t &ab, const map_location &loc) const
Checks whether this unit currently possesses a given ability, and that that ability is active.
Definition: abilities.cpp:1572
bool get_adj_ability_bool(const unit_ability_t &ab, 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:1581
std::vector< unit_ability_t::tooltip_info > ability_tooltips() const
Gets the names and descriptions of this unit's abilities.
Definition: abilities.cpp:817
bool incapacitated() const
Check if the unit has been petrified.
Definition: unit.hpp:826
const std::string & id() const
Gets this unit's id.
Definition: unit.hpp:286
int side() const
The side this unit belongs to.
Definition: unit.hpp:249
std::size_t underlying_id() const
This unit's unique internal ID.
Definition: unit.hpp:298
@ STATE_SLOWED
Definition: unit.hpp:775
@ STATE_POISONED
The unit is slowed - it moves slower and does less damage.
Definition: unit.hpp:776
std::vector< std::string > halo_or_icon_abilities(const std::string &image_type) const
Definition: abilities.cpp:917
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1327
std::size_t max_ability_radius_type(const std::string &tag_name) const
If this unit has abilities of tag_name type with [affect_adjacent] subtags, returns the radius of the...
Definition: unit.hpp:1202
std::size_t max_ability_radius_image() const
If this unit has abilities with [affect_adjacent] subtags and halo_image or overlay_image attributes,...
Definition: unit.hpp:1223
std::size_t max_ability_radius() const
If this unit has abilities with [affect_adjacent] subtags, returns the radius of the one with the fur...
Definition: unit.hpp:1213
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:199
const language_def & get_language()
Definition: language.cpp:317
New lexcical_cast header.
std::size_t distance_between(const map_location &a, const map_location &b)
Function which gives the number of hexes between two tiles (i.e.
Definition: location.cpp:584
void get_adjacent_tiles(const map_location &a, utils::span< map_location, 6 > res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:513
Standard logging facilities (interface).
const color_t INACTIVE_COLOR
const version_info wesnoth_version(VERSION)
static bool is_active(const widget *wgt)
Definition: window.cpp:1265
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:550
std::string span_color(const color_t &color, Args &&... data)
Applies Pango markup to the input specifying its display color.
Definition: markup.hpp:110
std::string tag(std::string_view tag, Args &&... data)
Wraps the given data in the specified tag.
Definition: markup.hpp:45
game_board * gameboard
Definition: resources.cpp:20
static std::string at(const std::string &file, int line)
static int individual_value_int(const config::attribute_value *v, int def, const active_ability &ability, const map_location &loc, const specials_context_t *ctx)
Definition: abilities.cpp:2154
static int individual_value_double(const config::attribute_value *v, int def, const active_ability &ability, const map_location &loc, const specials_context_t *ctx)
Definition: abilities.cpp:2162
bool filter_base_matches(const config &cfg, int def)
Definition: abilities.cpp:2135
@ EFFECT_WITHOUT_CLAMP_MIN_MAX
Definition: abilities.hpp:345
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:81
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:42
constexpr bool decayed_is_same
Equivalent to as std::is_same_v except both types are passed through std::decay first.
Definition: general.hpp:32
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 ...
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:88
std::string join(const Range &v, const std::string &s=",")
Generates a new string joining container items in a list.
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:108
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:133
std::vector< std::string > split(const config_attribute_value &val)
std::string to_string(const Range &range, const Func &op)
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_ability_t > ability_ptr
Definition: ptr.hpp:38
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition: sound.cpp:139
Data typedef for active_ability_list.
Definition: abilities.hpp:165
map_location teacher_loc
The location of the teacher, that is the unit who owns the ability tags (different from student becau...
Definition: abilities.hpp:184
const unit_ability_t & ability() const
Definition: abilities.hpp:187
map_location student_loc
Used by the formula in the ability.
Definition: abilities.hpp:179
std::string localename
Definition: language.hpp:32
Encapsulates the map of the game.
Definition: location.hpp:46
static std::vector< direction > parse_directions(const std::string &str)
Parse_directions takes a comma-separated list, and filters out any invalid directions.
Definition: location.cpp:138
bool valid() const
Definition: location.hpp:111
direction
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:48
direction get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition: location.cpp:238
void set(value_modifier t, int val, const config &abil, const map_location &l)
Definition: abilities.cpp:2127
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_party p
static map_location::direction s
unit_type_data unit_types
Definition: types.cpp:1499
#define d
#define e
#define f