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