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