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