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