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