The Battle for Wesnoth  1.19.19+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  const std::string& name_affected = p_ab->cfg().get_or("name_affected", "name").str();
1074  ability_names.insert(p_ab->substitute_variables(name_affected));
1075  });
1076  }
1077 
1078  if(!ability_names.empty()) {
1079  special_names.push_back("\n" + utils::join(ability_names, ", "));
1080  }
1081 
1082  return utils::join(special_names, ", ");
1083 }
1084 
1085 std::string specials_context_t::describe_weapon_specials_value(const attack_type& at, const std::set<std::string>& checking_tags) const
1086 {
1087  auto s_a_o = self_and_other(at);
1088  const auto& [self, other] = s_a_o;
1089 
1090  std::string res;
1091 
1092  std::set<std::string> wespon_specials;
1093  std::set<std::string> abilities_self;
1094  std::set<std::string> abilities_allies;
1095  std::set<std::string> abilities_enemies;
1096  std::set<std::string> opponents_abilities;
1097 
1098  auto quick_check = [&](const ability_ptr& p_ab) {
1099  return checking_tags.count(p_ab->tag()) != 0;
1100  };
1101 
1102  auto add_to_list = [&](const ability_ptr& p_ab, const specials_combatant& student, const auto& source) {
1103  if (&student == &s_a_o.other) {
1104  opponents_abilities.insert(p_ab->substitute_variables(p_ab->cfg()["name"].str()));
1105  } else if constexpr (utils::decayed_is_same<decltype(source), attack_type>) {
1106  wespon_specials.insert(p_ab->substitute_variables(p_ab->cfg()["name"].str()));
1107  } else if (&source == s_a_o.self.un.get()) {
1108  const std::string& name_affected = p_ab->cfg().get_or("name_affected", "name").str();
1109  abilities_self.insert(p_ab->substitute_variables(name_affected));
1110  } else if (!is_enemy(source.side(), s_a_o.self.un->side())) {
1111  const std::string& name_affected = p_ab->cfg().get_or("name_affected", "name").str();
1112  abilities_allies.insert(p_ab->substitute_variables(name_affected));
1113  } else {
1114  const std::string& name_affected = p_ab->cfg().get_or("name_affected", "name").str();
1115  abilities_enemies.insert(p_ab->substitute_variables(name_affected));
1116  }
1117  };
1118 
1119  auto add_to_res = [&](std::set<std::string>& to_add, const std::string& category_name) {
1120  to_add.erase("");
1121  if (!to_add.empty()) {
1122  //TODO: markup::span_color(font::TITLE_COLOR) ??
1123  res += (res.empty() ? "\n" : "") + category_name + utils::join(to_add, ", ");
1124  }
1125  };
1126 
1127 
1128  foreach_active_special(*this, self, quick_check, add_to_list);
1129 
1130  add_to_res(wespon_specials, "");
1131  add_to_res(abilities_self, _("Owned: "));
1132  // TRANSLATORS: Past-participle of "teach", used for an ability similar to leadership
1133  add_to_res(abilities_allies, _("Taught: "));
1134  // TRANSLATORS: Past-participle of "teach", used for an ability similar to leadership
1135  add_to_res(abilities_enemies, _("Taught: (by an enemy): "));
1136  add_to_res(opponents_abilities, _("Used by opponent: "));
1137 
1138  return res;
1139 }
1140 
1141 
1142 namespace { // Helpers for attack_type::special_active()
1143 
1144  /**
1145  * Returns whether or not the given special affects the opponent of the unit
1146  * with the special.
1147  * @param ab the ability/special
1148  * @param[in] is_attacker whether or not the unit with the special is the attacker
1149  */
1150  bool special_affects_opponent(const unit_ability_t& ab, bool is_attacker)
1151  {
1152  using apply_to_t = unit_ability_t::apply_to_t;
1153  const auto apply_to = ab.apply_to();
1154  if ( apply_to == apply_to_t::both)
1155  return true;
1156  if ( apply_to == apply_to_t::opponent )
1157  return true;
1158  if ( is_attacker && apply_to == apply_to_t::defender)
1159  return true;
1160  if ( !is_attacker && apply_to == apply_to_t::attacker)
1161  return true;
1162  return false;
1163  }
1164 
1165  /**
1166  * Returns whether or not the given special affects the unit with the special.
1167  * @param ab the ability/special
1168  * @param[in] is_attacker whether or not the unit with the special is the attacker
1169  */
1170  bool special_affects_self(const unit_ability_t& ab, bool is_attacker)
1171  {
1172  using apply_to_t = unit_ability_t::apply_to_t;
1173  const auto apply_to = ab.apply_to();
1174  if ( apply_to == apply_to_t::both )
1175  return true;
1176  if ( apply_to == apply_to_t::self)
1177  return true;
1178  if ( is_attacker && apply_to == apply_to_t::attacker)
1179  return true;
1180  if ( !is_attacker && apply_to == apply_to_t::defender)
1181  return true;
1182  return false;
1183  }
1184 
1185  static bool buildin_is_immune(const unit_ability_t& ab, const unit_const_ptr& them, map_location their_loc)
1186  {
1187  if (ab.tag() == "drains" && them && them->get_state("undrainable")) {
1188  return true;
1189  }
1190  if (ab.tag() == "plague" && them &&
1191  (them->get_state("unplagueable") ||
1192  resources::gameboard->map().is_village(their_loc))) {
1193  return true;
1194  }
1195  if (ab.tag() == "poison" && them &&
1196  (them->get_state("unpoisonable") || them->get_state(unit::STATE_POISONED))) {
1197  return true;
1198  }
1199  if (ab.tag() == "slow" && them &&
1200  (them->get_state("unslowable") || them->get_state(unit::STATE_SLOWED))) {
1201  return true;
1202  }
1203  if (ab.tag() == "petrifies" && them &&
1204  them->get_state("unpetrifiable")) {
1205  return true;
1206  }
1207  return false;
1208  }
1209  /**
1210  * Determines if a unit/weapon combination matches the specified child
1211  * (normally a [filter_*] child) of the provided filter.
1212  * @param[in] u A unit to filter.
1213  * @param[in] u2 Another unit to filter.
1214  * @param[in] loc The presumed location of @a unit.
1215  * @param[in] weapon The attack_type to filter.
1216  * @param[in] filter The filter containing the child filter to use.
1217  * @param[in] for_listing
1218  * @param[in] child_tag The tag of the child filter to use.
1219  * @param[in] applies_to_checked Parameter used for don't have infinite recusion for some filter attribute.
1220  */
1221  static bool special_unit_matches(const unit_const_ptr & u,
1222  const unit_const_ptr & u2,
1223  const map_location & loc,
1224  const const_attack_ptr& weapon,
1225  const unit_ability_t& ab,
1226  const bool for_listing,
1227  const std::string & child_tag, bool applies_to_checked)
1228  {
1229  if (for_listing && !loc.valid())
1230  // The special's context was set to ignore this unit, so assume we pass.
1231  // (This is used by reports.cpp to show active specials when the
1232  // opponent is not known. From a player's perspective, the special
1233  // is active, in that it can be used, even though the player might
1234  // need to select an appropriate opponent.)
1235  return true;
1236 
1237  const config& filter = ab.cfg();
1238  const config& filter_backstab = filter;
1239 
1240  auto filter_child = filter_backstab.optional_child(child_tag);
1241  if ( !filter_child )
1242  // The special does not filter on this unit, so we pass.
1243  return true;
1244 
1245  // If the primary unit doesn't exist, there's nothing to match
1246  if (!u) {
1247  return false;
1248  }
1249 
1250  unit_filter ufilt{vconfig(*filter_child)};
1251 
1252  // If the other unit doesn't exist, try matching without it
1253 
1254 
1255  auto filter_lock = ab.guard_against_recursion(*u);
1256  if(!filter_lock) {
1257  return false;
1258  }
1259  // Check for a weapon match.
1260  if (auto filter_weapon = filter_child->optional_child("filter_weapon") ) {
1261  std::string check_if_recursion = applies_to_checked ? ab.tag() : "";
1262  if ( !weapon || !weapon->matches_filter(*filter_weapon, check_if_recursion) )
1263  return false;
1264  }
1265 
1266  // Passed.
1267  // If the other unit doesn't exist, try matching without it
1268  if (!u2) {
1269  return ufilt.matches(*u, loc);
1270  }
1271  return ufilt.matches(*u, loc, *u2);
1272  }
1273 
1274 }//anonymous namespace
1275 
1276 
1277 /**
1278  * Returns a vector of names and descriptions for the specials of *this.
1279  * Each std::pair in the vector has first = name and second = description.
1280  *
1281  * This uses either the active or inactive name/description for each special,
1282  * based on the current context (see set_specials_context), provided
1283  * @a active_list is not nullptr. Otherwise specials are assumed active.
1284  * If the appropriate name is empty, the special is skipped.
1285  */
1286 std::vector<unit_ability_t::tooltip_info> specials_context_t::special_tooltips(const attack_type& at,
1287  boost::dynamic_bitset<>& active_list) const
1288 {
1289  //log_scope("special_tooltips");
1290  auto [self, other] = self_and_other(at);
1291 
1292  std::vector<unit_ability_t::tooltip_info> res;
1293  active_list.clear();
1294 
1295  for (const auto& p_ab : self.at->specials()) {
1296  bool active = is_special_active(self, *p_ab, unit_ability_t::affects_t::EITHER);
1297  auto name = p_ab->get_name(!active);
1298  auto desc = p_ab->get_description(!active);
1299 
1300  if (name.empty()) {
1301  continue;
1302  }
1303 
1304  res.AGGREGATE_EMPLACE(
1305  name,
1306  desc,
1307  p_ab->get_help_topic_id()
1308  );
1309 
1310  active_list.push_back(active);
1311  }
1312  return res;
1313 }
1314 
1315 namespace {
1316  /**
1317  * Returns whether or not the given special is active for the specified unit disregarding other units,
1318  * based on the current context (see specials_context).
1319  * @param ab the ability/special
1320  */
1321  bool special_tooltip_active(const specials_context_t& context, const specials_context_t::specials_combatant& self, const unit_ability_t& ab)
1322  {
1323  bool is_for_listing = context.is_for_listing;
1324 
1325  auto& other = context.other(self);
1326  bool is_attacker = &self == &context.attacker;
1327  //log_scope("special_tooltip_active");
1328 
1329  //here 'active_on' and checking of opponent weapon shouldn't implemented
1330  //because other_attack_ don't exist in sidebar display.
1331  //'apply_to' and some filters like [filter_student] are checked for know if
1332  //special must be displayed in sidebar.
1333 
1334  //only special who affect self are valid here.
1335  bool whom_is_self = special_affects_self(ab, is_attacker);
1336  if (!whom_is_self)
1337  return false;
1338 
1339  //this part of checking is similar to special_active but not the same.
1340  //"filter_opponent" is not checked here, and "filter_attacker/defender" only
1341  //if attacker/defender is self_.
1342  bool applied_both = ab.apply_to() == unit_ability_t::apply_to_t::both;
1343 
1344  if (!special_unit_matches(self.un, other.un, self.loc, self.at, ab, is_for_listing, "filter_student", applied_both || whom_is_self))
1345  return false;
1346  bool applied_to_attacker = applied_both || (whom_is_self && is_attacker);
1347  if (is_attacker && !special_unit_matches(self.un, other.un, self.loc, self.at, ab, is_for_listing, "filter_attacker", applied_to_attacker))
1348  return false;
1349  bool applied_to_defender = applied_both || (whom_is_self && !is_attacker);
1350  if (!is_attacker && !special_unit_matches(self.un, other.un, self.loc, self.at, ab, is_for_listing, "filter_defender", applied_to_defender))
1351  return false;
1352 
1353  return true;
1354  }
1355 
1356 }
1357 
1358 
1359 std::vector<unit_ability_t::tooltip_info> specials_context_t::abilities_special_tooltips(const attack_type& at,
1360  boost::dynamic_bitset<>& active_list) const
1361 {
1362  auto s_a_o = self_and_other(at);
1363  const auto& [self, other] = s_a_o;
1364 
1365  std::vector<unit_ability_t::tooltip_info> res;
1366  active_list.clear();
1367  std::set<std::string> checking_name;
1368  if (!self.un) {
1369  return res;
1370  }
1371  foreach_active_ability(*self.un, self.loc,
1372  [&](const ability_ptr&) {
1373  return true;
1374  },
1375  [&](const ability_ptr& p_ab, const unit&) {
1376  if (special_tooltip_active(*this, s_a_o.self, *p_ab)) {
1377  bool active = is_special_active(s_a_o.self, *p_ab, unit_ability_t::affects_t::SELF);
1378  const std::string name = p_ab->substitute_variables(p_ab->cfg()["name_affected"]);
1379  const std::string desc = p_ab->substitute_variables(p_ab->cfg()["description_affected"]);
1380 
1381  if (name.empty() || checking_name.count(name) != 0) {
1382  return;
1383  }
1384  res.AGGREGATE_EMPLACE(name, desc, p_ab->get_help_topic_id());
1385  checking_name.insert(name);
1386  active_list.push_back(active);
1387  }
1388  });
1389  return res;
1390 }
1391 
1392 
1393 //The following functions are intended to allow the use in combat of capacities
1394 //identical to special weapons and therefore to be able to use them on adjacent
1395 //units (abilities of type 'aura') or else on all types of weapons even if the
1396 //beneficiary unit does not have a corresponding weapon
1397 //(defense against ranged weapons abilities for a unit that only has melee attacks)
1399 {
1400  const std::string& apply_to = ab.cfg()["overwrite_specials"];
1401  return (apply_to == "one_side" || apply_to == "both_sides");
1402 }
1403 
1405 {
1406  //remove element without overwrite_specials key, if list empty after check return empty list.
1407  utils::erase_if(overwriters, [&](const active_ability& i) {
1408  return (!overwrite_special_affects(i.ability()));
1409  });
1410 
1411  // if empty, nothing is doing any overwriting
1412  if(overwriters.empty()){
1413  return overwriters;
1414  }
1415 
1416  // if there are specials/"specials as abilities" that could potentially overwrite each other
1417  if(overwriters.size() >= 2){
1418  // sort them by overwrite priority from highest to lowest (default priority is 0)
1419  utils::sort_if(overwriters,[](const active_ability& i, const active_ability& j){
1420  auto oi = i.ability_cfg().optional_child("overwrite");
1421  double l = 0;
1422  if(oi && !oi["priority"].empty()){
1423  l = oi["priority"].to_double(0);
1424  }
1425  auto oj = j.ability_cfg().optional_child("overwrite");
1426  double r = 0;
1427  if(oj && !oj["priority"].empty()){
1428  r = oj["priority"].to_double(0);
1429  }
1430  return l > r;
1431  });
1432  // remove any that need to be overwritten
1433  utils::erase_if(overwriters, [&](const active_ability& i) {
1434  return (overwrite_special_checking(overwriters, i.ability()));
1435  });
1436  }
1437  return overwriters;
1438 }
1439 
1441 {
1442  auto ctx = fallback_context();
1443  auto [self, other] = context_->self_and_other(*this);
1444  bool is_attacker = &self == &context_->attacker;
1445  if(overwriters.empty()){
1446  return false;
1447  }
1448 
1449  for(const auto& j : overwriters) {
1450  // whether the overwriter affects a single side
1451  bool affect_side = (j.ability_cfg()["overwrite_specials"] == "one_side");
1452  // the overwriter's priority, default of 0
1453  auto overwrite_specials = j.ability_cfg().optional_child("overwrite");
1454  double priority = overwrite_specials ? overwrite_specials["priority"].to_double(0) : 0.00;
1455  // the cfg being checked for whether it will be overwritten
1456  auto has_overwrite_specials = ab.cfg().optional_child("overwrite");
1457  // if the overwriter's priority is greater than 0, then true if the cfg being checked has a higher priority
1458  // else true
1459  bool prior = (priority > 0) ? (has_overwrite_specials && has_overwrite_specials["priority"].to_double(0) >= priority) : true;
1460  // 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
1461  // aka whether the cfg being checked can potentially be overwritten by the current overwriter
1462  bool is_overwritable = (overwrite_special_affects(ab) && !prior) || !overwrite_special_affects(ab);
1463  bool one_side_overwritable = true;
1464 
1465  // if the current overwriter affects one side and the cfg being checked can be overwritten by this overwriter
1466  // then check that the current overwriter and the cfg being checked both affect either this unit or its opponent
1467  if(affect_side && is_overwritable){
1468  if(special_affects_self(j.ability(), is_attacker)){
1469  one_side_overwritable = special_affects_self(ab, is_attacker);
1470  }
1471  else if(special_affects_opponent(j.ability(), !is_attacker)){
1472  one_side_overwritable = special_affects_opponent(ab, !is_attacker);
1473  }
1474  }
1475 
1476  // check whether the current overwriter is disabled due to a filter
1477  bool special_matches = true;
1478  if(overwrite_specials){
1479  auto overwrite_filter = (*overwrite_specials).optional_child("filter_specials");
1480  if(!overwrite_filter){
1481  overwrite_filter = (*overwrite_specials).optional_child("experimental_filter_specials");
1482  if(overwrite_filter){
1483  deprecated_message("experimental_filter_specials", DEP_LEVEL::INDEFINITE, "", "Use filter_specials instead.");
1484  }
1485  }
1486  if(overwrite_filter && is_overwritable && one_side_overwritable){
1487  special_matches = ab.matches_filter(*overwrite_filter);
1488  }
1489  }
1490 
1491  // if the cfg being checked should be overwritten
1492  // and either this unit or its opponent are affected
1493  // and the current overwriter is not disabled due to a filter
1494  if(is_overwritable && one_side_overwritable && special_matches){
1495  return true;
1496  }
1497  }
1498  return false;
1499 }
1500 
1502 {
1503  auto filter_lock = ab.guard_against_recursion(*this);
1504  if(!filter_lock) {
1505  return false;
1506  }
1507  return (ability_active_impl(ab, loc) && ability_affects_self(ab, loc));
1508 }
1509 
1510 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
1511 {
1512  auto filter_lock = ab.guard_against_recursion(from);;
1513  if(!filter_lock) {
1514  return false;
1515  }
1516  return (affects_side(ab, side(), from.side()) && from.ability_active_impl(ab, from_loc) && ability_affects_adjacent(ab, dist, dir, loc, from));
1517 }
1518 
1519 /**
1520  * Returns whether or not @a *this has a special ability with a tag or id equal to
1521  * @a special. the Check is for a special ability
1522  * active in the current context (see set_specials_context), including
1523  * specials obtained from the opponent's attack.
1524  */
1525 bool specials_context_t::has_active_special(const attack_type & at, const std::string & tag_name) const
1526 {
1527 
1528  auto quick_check = [&](const ability_ptr& p_ab) {
1529  return p_ab->tag() == tag_name;
1530  };
1531 
1532  auto [self, other] = self_and_other(at);
1533  return foreach_active_special(*this, self, quick_check, return_true);
1534 }
1535 
1536 bool specials_context_t::has_active_special_id(const attack_type& at, const std::string& special_id) const
1537 {
1538  //Now that filter_(second)attack in event supports special_id/type_active, including abilities used as weapons,
1539  //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.
1540  //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.
1541  if (at.range().empty()) {
1542  return false;
1543  }
1544 
1545 
1546  auto quick_check = [&](const ability_ptr& p_ab) {
1547  return p_ab->id() == special_id;
1548  };
1549 
1550  auto [self, other] = self_and_other(at);
1551  return foreach_active_special(*this, self, quick_check, return_true);
1552 }
1553 
1555 {
1556  auto s_a_o = self_and_other(at);
1557  const auto& [self, other] = s_a_o;
1558 
1559  const map_location loc = self.un ? self.un->get_location() : self.loc;
1560  active_ability_list res(loc);
1561  const std::set<std::string>& checking_tags = abilities_list::all_weapon_tags();
1562  auto quick_check = [&](const ability_ptr& p_ab) {
1563  return checking_tags.count(p_ab->tag()) != 0;
1564  };
1565 
1566  if (self.un) {
1567  foreach_distant_active_ability(*self.un, self.loc, quick_check,
1568  [&](const ability_ptr& p_ab, const unit& u_teacher) {
1569  if (is_special_active(s_a_o.self, *p_ab, unit_ability_t::affects_t::SELF)) {
1570  res.emplace_back(p_ab, s_a_o.self.un->get_location(), u_teacher.get_location());
1571  }
1572  }
1573  );
1574  }
1575  if (other.un) {
1576  foreach_distant_active_ability(*other.un, other.loc, quick_check,
1577  [&](const ability_ptr& p_ab, const unit& u_teacher) {
1578  if (is_special_active(s_a_o.other, *p_ab, unit_ability_t::affects_t::OTHER)) {
1579  res.emplace_back(p_ab, s_a_o.other.un->get_location(), u_teacher.get_location());
1580  }
1581  }
1582  );
1583  }
1584  return res;
1585 }
1586 
1588 {
1589  auto [self, other] = self_and_other(at);
1590  const map_location loc = self.un ? self.un->get_location() : self.loc;
1591  active_ability_list res(loc);
1592 
1593  auto quick_check = [&](const ability_ptr& p_ab) {
1594  return p_ab->tag() == tag_name;
1595  };
1596 
1597  auto add_to_list = utils::overload {
1598  [&](const ability_ptr& p_ab, const specials_combatant& student, const attack_type&) {
1599  res.emplace_back(p_ab, student.loc, student.loc);
1600  },
1601  [&](const ability_ptr& p_ab, const specials_combatant& student, const unit& source) {
1602  res.emplace_back(p_ab, student.un->get_location(), source.get_location());
1603  }
1604  };
1605 
1606 
1607  foreach_active_special(*this, self, quick_check, add_to_list);
1608  return res;
1609 }
1610 
1611 active_ability_list specials_context_t::get_abilities_weapons(const std::string& tag_name, const unit& un) const
1612 {
1613  auto s_a_o = self_and_other(un);
1614  const auto& [self, other] = s_a_o;
1615  //TODO: fall back to un.get_location() ?
1616  active_ability_list res = un.get_abilities(tag_name, self.loc);
1617 
1618  utils::erase_if(res, [&](const active_ability& i) {
1619  //If no weapon is given, assume the ability is active. this is used by ai code.
1620  return !is_special_active(s_a_o.self, i.ability(), unit_ability_t::affects_t::SELF);
1621  });
1622  return res;
1623 
1624 }
1625 
1626 
1627 //end of emulate weapon special functions.
1628 
1629 namespace
1630 {
1631  bool exclude_ability_attributes(const std::string& tag_name, const config & filter)
1632  {
1633  ///check what filter attributes used can be used in type of ability checked.
1634  bool abilities_check = abilities_list::ability_value_tags().count(tag_name) != 0 || abilities_list::ability_no_value_tags().count(tag_name) != 0;
1635  if(filter.has_attribute("active_on") && tag_name != "resistance" && abilities_check)
1636  return false;
1637  if(filter.has_attribute("apply_to") && tag_name != "resistance" && abilities_check)
1638  return false;
1639 
1640  if(filter.has_attribute("overwrite_specials") && abilities_list::weapon_math_tags().count(tag_name) == 0)
1641  return false;
1642 
1643  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;
1644  if(filter.has_attribute("cumulative") && no_value_weapon_abilities_check && (tag_name != "swarm" || tag_name != "berserk"))
1645  return false;
1646  if(filter.has_attribute("value") && (no_value_weapon_abilities_check && tag_name != "berserk"))
1647  return false;
1648  if(filter.has_attribute("add") && no_value_weapon_abilities_check)
1649  return false;
1650  if(filter.has_attribute("sub") && no_value_weapon_abilities_check)
1651  return false;
1652  if(filter.has_attribute("multiply") && no_value_weapon_abilities_check)
1653  return false;
1654  if(filter.has_attribute("divide") && no_value_weapon_abilities_check)
1655  return false;
1656  if(filter.has_attribute("priority") && no_value_weapon_abilities_check)
1657  return false;
1658 
1659  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;
1660  if(filter.has_attribute("replacement_type") && tag_name != "damage_type" && all_engine)
1661  return false;
1662  if(filter.has_attribute("alternative_type") && tag_name != "damage_type" && all_engine)
1663  return false;
1664  if(filter.has_attribute("type") && tag_name != "plague" && all_engine)
1665  return false;
1666 
1667  return true;
1668  }
1669 
1670  bool matches_ability_filter(const config & cfg, const std::string& tag_name, const config & filter)
1671  {
1672  using namespace utils::config_filters;
1673 
1674  //check if attributes have right to be in type of ability checked
1675  if(!exclude_ability_attributes(tag_name, filter))
1676  return false;
1677 
1678  // tag_name and id are equivalent of ability ability_type and ability_id/type_active filters
1679  //can be extent to special_id/type_active. If tag_name or id matche if present in list.
1680  const std::vector<std::string> filter_type = utils::split(filter["tag_name"]);
1681  if(!filter_type.empty() && !utils::contains(filter_type, tag_name))
1682  return false;
1683 
1684  if(!string_matches_if_present(filter, cfg, "id", ""))
1685  return false;
1686 
1687  //when affect_adjacent=yes detect presence of [affect_adjacent] in abilities, if no
1688  //then matches when tag not present.
1689  if(!filter["affect_adjacent"].empty()){
1690  bool adjacent = cfg.has_child("affect_adjacent");
1691  if(filter["affect_adjacent"].to_bool() != adjacent){
1692  return false;
1693  }
1694  }
1695 
1696  //these attributs below filter attribute used in all engine abilities.
1697  //matches if filter attribute have same boolean value what attribute
1698  if(!bool_matches_if_present(filter, cfg, "affect_self", true))
1699  return false;
1700 
1701  //here if value of affect_allies but also his presence who is checked because
1702  //when affect_allies not specified, ability affect unit of same side what owner only.
1703  if(!bool_or_empty(filter, cfg, "affect_allies"))
1704  return false;
1705 
1706  if(!bool_matches_if_present(filter, cfg, "affect_enemies", false))
1707  return false;
1708 
1709 
1710  //cumulative, overwrite_specials and active_on check attributes used in all abilities
1711  //who return a numerical value.
1712  if(!bool_matches_if_present(filter, cfg, "cumulative", false))
1713  return false;
1714 
1715  if(!string_matches_if_present(filter, cfg, "overwrite_specials", "none"))
1716  return false;
1717 
1718  if(!string_matches_if_present(filter, cfg, "active_on", "both"))
1719  return false;
1720 
1721  if(abilities_list::weapon_math_tags().count(tag_name) != 0 || abilities_list::ability_value_tags().count(tag_name) != 0) {
1722  if(!double_matches_if_present(filter, cfg, "priority", 0.00)) {
1723  return false;
1724  }
1725  } else {
1726  if(!double_matches_if_present(filter, cfg, "priority")) {
1727  return false;
1728  }
1729  }
1730 
1731  //value, add, sub multiply and divide check values of attribute used in engines abilities(default value of 'value' can be checked when not specified)
1732  //who return numericals value but can also check in non-engine abilities(in last case if 'value' not specified none value can matches)
1733  if(!filter["value"].empty()){
1734  if(tag_name == "drains"){
1735  if(!int_matches_if_present(filter, cfg, "value", 50)){
1736  return false;
1737  }
1738  } else if(tag_name == "berserk"){
1739  if(!int_matches_if_present(filter, cfg, "value", 1)){
1740  return false;
1741  }
1742  } else if(tag_name == "heal_on_hit" || tag_name == "heals" || tag_name == "regenerate" || tag_name == "leadership"){
1743  if(!int_matches_if_present(filter, cfg, "value" , 0)){
1744  return false;
1745  }
1746  } else {
1747  if(!int_matches_if_present(filter, cfg, "value")){
1748  return false;
1749  }
1750  }
1751  }
1752 
1753  if(!int_matches_if_present_or_negative(filter, cfg, "add", "sub"))
1754  return false;
1755 
1756  if(!int_matches_if_present_or_negative(filter, cfg, "sub", "add"))
1757  return false;
1758 
1759  if(!double_matches_if_present(filter, cfg, "multiply"))
1760  return false;
1761 
1762  if(!double_matches_if_present(filter, cfg, "divide"))
1763  return false;
1764 
1765 
1766  //apply_to is a special case, in resistance ability, it check a list of damage type used by [resistance]
1767  //but in weapon specials, check identity of unit affected by special(self, opponent tc...)
1768  if(tag_name == "resistance"){
1769  if(!set_includes_if_present(filter, cfg, "apply_to")){
1770  return false;
1771  }
1772  } else {
1773  if(!string_matches_if_present(filter, cfg, "apply_to", "self")){
1774  return false;
1775  }
1776  }
1777 
1778  //the three attribute below are used for check in specifics abilitie:
1779  //replacement_type and alternative_type are present in [damage_type] only for engine abilities
1780  //and type for [plague], but if someone want use this in non-engine abilities, these attribute can be checked outside type mentioned.
1781  //
1782 
1783  //for damage_type only(in engine cases)
1784  if(!string_matches_if_present(filter, cfg, "replacement_type", ""))
1785  return false;
1786 
1787  if(!string_matches_if_present(filter, cfg, "alternative_type", ""))
1788  return false;
1789 
1790  //for plague only(in engine cases)
1791  if(!string_matches_if_present(filter, cfg, "type", ""))
1792  return false;
1793 
1794  //the wml_filter is used in cases where the attribute we are looking for is not
1795  //previously listed or to check the contents of the sub_tags ([filter_adjacent],[filter_self],[filter_opponent] etc.
1796  //If the checked set does not exactly match the content of the capability, the function returns a false response.
1797  auto fwml = filter.optional_child("filter_wml");
1798  if (fwml){
1799  if(!cfg.matches(*fwml)){
1800  return false;
1801  }
1802  }
1803 
1804  // Passed all tests.
1805  return true;
1806  }
1807 
1808  static bool common_matches_filter(const config & cfg, const std::string& tag_name, const config & filter)
1809  {
1810  // Handle the basic filter.
1811  bool matches = matches_ability_filter(cfg, tag_name, filter);
1812 
1813  // Handle [and], [or], and [not] with in-order precedence
1814  for(const auto [key, condition_cfg] : filter.all_children_view() )
1815  {
1816  // Handle [and]
1817  if ( key == "and" )
1818  matches = matches && common_matches_filter(cfg, tag_name, condition_cfg);
1819 
1820  // Handle [or]
1821  else if ( key == "or" )
1822  matches = matches || common_matches_filter(cfg, tag_name, condition_cfg);
1823 
1824  // Handle [not]
1825  else if ( key == "not" )
1826  matches = matches && !common_matches_filter(cfg, tag_name, condition_cfg);
1827  }
1828 
1829  return matches;
1830  }
1831 }
1832 
1834 {
1835  return common_matches_filter(cfg(), tag(), filter);
1836 }
1837 
1839 {
1840  if(at.range().empty()){
1841  return false;
1842  }
1843 
1844  bool skip_adjacent = !filter["affect_adjacent"].to_bool(true);
1845 
1846  auto quick_check = [&](const ability_ptr& p_ab) {
1847  return p_ab->matches_filter(filter);
1848  };
1849 
1850  auto [self, other] = self_and_other(at);
1851  return foreach_active_special(*this, self, quick_check, return_true, skip_adjacent);
1852 }
1853 
1854 namespace {
1855  class temporary_facing
1856  {
1857  map_location::direction save_dir_;
1858  unit_const_ptr u_;
1859  public:
1860  temporary_facing(const unit_const_ptr& u, map_location::direction new_dir)
1861  : save_dir_(u ? u->facing() : map_location::direction::indeterminate)
1862  , u_(u)
1863  {
1864  if (u_) {
1865  u_->set_facing(new_dir);
1866  }
1867  }
1868  ~temporary_facing()
1869  {
1870  if (u_) {
1871  u_->set_facing(save_dir_);
1872  }
1873  }
1874  };
1875 }
1876 /**
1877  * Returns whether or not the given special is active for the specified unit,
1878  * based on the current context (see set_specials_context).
1879  * @param self this combatant
1880  * @param ab the ability
1881  * @param whom specifies which combatant we care about
1882  */
1884 {
1885  bool is_attacker = &self == &attacker;
1886  const auto& other = this->other(self);
1887 
1888  bool is_for_listing = this->is_for_listing;
1889  //log_scope("special_active");
1890 
1891 
1892  // Does this affect the specified unit?
1893  if ( whom == unit_ability_t::affects_t::SELF ) {
1894  if ( !special_affects_self(ab, is_attacker) )
1895  return false;
1896  }
1897  if ( whom == unit_ability_t::affects_t::OTHER ) {
1898  if ( !special_affects_opponent(ab, is_attacker) )
1899  return false;
1900  }
1901 
1902  // Is this active on attack/defense?
1903  if (!ab.active_on_matches(is_attacker)) {
1904  return false;
1905  }
1906 
1907  // Get the units involved.
1908  const unit_map& units = get_unit_map();
1909 
1910  unit_const_ptr self_u = self.un;
1911  unit_const_ptr other_u = other.un;
1912 
1913  // We also set the weapons context during (attack) wml events, in that case we identify the units via locations because wml might change
1914  // the actual unit and usually does so via replacing, in that case self_ is set to nullptr.
1915  // TODO: does this really make sense? if wml replaces the unit it also replaces the attack object, deleting the attack context properties
1916  if(self_u == nullptr) {
1917  unit_map::const_iterator it = units.find(self.loc);
1918  if(it.valid()) {
1919  self_u = it.get_shared_ptr();
1920  }
1921  }
1922  if(other_u == nullptr) {
1923  unit_map::const_iterator it = units.find(other.loc);
1924  if(it.valid()) {
1925  other_u = it.get_shared_ptr();
1926  }
1927  }
1928 
1929  // Make sure they're facing each other.
1930  temporary_facing self_facing(self_u, self.loc.get_relative_dir(other.loc));
1931  temporary_facing other_facing(other_u, other.loc.get_relative_dir(self.loc));
1932 
1933  // Filter poison, plague, drain, slow, petrifies
1934  // True if "whom" corresponds to "self", false if "whom" is "other"
1935  bool whom_is_self = ((whom == unit_ability_t::affects_t::SELF) || ((whom == unit_ability_t::affects_t::EITHER) && special_affects_self(ab, is_attacker)));
1936  unit_const_ptr them = whom_is_self ? other_u : self_u;
1937  map_location their_loc = whom_is_self ? other.loc : self.loc;
1938 
1939  if (buildin_is_immune(ab, them, their_loc)) {
1940  return false;
1941  }
1942 
1943 
1944  // Translate our context into terms of "attacker" and "defender".
1945  unit_const_ptr & att = is_attacker ? self_u : other_u;
1946  unit_const_ptr & def = is_attacker ? other_u : self_u;
1947 
1948  // Filter firststrike here, if both units have first strike then the effects cancel out. Only check
1949  // the opponent if "whom" is the defender, otherwise this leads to infinite recursion.
1950  if (ab.tag() == "firststrike") {
1951  bool whom_is_defender = whom_is_self ? !is_attacker : is_attacker;
1952  if (whom_is_defender && attacker.at && attacker.at->has_special_or_ability("firststrike"))
1953  return false;
1954  }
1955 
1956  // Filter the units involved.
1957  //If filter concerns the unit on which special is applied,
1958  //then the type of special must be entered to avoid calling
1959  //the function of this special in matches_filter()
1960  //In apply_to=both case, ab.tag() must be checked in all filter because special applied to both self and opponent.
1961  bool applied_both = ab.apply_to() == unit_ability_t::apply_to_t::both;
1962  const std::string& filter_self = ab.in_specials_tag() ? "filter_self" : "filter_student";
1963 
1964  bool applied_to_self = (applied_both || whom_is_self);
1965  if (!special_unit_matches(self_u, other_u, self.loc, self.at, ab, is_for_listing, filter_self, applied_to_self))
1966  return false;
1967  bool applied_to_opp = (applied_both || !whom_is_self);
1968  if (!special_unit_matches(other_u, self_u, other.loc, other.at, ab, is_for_listing, "filter_opponent", applied_to_opp))
1969  return false;
1970  //in case of apply_to=attacker|defender, if both [filter_attacker] and [filter_defender] are used,
1971  //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.
1972  bool applied_to_attacker = applied_both || (whom_is_self && is_attacker) || (!whom_is_self && !is_attacker);
1973  if (!special_unit_matches(att, def, attacker.loc, attacker.at, ab, is_for_listing, "filter_attacker", applied_to_attacker))
1974  return false;
1975  bool applied_to_defender = applied_both || (whom_is_self && !is_attacker) || (!whom_is_self && is_attacker);
1976  if (!special_unit_matches(def, att, defender.loc, defender.at, ab, is_for_listing, "filter_defender", applied_to_defender))
1977  return false;
1978 
1979  return true;
1980 }
1981 
1983 {
1984 
1985 void individual_effect::set(value_modifier t, int val, const config& abil, const map_location &l)
1986 {
1987  type = t;
1988  value = val;
1989  ability = &abil;
1990  loc = l;
1991 }
1992 
1993 bool filter_base_matches(const config& cfg, int def)
1994 {
1995  if (auto apply_filter = cfg.optional_child("filter_base_value")) {
1996  config::attribute_value cond_eq = apply_filter["equals"];
1997  config::attribute_value cond_ne = apply_filter["not_equals"];
1998  config::attribute_value cond_lt = apply_filter["less_than"];
1999  config::attribute_value cond_gt = apply_filter["greater_than"];
2000  config::attribute_value cond_ge = apply_filter["greater_than_equal_to"];
2001  config::attribute_value cond_le = apply_filter["less_than_equal_to"];
2002  return (cond_eq.empty() || def == cond_eq.to_int()) &&
2003  (cond_ne.empty() || def != cond_ne.to_int()) &&
2004  (cond_lt.empty() || def < cond_lt.to_int()) &&
2005  (cond_gt.empty() || def > cond_gt.to_int()) &&
2006  (cond_ge.empty() || def >= cond_ge.to_int()) &&
2007  (cond_le.empty() || def <= cond_le.to_int());
2008  }
2009  return true;
2010 }
2011 
2012 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) {
2013  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) {
2014  callable.add("base_value", wfl::variant(def));
2015  return std::round(formula.evaluate(callable).as_int());
2016  }));
2017  return value;
2018 }
2019 
2020 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) {
2021  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) {
2022  callable.add("base_value", wfl::variant(def));
2023  return formula.evaluate(callable).as_decimal() / 1000.0 ;
2024  }) * 100);
2025  return value;
2026 }
2027 
2028 effect::effect(const active_ability_list& list, int def, const const_attack_ptr& att, EFFECTS wham) :
2029  effect_list_(),
2030  composite_value_(def),
2031  composite_double_value_(def)
2032 {
2033  std::map<double, active_ability_list> base_list;
2034  for(const active_ability& i : list) {
2035  double priority = i.ability().priority();
2036  if(base_list[priority].empty()) {
2037  base_list[priority] = active_ability_list(list.loc());
2038  }
2039  base_list[priority].emplace_back(i);
2040  }
2041  int value = def;
2042  for(auto base : base_list) {
2043  effect::effect_impl(base.second, value, att, wham);
2044  value = composite_value_;
2045  }
2046 }
2047 
2048 void effect::effect_impl(const active_ability_list& list, int def, const const_attack_ptr& att, EFFECTS wham )
2049 {
2050  int value_set = def;
2051  std::map<std::string,individual_effect> values_add;
2052  std::map<std::string,individual_effect> values_sub;
2053  std::map<std::string,individual_effect> values_mul;
2054  std::map<std::string,individual_effect> values_div;
2055 
2056  individual_effect set_effect_max;
2057  individual_effect set_effect_min;
2058  individual_effect set_effect_cum;
2059  utils::optional<int> max_value = utils::nullopt;
2060  utils::optional<int> min_value = utils::nullopt;
2061 
2062  for (const active_ability & ability : list) {
2063  const config& cfg = ability.ability_cfg();
2064  const std::string& effect_id = cfg[cfg["id"].empty() ? "name" : "id"];
2065 
2066  if (!filter_base_matches(cfg, def))
2067  continue;
2068 
2069  if (const config::attribute_value *v = cfg.get("value")) {
2070  int value = individual_value_int(v, def, ability, list.loc(), att);
2071  int value_cum = wham != EFFECT_CUMULABLE && cfg["cumulative"].to_bool() ? std::max(def, value) : value;
2072  if(set_effect_cum.type != NOT_USED && wham == EFFECT_CUMULABLE && cfg["cumulative"].to_bool()) {
2073  set_effect_cum.set(SET, set_effect_cum.value + value_cum, ability.ability_cfg(), ability.teacher_loc);
2074  } else if(wham == EFFECT_CUMULABLE && cfg["cumulative"].to_bool()) {
2075  set_effect_cum.set(SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2076  } else {
2077  assert((set_effect_min.type != NOT_USED) == (set_effect_max.type != NOT_USED));
2078  if(set_effect_min.type == NOT_USED) {
2079  set_effect_min.set(SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2080  set_effect_max.set(SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2081  }
2082  else {
2083  if(value_cum > set_effect_max.value) {
2084  set_effect_max.set(SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2085  }
2086  if(value_cum < set_effect_min.value) {
2087  set_effect_min.set(SET, value_cum, ability.ability_cfg(), ability.teacher_loc);
2088  }
2089  }
2090  }
2091  }
2092 
2093  if(wham != EFFECT_WITHOUT_CLAMP_MIN_MAX) {
2094  if(const config::attribute_value *v = cfg.get("max_value")) {
2095  int value = individual_value_int(v, def, ability, list.loc(), att);
2096  max_value = max_value ? std::min(*max_value, value) : value;
2097  }
2098  if(const config::attribute_value *v = cfg.get("min_value")) {
2099  int value = individual_value_int(v, def, ability, list.loc(), att);
2100  min_value = min_value ? std::max(*min_value, value) : value;
2101  }
2102  }
2103 
2104  if (const config::attribute_value *v = cfg.get("add")) {
2105  int add = individual_value_int(v, def, ability, list.loc(), att);
2106  std::map<std::string,individual_effect>::iterator add_effect = values_add.find(effect_id);
2107  if(add_effect == values_add.end() || add > add_effect->second.value) {
2108  values_add[effect_id].set(ADD, add, ability.ability_cfg(), ability.teacher_loc);
2109  }
2110  }
2111  if (const config::attribute_value *v = cfg.get("sub")) {
2112  int sub = - individual_value_int(v, def, ability, list.loc(), att);
2113  std::map<std::string,individual_effect>::iterator sub_effect = values_sub.find(effect_id);
2114  if(sub_effect == values_sub.end() || sub < sub_effect->second.value) {
2115  values_sub[effect_id].set(ADD, sub, ability.ability_cfg(), ability.teacher_loc);
2116  }
2117  }
2118  if (const config::attribute_value *v = cfg.get("multiply")) {
2119  int multiply = individual_value_double(v, def, ability, list.loc(), att);
2120  std::map<std::string,individual_effect>::iterator mul_effect = values_mul.find(effect_id);
2121  if(mul_effect == values_mul.end() || multiply > mul_effect->second.value) {
2122  values_mul[effect_id].set(MUL, multiply, ability.ability_cfg(), ability.teacher_loc);
2123  }
2124  }
2125  if (const config::attribute_value *v = cfg.get("divide")) {
2126  int divide = individual_value_double(v, def, ability, list.loc(), att);
2127 
2128  if (divide == 0) {
2129  ERR_NG << "division by zero with divide= in ability/weapon special " << effect_id;
2130  }
2131  else {
2132  std::map<std::string,individual_effect>::iterator div_effect = values_div.find(effect_id);
2133  if(div_effect == values_div.end() || divide > div_effect->second.value) {
2134  values_div[effect_id].set(DIV, divide, ability.ability_cfg(), ability.teacher_loc);
2135  }
2136  }
2137  }
2138  }
2139 
2140  if(set_effect_max.type != NOT_USED) {
2141  value_set = std::max(set_effect_max.value, 0) + std::min(set_effect_min.value, 0);
2142  if(set_effect_max.value > def) {
2143  effect_list_.push_back(set_effect_max);
2144  }
2145  if(set_effect_min.value < def) {
2146  effect_list_.push_back(set_effect_min);
2147  }
2148  }
2149 
2150  /* Do multiplication with floating point values rather than integers
2151  * We want two places of precision for each multiplier
2152  * Using integers multiplied by 100 to keep precision causes overflow
2153  * after 3-4 abilities for 32-bit values and ~8 for 64-bit
2154  * Avoiding the overflow by dividing after each step introduces rounding errors
2155  * that may vary depending on the order effects are applied
2156  * As the final values are likely <1000 (always true for mainline), loss of less significant digits is not an issue
2157  */
2158  double multiplier = 1.0;
2159  double divisor = 1.0;
2160 
2161  for(const auto& val : values_mul) {
2162  multiplier *= val.second.value/100.0;
2163  effect_list_.push_back(val.second);
2164  }
2165 
2166  for(const auto& val : values_div) {
2167  divisor *= val.second.value/100.0;
2168  effect_list_.push_back(val.second);
2169  }
2170 
2171  int addition = 0;
2172  for(const auto& val : values_add) {
2173  addition += val.second.value;
2174  effect_list_.push_back(val.second);
2175  }
2176 
2177  /* Additional and subtraction are independent since Wesnoth 1.19.4. Prior to that, they affected each other.
2178  */
2179  int substraction = 0;
2180  for(const auto& val : values_sub) {
2181  substraction += val.second.value;
2182  effect_list_.push_back(val.second);
2183  }
2184 
2185  if(set_effect_cum.type != NOT_USED) {
2186  value_set += set_effect_cum.value;
2187  effect_list_.push_back(set_effect_cum);
2188  }
2189 
2190  composite_double_value_ = (value_set + addition + substraction) * multiplier / divisor;
2191  //clamp what if min_value < max_value or one attribute only used.
2192  if(max_value && min_value && *min_value < *max_value) {
2193  composite_double_value_ = std::clamp(static_cast<double>(*min_value), static_cast<double>(*max_value), composite_double_value_);
2194  } else if(max_value && !min_value) {
2195  composite_double_value_ = std::min(static_cast<double>(*max_value), composite_double_value_);
2196  } else if(min_value && !max_value) {
2197  composite_double_value_ = std::max(static_cast<double>(*min_value), composite_double_value_);
2198  }
2200 }
2201 
2202 } // 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:1398
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:1404
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:1440
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:1525
active_ability_list get_abilities_weapons(const std::string &tag, const unit &un) const
Definition: abilities.cpp:1611
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:1883
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:1286
std::string describe_weapon_specials_value(const attack_type &at, const std::set< std::string > &checking_tags) const
Definition: abilities.cpp:1085
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:1536
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:1359
bool has_active_special_matching_filter(const attack_type &at, const config &filter) const
Definition: abilities.cpp:1838
active_ability_list get_active_specials(const attack_type &at, const std::string &tag) const
Definition: abilities.cpp:1587
active_ability_list get_active_combat_teachers(const attack_type &at) const
Definition: abilities.cpp:1554
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:2028
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:2048
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:1833
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:300
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:29
map_display and display: classes which take care of displaying the map and game-data on the screen.
const config * cfg
std::size_t i
Definition: function.cpp:1031
Interfaces for manipulating version numbers of engine, add-ons, etc.
static std::string _(const char *str)
Definition: gettext.hpp:97
const ability_vector & abilities() const
Definition: unit.hpp:1708
bool ability_active_impl(const unit_ability_t &ab, const map_location &loc) const
Check if an ability is active.
Definition: abilities.cpp: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:1501
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:1510
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:2020
bool filter_base_matches(const config &cfg, int def)
Definition: abilities.cpp:1993
@ 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:2012
Utility functions for implementing [filter], [filter_ability], [filter_weapon], etc.
bool int_matches_if_present(const config &filter, const config &cfg, const std::string &attribute, utils::optional< int > def=utils::nullopt)
bool set_includes_if_present(const config &filter, const config &cfg, const std::string &attribute)
filter[attribute] and cfg[attribute] are assumed to be comma-separated lists.
bool double_matches_if_present(const config &filter, const config &cfg, const std::string &attribute, utils::optional< double > def=utils::nullopt)
Checks whether the filter matches the value of cfg[attribute].
bool int_matches_if_present_or_negative(const config &filter, const config &cfg, const std::string &attribute, const std::string &opposite, utils::optional< int > def=utils::nullopt)
Supports filters using "add" and "sub" attributes, for example a filter add=1 matching a cfg containi...
bool string_matches_if_present(const config &filter, const config &cfg, const std::string &attribute, const std::string &def)
bool bool_or_empty(const config &filter, const config &cfg, const std::string &attribute)
bool bool_matches_if_present(const config &filter, const config &cfg, const std::string &attribute, bool def)
Checks whether the filter matches the value of cfg[attribute].
constexpr auto filter
Definition: ranges.hpp:42
constexpr bool decayed_is_same
Equivalent to as std::is_same_v except both types are passed through std::decay first.
Definition: general.hpp:31
void trim(std::string_view &s)
std::string interpolate_variables_into_string(const std::string &str, const string_map *const symbols)
Function which will interpolate variables, starting with '$' in the string 'str' with the equivalent ...
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:87
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition: general.hpp:107
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
std::map< std::string, t_string > string_map
void sort_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::sort on a container.
Definition: general.hpp:132
std::vector< std::string > split(const config_attribute_value &val)
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
std::shared_ptr< const unit > unit_const_ptr
Definition: ptr.hpp:27
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:34
std::shared_ptr< unit_ability_t > ability_ptr
Definition: ptr.hpp:38
Data typedef for active_ability_list.
Definition: abilities.hpp: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:1985
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