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