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