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