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