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