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