The Battle for Wesnoth  1.19.11+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"
26 #include "formula/callable_objects.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/unit.hpp"
42 #include "units/abilities.hpp"
43 #include "units/ability_tags.hpp"
44 #include "units/filter.hpp"
45 #include "units/map.hpp"
46 #include "utils/config_filters.hpp"
47 #include "units/filter.hpp"
48 
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 namespace {
60  class temporary_facing
61  {
62  map_location::direction save_dir_;
63  unit_const_ptr u_;
64  public:
65  temporary_facing(const unit_const_ptr& u, map_location::direction new_dir)
66  : save_dir_(u ? u->facing() : map_location::direction::indeterminate)
67  , u_(u)
68  {
69  if (u_) {
70  u_->set_facing(new_dir);
71  }
72  }
73  ~temporary_facing()
74  {
75  if (u_) {
76  u_->set_facing(save_dir_);
77  }
78  }
79  };
80 }
81 
82 /*
83  *
84  * [abilities]
85  * ...
86  *
87  * [heals]
88  * value=4
89  * max_value=8
90  * cumulative=no
91  * affect_allies=yes
92  * name= _ "heals"
93  * female_name= _ "female^heals"
94  * name_inactive=null
95  * female_name_inactive=null
96  * description= _ "Heals:
97 Allows the unit to heal adjacent friendly units at the beginning of each turn.
98 
99 A unit cared for by a healer may heal up to 4 HP per turn.
100 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."
101  * description_inactive=null
102  *
103  * affect_self=yes
104  * [filter] // SUF
105  * ...
106  * [/filter]
107  * [filter_self] // SUF
108  * ...
109  * [/filter_self]
110  * [filter_adjacent] // SUF
111  * adjacent=n,ne,nw
112  * ...
113  * [/filter_adjacent]
114  * [filter_adjacent_location]
115  * adjacent=n,ne,nw
116  * ...
117  * [/filter_adjacent]
118  * [affect_adjacent]
119  * adjacent=n,ne,nw
120  * [filter] // SUF
121  * ...
122  * [/filter]
123  * [/affect_adjacent]
124  * [affect_adjacent]
125  * adjacent=s,se,sw
126  * [filter] // SUF
127  * ...
128  * [/filter]
129  * [/affect_adjacent]
130  *
131  * [/heals]
132  *
133  * ...
134  * [/abilities]
135  *
136  */
137 
138 
139 namespace {
140 
141 const unit_map& get_unit_map()
142 {
143  // Used if we're in the game, including during the construction of the display_context
145  return resources::gameboard->units();
146  }
147 
148  // If we get here, we're in the scenario editor
149  assert(display::get_singleton());
150  return display::get_singleton()->context().units();
151 }
152 
153 const team& get_team(std::size_t side)
154 {
155  // Used if we're in the game, including during the construction of the display_context
157  return resources::gameboard->get_team(side);
158  }
159 
160  // If we get here, we're in the scenario editor
161  assert(display::get_singleton());
162  return display::get_singleton()->context().get_team(side);
163 }
164 
165 /**
166  * Common code for the question "some other unit has an ability, can that ability affect this
167  * unit" - it's not the full answer to that question, just a part of it.
168  *
169  * Although this is called while checking which units' "hides" abilities are active, that's only
170  * for the question "is this unit next to an ally that has a 'camoflages adjacent allies' ability";
171  * not the question "is this unit next to an enemy, therefore visible".
172  */
173 bool affects_side(const config& cfg, std::size_t side, std::size_t other_side)
174 {
175  const team& side_team = get_team(side);
176 
177  if(side == other_side)
178  return cfg["affect_allies"].to_bool(true);
179  if(side_team.is_enemy(other_side))
180  return cfg["affect_enemies"].to_bool();
181  else
182  return cfg["affect_allies"].to_bool();
183 }
184 
185 }
186 
187 bool unit::get_ability_bool(const std::string& tag_name, const map_location& loc) const
188 {
189  for (const config &i : this->abilities_.child_range(tag_name)) {
190  if (get_self_ability_bool(i, tag_name, loc))
191  {
192  return true;
193  }
194  }
195 
196  const unit_map& units = get_unit_map();
197 
198  const auto adjacent = get_adjacent_tiles(loc);
199  for(unsigned i = 0; i < adjacent.size(); ++i) {
200  const unit_map::const_iterator it = units.find(adjacent[i]);
201  if (it == units.end() || it->incapacitated())
202  continue;
203  // Abilities may be tested at locations other than the unit's current
204  // location. This is intentional to allow for less messing with the unit
205  // map during calculations, particularly with regards to movement.
206  // Thus, we need to make sure the adjacent unit (*it) is not actually
207  // ourself.
208  if ( &*it == this )
209  continue;
210  for (const config &j : it->abilities_.child_range(tag_name)) {
211  if (get_adj_ability_bool(j, tag_name, i, loc,*it))
212  {
213  return true;
214  }
215  }
216  }
217 
218 
219  return false;
220 }
221 
222 unit_ability_list unit::get_abilities(const std::string& tag_name, const map_location& loc) const
223 {
224  unit_ability_list res(loc_);
225 
226  for(const config& i : this->abilities_.child_range(tag_name)) {
227  if (get_self_ability_bool(i, tag_name, loc))
228  {
229  res.emplace_back(&i, loc, loc);
230  }
231  }
232 
233  const unit_map& units = get_unit_map();
234 
235  const auto adjacent = get_adjacent_tiles(loc);
236  for(unsigned i = 0; i < adjacent.size(); ++i) {
237  const unit_map::const_iterator it = units.find(adjacent[i]);
238  if (it == units.end() || it->incapacitated())
239  continue;
240  // Abilities may be tested at locations other than the unit's current
241  // location. This is intentional to allow for less messing with the unit
242  // map during calculations, particularly with regards to movement.
243  // Thus, we need to make sure the adjacent unit (*it) is not actually
244  // ourself.
245  if ( &*it == this )
246  continue;
247  for(const config& j : it->abilities_.child_range(tag_name)) {
248  if(get_adj_ability_bool(j, tag_name, i, loc,*it)) {
249  res.emplace_back(&j, loc, adjacent[i]);
250  }
251  }
252  }
253 
254 
255  return res;
256 }
257 
258 unit_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
259 {
260  unit_ability_list res = get_abilities(tag_name, loc);
261  utils::erase_if(res, [&](const unit_ability& i) {
262  return !ability_affects_weapon(*i.ability_cfg, weapon, false) || !ability_affects_weapon(*i.ability_cfg, opp_weapon, true);
263  });
264  return res;
265 }
266 
267 std::vector<std::string> unit::get_ability_list() const
268 {
269  std::vector<std::string> res;
270 
271  for(const auto [key, cfg] : this->abilities_.all_children_view()) {
272  std::string id = cfg["id"];
273  if (!id.empty())
274  res.push_back(std::move(id));
275  }
276  return res;
277 }
278 
279 
280 namespace {
281  /**
282  * Adds a quadruple consisting of (in order) id, base name,
283  * male or female name as appropriate for the unit, and description.
284  *
285  * @returns Whether name was resolved and quadruple added.
286  */
287  bool add_ability_tooltip(const config& ab, unit_race::GENDER gender, std::vector<std::tuple<std::string, t_string,t_string,t_string>>& res, bool active)
288  {
289  if (active) {
290  const t_string& name = gender_value(ab, gender, "name", "female_name", "name").t_str();
291 
292  if (!name.empty()) {
293  res.emplace_back(
294  ab["id"],
295  ab["name"].t_str(),
296  name,
297  ab["description"].t_str() );
298  return true;
299  }
300  } else {
301  // See if an inactive name was specified.
302  const config::attribute_value& inactive_value =
303  gender_value(ab, gender, "name_inactive",
304  "female_name_inactive", "name_inactive");
305  const t_string& name = !inactive_value.blank() ? inactive_value.t_str() :
306  gender_value(ab, gender, "name", "female_name", "name").t_str();
307 
308  if (!name.empty()) {
309  res.emplace_back(
310  ab["id"],
311  ab.get_or("name_inactive", "name").t_str(),
312  name,
313  ab.get_or("description_inactive", "description").t_str() );
314  return true;
315  }
316  }
317 
318  return false;
319  }
320 }
321 
322 std::vector<std::tuple<std::string, t_string, t_string, t_string>> unit::ability_tooltips() const
323 {
324  std::vector<std::tuple<std::string, t_string,t_string,t_string>> res;
325 
326  for(const auto [_, cfg] : this->abilities_.all_children_view())
327  {
328  add_ability_tooltip(cfg, gender_, res, true);
329  }
330 
331  return res;
332 }
333 
334 std::vector<std::tuple<std::string, t_string, t_string, t_string>> unit::ability_tooltips(boost::dynamic_bitset<>& active_list, const map_location& loc) const
335 {
336  std::vector<std::tuple<std::string, t_string,t_string,t_string>> res;
337  active_list.clear();
338 
339  for(const auto [key, cfg] : this->abilities_.all_children_view())
340  {
341  bool active = ability_active(key, cfg, loc);
342  if (add_ability_tooltip(cfg, gender_, res, active))
343  {
344  active_list.push_back(active);
345  }
346  }
347  return res;
348 }
349 
350 namespace {
351  /**
352  * Print "Recursion limit reached" log messages, including deduplication if the same problem has
353  * already been logged.
354  */
355  void show_recursion_warning(const unit& unit, const config& filter) {
356  // This function is only called when an ability is checked for the second time
357  // filter has already been parsed multiple times, so I'm not trying to optimize the performance
358  // of this; it's merely to prevent the logs getting spammed. For example, each of
359  // four_cycle_recursion_branching and event_test_filter_attack_student_weapon_condition only log
360  // 3 unique messages, but without deduplication they'd log 1280 and 392 respectively.
361  static std::vector<std::tuple<std::string, std::string>> already_shown;
362 
363  auto identifier = std::tuple<std::string, std::string>{unit.id(), filter.debug()};
364  if(utils::contains(already_shown, identifier)) {
365  return;
366  }
367 
368  std::string_view filter_text_view = std::get<1>(identifier);
369  utils::trim(filter_text_view);
370  ERR_NG << "Looped recursion error for unit '" << unit.id()
371  << "' while checking ability '" << filter_text_view << "'";
372 
373  // Arbitrary limit, just ensuring that having a huge number of specials causing recursion
374  // warnings can't lead to unbounded memory consumption here.
375  if(already_shown.size() > 100) {
376  already_shown.clear();
377  }
378  already_shown.push_back(std::move(identifier));
379  }
380 }//anonymous namespace
381 
383 {
384  if(utils::contains(open_queries_, &ability)) {
385  return recursion_guard();
386  }
387  return recursion_guard(*this, ability);
388 }
389 
391 
393  : parent(u.shared_from_this())
394 {
395  u.open_queries_.emplace_back(&ability);
396 }
397 
399 {
400  std::swap(parent, other.parent);
401 }
402 
403 unit::recursion_guard::operator bool() const {
404  return bool(parent);
405 }
406 
408 {
409  assert(this != &other);
410  assert(!parent);
411  std::swap(parent, other.parent);
412  return *this;
413 }
414 
416 {
417  if(parent) {
418  assert(!parent->open_queries_.empty());
419  parent->open_queries_.pop_back();
420  }
421 }
422 
423 bool unit::ability_active(const std::string& ability,const config& cfg,const map_location& loc) const
424 {
425  auto filter_lock = update_variables_recursion(cfg);
426  if(!filter_lock) {
427  show_recursion_warning(*this, cfg);
428  return false;
429  }
430  return ability_active_impl(ability, cfg, loc);
431 }
432 
433 bool unit::ability_active_impl(const std::string& ability,const config& cfg,const map_location& loc) const
434 {
435  bool illuminates = ability == "illuminates";
436 
437  if (auto afilter = cfg.optional_child("filter"))
438  if ( !unit_filter(vconfig(*afilter)).set_use_flat_tod(illuminates).matches(*this, loc) )
439  return false;
440 
441  const auto adjacent = get_adjacent_tiles(loc);
442 
443  const unit_map& units = get_unit_map();
444 
445  for (const config &i : cfg.child_range("filter_adjacent"))
446  {
447  std::size_t count = 0;
448  unit_filter ufilt{ vconfig(i) };
449  ufilt.set_use_flat_tod(illuminates);
450  std::vector<map_location::direction> dirs = i["adjacent"].empty() ? map_location::all_directions() : map_location::parse_directions(i["adjacent"]);
451  for (const map_location::direction index : dirs)
452  {
453  unit_map::const_iterator unit = units.find(adjacent[static_cast<int>(index)]);
454  if (unit == units.end())
455  continue;
456  if (!ufilt(*unit, *this))
457  continue;
458  if((*this).id() == (*unit).id())
459  continue;
460  if (i.has_attribute("is_enemy")) {
462  if (i["is_enemy"].to_bool() != dc.get_team(unit->side()).is_enemy(side_)) {
463  continue;
464  }
465  }
466  count++;
467  }
468  static std::vector<std::pair<int,int>> default_counts = utils::parse_ranges_unsigned("1-6");
469  config::attribute_value i_count =i["count"];
470  if(!in_ranges<int>(count, !i_count.blank() ? utils::parse_ranges_unsigned(i_count) : default_counts)){
471  return false;
472  }
473  }
474 
475  for (const config &i : cfg.child_range("filter_adjacent_location"))
476  {
477  std::size_t count = 0;
478  terrain_filter adj_filter(vconfig(i), resources::filter_con, false);
479  adj_filter.flatten(illuminates);
480 
481  std::vector<map_location::direction> dirs = i["adjacent"].empty() ? map_location::all_directions() : map_location::parse_directions(i["adjacent"]);
482  for (const map_location::direction index : dirs)
483  {
484  if(!adj_filter.match(adjacent[static_cast<int>(index)])) {
485  continue;
486  }
487  count++;
488  }
489  static std::vector<std::pair<int,int>> default_counts = utils::parse_ranges_unsigned("1-6");
490  config::attribute_value i_count =i["count"];
491  if(!in_ranges<int>(count, !i_count.blank() ? utils::parse_ranges_unsigned(i_count) : default_counts)){
492  return false;
493  }
494  }
495  return true;
496 }
497 
498 bool unit::ability_affects_adjacent(const std::string& ability, const config& cfg,int dir,const map_location& loc,const unit& from) const
499 {
500  bool illuminates = ability == "illuminates";
501 
502  assert(dir >=0 && dir <= 5);
503  //correct reversion of direction
504  map_location::direction direction{ dir = dir < 3 ? dir + 3 : dir - 3 };
505 
506  for (const config &i : cfg.child_range("affect_adjacent"))
507  {
508  if (i.has_attribute("adjacent")) { //key adjacent defined
509  std::vector<map_location::direction> dirs = map_location::parse_directions(i["adjacent"]);
510  if (std::find(dirs.begin(), dirs.end(), direction) == dirs.end()) {
511  continue;
512  }
513  }
514  if((*this).id() == from.id()){
515  return false;
516  }
517  auto filter = i.optional_child("filter");
518  if (!filter || //filter tag given
519  unit_filter(vconfig(*filter)).set_use_flat_tod(illuminates).matches(*this, loc, from) ) {
520  return true;
521  }
522  }
523  return false;
524 }
525 
526 bool unit::ability_affects_self(const std::string& ability,const config& cfg,const map_location& loc) const
527 {
528  auto filter = cfg.optional_child("filter_self");
529  bool affect_self = cfg["affect_self"].to_bool(true);
530  if (!filter || !affect_self) return affect_self;
531  return unit_filter(vconfig(*filter)).set_use_flat_tod(ability == "illuminates").matches(*this, loc);
532 }
533 
534 bool unit::ability_affects_weapon(const config& cfg, const const_attack_ptr& weapon, bool is_opp) const
535 {
536  const std::string filter_tag_name = is_opp ? "filter_second_weapon" : "filter_weapon";
537  if(!cfg.has_child(filter_tag_name)) {
538  return true;
539  }
540  const config& filter = cfg.mandatory_child(filter_tag_name);
541  if(!weapon) {
542  return false;
543  }
544  attack_type::recursion_guard filter_lock;
545  filter_lock = weapon->update_variables_recursion(cfg);
546  if(!filter_lock) {
547  show_recursion_warning(*this, cfg);
548  return false;
549  }
550  return weapon->matches_filter(filter);
551 }
552 
553 bool unit::has_ability_type(const std::string& ability) const
554 {
555  return !abilities_.child_range(ability).empty();
556 }
557 
558 //these two functions below are used in order to add to the unit
559 //a second set of halo encoded in the abilities (like illuminates halo in [illuminates] ability for example)
560 static void add_string_to_vector(std::vector<std::string>& image_list, const config& cfg, const std::string& attribute_name)
561 {
562  auto ret = std::find(image_list.begin(), image_list.end(), cfg[attribute_name].str());
563  if(ret == image_list.end()){
564  image_list.push_back(cfg[attribute_name].str());
565  }
566 }
567 
568 std::vector<std::string> unit::halo_or_icon_abilities(const std::string& image_type) const
569 {
570  std::vector<std::string> image_list;
571  for(const auto [key, cfg] : abilities_.all_children_view()){
572  bool is_active = ability_active(key, cfg, loc_);
573  //Add halo/overlay to owner of ability if active and affect_self is true.
574  if( !cfg[image_type + "_image"].str().empty() && is_active && ability_affects_self(key, cfg, loc_)){
575  add_string_to_vector(image_list, cfg,image_type + "_image");
576  }
577  //Add halo/overlay to owner of ability who affect adjacent only if active.
578  if(!cfg[image_type + "_image_self"].str().empty() && is_active){
579  add_string_to_vector(image_list, cfg, image_type + "_image_self");
580  }
581  }
582 
583  const unit_map& units = get_unit_map();
584 
585  //Add halo/overlay to unit under abilities owned by adjacent who has [affect_adjacent]
586  //if condition matched
587  const auto adjacent = get_adjacent_tiles(loc_);
588  for(unsigned i = 0; i < adjacent.size(); ++i) {
589  const unit_map::const_iterator it = units.find(adjacent[i]);
590  if (it == units.end() || it->incapacitated())
591  continue;
592  if ( &*it == this )
593  continue;
594  for(const auto [key, cfg] : it->abilities_.all_children_view()) {
595  if(!cfg[image_type + "_image"].str().empty() && affects_side(cfg, side(), it->side()) && it->ability_active(key, cfg, adjacent[i]) && ability_affects_adjacent(key, cfg, i, loc_, *it))
596  {
597  add_string_to_vector(image_list, cfg, image_type + "_image");
598  }
599  }
600  }
601  //rearranges vector alphabetically when its size equals or exceeds two.
602  if(image_list.size() >= 2){
603  std::sort(image_list.begin(), image_list.end());
604  }
605  return image_list;
606 }
607 
609 {
610  if(unit_const_ptr & att = is_attacker_ ? self_ : other_) {
611  callable.add("attacker", wfl::variant(std::make_shared<wfl::unit_callable>(*att)));
612  }
613  if(unit_const_ptr & def = is_attacker_ ? other_ : self_) {
614  callable.add("defender", wfl::variant(std::make_shared<wfl::unit_callable>(*def)));
615  }
616 }
617 
618 namespace {
619 
620 
621 template<typename T, typename TFuncFormula>
622 class get_ability_value_visitor
623 #ifdef USING_BOOST_VARIANT
624  : public boost::static_visitor<T>
625 #endif
626 {
627 public:
628  // Constructor stores the default value.
629  get_ability_value_visitor(T def, const TFuncFormula& formula_handler) : def_(def), formula_handler_(formula_handler) {}
630 
631  T operator()(const utils::monostate&) const { return def_; }
632  T operator()(bool) const { return def_; }
633  T operator()(int i) const { return static_cast<T>(i); }
634  T operator()(unsigned long long u) const { return static_cast<T>(u); }
635  T operator()(double d) const { return static_cast<T>(d); }
636  T operator()(const t_string&) const { return def_; }
637  T operator()(const std::string& s) const
638  {
639  if(s.size() >= 2 && s[0] == '(') {
640  return formula_handler_(s);
641  }
642  return lexical_cast_default<T>(s, def_);
643  }
644 
645 private:
646  const T def_;
647  const TFuncFormula& formula_handler_;
648 };
649 
650 template<typename T, typename TFuncFormula>
651 T get_single_ability_value(const config::attribute_value& v, T def, const unit_ability& ability_info, const map_location& receiver_loc, const const_attack_ptr& att, const TFuncFormula& formula_handler)
652 {
653  return v.apply_visitor(get_ability_value_visitor(def, [&](const std::string& s) {
654 
655  try {
656  const unit_map& units = get_unit_map();
657 
658  auto u_itor = units.find(ability_info.teacher_loc);
659 
660  if(u_itor == units.end()) {
661  return def;
662  }
663  wfl::map_formula_callable callable(std::make_shared<wfl::unit_callable>(*u_itor));
664  if(att) {
665  att->add_formula_context(callable);
666  }
667  if (auto uptr = units.find_unit_ptr(ability_info.student_loc)) {
668  callable.add("student", wfl::variant(std::make_shared<wfl::unit_callable>(*uptr)));
669  }
670  if (auto uptr = units.find_unit_ptr(receiver_loc)) {
671  callable.add("other", wfl::variant(std::make_shared<wfl::unit_callable>(*uptr)));
672  }
673  return formula_handler(wfl::formula(s, new wfl::gamestate_function_symbol_table, true), callable);
674  } catch(const wfl::formula_error& e) {
675  lg::log_to_chat() << "Formula error in ability or weapon special: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
676  ERR_WML << "Formula error in ability or weapon special: " << e.type << " at " << e.filename << ':' << e.line << ")";
677  return def;
678  }
679  }));
680 }
681 }
682 
683 template<typename TComp>
684 std::pair<int,map_location> unit_ability_list::get_extremum(const std::string& key, int def, const TComp& comp) const
685 {
686  if ( cfgs_.empty() ) {
687  return std::pair(def, map_location());
688  }
689  // The returned location is the best non-cumulative one, if any,
690  // the best absolute cumulative one otherwise.
691  map_location best_loc;
692  bool only_cumulative = true;
693  int abs_max = 0;
694  int flat = 0;
695  int stack = 0;
696  for (const unit_ability& p : cfgs_)
697  {
698  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) {
699  return std::round(formula.evaluate(callable).as_int());
700  }));
701 
702  if ((*p.ability_cfg)["cumulative"].to_bool()) {
703  stack += value;
704  if (value < 0) value = -value;
705  if (only_cumulative && !comp(value, abs_max)) {
706  abs_max = value;
707  best_loc = p.teacher_loc;
708  }
709  } else if (only_cumulative || comp(flat, value)) {
710  only_cumulative = false;
711  flat = value;
712  best_loc = p.teacher_loc;
713  }
714  }
715  return std::pair(flat + stack, best_loc);
716 }
717 
718 template std::pair<int, map_location> unit_ability_list::get_extremum<std::less<int>>(const std::string& key, int def, const std::less<int>& comp) const;
719 template std::pair<int, map_location> unit_ability_list::get_extremum<std::greater<int>>(const std::string& key, int def, const std::greater<int>& comp) const;
720 
721 /*
722  *
723  * [special]
724  * [swarm]
725  * name= _ "swarm"
726  * name_inactive= _ ""
727  * description= _ ""
728  * description_inactive= _ ""
729  * cumulative=no
730  * apply_to=self #self,opponent,defender,attacker,both
731  * #active_on=defense # or offense; omitting this means "both"
732  *
733  * swarm_attacks_max=4
734  * swarm_attacks_min=2
735  *
736  * [filter_self] // SUF
737  * ...
738  * [/filter_self]
739  * [filter_opponent] // SUF
740  * [filter_attacker] // SUF
741  * [filter_defender] // SUF
742  * [filter_adjacent] // SAUF
743  * [filter_adjacent_location] // SAUF + locs
744  * [/swarm]
745  * [/special]
746  *
747  */
748 
749 namespace {
750 
751  struct special_match
752  {
753  std::string tag_name;
754  const config* cfg;
755  };
756 
757  /**
758  * Gets the children of @parent (which should be the specials for an
759  * attack_type) and places the ones whose tag or id= matches @a id into
760  * @a tag_result and @a id_result.
761  *
762  * If @a just_peeking is set to true, then @a tag_result and @a id_result
763  * are not touched; instead the return value is used to indicate if any
764  * matching children were found.
765  *
766  * @returns true if @a just_peeking is true and a match was found;
767  * false otherwise.
768  */
769  bool get_special_children(std::vector<special_match>& tag_result,
770  std::vector<special_match>& id_result,
771  const config& parent, const std::string& id,
772  bool just_peeking=false) {
773  for(const auto [key, cfg] : parent.all_children_view())
774  {
775  if (just_peeking && (key == id || cfg["id"] == id)) {
776  return true; // peek succeeded; done
777  }
778 
779  if(key == id) {
780  special_match special = { key, &cfg };
781  tag_result.push_back(special);
782  }
783  if(cfg["id"] == id) {
784  special_match special = { key, &cfg };
785  id_result.push_back(special);
786  }
787  }
788  return false;
789  }
790 
791  bool get_special_children_id(std::vector<special_match>& id_result,
792  const config& parent, const std::string& id,
793  bool just_peeking=false) {
794  for(const auto [key, cfg] : parent.all_children_view())
795  {
796  if (just_peeking && (cfg["id"] == id)) {
797  return true; // peek succeeded; done
798  }
799 
800  if(cfg["id"] == id) {
801  special_match special = { key, &cfg };
802  id_result.push_back(special);
803  }
804  }
805  return false;
806  }
807 
808  bool get_special_children_tags(std::vector<special_match>& tag_result,
809  const config& parent, const std::string& id,
810  bool just_peeking=false) {
811  for(const auto [key, cfg] : parent.all_children_view())
812  {
813  if (just_peeking && (key == id)) {
814  return true; // peek succeeded; done
815  }
816 
817  if(key == id) {
818  special_match special = { key, &cfg };
819  tag_result.push_back(special);
820  }
821  }
822  return false;
823  }
824 }
825 
826 /**
827  * Returns whether or not @a *this has a special with a tag or id equal to
828  * @a special. If @a simple_check is set to true, then the check is merely
829  * for being present. Otherwise (the default), the check is for a special
830  * active in the current context (see set_specials_context), including
831  * specials obtained from the opponent's attack.
832  */
833 bool attack_type::has_special(const std::string& special, bool simple_check, bool special_id, bool special_tags) const
834 {
835  {
836  std::vector<special_match> special_tag_matches;
837  std::vector<special_match> special_id_matches;
838  if(special_id && special_tags){
839  if ( get_special_children(special_tag_matches, special_id_matches, specials_, special, simple_check) ) {
840  return true;
841  }
842  } else if(special_id && !special_tags){
843  if ( get_special_children_id(special_id_matches, specials_, special, simple_check) ) {
844  return true;
845  }
846  } else if(!special_id && special_tags){
847  if ( get_special_children_tags(special_tag_matches, specials_, special, simple_check) ) {
848  return true;
849  }
850  }
851  // If we make it to here, then either list.empty() or !simple_check.
852  // So if the list is not empty, then this is not a simple check and
853  // we need to check each special in the list to see if any are active.
854  if(special_tags){
855  for(const special_match& entry : special_tag_matches) {
856  if ( special_active(*entry.cfg, AFFECT_SELF, entry.tag_name) ) {
857  return true;
858  }
859  }
860  }
861  if(special_id){
862  for(const special_match& entry : special_id_matches) {
863  if ( special_active(*entry.cfg, AFFECT_SELF, entry.tag_name) ) {
864  return true;
865  }
866  }
867  }
868  }
869 
870  // Skip checking the opponent's attack?
871  if ( simple_check || !other_attack_ ) {
872  return false;
873  }
874 
875  std::vector<special_match> special_tag_matches;
876  std::vector<special_match> special_id_matches;
877  if(special_id && special_tags){
878  get_special_children(special_tag_matches, special_id_matches, other_attack_->specials_, special);
879  } else if(special_id && !special_tags){
880  get_special_children_id(special_id_matches, other_attack_->specials_, special);
881  } else if(!special_id && special_tags){
882  get_special_children_tags(special_tag_matches, other_attack_->specials_, special);
883  }
884  if(special_tags){
885  for(const special_match& entry : special_tag_matches) {
886  if ( other_attack_->special_active(*entry.cfg, AFFECT_OTHER, entry.tag_name) ) {
887  return true;
888  }
889  }
890  }
891  if(special_id){
892  for(const special_match& entry : special_id_matches) {
893  if ( other_attack_->special_active(*entry.cfg, AFFECT_OTHER, entry.tag_name) ) {
894  return true;
895  }
896  }
897  }
898  return false;
899 }
900 
901 /**
902  * Returns the currently active specials as an ability list, given the current
903  * context (see set_specials_context).
904  */
905 unit_ability_list attack_type::get_specials(const std::string& special) const
906 {
907  //log_scope("get_specials");
908  const map_location loc = self_ ? self_->get_location() : self_loc_;
909  unit_ability_list res(loc);
910 
911  for(const config& i : specials_.child_range(special)) {
912  if(special_active(i, AFFECT_SELF, special)) {
913  res.emplace_back(&i, loc, loc);
914  }
915  }
916 
917  if(!other_attack_) {
918  return res;
919  }
920 
921  for(const config& i : other_attack_->specials_.child_range(special)) {
922  if(other_attack_->special_active(i, AFFECT_OTHER, special)) {
923  res.emplace_back(&i, other_loc_, other_loc_);
924  }
925  }
926  return res;
927 }
928 
929 /**
930  * Returns a vector of names and descriptions for the specials of *this.
931  * Each std::pair in the vector has first = name and second = description.
932  *
933  * This uses either the active or inactive name/description for each special,
934  * based on the current context (see set_specials_context), provided
935  * @a active_list is not nullptr. Otherwise specials are assumed active.
936  * If the appropriate name is empty, the special is skipped.
937  */
938 std::vector<std::pair<t_string, t_string>> attack_type::special_tooltips(
939  boost::dynamic_bitset<>* active_list) const
940 {
941  //log_scope("special_tooltips");
942  std::vector<std::pair<t_string, t_string>> res;
943  if(active_list) {
944  active_list->clear();
945  }
946 
947  for(const auto [key, cfg] : specials_.all_children_view()) {
948  if(!active_list || special_active(cfg, AFFECT_EITHER, key)) {
949  const t_string& name = cfg["name"];
950  if(!name.empty()) {
951  if(key == "plague") {
952  const unit_type type = unit_types.types().at(cfg["type"].str());
953  res.emplace_back(
954  VGETTEXT(cfg["name"].t_str().c_str(), {{"type", type.type_name()}}),
955  VGETTEXT(cfg["description"].t_str().c_str(), {{"type", type.type_name()}}));
956  } else {
957  res.emplace_back(name, cfg["description"].t_str());
958  }
959 
960  if(active_list) {
961  active_list->push_back(true);
962  }
963  }
964  } else {
965  const t_string& name = cfg.get_or("name_inactive", "name").t_str();
966  if(!name.empty()) {
967  res.emplace_back(name, cfg.get_or("description_inactive", "description").t_str() );
968  active_list->push_back(false);
969  }
970  }
971  }
972  return res;
973 }
974 
975 /**
976  * static used in weapon_specials (bool only_active) and
977  * @return a string and a set_string for the weapon_specials function below.
978  * @param[in,out] temp_string the string modified and returned
979  * @param[in] active the boolean for determine if @name can be added or not
980  * @param[in] name string who must be or not added
981  * @param[in,out] checking_name the reference for checking if @name already added
982  */
983 static void add_name(std::string& temp_string, bool active, const std::string& name, std::set<std::string>& checking_name)
984 {
985  if (active) {
986  if (!name.empty() && checking_name.count(name) == 0) {
987  checking_name.insert(name);
988  if (!temp_string.empty()) temp_string += ", ";
989  temp_string += markup::span_color(font::TITLE_COLOR, name);
990  }
991  }
992 }
993 
994 /**
995  * Returns a comma-separated string of active names for the specials of *this.
996  * Empty names are skipped.
997  *
998  * Whether or not a special is active depends
999  * on the current context (see set_specials_context)
1000  */
1001 std::string attack_type::weapon_specials() const
1002 {
1003  //log_scope("weapon_specials");
1004  std::string res;
1005  for(const auto [key, cfg] : specials_.all_children_view())
1006  {
1007  const bool active = special_active(cfg, AFFECT_EITHER, key);
1008 
1009  const std::string& name =
1010  active
1011  ? cfg["name"].str()
1012  : cfg.get_or("name_inactive", "name").str();
1013  if (!name.empty()) {
1014  if (!res.empty()) {
1015  res += ", ";
1016  }
1017 
1018  if (!active) {
1020  } else {
1021  res += name;
1022  }
1023  }
1024  }
1025  std::string temp_string;
1026  std::set<std::string> checking_name;
1027  weapon_specials_impl_self(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name);
1028  weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, {}, "affect_allies");
1029  if(!temp_string.empty() && !res.empty()) {
1030  temp_string = ", \n" + temp_string;
1031  res += temp_string;
1032  } else if (!temp_string.empty()){
1033  res = temp_string;
1034  }
1035  return res;
1036 }
1037 
1038 static void add_name_list(std::string& temp_string, std::string& weapon_abilities, std::set<std::string>& checking_name, const std::string& from_str)
1039 {
1040  if(!temp_string.empty()){
1041  temp_string = from_str.c_str() + temp_string;
1042  weapon_abilities += (!weapon_abilities.empty() && !temp_string.empty()) ? "\n" : "";
1043  weapon_abilities += temp_string;
1044  temp_string.clear();
1045  checking_name.clear();
1046  }
1047 }
1048 
1049 std::string attack_type::weapon_specials_value(const std::set<std::string>& checking_tags) const
1050 {
1051  //log_scope("weapon_specials_value");
1052  std::string temp_string, weapon_abilities;
1053  std::set<std::string> checking_name;
1054  for(const auto [key, cfg] : specials_.all_children_view()) {
1055  if((checking_tags.count(key) != 0)){
1056  const bool active = special_active(cfg, AFFECT_SELF, key);
1057  add_name(temp_string, active, cfg["name"].str(), checking_name);
1058  }
1059  }
1060  add_name_list(temp_string, weapon_abilities, checking_name, "");
1061 
1062  weapon_specials_impl_self(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, checking_tags, true);
1063  add_name_list(temp_string, weapon_abilities, checking_name, _("Owned: "));
1064 
1065  weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, checking_tags, "affect_allies", true);
1066  // TRANSLATORS: Past-participle of "teach", used for an ability similar to leadership
1067  add_name_list(temp_string, weapon_abilities, checking_name, _("Taught: "));
1068 
1069  weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, checking_tags, "affect_enemies", true);
1070  // TRANSLATORS: Past-participle of "teach", used for an ability similar to leadership
1071  add_name_list(temp_string, weapon_abilities, checking_name, _("Taught: (by an enemy): "));
1072 
1073 
1074  if(other_attack_) {
1075  for(const auto [key, cfg] : other_attack_->specials_.all_children_view()) {
1076  if((checking_tags.count(key) != 0)){
1077  const bool active = other_attack_->special_active(cfg, AFFECT_OTHER, key);
1078  add_name(temp_string, active, cfg["name"].str(), checking_name);
1079  }
1080  }
1081  }
1082  weapon_specials_impl_self(temp_string, other_, other_attack_, shared_from_this(), other_loc_, AFFECT_OTHER, checking_name, checking_tags);
1083  weapon_specials_impl_adj(temp_string, other_, other_attack_, shared_from_this(), other_loc_, AFFECT_OTHER, checking_name, checking_tags);
1084  add_name_list(temp_string, weapon_abilities, checking_name, _("Used by opponent: "));
1085 
1086  return weapon_abilities;
1087 }
1088 
1090  std::string& temp_string,
1091  const unit_const_ptr& self,
1092  const const_attack_ptr& self_attack,
1093  const const_attack_ptr& other_attack,
1094  const map_location& self_loc,
1095  AFFECTS whom,
1096  std::set<std::string>& checking_name,
1097  const std::set<std::string>& checking_tags,
1098  bool leader_bool)
1099 {
1100  if(self){
1101  for(const auto [key, cfg] : self->abilities().all_children_view()){
1102  bool tag_checked = (!checking_tags.empty()) ? (checking_tags.count(key) != 0) : true;
1103  const bool active = tag_checked && check_self_abilities_impl(self_attack, other_attack, cfg, self, self_loc, whom, key, leader_bool);
1104  add_name(temp_string, active, cfg["name"].str(), checking_name);
1105  }
1106  }
1107 }
1108 
1110  std::string& temp_string,
1111  const unit_const_ptr& self,
1112  const const_attack_ptr& self_attack,
1113  const const_attack_ptr& other_attack,
1114  const map_location& self_loc,
1115  AFFECTS whom,
1116  std::set<std::string>& checking_name,
1117  const std::set<std::string>& checking_tags,
1118  const std::string& affect_adjacents,
1119  bool leader_bool)
1120 {
1121  const unit_map& units = get_unit_map();
1122  if(self){
1123  const auto adjacent = get_adjacent_tiles(self_loc);
1124  for(unsigned i = 0; i < adjacent.size(); ++i) {
1125  const unit_map::const_iterator it = units.find(adjacent[i]);
1126  if (it == units.end() || it->incapacitated())
1127  continue;
1128  if(&*it == self.get())
1129  continue;
1130  for(const auto [key, cfg] : it->abilities().all_children_view()) {
1131  bool tag_checked = (!checking_tags.empty()) ? (checking_tags.count(key) != 0) : true;
1132  bool default_bool = (affect_adjacents == "affect_allies") ? true : false;
1133  bool affect_allies = (!affect_adjacents.empty()) ? cfg[affect_adjacents].to_bool(default_bool) : true;
1134  const bool active = tag_checked && check_adj_abilities_impl(self_attack, other_attack, cfg, self, *it, i, self_loc, whom, key, leader_bool) && affect_allies;
1135  add_name(temp_string, active, cfg["name"].str(), checking_name);
1136  }
1137  }
1138  }
1139 }
1140 
1141 
1142 /**
1143  * Sets the context under which specials will be checked for being active.
1144  * This version is appropriate if both units in a combat are known.
1145  * @param[in] weapon The weapon being considered.
1146  * @param[in] self A reference to the unit with this weapon.
1147  * @param[in] other A reference to the other unit in the combat.
1148  * @param[in] unit_loc The location of the unit with this weapon.
1149  * @param[in] other_loc The location of the other unit in the combat.
1150  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
1151  * @param[in] other_attack The attack used by the other unit.
1152  */
1154  const attack_type& weapon,
1155  const_attack_ptr other_attack,
1156  unit_const_ptr self,
1157  unit_const_ptr other,
1158  const map_location& unit_loc,
1159  const map_location& other_loc,
1160  bool attacking)
1161  : parent(weapon.shared_from_this())
1162 {
1163  weapon.self_ = std::move(self);
1164  weapon.other_ = std::move(other);
1165  weapon.self_loc_ = unit_loc;
1166  weapon.other_loc_ = other_loc;
1167  weapon.is_attacker_ = attacking;
1168  weapon.other_attack_ = std::move(other_attack);
1169  weapon.is_for_listing_ = false;
1170 }
1171 
1172 /**
1173  * Sets the context under which specials will be checked for being active.
1174  * This version is appropriate if there is no specific combat being considered.
1175  * @param[in] weapon The weapon being considered.
1176  * @param[in] self A reference to the unit with this weapon.
1177  * @param[in] loc The location of the unit with this weapon.
1178  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
1179  */
1181  : parent(weapon.shared_from_this())
1182 {
1183  weapon.self_ = std::move(self);
1184  weapon.other_ = unit_ptr();
1185  weapon.self_loc_ = loc;
1187  weapon.is_attacker_ = attacking;
1188  weapon.other_attack_ = nullptr;
1189  weapon.is_for_listing_ = false;
1190 }
1191 
1192 /**
1193  * Sets the context under which specials will be checked for being active.
1194  * This version is appropriate for theoretical units of a particular type.
1195  * @param[in] weapon The weapon being considered.
1196  * @param[in] self_type A reference to the type of the unit with this weapon.
1197  * @param[in] loc The location of the unit with this weapon.
1198  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
1199  */
1200 attack_type::specials_context_t::specials_context_t(const attack_type& weapon, const unit_type& /*self_type*/, const map_location& loc, bool attacking)
1201  : parent(weapon.shared_from_this())
1202 {
1203  weapon.self_ = unit_ptr();
1204  weapon.other_ = unit_ptr();
1205  weapon.self_loc_ = loc;
1207  weapon.is_attacker_ = attacking;
1208  weapon.other_attack_ = nullptr;
1209  weapon.is_for_listing_ = false;
1210 }
1211 
1213  : parent(weapon.shared_from_this())
1214 {
1215  weapon.is_for_listing_ = true;
1216  weapon.is_attacker_ = attacking;
1217 }
1218 
1220 {
1221  if(was_moved) return;
1222  parent->self_ = unit_ptr();
1223  parent->other_ = unit_ptr();
1224  parent->self_loc_ = map_location::null_location();
1225  parent->other_loc_ = map_location::null_location();
1226  parent->is_attacker_ = false;
1227  parent->other_attack_ = nullptr;
1228  parent->is_for_listing_ = false;
1229 }
1230 
1232  : parent(std::move(other.parent))
1233 {
1234  other.was_moved = true;
1235 }
1236 
1237 /**
1238  * Calculates the number of attacks this weapon has, considering specials.
1239  * This returns two numbers because of the swarm special. The actual number of
1240  * attacks depends on the unit's health and should be:
1241  * min_attacks + (max_attacks - min_attacks) * (current hp) / (max hp)
1242  * c.f. swarm_blows()
1243  */
1244 void attack_type::modified_attacks(unsigned & min_attacks,
1245  unsigned & max_attacks) const
1246 {
1247  // Apply [attacks].
1248  int attacks_value = composite_value(get_specials_and_abilities("attacks"), num_attacks());
1249 
1250  if ( attacks_value < 0 ) {
1251  attacks_value = 0;
1252  ERR_NG << "negative number of strikes after applying weapon specials";
1253  }
1254 
1255  // Apply [swarm].
1256  unit_ability_list swarm_specials = get_specials_and_abilities("swarm");
1257  if ( !swarm_specials.empty() ) {
1258  min_attacks = std::max<int>(0, swarm_specials.highest("swarm_attacks_min").first);
1259  max_attacks = std::max<int>(0, swarm_specials.highest("swarm_attacks_max", attacks_value).first);
1260  } else {
1261  min_attacks = max_attacks = attacks_value;
1262  }
1263 }
1264 
1265 std::string attack_type::select_replacement_type(const unit_ability_list& damage_type_list) const
1266 {
1267  std::map<std::string, unsigned int> type_count;
1268  unsigned int max = 0;
1269  for(auto& i : damage_type_list) {
1270  const config& c = *i.ability_cfg;
1271  if(c.has_attribute("replacement_type")) {
1272  std::string type = c["replacement_type"].str();
1273  unsigned int count = ++type_count[type];
1274  if((count > max)) {
1275  max = count;
1276  }
1277  }
1278  }
1279 
1280  if (type_count.empty()) return type();
1281 
1282  std::vector<std::string> type_list;
1283  for(auto& i : type_count){
1284  if(i.second == max){
1285  type_list.push_back(i.first);
1286  }
1287  }
1288 
1289  if(type_list.empty()) return type();
1290 
1291  return type_list.front();
1292 }
1293 
1294 std::pair<std::string, int> attack_type::select_alternative_type(const unit_ability_list& damage_type_list, const unit_ability_list& resistance_list) const
1295 {
1296  std::map<std::string, int> type_res;
1297  int max_res = INT_MIN;
1298  if(other_){
1299  for(auto& i : damage_type_list) {
1300  const config& c = *i.ability_cfg;
1301  if(c.has_attribute("alternative_type")) {
1302  std::string type = c["alternative_type"].str();
1303  if(type_res.count(type) == 0){
1304  type_res[type] = (*other_).resistance_value(resistance_list, type);
1305  max_res = std::max(max_res, type_res[type]);
1306  }
1307  }
1308  }
1309  }
1310 
1311  if (type_res.empty()) return {"", INT_MIN};
1312 
1313  std::vector<std::string> type_list;
1314  for(auto& i : type_res){
1315  if(i.second == max_res){
1316  type_list.push_back(i.first);
1317  }
1318  }
1319  if(type_list.empty()) return {"", INT_MIN};
1320 
1321  return {type_list.front(), max_res};
1322 }
1323 
1324 /**
1325  * The type of attack used and the resistance value that does the most damage.
1326  */
1327 std::pair<std::string, int> attack_type::effective_damage_type() const
1328 {
1329  if(attack_empty()){
1330  return {"", 100};
1331  }
1332  unit_ability_list resistance_list;
1333  if(other_){
1334  resistance_list = (*other_).get_abilities_weapons("resistance", other_loc_, other_attack_, shared_from_this());
1335  utils::erase_if(resistance_list, [&](const unit_ability& i) {
1336  return (!((*i.ability_cfg)["active_on"].empty() || (!is_attacker_ && (*i.ability_cfg)["active_on"] == "offense") || (is_attacker_ && (*i.ability_cfg)["active_on"] == "defense")));
1337  });
1338  }
1339  unit_ability_list damage_type_list = get_specials_and_abilities("damage_type");
1340  int res = other_ ? (*other_).resistance_value(resistance_list, type()) : 100;
1341  if(damage_type_list.empty()){
1342  return {type(), res};
1343  }
1344  std::string replacement_type = select_replacement_type(damage_type_list);
1345  std::pair<std::string, int> alternative_type = select_alternative_type(damage_type_list, resistance_list);
1346 
1347  if(other_){
1348  res = replacement_type != type() ? (*other_).resistance_value(resistance_list, replacement_type) : res;
1349  replacement_type = alternative_type.second > res ? alternative_type.first : replacement_type;
1350  res = std::max(res, alternative_type.second);
1351  }
1352  return {replacement_type, res};
1353 }
1354 
1355 /**
1356  * Return a type()/replacement_type and a list of alternative_types that should be displayed in the selected unit's report.
1357  */
1358 std::pair<std::string, std::set<std::string>> attack_type::damage_types() const
1359 {
1360  unit_ability_list damage_type_list = get_specials_and_abilities("damage_type");
1361  std::set<std::string> alternative_damage_types;
1362  if(damage_type_list.empty()){
1363  return {type(), alternative_damage_types};
1364  }
1365  std::string replacement_type = select_replacement_type(damage_type_list);
1366  for(auto& i : damage_type_list) {
1367  const config& c = *i.ability_cfg;
1368  if(c.has_attribute("alternative_type")){
1369  alternative_damage_types.insert(c["alternative_type"].str());
1370  }
1371  }
1372 
1373  return {replacement_type, alternative_damage_types};
1374 }
1375 
1376 /**
1377  * Returns the damage per attack of this weapon, considering specials.
1378  */
1380 {
1381  double damage_value = unit_abilities::effect(get_specials_and_abilities("damage"), damage(), shared_from_this()).get_composite_double_value();
1382  return damage_value;
1383 }
1384 
1385 
1386 namespace { // Helpers for attack_type::special_active()
1387 
1388  /**
1389  * Returns whether or not the given special affects the opponent of the unit
1390  * with the special.
1391  * @param[in] special a weapon special WML structure
1392  * @param[in] is_attacker whether or not the unit with the special is the attacker
1393  */
1394  bool special_affects_opponent(const config& special, bool is_attacker)
1395  {
1396  //log_scope("special_affects_opponent");
1397  const std::string& apply_to = special["apply_to"];
1398  if ( apply_to.empty() )
1399  return false;
1400  if ( apply_to == "both" )
1401  return true;
1402  if ( apply_to == "opponent" )
1403  return true;
1404  if ( is_attacker && apply_to == "defender" )
1405  return true;
1406  if ( !is_attacker && apply_to == "attacker" )
1407  return true;
1408  return false;
1409  }
1410 
1411  /**
1412  * Returns whether or not the given special affects the unit with the special.
1413  * @param[in] special a weapon special WML structure
1414  * @param[in] is_attacker whether or not the unit with the special is the attacker
1415  */
1416  bool special_affects_self(const config& special, bool is_attacker)
1417  {
1418  //log_scope("special_affects_self");
1419  const std::string& apply_to = special["apply_to"];
1420  if ( apply_to.empty() )
1421  return true;
1422  if ( apply_to == "both" )
1423  return true;
1424  if ( apply_to == "self" )
1425  return true;
1426  if ( is_attacker && apply_to == "attacker" )
1427  return true;
1428  if ( !is_attacker && apply_to == "defender")
1429  return true;
1430  return false;
1431  }
1432 
1433  /**
1434  * Print "Recursion limit reached" log messages, including deduplication if the same problem has
1435  * already been logged.
1436  */
1437  void show_recursion_warning(const const_attack_ptr& attack, const config& filter) {
1438  // This function is only called when a special is checked for the second time
1439  // filter has already been parsed multiple times, so I'm not trying to optimize the performance
1440  // of this; it's merely to prevent the logs getting spammed. For example, each of
1441  // four_cycle_recursion_branching and event_test_filter_attack_student_weapon_condition only log
1442  // 3 unique messages, but without deduplication they'd log 1280 and 392 respectively.
1443  static std::vector<std::tuple<std::string, std::string>> already_shown;
1444 
1445  auto identifier = std::tuple<std::string, std::string>{attack->id(), filter.debug()};
1446  if(utils::contains(already_shown, identifier)) {
1447  return;
1448  }
1449 
1450  std::string_view filter_text_view = std::get<1>(identifier);
1451  utils::trim(filter_text_view);
1452  ERR_NG << "Looped recursion error for weapon '" << attack->id()
1453  << "' while checking weapon special '" << filter_text_view << "'";
1454 
1455  // Arbitrary limit, just ensuring that having a huge number of specials causing recursion
1456  // warnings can't lead to unbounded memory consumption here.
1457  if(already_shown.size() > 100) {
1458  already_shown.clear();
1459  }
1460  already_shown.push_back(std::move(identifier));
1461  }
1462 
1463  /**
1464  * Determines if a unit/weapon combination matches the specified child
1465  * (normally a [filter_*] child) of the provided filter.
1466  * @param[in] u A unit to filter.
1467  * @param[in] u2 Another unit to filter.
1468  * @param[in] loc The presumed location of @a unit.
1469  * @param[in] weapon The attack_type to filter.
1470  * @param[in] filter The filter containing the child filter to use.
1471  * @param[in] for_listing
1472  * @param[in] child_tag The tag of the child filter to use.
1473  * @param[in] check_if_recursion Parameter used for don't have infinite recusion for some filter attribute.
1474  */
1475  static bool special_unit_matches(unit_const_ptr & u,
1476  unit_const_ptr & u2,
1477  const map_location & loc,
1478  const const_attack_ptr& weapon,
1479  const config & filter,
1480  const bool for_listing,
1481  const std::string & child_tag, const std::string& check_if_recursion)
1482  {
1483  if (for_listing && !loc.valid())
1484  // The special's context was set to ignore this unit, so assume we pass.
1485  // (This is used by reports.cpp to show active specials when the
1486  // opponent is not known. From a player's perspective, the special
1487  // is active, in that it can be used, even though the player might
1488  // need to select an appropriate opponent.)
1489  return true;
1490 
1491  //Add wml filter if "backstab" attribute used.
1492  if (!filter["backstab"].blank() && child_tag == "filter_opponent") {
1493  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.");
1494  }
1495  config cfg = filter;
1496  if(filter["backstab"].to_bool() && child_tag == "filter_opponent"){
1497  const std::string& backstab_formula = "enemy_of(self, flanker) and not flanker.petrified where flanker = unit_at(direction_from(loc, other.facing))";
1498  config& filter_child = cfg.child_or_add("filter_opponent");
1499  if(!filter.has_child("filter_opponent")){
1500  filter_child["formula"] = backstab_formula;
1501  } else {
1502  config filter_opponent;
1503  filter_opponent["formula"] = backstab_formula;
1504  filter_child.add_child("and", filter_opponent);
1505  }
1506  }
1507  const config& filter_backstab = filter["backstab"].to_bool() ? cfg : filter;
1508 
1509  auto filter_child = filter_backstab.optional_child(child_tag);
1510  if ( !filter_child )
1511  // The special does not filter on this unit, so we pass.
1512  return true;
1513 
1514  // If the primary unit doesn't exist, there's nothing to match
1515  if (!u) {
1516  return false;
1517  }
1518 
1519  unit_filter ufilt{vconfig(*filter_child)};
1520 
1521  // If the other unit doesn't exist, try matching without it
1522 
1523 
1524  attack_type::recursion_guard filter_lock;
1525  if (weapon && (filter_child->optional_child("has_attack") || filter_child->optional_child("filter_weapon"))) {
1526  filter_lock = weapon->update_variables_recursion(filter);
1527  if(!filter_lock) {
1528  show_recursion_warning(weapon, filter);
1529  return false;
1530  }
1531  }
1532  // Check for a weapon match.
1533  if (auto filter_weapon = filter_child->optional_child("filter_weapon") ) {
1534  if ( !weapon || !weapon->matches_filter(*filter_weapon, check_if_recursion) )
1535  return false;
1536  }
1537 
1538  // Passed.
1539  // If the other unit doesn't exist, try matching without it
1540  if (!u2) {
1541  return ufilt.matches(*u, loc);
1542  }
1543  return ufilt.matches(*u, loc, *u2);
1544  }
1545 
1546 }//anonymous namespace
1547 
1548 
1549 //The following functions are intended to allow the use in combat of capacities
1550 //identical to special weapons and therefore to be able to use them on adjacent
1551 //units (abilities of type 'aura') or else on all types of weapons even if the
1552 //beneficiary unit does not have a corresponding weapon
1553 //(defense against ranged weapons abilities for a unit that only has melee attacks)
1554 
1555 unit_ability_list attack_type::get_weapon_ability(const std::string& ability) const
1556 {
1557  const map_location loc = self_ ? self_->get_location() : self_loc_;
1558  unit_ability_list abil_list(loc);
1559  if(self_) {
1560  abil_list.append_if((*self_).get_abilities(ability, self_loc_), [&](const unit_ability& i) {
1561  return special_active(*i.ability_cfg, AFFECT_SELF, ability, true);
1562  });
1563  }
1564 
1565  if(other_) {
1566  abil_list.append_if((*other_).get_abilities(ability, other_loc_), [&](const unit_ability& i) {
1567  return special_active_impl(other_attack_, shared_from_this(), *i.ability_cfg, AFFECT_OTHER, ability, true);
1568  });
1569  }
1570 
1571  return abil_list;
1572 }
1573 
1575 {
1576  // get all weapon specials of the provided type
1577  unit_ability_list abil_list = get_specials(special);
1578  // append all such weapon specials as abilities as well
1579  abil_list.append(get_weapon_ability(special));
1580  // get a list of specials/"specials as abilities" that may potentially overwrite others
1581  unit_ability_list overwriters = overwrite_special_overwriter(abil_list, special);
1582  if(!abil_list.empty() && !overwriters.empty()){
1583  // remove all abilities that would be overwritten
1584  utils::erase_if(abil_list, [&](const unit_ability& j) {
1585  return (overwrite_special_checking(overwriters, *j.ability_cfg, special));
1586  });
1587  }
1588  return abil_list;
1589 }
1590 
1591 int attack_type::composite_value(const unit_ability_list& abil_list, int base_value) const
1592 {
1593  return unit_abilities::effect(abil_list, base_value, shared_from_this()).get_composite_value();
1594 }
1595 
1596 static bool overwrite_special_affects(const config& special)
1597 {
1598  const std::string& apply_to = special["overwrite_specials"];
1599  return (apply_to == "one_side" || apply_to == "both_sides");
1600 }
1601 
1603 {
1604  //remove element without overwrite_specials key, if list empty after check return empty list.
1605  utils::erase_if(overwriters, [&](const unit_ability& i) {
1606  return (!overwrite_special_affects(*i.ability_cfg));
1607  });
1608 
1609  // if empty, nothing is doing any overwriting
1610  if(overwriters.empty()){
1611  return overwriters;
1612  }
1613 
1614  // if there are specials/"specials as abilities" that could potentially overwrite each other
1615  if(overwriters.size() >= 2){
1616  // sort them by overwrite priority from highest to lowest (default priority is 0)
1617  utils::sort_if(overwriters,[](const unit_ability& i, const unit_ability& j){
1618  auto oi = (*i.ability_cfg).optional_child("overwrite");
1619  double l = 0;
1620  if(oi && !oi["priority"].empty()){
1621  l = oi["priority"].to_double(0);
1622  }
1623  auto oj = (*j.ability_cfg).optional_child("overwrite");
1624  double r = 0;
1625  if(oj && !oj["priority"].empty()){
1626  r = oj["priority"].to_double(0);
1627  }
1628  return l > r;
1629  });
1630  // remove any that need to be overwritten
1631  utils::erase_if(overwriters, [&](const unit_ability& i) {
1632  return (overwrite_special_checking(overwriters, *i.ability_cfg, tag_name));
1633  });
1634  }
1635  return overwriters;
1636 }
1637 
1638 bool attack_type::overwrite_special_checking(unit_ability_list& overwriters, const config& cfg, const std::string& tag_name) const
1639 {
1640  if(overwriters.empty()){
1641  return false;
1642  }
1643 
1644  for(const auto& j : overwriters) {
1645  // whether the overwriter affects a single side
1646  bool affect_side = ((*j.ability_cfg)["overwrite_specials"] == "one_side");
1647  // the overwriter's priority, default of 0
1648  auto overwrite_specials = (*j.ability_cfg).optional_child("overwrite");
1649  double priority = overwrite_specials ? overwrite_specials["priority"].to_double(0) : 0.00;
1650  // the cfg being checked for whether it will be overwritten
1651  auto has_overwrite_specials = cfg.optional_child("overwrite");
1652  // if the overwriter's priority is greater than 0, then true if the cfg being checked has a higher priority
1653  // else true
1654  bool prior = (priority > 0) ? (has_overwrite_specials && has_overwrite_specials["priority"].to_double(0) >= priority) : true;
1655  // 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
1656  // aka whether the cfg being checked can potentially be overwritten by the current overwriter
1657  bool is_overwritable = (overwrite_special_affects(cfg) && !prior) || !overwrite_special_affects(cfg);
1658  bool one_side_overwritable = true;
1659 
1660  // if the current overwriter affects one side and the cfg being checked can be overwritten by this overwriter
1661  // then check that the current overwriter and the cfg being checked both affect either this unit or its opponent
1662  if(affect_side && is_overwritable){
1663  if(special_affects_self(*j.ability_cfg, is_attacker_)){
1664  one_side_overwritable = special_affects_self(cfg, is_attacker_);
1665  }
1666  else if(special_affects_opponent(*j.ability_cfg, !is_attacker_)){
1667  one_side_overwritable = special_affects_opponent(cfg, !is_attacker_);
1668  }
1669  }
1670 
1671  // check whether the current overwriter is disabled due to a filter
1672  bool special_matches = true;
1673  if(overwrite_specials){
1674  auto overwrite_filter = (*overwrite_specials).optional_child("filter_specials");
1675  if(!overwrite_filter){
1676  overwrite_filter = (*overwrite_specials).optional_child("experimental_filter_specials");
1677  if(overwrite_filter){
1678  deprecated_message("experimental_filter_specials", DEP_LEVEL::INDEFINITE, "", "Use filter_specials instead.");
1679  }
1680  }
1681  if(overwrite_filter && is_overwritable && one_side_overwritable){
1682  special_matches = special_matches_filter(cfg, tag_name, *overwrite_filter);
1683  }
1684  }
1685 
1686  // if the cfg being checked should be overwritten
1687  // and either this unit or its opponent are affected
1688  // and the current overwriter is not disabled due to a filter
1689  if(is_overwritable && one_side_overwritable && special_matches){
1690  return true;
1691  }
1692  }
1693  return false;
1694 }
1695 
1696  /**
1697  * Gets the children of parent (which should be the abilities for an
1698  * attack_type) and places the ones whose tag or id= matches @a id into
1699  * @a tag_result and @a id_result.
1700  * @param tag_result receive the children whose tag matches @a id
1701  * @param id_result receive the children whose id matches @a id
1702  * @param parent the tags whose contain children (abilities here)
1703  * @param id tag or id of child tested
1704  * @param special_id if true, children check by id
1705  * @param special_tags if true, children check by tags
1706  */
1707 static void get_ability_children(std::vector<special_match>& tag_result,
1708  std::vector<special_match>& id_result,
1709  const config& parent, const std::string& id,
1710  bool special_id=true, bool special_tags=true) {
1711  if(special_id && special_tags){
1712  get_special_children(tag_result, id_result, parent, id);
1713  } else if(special_id && !special_tags){
1714  get_special_children_id(id_result, parent, id);
1715  } else if(!special_id && special_tags){
1716  get_special_children_tags(tag_result, parent, id);
1717  }
1718 }
1719 
1720 bool unit::get_self_ability_bool(const config& cfg, const std::string& ability, const map_location& loc) const
1721 {
1722  auto filter_lock = update_variables_recursion(cfg);
1723  if(!filter_lock) {
1724  show_recursion_warning(*this, cfg);
1725  return false;
1726  }
1727  return (ability_active_impl(ability, cfg, loc) && ability_affects_self(ability, cfg, loc));
1728 }
1729 
1730 bool unit::get_adj_ability_bool(const config& cfg, const std::string& ability, int dir, const map_location& loc, const unit& from) const
1731 {
1732  auto filter_lock = from.update_variables_recursion(cfg);
1733  if(!filter_lock) {
1734  show_recursion_warning(from, cfg);
1735  return false;
1736  }
1737  const auto adjacent = get_adjacent_tiles(loc);
1738  return (affects_side(cfg, side(), from.side()) && from.ability_active_impl(ability, cfg, adjacent[dir]) && ability_affects_adjacent(ability, cfg, dir, loc, from));
1739 }
1740 
1741 bool unit::get_self_ability_bool_weapon(const config& special, const std::string& tag_name, const map_location& loc, const const_attack_ptr& weapon, const const_attack_ptr& opp_weapon) const
1742 {
1743  return (get_self_ability_bool(special, tag_name, loc) && ability_affects_weapon(special, weapon, false) && ability_affects_weapon(special, opp_weapon, true));
1744 }
1745 
1746 bool unit::get_adj_ability_bool_weapon(const config& special, const std::string& tag_name, int dir, const map_location& loc, const unit& from, const const_attack_ptr& weapon, const const_attack_ptr& opp_weapon) const
1747 {
1748  return (get_adj_ability_bool(special, tag_name, dir, loc, from) && ability_affects_weapon(special, weapon, false) && ability_affects_weapon(special, opp_weapon, true));
1749 }
1750 
1751 bool attack_type::check_self_abilities(const config& cfg, const std::string& special) const
1752 {
1753  return check_self_abilities_impl(shared_from_this(), other_attack_, cfg, self_, self_loc_, AFFECT_SELF, special, true);
1754 }
1755 
1756 bool attack_type::check_self_abilities_impl(const const_attack_ptr& self_attack, const const_attack_ptr& other_attack, const config& special, const unit_const_ptr& u, const map_location& loc, AFFECTS whom, const std::string& tag_name, bool leader_bool)
1757 {
1758  if(tag_name == "leadership" && leader_bool){
1759  if((*u).get_self_ability_bool_weapon(special, tag_name, loc, self_attack, other_attack)) {
1760  return true;
1761  }
1762  }
1763  if((*u).checking_tags().count(tag_name) != 0){
1764  if((*u).get_self_ability_bool(special, tag_name, loc) && special_active_impl(self_attack, other_attack, special, whom, tag_name, true)) {
1765  return true;
1766  }
1767  }
1768  return false;
1769 }
1770 
1771 bool attack_type::check_adj_abilities(const config& cfg, const std::string& special, int dir, const unit& from) const
1772 {
1773  return check_adj_abilities_impl(shared_from_this(), other_attack_, cfg, self_, from, dir, self_loc_, AFFECT_SELF, special, true);
1774 }
1775 
1776 bool attack_type::check_adj_abilities_impl(const const_attack_ptr& self_attack, const const_attack_ptr& other_attack, const config& special, const unit_const_ptr& u, const unit& from, int dir, const map_location& loc, AFFECTS whom, const std::string& tag_name, bool leader_bool)
1777 {
1778  if(tag_name == "leadership" && leader_bool){
1779  if((*u).get_adj_ability_bool_weapon(special, tag_name, dir, loc, from, self_attack, other_attack)) {
1780  return true;
1781  }
1782  }
1783  if((*u).checking_tags().count(tag_name) != 0){
1784  if((*u).get_adj_ability_bool(special, tag_name, dir, loc, from) && special_active_impl(self_attack, other_attack, special, whom, tag_name, true)) {
1785  return true;
1786  }
1787  }
1788  return false;
1789 }
1790 /**
1791  * Returns whether or not @a *this has a special ability with a tag or id equal to
1792  * @a special. the Check is for a special ability
1793  * active in the current context (see set_specials_context), including
1794  * specials obtained from the opponent's attack.
1795  */
1796 bool attack_type::has_weapon_ability(const std::string& special, bool special_id, bool special_tags) const
1797 {
1798  const unit_map& units = get_unit_map();
1799  if(self_){
1800  std::vector<special_match> special_tag_matches_self;
1801  std::vector<special_match> special_id_matches_self;
1802  get_ability_children(special_tag_matches_self, special_id_matches_self, (*self_).abilities(), special, special_id , special_tags);
1803  if(special_tags){
1804  for(const special_match& entry : special_tag_matches_self) {
1805  if(check_self_abilities(*entry.cfg, entry.tag_name)){
1806  return true;
1807  }
1808  }
1809  }
1810  if(special_id){
1811  for(const special_match& entry : special_id_matches_self) {
1812  if(check_self_abilities(*entry.cfg, entry.tag_name)){
1813  return true;
1814  }
1815  }
1816  }
1817 
1818  const auto adjacent = get_adjacent_tiles(self_loc_);
1819  for(unsigned i = 0; i < adjacent.size(); ++i) {
1820  const unit_map::const_iterator it = units.find(adjacent[i]);
1821  if (it == units.end() || it->incapacitated())
1822  continue;
1823  if ( &*it == self_.get() )
1824  continue;
1825 
1826  std::vector<special_match> special_tag_matches_adj;
1827  std::vector<special_match> special_id_matches_adj;
1828  get_ability_children(special_tag_matches_adj, special_id_matches_adj, it->abilities(), special, special_id , special_tags);
1829  if(special_tags){
1830  for(const special_match& entry : special_tag_matches_adj) {
1831  if(check_adj_abilities(*entry.cfg, entry.tag_name, i , *it)){
1832  return true;
1833  }
1834  }
1835  }
1836  if(special_id){
1837  for(const special_match& entry : special_id_matches_adj) {
1838  if(check_adj_abilities(*entry.cfg, entry.tag_name, i , *it)){
1839  return true;
1840  }
1841  }
1842  }
1843  }
1844  }
1845 
1846  if(other_){
1847  std::vector<special_match> special_tag_matches_other;
1848  std::vector<special_match> special_id_matches_other;
1849  get_ability_children(special_tag_matches_other, special_id_matches_other, (*other_).abilities(), special, special_id , special_tags);
1850  if(special_tags){
1851  for(const special_match& entry : special_tag_matches_other) {
1852  if(check_self_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, other_loc_, AFFECT_OTHER, entry.tag_name)){
1853  return true;
1854  }
1855  }
1856  }
1857 
1858  if(special_id){
1859  for(const special_match& entry : special_id_matches_other) {
1860  if(check_self_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, other_loc_, AFFECT_OTHER, entry.tag_name)){
1861  return true;
1862  }
1863  }
1864  }
1865 
1866  const auto adjacent = get_adjacent_tiles(other_loc_);
1867  for(unsigned i = 0; i < adjacent.size(); ++i) {
1868  const unit_map::const_iterator it = units.find(adjacent[i]);
1869  if (it == units.end() || it->incapacitated())
1870  continue;
1871  if ( &*it == other_.get() )
1872  continue;
1873 
1874  std::vector<special_match> special_tag_matches_oadj;
1875  std::vector<special_match> special_id_matches_oadj;
1876  get_ability_children(special_tag_matches_oadj, special_id_matches_oadj, it->abilities(), special, special_id , special_tags);
1877  if(special_tags){
1878  for(const special_match& entry : special_tag_matches_oadj) {
1879  if(check_adj_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, *it, i, other_loc_, AFFECT_OTHER, entry.tag_name)){
1880  return true;
1881  }
1882  }
1883  }
1884 
1885  if(special_id){
1886  for(const special_match& entry : special_id_matches_oadj) {
1887  if(check_adj_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, *it, i, other_loc_, AFFECT_OTHER, entry.tag_name)){
1888  return true;
1889  }
1890  }
1891  }
1892  }
1893  }
1894  return false;
1895 }
1896 
1897 bool attack_type::has_special_or_ability(const std::string& special, bool special_id, bool special_tags) const
1898 {
1899  //Now that filter_(second)attack in event supports special_id/type_active, including abilities used as weapons,
1900  //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.
1901  //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.
1902  if(range().empty()){
1903  return false;
1904  }
1905  return (has_special(special, false, special_id, special_tags) || has_weapon_ability(special, special_id, special_tags));
1906 }
1907 //end of emulate weapon special functions.
1908 
1909 namespace
1910 {
1911  bool exclude_ability_attributes(const std::string& tag_name, const config & filter)
1912  {
1913  ///check what filter attributes used can be used in type of ability checked.
1914  bool abilities_check = abilities_list::ability_value_tags().count(tag_name) != 0 || abilities_list::ability_no_value_tags().count(tag_name) != 0;
1915  if(filter.has_attribute("active_on") && tag_name != "resistance" && abilities_check)
1916  return false;
1917  if(filter.has_attribute("apply_to") && tag_name != "resistance" && abilities_check)
1918  return false;
1919 
1920  if(filter.has_attribute("overwrite_specials") && abilities_list::weapon_number_tags().count(tag_name) == 0)
1921  return false;
1922 
1923  bool no_value_weapon_abilities_check = abilities_list::no_weapon_number_tags().count(tag_name) != 0 || abilities_list::ability_no_value_tags().count(tag_name) != 0;
1924  if(filter.has_attribute("value") && no_value_weapon_abilities_check)
1925  return false;
1926  if(filter.has_attribute("add") && no_value_weapon_abilities_check)
1927  return false;
1928  if(filter.has_attribute("sub") && no_value_weapon_abilities_check)
1929  return false;
1930  if(filter.has_attribute("multiply") && no_value_weapon_abilities_check)
1931  return false;
1932  if(filter.has_attribute("divide") && no_value_weapon_abilities_check)
1933  return false;
1934 
1935  bool all_engine = abilities_list::no_weapon_number_tags().count(tag_name) != 0 || abilities_list::weapon_number_tags().count(tag_name) != 0 || abilities_list::ability_value_tags().count(tag_name) != 0 || abilities_list::ability_no_value_tags().count(tag_name) != 0;
1936  if(filter.has_attribute("replacement_type") && tag_name != "damage_type" && all_engine)
1937  return false;
1938  if(filter.has_attribute("alternative_type") && tag_name != "damage_type" && all_engine)
1939  return false;
1940  if(filter.has_attribute("type") && tag_name != "plague" && all_engine)
1941  return false;
1942 
1943  return true;
1944  }
1945 
1946  bool matches_ability_filter(const config & cfg, const std::string& tag_name, const config & filter)
1947  {
1948  using namespace utils::config_filters;
1949 
1950  //check if attributes have right to be in type of ability checked
1951  if(!exclude_ability_attributes(tag_name, filter))
1952  return false;
1953 
1954  // tag_name and id are equivalent of ability ability_type and ability_id/type_active filters
1955  //can be extent to special_id/type_active. If tag_name or id matche if present in list.
1956  const std::vector<std::string> filter_type = utils::split(filter["tag_name"]);
1957  if ( !filter_type.empty() && std::find(filter_type.begin(), filter_type.end(), tag_name) == filter_type.end() )
1958  return false;
1959 
1960  if(!string_matches_if_present(filter, cfg, "id", ""))
1961  return false;
1962 
1963  //when affect_adjacent=yes detect presence of [affect_adjacent] in abilities, if no
1964  //then matches when tag not present.
1965  if(!filter["affect_adjacent"].empty()){
1966  bool adjacent = cfg.has_child("affect_adjacent");
1967  if(filter["affect_adjacent"].to_bool() != adjacent){
1968  return false;
1969  }
1970  }
1971 
1972  //these attributs below filter attribute used in all engine abilities.
1973  //matches if filter attribute have same boolean value what attribute
1974  if(!bool_matches_if_present(filter, cfg, "affect_self", true))
1975  return false;
1976 
1977  //here if value of affect_allies but also his presence who is checked because
1978  //when affect_allies not specified, ability affect unit of same side what owner only.
1979  if(!bool_or_empty(filter, cfg, "affect_allies"))
1980  return false;
1981 
1982  if(!bool_matches_if_present(filter, cfg, "affect_enemies", false))
1983  return false;
1984 
1985 
1986  //cumulative, overwrite_specials and active_on check attributes used in all abilities
1987  //who return a numerical value.
1988  if(!bool_matches_if_present(filter, cfg, "cumulative", false))
1989  return false;
1990 
1991  if(!string_matches_if_present(filter, cfg, "overwrite_specials", "none"))
1992  return false;
1993 
1994  if(!string_matches_if_present(filter, cfg, "active_on", "both"))
1995  return false;
1996 
1997  //value, add, sub multiply and divide check values of attribute used in engines abilities(default value of 'value' can be checked when not specified)
1998  //who return numericals value but can also check in non-engine abilities(in last case if 'value' not specified none value can matches)
1999  if(!filter["value"].empty()){
2000  if(tag_name == "drains"){
2001  if(!int_matches_if_present(filter, cfg, "value", 50)){
2002  return false;
2003  }
2004  } else if(tag_name == "berserk"){
2005  if(!int_matches_if_present(filter, cfg, "value", 1)){
2006  return false;
2007  }
2008  } else if(tag_name == "heal_on_hit" || tag_name == "heals" || tag_name == "regenerate" || tag_name == "leadership"){
2009  if(!int_matches_if_present(filter, cfg, "value" , 0)){
2010  return false;
2011  }
2012  } else {
2013  if(!int_matches_if_present(filter, cfg, "value")){
2014  return false;
2015  }
2016  }
2017  }
2018 
2019  if(!int_matches_if_present_or_negative(filter, cfg, "add", "sub"))
2020  return false;
2021 
2022  if(!int_matches_if_present_or_negative(filter, cfg, "sub", "add"))
2023  return false;
2024 
2025  if(!double_matches_if_present(filter, cfg, "multiply"))
2026  return false;
2027 
2028  if(!double_matches_if_present(filter, cfg, "divide"))
2029  return false;
2030 
2031 
2032  //apply_to is a special case, in resistance ability, it check a list of damage type used by [resistance]
2033  //but in weapon specials, check identity of unit affected by special(self, opponent tc...)
2034  if(tag_name == "resistance"){
2035  if(!set_includes_if_present(filter, cfg, "apply_to")){
2036  return false;
2037  }
2038  } else {
2039  if(!string_matches_if_present(filter, cfg, "apply_to", "self")){
2040  return false;
2041  }
2042  }
2043 
2044  //the three attribute below are used for check in specifics abilitie:
2045  //replacement_type and alternative_type are present in [damage_type] only for engine abilities
2046  //and type for [plague], but if someone want use this in non-engine abilities, these attribute can be checked outside type mentioned.
2047  //
2048 
2049  //for damage_type only(in engine cases)
2050  if(!string_matches_if_present(filter, cfg, "replacement_type", ""))
2051  return false;
2052 
2053  if(!string_matches_if_present(filter, cfg, "alternative_type", ""))
2054  return false;
2055 
2056  //for plague only(in engine cases)
2057  if(!string_matches_if_present(filter, cfg, "type", ""))
2058  return false;
2059 
2060  //the wml_filter is used in cases where the attribute we are looking for is not
2061  //previously listed or to check the contents of the sub_tags ([filter_adjacent],[filter_self],[filter_opponent] etc.
2062  //If the checked set does not exactly match the content of the capability, the function returns a false response.
2063  auto fwml = filter.optional_child("filter_wml");
2064  if (fwml){
2065  if(!cfg.matches(*fwml)){
2066  return false;
2067  }
2068  }
2069 
2070  // Passed all tests.
2071  return true;
2072  }
2073 
2074  static bool common_matches_filter(const config & cfg, const std::string& tag_name, const config & filter)
2075  {
2076  // Handle the basic filter.
2077  bool matches = matches_ability_filter(cfg, tag_name, filter);
2078 
2079  // Handle [and], [or], and [not] with in-order precedence
2080  for(const auto [key, condition_cfg] : filter.all_children_view() )
2081  {
2082  // Handle [and]
2083  if ( key == "and" )
2084  matches = matches && common_matches_filter(cfg, tag_name, condition_cfg);
2085 
2086  // Handle [or]
2087  else if ( key == "or" )
2088  matches = matches || common_matches_filter(cfg, tag_name, condition_cfg);
2089 
2090  // Handle [not]
2091  else if ( key == "not" )
2092  matches = matches && !common_matches_filter(cfg, tag_name, condition_cfg);
2093  }
2094 
2095  return matches;
2096  }
2097 }
2098 
2099 bool unit::ability_matches_filter(const config & cfg, const std::string& tag_name, const config & filter) const
2100 {
2101  return common_matches_filter(cfg, tag_name, filter);
2102 }
2103 
2104 bool attack_type::special_matches_filter(const config & cfg, const std::string& tag_name, const config & filter) const
2105 {
2106  return common_matches_filter(cfg, tag_name, filter);
2107 }
2108 
2110 {
2111  using namespace utils::config_filters;
2112  bool check_if_active = filter["active"].to_bool();
2113  for(const auto [key, cfg] : specials().all_children_view()) {
2114  if(special_matches_filter(cfg, key, filter)){
2115  if(!check_if_active){
2116  return true;
2117  }
2118  if ( special_active(cfg, AFFECT_SELF, key) ) {
2119  return true;
2120  }
2121  }
2122  }
2123 
2124  if(!check_if_active || !other_attack_){
2125  return false;
2126  }
2127 
2128  for(const auto [key, cfg] : other_attack_->specials().all_children_view()) {
2129  if(other_attack_->special_matches_filter(cfg, key, filter)){
2130  if ( other_attack_->special_active(cfg, AFFECT_OTHER, key) ) {
2131  return true;
2132  }
2133  }
2134  }
2135 
2136  return false;
2137 }
2138 
2140 {
2141  if(!filter["active"].to_bool()){
2142  return false;
2143  }
2144  const unit_map& units = get_unit_map();
2145  if(self_){
2146  for(const auto [key, cfg] : (*self_).abilities().all_children_view()) {
2147  if(self_->ability_matches_filter(cfg, key, filter)){
2148  if(check_self_abilities(cfg, key)){
2149  return true;
2150  }
2151  }
2152  }
2153 
2154  const auto adjacent = get_adjacent_tiles(self_loc_);
2155  for(unsigned i = 0; i < adjacent.size(); ++i) {
2156  const unit_map::const_iterator it = units.find(adjacent[i]);
2157  if (it == units.end() || it->incapacitated())
2158  continue;
2159  if ( &*it == self_.get() )
2160  continue;
2161 
2162  for(const auto [key, cfg] : it->abilities().all_children_view()) {
2163  if(it->ability_matches_filter(cfg, key, filter) && check_adj_abilities(cfg, key, i , *it)){
2164  return true;
2165  }
2166  }
2167  }
2168  }
2169 
2170  if(other_){
2171  for(const auto [key, cfg] : (*other_).abilities().all_children_view()) {
2172  if(other_->ability_matches_filter(cfg, key, filter) && check_self_abilities_impl(other_attack_, shared_from_this(), cfg, other_, other_loc_, AFFECT_OTHER, key)){
2173  return true;
2174  }
2175  }
2176 
2177  const auto adjacent = get_adjacent_tiles(other_loc_);
2178  for(unsigned i = 0; i < adjacent.size(); ++i) {
2179  const unit_map::const_iterator it = units.find(adjacent[i]);
2180  if (it == units.end() || it->incapacitated())
2181  continue;
2182  if ( &*it == other_.get() )
2183  continue;
2184 
2185  for(const auto [key, cfg] : it->abilities().all_children_view()) {
2186  if(it->ability_matches_filter(cfg, key, filter) && check_adj_abilities_impl(other_attack_, shared_from_this(), cfg, other_, *it, i, other_loc_, AFFECT_OTHER, key)){
2187  return true;
2188  }
2189  }
2190  }
2191  }
2192  return false;
2193 }
2194 
2196 {
2197  if(range().empty()){
2198  return false;
2199  }
2201 }
2202 
2203 bool attack_type::special_active(const config& special, AFFECTS whom, const std::string& tag_name,
2204  bool in_abilities_tag) const
2205 {
2206  return special_active_impl(shared_from_this(), other_attack_, special, whom, tag_name, in_abilities_tag);
2207 }
2208 
2209 /**
2210  * Returns whether or not the given special is active for the specified unit,
2211  * based on the current context (see set_specials_context).
2212  * @param self_attack this unit's attack
2213  * @param other_attack the other unit's attack
2214  * @param special a weapon special WML structure
2215  * @param whom specifies which combatant we care about
2216  * @param tag_name tag name of the special config
2217  * @param in_abilities_tag if special coded in [specials] or [abilities] tags
2218  */
2220  const const_attack_ptr& self_attack,
2221  const const_attack_ptr& other_attack,
2222  const config& special,
2223  AFFECTS whom,
2224  const std::string& tag_name,
2225  bool in_abilities_tag)
2226 {
2227  assert(self_attack || other_attack);
2228  bool is_attacker = self_attack ? self_attack->is_attacker_ : !other_attack->is_attacker_;
2229  bool is_for_listing = self_attack ? self_attack->is_for_listing_ : other_attack->is_for_listing_;
2230  //log_scope("special_active");
2231 
2232 
2233  // Does this affect the specified unit?
2234  if ( whom == AFFECT_SELF ) {
2235  if ( !special_affects_self(special, is_attacker) )
2236  return false;
2237  }
2238  if ( whom == AFFECT_OTHER ) {
2239  if ( !special_affects_opponent(special, is_attacker) )
2240  return false;
2241  }
2242 
2243  // Is this active on attack/defense?
2244  const std::string & active_on = special["active_on"];
2245  if ( !active_on.empty() ) {
2246  if ( is_attacker && active_on != "offense" )
2247  return false;
2248  if ( !is_attacker && active_on != "defense" )
2249  return false;
2250  }
2251 
2252  // Get the units involved.
2253  const unit_map& units = get_unit_map();
2254 
2255  unit_const_ptr self = self_attack ? self_attack->self_ : other_attack->other_;
2256  unit_const_ptr other = self_attack ? self_attack->other_ : other_attack->self_;
2257  map_location self_loc = self_attack ? self_attack->self_loc_ : other_attack->other_loc_;
2258  map_location other_loc = self_attack ? self_attack->other_loc_ : other_attack->self_loc_;
2259  //TODO: why is this needed?
2260  if(self == nullptr) {
2261  unit_map::const_iterator it = units.find(self_loc);
2262  if(it.valid()) {
2263  self = it.get_shared_ptr();
2264  }
2265  }
2266  if(other == nullptr) {
2267  unit_map::const_iterator it = units.find(other_loc);
2268  if(it.valid()) {
2269  other = it.get_shared_ptr();
2270  }
2271  }
2272 
2273  // Make sure they're facing each other.
2274  temporary_facing self_facing(self, self_loc.get_relative_dir(other_loc));
2275  temporary_facing other_facing(other, other_loc.get_relative_dir(self_loc));
2276 
2277  // Filter poison, plague, drain, slow, petrifies
2278  // True if "whom" corresponds to "self", false if "whom" is "other"
2279  bool whom_is_self = ((whom == AFFECT_SELF) || ((whom == AFFECT_EITHER) && special_affects_self(special, is_attacker)));
2280  unit_const_ptr them = whom_is_self ? other : self;
2281  map_location their_loc = whom_is_self ? other_loc : self_loc;
2282 
2283  if (tag_name == "drains" && them && them->get_state("undrainable")) {
2284  return false;
2285  }
2286  if (tag_name == "plague" && them &&
2287  (them->get_state("unplagueable") ||
2288  resources::gameboard->map().is_village(their_loc))) {
2289  return false;
2290  }
2291  if (tag_name == "poison" && them &&
2292  (them->get_state("unpoisonable") || them->get_state(unit::STATE_POISONED))) {
2293  return false;
2294  }
2295  if (tag_name == "slow" && them &&
2296  (them->get_state("unslowable") || them->get_state(unit::STATE_SLOWED))) {
2297  return false;
2298  }
2299  if (tag_name == "petrifies" && them &&
2300  them->get_state("unpetrifiable")) {
2301  return false;
2302  }
2303 
2304 
2305  // Translate our context into terms of "attacker" and "defender".
2306  unit_const_ptr & att = is_attacker ? self : other;
2307  unit_const_ptr & def = is_attacker ? other : self;
2308  const map_location & att_loc = is_attacker ? self_loc : other_loc;
2309  const map_location & def_loc = is_attacker ? other_loc : self_loc;
2310  const const_attack_ptr& att_weapon = is_attacker ? self_attack : other_attack;
2311  const const_attack_ptr& def_weapon = is_attacker ? other_attack : self_attack;
2312 
2313  // Filter firststrike here, if both units have first strike then the effects cancel out. Only check
2314  // the opponent if "whom" is the defender, otherwise this leads to infinite recursion.
2315  if (tag_name == "firststrike") {
2316  bool whom_is_defender = whom_is_self ? !is_attacker : is_attacker;
2317  if (whom_is_defender && att_weapon && att_weapon->has_special_or_ability("firststrike"))
2318  return false;
2319  }
2320 
2321  // Filter the units involved.
2322  //If filter concerns the unit on which special is applied,
2323  //then the type of special must be entered to avoid calling
2324  //the function of this special in matches_filter()
2325  //In apply_to=both case, tag_name must be checked in all filter because special applied to both self and opponent.
2326  bool applied_both = special["apply_to"] == "both";
2327  const std::string& filter_self = in_abilities_tag ? "filter_student" : "filter_self";
2328  std::string self_check_if_recursion = (applied_both || whom_is_self) ? tag_name : "";
2329  if (!special_unit_matches(self, other, self_loc, self_attack, special, is_for_listing, filter_self, self_check_if_recursion))
2330  return false;
2331  std::string opp_check_if_recursion = (applied_both || !whom_is_self) ? tag_name : "";
2332  if (!special_unit_matches(other, self, other_loc, other_attack, special, is_for_listing, "filter_opponent", opp_check_if_recursion))
2333  return false;
2334  //in case of apply_to=attacker|defender, if both [filter_attacker] and [filter_defender] are used,
2335  //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.
2336  bool applied_to_attacker = applied_both || (whom_is_self && is_attacker) || (!whom_is_self && !is_attacker);
2337  std::string att_check_if_recursion = applied_to_attacker ? tag_name : "";
2338  if (!special_unit_matches(att, def, att_loc, att_weapon, special, is_for_listing, "filter_attacker", att_check_if_recursion))
2339  return false;
2340  bool applied_to_defender = applied_both || (whom_is_self && !is_attacker) || (!whom_is_self && is_attacker);
2341  std::string def_check_if_recursion= applied_to_defender ? tag_name : "";
2342  if (!special_unit_matches(def, att, def_loc, def_weapon, special, is_for_listing, "filter_defender", def_check_if_recursion))
2343  return false;
2344 
2345  //if filter_self != "filter_self" then it's in [abilities] tags and
2346  //[filter_student_adjacent] and [filter_student_adjacent] then designate 'the student' (which may be different from the owner of the ability),
2347  //while in the tags[specials] the usual names are kept.
2348  const std::string& filter_adjacent = in_abilities_tag ? "filter_adjacent_student" : "filter_adjacent";
2349  const std::string& filter_adjacent_location = in_abilities_tag ? "filter_adjacent_student_location" : "filter_adjacent_location";
2350 
2351  const auto adjacent = get_adjacent_tiles(self_loc);
2352 
2353  // Filter the adjacent units.
2354  for (const config &i : special.child_range(filter_adjacent))
2355  {
2356  std::size_t count = 0;
2357  std::vector<map_location::direction> dirs = i["adjacent"].empty() ? map_location::all_directions() : map_location::parse_directions(i["adjacent"]);
2359  for (const map_location::direction index : dirs)
2360  {
2361  unit_map::const_iterator unit = units.find(adjacent[static_cast<int>(index)]);
2362  if (unit == units.end() || !filter.matches(*unit, adjacent[static_cast<int>(index)], *self))
2363  continue;
2364  if (i.has_attribute("is_enemy")) {
2366  if (i["is_enemy"].to_bool() != dc.get_team(unit->side()).is_enemy(self->side())) {
2367  continue;
2368  }
2369  }
2370  count++;
2371  }
2372  static std::vector<std::pair<int,int>> default_counts = utils::parse_ranges_unsigned("1-6");
2373  config::attribute_value i_count =i["count"];
2374  if(!in_ranges<int>(count, !i_count.blank() ? utils::parse_ranges_unsigned(i_count) : default_counts)){
2375  return false;
2376  }
2377  }
2378 
2379  // Filter the adjacent locations.
2380  for (const config &i : special.child_range(filter_adjacent_location))
2381  {
2382  std::size_t count = 0;
2383  std::vector<map_location::direction> dirs = i["adjacent"].empty() ? map_location::all_directions() : map_location::parse_directions(i["adjacent"]);
2384  terrain_filter adj_filter(vconfig(i), resources::filter_con, false);
2385  for (const map_location::direction index : dirs)
2386  {
2387  if(!adj_filter.match(adjacent[static_cast<int>(index)])) {
2388  continue;
2389  }
2390  count++;
2391  }
2392  static std::vector<std::pair<int,int>> default_counts = utils::parse_ranges_unsigned("1-6");
2393  config::attribute_value i_count =i["count"];
2394  if(!in_ranges<int>(count, !i_count.blank() ? utils::parse_ranges_unsigned(i_count) : default_counts)){
2395  return false;
2396  }
2397  }
2398 
2399  return true;
2400 }
2401 
2402 
2403 
2405 {
2406 
2407 void individual_effect::set(value_modifier t, int val, const config *abil, const map_location &l)
2408 {
2409  type=t;
2410  value=val;
2411  ability=abil;
2412  loc=l;
2413 }
2414 
2415 bool filter_base_matches(const config& cfg, int def)
2416 {
2417  if (auto apply_filter = cfg.optional_child("filter_base_value")) {
2418  config::attribute_value cond_eq = apply_filter["equals"];
2419  config::attribute_value cond_ne = apply_filter["not_equals"];
2420  config::attribute_value cond_lt = apply_filter["less_than"];
2421  config::attribute_value cond_gt = apply_filter["greater_than"];
2422  config::attribute_value cond_ge = apply_filter["greater_than_equal_to"];
2423  config::attribute_value cond_le = apply_filter["less_than_equal_to"];
2424  return (cond_eq.empty() || def == cond_eq.to_int()) &&
2425  (cond_ne.empty() || def != cond_ne.to_int()) &&
2426  (cond_lt.empty() || def < cond_lt.to_int()) &&
2427  (cond_gt.empty() || def > cond_gt.to_int()) &&
2428  (cond_ge.empty() || def >= cond_ge.to_int()) &&
2429  (cond_le.empty() || def <= cond_le.to_int());
2430  }
2431  return true;
2432 }
2433 
2434 effect::effect(const unit_ability_list& list, int def, const const_attack_ptr& att, EFFECTS wham) :
2435  effect_list_(),
2436  composite_value_(0)
2437 {
2438 
2439  int value_set = (wham == EFFECT_CUMULABLE) ? std::max(list.highest("value").first, 0) + std::min(list.lowest("value").first, 0) : def;
2440  std::map<std::string,individual_effect> values_add;
2441  std::map<std::string,individual_effect> values_sub;
2442  std::map<std::string,individual_effect> values_mul;
2443  std::map<std::string,individual_effect> values_div;
2444 
2445  individual_effect set_effect_max;
2446  individual_effect set_effect_min;
2447  utils::optional<int> max_value = utils::nullopt;
2448  utils::optional<int> min_value = utils::nullopt;
2449 
2450  for (const unit_ability & ability : list) {
2451  const config& cfg = *ability.ability_cfg;
2452  const std::string& effect_id = cfg[cfg["id"].empty() ? "name" : "id"];
2453 
2454  if (!filter_base_matches(cfg, def))
2455  continue;
2456 
2457  if(wham != EFFECT_CUMULABLE){
2458  if (const config::attribute_value *v = cfg.get("value")) {
2459  int value = std::round(get_single_ability_value(*v, static_cast<double>(def), ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2460  callable.add("base_value", wfl::variant(def));
2461  return std::round(formula.evaluate(callable).as_int());
2462  }));
2463 
2464  int value_cum = cfg["cumulative"].to_bool() ? std::max(def, value) : value;
2465  assert((set_effect_min.type != NOT_USED) == (set_effect_max.type != NOT_USED));
2466  if(set_effect_min.type == NOT_USED) {
2467  set_effect_min.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2468  set_effect_max.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2469  }
2470  else {
2471  if(value_cum > set_effect_max.value) {
2472  set_effect_max.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2473  }
2474  if(value_cum < set_effect_min.value) {
2475  set_effect_min.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2476  }
2477  }
2478  }
2479  }
2480 
2481  if(wham == EFFECT_DEFAULT || wham == EFFECT_CUMULABLE){
2482  if(cfg.has_attribute("max_value")){
2483  max_value = max_value ? std::min(*max_value, cfg["max_value"].to_int()) : cfg["max_value"].to_int();
2484  }
2485  if(cfg.has_attribute("min_value")){
2486  min_value = min_value ? std::max(*min_value, cfg["min_value"].to_int()) : cfg["min_value"].to_int();
2487  }
2488  }
2489 
2490  if (const config::attribute_value *v = cfg.get("add")) {
2491  int add = std::round(get_single_ability_value(*v, static_cast<double>(def), ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2492  callable.add("base_value", wfl::variant(def));
2493  return std::round(formula.evaluate(callable).as_int());
2494  }));
2495  std::map<std::string,individual_effect>::iterator add_effect = values_add.find(effect_id);
2496  if(add_effect == values_add.end() || add > add_effect->second.value) {
2497  values_add[effect_id].set(ADD, add, ability.ability_cfg, ability.teacher_loc);
2498  }
2499  }
2500  if (const config::attribute_value *v = cfg.get("sub")) {
2501  int sub = - std::round(get_single_ability_value(*v, static_cast<double>(def), ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2502  callable.add("base_value", wfl::variant(def));
2503  return std::round(formula.evaluate(callable).as_int());
2504  }));
2505  std::map<std::string,individual_effect>::iterator sub_effect = values_sub.find(effect_id);
2506  if(sub_effect == values_sub.end() || sub < sub_effect->second.value) {
2507  values_sub[effect_id].set(ADD, sub, ability.ability_cfg, ability.teacher_loc);
2508  }
2509  }
2510  if (const config::attribute_value *v = cfg.get("multiply")) {
2511  int multiply = std::round(get_single_ability_value(*v, static_cast<double>(def), ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2512  callable.add("base_value", wfl::variant(def));
2513  return formula.evaluate(callable).as_decimal() / 1000.0 ;
2514  }) * 100);
2515  std::map<std::string,individual_effect>::iterator mul_effect = values_mul.find(effect_id);
2516  if(mul_effect == values_mul.end() || multiply > mul_effect->second.value) {
2517  values_mul[effect_id].set(MUL, multiply, ability.ability_cfg, ability.teacher_loc);
2518  }
2519  }
2520  if (const config::attribute_value *v = cfg.get("divide")) {
2521  int divide = std::round(get_single_ability_value(*v, static_cast<double>(def), ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2522  callable.add("base_value", wfl::variant(def));
2523  return formula.evaluate(callable).as_decimal() / 1000.0 ;
2524  }) * 100);
2525 
2526  if (divide == 0) {
2527  ERR_NG << "division by zero with divide= in ability/weapon special " << effect_id;
2528  }
2529  else {
2530  std::map<std::string,individual_effect>::iterator div_effect = values_div.find(effect_id);
2531  if(div_effect == values_div.end() || divide > div_effect->second.value) {
2532  values_div[effect_id].set(DIV, divide, ability.ability_cfg, ability.teacher_loc);
2533  }
2534  }
2535  }
2536  }
2537 
2538  if((wham != EFFECT_CUMULABLE) && set_effect_max.type != NOT_USED) {
2539  value_set = std::max(set_effect_max.value, 0) + std::min(set_effect_min.value, 0);
2540  if(set_effect_max.value > def) {
2541  effect_list_.push_back(set_effect_max);
2542  }
2543  if(set_effect_min.value < def) {
2544  effect_list_.push_back(set_effect_min);
2545  }
2546  }
2547 
2548  /* Do multiplication with floating point values rather than integers
2549  * We want two places of precision for each multiplier
2550  * Using integers multiplied by 100 to keep precision causes overflow
2551  * after 3-4 abilities for 32-bit values and ~8 for 64-bit
2552  * Avoiding the overflow by dividing after each step introduces rounding errors
2553  * that may vary depending on the order effects are applied
2554  * As the final values are likely <1000 (always true for mainline), loss of less significant digits is not an issue
2555  */
2556  double multiplier = 1.0;
2557  double divisor = 1.0;
2558 
2559  for(const auto& val : values_mul) {
2560  multiplier *= val.second.value/100.0;
2561  effect_list_.push_back(val.second);
2562  }
2563 
2564  for(const auto& val : values_div) {
2565  divisor *= val.second.value/100.0;
2566  effect_list_.push_back(val.second);
2567  }
2568 
2569  int addition = 0;
2570  for(const auto& val : values_add) {
2571  addition += val.second.value;
2572  effect_list_.push_back(val.second);
2573  }
2574 
2575  /* Additional and subtraction are independent since Wesnoth 1.19.4. Prior to that, they affected each other.
2576  */
2577  int substraction = 0;
2578  for(const auto& val : values_sub) {
2579  substraction += val.second.value;
2580  effect_list_.push_back(val.second);
2581  }
2582 
2583  composite_double_value_ = (value_set + addition + substraction) * multiplier / divisor;
2584  //clamp what if min_value < max_value or one attribute only used.
2585  if(max_value && min_value && *min_value < *max_value) {
2586  composite_double_value_ = std::clamp(static_cast<double>(*min_value), static_cast<double>(*max_value), composite_double_value_);
2587  } else if(max_value && !min_value) {
2588  composite_double_value_ = std::min(static_cast<double>(*max_value), composite_double_value_);
2589  } else if(min_value && !max_value) {
2590  composite_double_value_ = std::max(static_cast<double>(*min_value), composite_double_value_);
2591  }
2593 }
2594 
2595 } // 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:983
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:560
#define ERR_WML
Definition: abilities.cpp:57
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:1038
static bool overwrite_special_affects(const config &special)
Definition: abilities.cpp:1596
static lg::log_domain log_wml("wml")
static void get_ability_children(std::vector< special_match > &tag_result, std::vector< special_match > &id_result, const config &parent, const std::string &id, bool special_id=true, bool special_tags=true)
Gets the children of parent (which should be the abilities for an attack_type) and places the ones wh...
Definition: abilities.cpp:1707
map_location loc
Definition: move.cpp:172
double t
Definition: astarsearch.cpp:63
Helper similar to std::unique_lock for detecting when calculations such as has_special have entered i...
specials_context_t(const attack_type &weapon, bool attacking)
Initialize weapon specials context for listing.
Definition: abilities.cpp:1212
const config & specials() const
Definition: attack_type.hpp:56
bool special_active(const config &special, AFFECTS whom, const std::string &tag_name, bool in_abilities_tag=false) const
Definition: abilities.cpp:2203
map_location other_loc_
std::string weapon_specials() const
Returns a comma-separated string of active names for the specials of *this.
Definition: abilities.cpp:1001
bool check_self_abilities(const config &cfg, const std::string &special) const
check_self_abilities : return an boolean value for checking of activities of abilities used like weap...
Definition: abilities.cpp:1751
bool has_special(const std::string &special, bool simple_check=false, bool special_id=true, bool special_tags=true) const
Returns whether or not *this has a special with a tag or id equal to special.
Definition: abilities.cpp:833
std::string weapon_specials_value(const std::set< std::string > &checking_tags) const
Definition: abilities.cpp:1049
int composite_value(const unit_ability_list &abil_list, int base_value) const
Return the special weapon value, considering specials.
Definition: abilities.cpp:1591
const_attack_ptr other_attack_
void add_formula_context(wfl::map_formula_callable &) const
Definition: abilities.cpp:608
const std::string & range() const
Definition: attack_type.hpp:46
map_location self_loc_
bool has_special_or_ability_with_filter(const config &filter) const
Definition: abilities.cpp:2195
const std::string & type() const
Definition: attack_type.hpp:44
std::string select_replacement_type(const unit_ability_list &damage_type_list) const
Select best damage type based on frequency count for replacement_type.
Definition: abilities.cpp:1265
unit_ability_list get_weapon_ability(const std::string &ability) const
Returns list for weapon like abilities for each ability type.
Definition: abilities.cpp:1555
bool has_ability_with_filter(const config &filter) const
Definition: abilities.cpp:2139
double modified_damage() const
Returns the damage per attack of this weapon, considering specials.
Definition: abilities.cpp:1379
unit_ability_list get_specials_and_abilities(const std::string &special) const
Definition: abilities.cpp:1574
static bool special_active_impl(const const_attack_ptr &self_attack, const const_attack_ptr &other_attack, const config &special, AFFECTS whom, const std::string &tag_name, bool in_abilities_tag=false)
Returns whether or not the given special is active for the specified unit, based on the current conte...
Definition: abilities.cpp:2219
bool overwrite_special_checking(unit_ability_list &overwriters, const config &cfg, const std::string &tag_name) const
Check whether cfg would be overwritten by any element of overwriters.
Definition: abilities.cpp:1638
unit_const_ptr self_
bool check_adj_abilities(const config &cfg, const std::string &special, int dir, const unit &from) const
check_adj_abilities : return an boolean value for checking of activities of abilities used like weapo...
Definition: abilities.cpp:1771
int num_attacks() const
Definition: attack_type.hpp:53
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="", bool leader_bool=false)
Definition: abilities.cpp:1109
unit_ability_list overwrite_special_overwriter(unit_ability_list overwriters, const std::string &tag_name) const
Filter a list of abilities or weapon specials, removing any entries that don't own the overwrite_spec...
Definition: abilities.cpp:1602
recursion_guard update_variables_recursion(const config &special) const
Tests which might otherwise cause infinite recursion should call this, check that the returned object...
std::pair< std::string, int > select_alternative_type(const unit_ability_list &damage_type_list, const unit_ability_list &resistance_list) const
Select best damage type based on highest damage for alternative_type.
Definition: abilities.cpp:1294
std::vector< std::pair< t_string, t_string > > special_tooltips(boost::dynamic_bitset<> *active_list=nullptr) const
Returns a vector of names and descriptions for the specials of *this.
Definition: abilities.cpp:938
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={}, bool leader_bool=false)
weapon_specials_impl_self and weapon_specials_impl_adj : check if special name can be added.
Definition: abilities.cpp:1089
std::pair< std::string, std::set< std::string > > damage_types() const
Return a type()/replacement_type and a list of alternative_types that should be displayed in the sele...
Definition: abilities.cpp:1358
static bool check_self_abilities_impl(const const_attack_ptr &self_attack, const const_attack_ptr &other_attack, const config &special, const unit_const_ptr &u, const map_location &loc, AFFECTS whom, const std::string &tag_name, bool leader_bool=false)
check_self_abilities_impl : return an boolean value for checking of activities of abilities used like...
Definition: abilities.cpp:1756
bool attack_empty() const
Returns true if this is a dummy attack_type, for example the placeholder that the unit_attack dialog ...
bool special_matches_filter(const config &cfg, const std::string &tag_name, const config &filter) const
Filter a list of abilities or weapon specials.
Definition: abilities.cpp:2104
void modified_attacks(unsigned &min_attacks, unsigned &max_attacks) const
Calculates the number of attacks this weapon has, considering specials.
Definition: abilities.cpp:1244
unit_const_ptr other_
static bool check_adj_abilities_impl(const const_attack_ptr &self_attack, const const_attack_ptr &other_attack, const config &special, const unit_const_ptr &u, const unit &from, int dir, const map_location &loc, AFFECTS whom, const std::string &tag_name, bool leader_bool=false)
check_adj_abilities_impl : return an boolean value for checking of activities of abilities used like ...
Definition: abilities.cpp:1776
bool has_special_with_filter(const config &filter) const
check if special matche
Definition: abilities.cpp:2109
unit_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:905
int damage() const
Definition: attack_type.hpp:52
bool has_special_or_ability(const std::string &special, bool special_id=true, bool special_tags=true) const
used for abilities used like weapon and true specials
Definition: abilities.cpp:1897
bool has_weapon_ability(const std::string &special, bool special_id=true, bool special_tags=true) const
used for abilities used like weapon
Definition: abilities.cpp:1796
bool is_for_listing_
std::pair< std::string, int > effective_damage_type() const
The type of attack used and the resistance value that does the most damage.
Definition: abilities.cpp:1327
Variant for storing WML attributes.
std::string str(const std::string &fallback="") const
auto apply_visitor(const V &visitor) const
Visitor support: Applies a visitor to the underlying variant.
bool blank() const
Tests for an attribute that was never set.
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:158
const attribute_value & get_or(const config_key_type key, const config_key_type default_key) const
Chooses a value.
Definition: config.cpp:687
config & mandatory_child(config_key_type key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition: config.cpp:362
bool matches(const config &filter) const
Definition: config.cpp:1190
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:796
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:312
bool has_attribute(config_key_type key) const
Definition: config.cpp:157
child_itors child_range(config_key_type key)
Definition: config.cpp:268
config & child_or_add(config_key_type key)
Returns a reference to the first child with the given key.
Definition: config.cpp:401
bool empty() const
Definition: config.cpp:845
const attribute_value * get(config_key_type key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
Definition: config.cpp:681
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:380
config & add_child(config_key_type key)
Definition: config.cpp:436
Abstract class for exposing game data that doesn't depend on the GUI, however which for historical re...
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:192
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:110
virtual const display_context & get_disp_context() const =0
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:65
bool empty() const
Definition: tstring.hpp:197
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:75
bool is_enemy(int n) const
Definition: team.hpp:234
bool match(const map_location &loc) const
Definition: filter.hpp:43
Helper similar to std::unique_lock for detecting when calculations such as abilities have entered inf...
Definition: unit.hpp:1891
int get_composite_value() const
Definition: abilities.hpp:49
std::vector< individual_effect > effect_list_
Definition: abilities.hpp:58
double get_composite_double_value() const
Definition: abilities.hpp:51
double composite_double_value_
Definition: abilities.hpp:60
void append(const unit_ability_list &other)
Appends the abilities from other to this, ignores other.loc()
Definition: unit.hpp:106
std::pair< int, map_location > lowest(const std::string &key, int def=0) const
Definition: unit.hpp:72
const map_location & loc() const
Definition: unit.hpp:103
void emplace_back(T &&... args)
Definition: unit.hpp:101
std::pair< int, map_location > highest(const std::string &key, int def=0) const
Definition: unit.hpp:68
std::size_t size()
Definition: unit.hpp:95
bool empty() const
Definition: unit.hpp:90
void append_if(const unit_ability_list &other, const Predicate &predicate)
Appends any abilities from other for which the given condition returns true to this,...
Definition: unit.hpp:118
std::pair< int, map_location > get_extremum(const std::string &key, int def, const TComp &comp) const
Definition: abilities.cpp:684
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
const unit_type_map & types() const
Definition: types.hpp:396
A single unit type that the player may recruit.
Definition: types.hpp:43
const t_string & type_name() const
The name of the unit in the current language setting.
Definition: types.hpp:138
This class represents a single unit of a specific type.
Definition: unit.hpp:133
A variable-expanding proxy for the config class.
Definition: variable.hpp:45
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() const
Definition: variant.cpp:291
void swap(config &lhs, config &rhs)
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1339
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.
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:1030
Interfaces for manipulating version numbers of engine, add-ons, etc.
static std::string _(const char *str)
Definition: gettext.hpp:97
bool ability_active(const std::string &ability, const config &cfg, const map_location &loc) const
Check if an ability is active.
Definition: abilities.cpp:423
bool ability_affects_self(const std::string &ability, const config &cfg, const map_location &loc) const
Check if an ability affects the owning unit.
Definition: abilities.cpp:526
std::vector< const config * > open_queries_
While processing a recursive match, all the filters that are currently being checked,...
Definition: unit.hpp:2084
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:187
bool ability_affects_adjacent(const std::string &ability, const config &cfg, int dir, const map_location &loc, const unit &from) const
Check if an ability affects adjacent units.
Definition: abilities.cpp:498
std::vector< std::tuple< std::string, t_string, t_string, t_string > > ability_tooltips() const
Gets the names and descriptions of this unit's abilities.
Definition: abilities.cpp:322
config abilities_
Definition: unit.hpp:2111
unit_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:258
int side_
Definition: unit.hpp:2042
bool has_ability_type(const std::string &ability) const
Check if the unit has an ability of a specific type.
Definition: abilities.cpp:553
unit_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:222
unit_race::GENDER gender_
Definition: unit.hpp:2044
bool get_self_ability_bool_weapon(const config &special, const std::string &tag_name, const map_location &loc, const const_attack_ptr &weapon=nullptr, const const_attack_ptr &opp_weapon=nullptr) const
Checks whether this unit currently possesses a given ability of leadership type.
Definition: abilities.cpp:1741
recursion_guard()
Construct an empty instance, only useful for extending the lifetime of a recursion_guard returned fro...
bool get_self_ability_bool(const config &cfg, const std::string &ability, const map_location &loc) const
Checks whether this unit currently possesses a given ability, and that that ability is active.
Definition: abilities.cpp:1720
std::vector< std::string > get_ability_list() const
Get a list of all abilities by ID.
Definition: abilities.cpp:267
bool ability_active_impl(const std::string &ability, const config &cfg, const map_location &loc) const
Check if an ability is active.
Definition: abilities.cpp:433
bool get_adj_ability_bool_weapon(const config &special, const std::string &tag_name, int dir, const map_location &loc, const unit &from, const const_attack_ptr &weapon=nullptr, const const_attack_ptr &opp_weapon=nullptr) const
Checks whether this unit is affected by a given ability of leadership type.
Definition: abilities.cpp:1746
map_location loc_
Definition: unit.hpp:2003
recursion_guard update_variables_recursion(const config &ability) const
Definition: abilities.cpp:382
recursion_guard & operator=(recursion_guard &&)
Definition: abilities.cpp:407
const std::set< std::string > & checking_tags() const
Definition: unit.hpp:1818
bool get_adj_ability_bool(const config &cfg, const std::string &ability, int dir, const map_location &loc, const unit &from) const
Checks whether this unit is affected by a given ability, and that that ability is active.
Definition: abilities.cpp:1730
bool ability_matches_filter(const config &cfg, const std::string &tag_name, const config &filter) const
Verify what abilities attributes match with filter.
Definition: abilities.cpp:2099
bool ability_affects_weapon(const config &cfg, const const_attack_ptr &weapon, bool is_opp) const
filters the weapons that condition the use of abilities for combat ([resistance],[leadership] or abil...
Definition: abilities.cpp:534
const unit_type & type() const
This unit's type, accounting for gender and variation.
Definition: unit.hpp:355
const std::string & id() const
Gets this unit's id.
Definition: unit.hpp:380
int side() const
The side this unit belongs to.
Definition: unit.hpp:343
const t_string & name() const
Gets this unit's translatable display name.
Definition: unit.hpp:403
@ STATE_SLOWED
Definition: unit.hpp:869
@ STATE_POISONED
The unit is slowed - it moves slower and does less damage.
Definition: unit.hpp:870
std::vector< std::string > halo_or_icon_abilities(const std::string &image_type) const
Definition: abilities.cpp:568
New lexcical_cast header.
void get_adjacent_tiles(const map_location &a, map_location *res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:479
Standard logging facilities (interface).
CURSOR_TYPE get()
Definition: cursor.cpp:218
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
std::vector< std::pair< int, int > > default_counts
static bool is_active(const widget *wgt)
Definition: window.cpp:1261
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:517
std::string span_color(const color_t &color, Args &&... data)
Applies Pango markup to the input specifying its display color.
Definition: markup.hpp:110
game_board * gameboard
Definition: resources.cpp:20
filter_context * filter_con
Definition: resources.cpp:23
bool filter_base_matches(const config &cfg, int def)
Definition: abilities.cpp:2415
std::size_t index(std::string_view str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
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)
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:86
std::vector< std::pair< int, int > > parse_ranges_unsigned(const std::string &str)
Handles a comma-separated list of inputs to parse_range, in a context that does not expect negative v...
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition: general.hpp:106
void sort_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::sort on a container.
Definition: general.hpp:130
std::vector< std::string > split(const config_attribute_value &val)
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:140
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 > unit_ptr
Definition: ptr.hpp:26
const config::attribute_value & gender_value(const config &cfg, unit_race::GENDER gender, const std::string &male_key, const std::string &female_key, const std::string &default_key)
Chooses a value from the given config based on gender.
Definition: race.cpp:156
Encapsulates the map of the game.
Definition: location.hpp:45
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
static std::vector< direction > all_directions()
Definition: location.cpp:61
bool valid() const
Definition: location.hpp:110
direction
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:47
direction get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition: location.cpp:240
static const map_location & null_location()
Definition: location.hpp:102
void set(value_modifier t, int val, const config *abil, const map_location &l)
Definition: abilities.cpp:2407
Data typedef for unit_ability_list.
Definition: unit.hpp:38
map_location student_loc
Used by the formula in the ability.
Definition: unit.hpp:52
const config * ability_cfg
The contents of the ability tag, never nullptr.
Definition: unit.hpp:59
map_location teacher_loc
The location of the teacher, that is the unit who owns the ability tags (different from student becau...
Definition: unit.hpp:57
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_char c
mock_party p
static map_location::direction s
unit_type_data unit_types
Definition: types.cpp:1504
#define d
#define e