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