The Battle for Wesnoth  1.19.0-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 "global.hpp"
28 #include "lexical_cast.hpp"
29 #include "log.hpp"
30 #include "map/map.hpp"
31 #include "resources.hpp"
32 #include "team.hpp"
33 #include "terrain/filter.hpp"
34 #include "units/unit.hpp"
35 #include "units/abilities.hpp"
36 #include "units/filter.hpp"
37 #include "units/map.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 bool unit::ability_active(const std::string& ability,const config& cfg,const map_location& loc) const
353 {
354  bool illuminates = ability == "illuminates";
355 
356  if (auto afilter = cfg.optional_child("filter"))
357  if ( !unit_filter(vconfig(*afilter)).set_use_flat_tod(illuminates).matches(*this, loc) )
358  return false;
359 
360  const auto adjacent = get_adjacent_tiles(loc);
361 
362  const unit_map& units = get_unit_map();
363 
364  for (const config &i : cfg.child_range("filter_adjacent"))
365  {
366  std::size_t count = 0;
367  unit_filter ufilt{ vconfig(i) };
368  ufilt.set_use_flat_tod(illuminates);
369  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
370  for (const map_location::DIRECTION index : dirs)
371  {
373  continue;
374  unit_map::const_iterator unit = units.find(adjacent[index]);
375  if (unit == units.end())
376  return false;
377  if (!ufilt(*unit, *this))
378  return false;
379  if((*this).id() == (*unit).id())
380  return false;
381  if (i.has_attribute("is_enemy")) {
383  if (i["is_enemy"].to_bool() != dc.get_team(unit->side()).is_enemy(side_)) {
384  continue;
385  }
386  }
387  count++;
388  }
389  if (i["count"].empty() && count != dirs.size()) {
390  return false;
391  }
392  if (!in_ranges<int>(count, utils::parse_ranges_unsigned(i["count"].str()))) {
393  return false;
394  }
395  }
396 
397  for (const config &i : cfg.child_range("filter_adjacent_location"))
398  {
399  std::size_t count = 0;
400  terrain_filter adj_filter(vconfig(i), resources::filter_con, false);
401  adj_filter.flatten(illuminates);
402 
403  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
404  for (const map_location::DIRECTION index : dirs)
405  {
407  continue;
408  }
409  if(!adj_filter.match(adjacent[index])) {
410  return false;
411  }
412  count++;
413  }
414  if (i["count"].empty() && count != dirs.size()) {
415  return false;
416  }
417  if (!in_ranges<int>(count, utils::parse_ranges_unsigned(i["count"].str()))) {
418  return false;
419  }
420  }
421  return true;
422 }
423 
424 bool unit::ability_affects_adjacent(const std::string& ability, const config& cfg,int dir,const map_location& loc,const unit& from) const
425 {
426  bool illuminates = ability == "illuminates";
427 
428  assert(dir >=0 && dir <= 5);
429  map_location::DIRECTION direction = static_cast<map_location::DIRECTION>(dir);
430 
431  for (const config &i : cfg.child_range("affect_adjacent"))
432  {
433  if (i.has_attribute("adjacent")) { //key adjacent defined
434  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
435  if (std::find(dirs.begin(), dirs.end(), direction) == dirs.end()) {
436  continue;
437  }
438  }
439  if((*this).id() == from.id()){
440  return false;
441  }
442  auto filter = i.optional_child("filter");
443  if (!filter || //filter tag given
444  unit_filter(vconfig(*filter)).set_use_flat_tod(illuminates).matches(*this, loc, from) ) {
445  return true;
446  }
447  }
448  return false;
449 }
450 
451 bool unit::ability_affects_self(const std::string& ability,const config& cfg,const map_location& loc) const
452 {
453  auto filter = cfg.optional_child("filter_self");
454  bool affect_self = cfg["affect_self"].to_bool(true);
455  if (!filter || !affect_self) return affect_self;
456  return unit_filter(vconfig(*filter)).set_use_flat_tod(ability == "illuminates").matches(*this, loc);
457 }
458 
459 bool unit::ability_affects_weapon(const config& cfg, const_attack_ptr weapon, bool is_opp) const
460 {
461  const std::string filter_tag_name = is_opp ? "filter_second_weapon" : "filter_weapon";
462  if(!cfg.has_child(filter_tag_name)) {
463  return true;
464  }
465  const config& filter = cfg.mandatory_child(filter_tag_name);
466  if(!weapon) {
467  return false;
468  }
469  return weapon->matches_filter(filter);
470 }
471 
472 bool unit::has_ability_type(const std::string& ability) const
473 {
474  return !abilities_.child_range(ability).empty();
475 }
476 
477 //these two functions below are used in order to add to the unit
478 //a second set of halo encoded in the abilities (like illuminates halo in [illuminates] ability for example)
479 static void add_string_to_vector(std::vector<std::string>& image_list, const config& cfg, const std::string& attribute_name)
480 {
481  auto ret = std::find(image_list.begin(), image_list.end(), cfg[attribute_name].str());
482  if(ret == image_list.end()){
483  image_list.push_back(cfg[attribute_name].str());
484  }
485 }
486 
487 const std::vector<std::string> unit::halo_or_icon_abilities(const std::string& image_type) const
488 {
489  std::vector<std::string> image_list;
491  bool is_active = ability_active(sp.key, sp.cfg, loc_);
492  //Add halo/overlay to owner of ability if active and affect_self is true.
493  if( !(sp.cfg)[image_type + "_image"].str().empty() && is_active && ability_affects_self(sp.key, sp.cfg, loc_)){
494  add_string_to_vector(image_list, sp.cfg,image_type + "_image");
495  }
496  //Add halo/overlay to owner of ability who affect adjacent only if active.
497  if(!(sp.cfg)[image_type + "_image_self"].str().empty() && is_active){
498  add_string_to_vector(image_list, sp.cfg, image_type + "_image_self");
499  }
500  }
501 
502  const unit_map& units = get_unit_map();
503 
504  //Add halo/overlay to unit under abilities owned by adjacent who has [affect_adjacent]
505  //if condition matched
506  const auto adjacent = get_adjacent_tiles(loc_);
507  for(unsigned i = 0; i < adjacent.size(); ++i) {
508  const unit_map::const_iterator it = units.find(adjacent[i]);
509  if (it == units.end() || it->incapacitated())
510  continue;
511  if ( &*it == this )
512  continue;
513  for(const config::any_child j : it->abilities_.all_children_range()) {
514  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))
515  {
516  add_string_to_vector(image_list, j.cfg, image_type + "_image");
517  }
518  }
519  }
520  //rearranges vector alphabetically when its size equals or exceeds two.
521  if(image_list.size() >= 2){
522  std::sort(image_list.begin(), image_list.end());
523  }
524  return image_list;
525 }
526 
528 {
529  if(unit_const_ptr & att = is_attacker_ ? self_ : other_) {
530  callable.add("attacker", wfl::variant(std::make_shared<wfl::unit_callable>(*att)));
531  }
532  if(unit_const_ptr & def = is_attacker_ ? other_ : self_) {
533  callable.add("defender", wfl::variant(std::make_shared<wfl::unit_callable>(*def)));
534  }
535 }
536 
537 namespace {
538 
539 
540 template<typename T, typename TFuncFormula>
541 class get_ability_value_visitor
542 #ifdef USING_BOOST_VARIANT
543  : public boost::static_visitor<T>
544 #endif
545 {
546 public:
547  // Constructor stores the default value.
548  get_ability_value_visitor(T def, const TFuncFormula& formula_handler) : def_(def), formula_handler_(formula_handler) {}
549 
550  T operator()(const utils::monostate&) const { return def_; }
551  T operator()(bool) const { return def_; }
552  T operator()(int i) const { return static_cast<T>(i); }
553  T operator()(unsigned long long u) const { return static_cast<T>(u); }
554  T operator()(double d) const { return static_cast<T>(d); }
555  T operator()(const t_string&) const { return def_; }
556  T operator()(const std::string& s) const
557  {
558  if(s.size() >= 2 && s[0] == '(') {
559  return formula_handler_(s);
560  }
561  return lexical_cast_default<T>(s, def_);
562  }
563 
564 private:
565  const T def_;
566  const TFuncFormula& formula_handler_;
567 };
568 
569 template<typename T, typename TFuncFormula>
570 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)
571 {
572  return v.apply_visitor(get_ability_value_visitor(def, [&](const std::string& s) {
573 
574  try {
575  const unit_map& units = get_unit_map();
576 
577  auto u_itor = units.find(ability_info.teacher_loc);
578 
579  if(u_itor == units.end()) {
580  return def;
581  }
582  wfl::map_formula_callable callable(std::make_shared<wfl::unit_callable>(*u_itor));
583  if(att) {
584  att->add_formula_context(callable);
585  }
586  if (auto uptr = units.find_unit_ptr(ability_info.student_loc)) {
587  callable.add("student", wfl::variant(std::make_shared<wfl::unit_callable>(*uptr)));
588  }
589  if (auto uptr = units.find_unit_ptr(receiver_loc)) {
590  callable.add("other", wfl::variant(std::make_shared<wfl::unit_callable>(*uptr)));
591  }
592  return formula_handler(wfl::formula(s, new wfl::gamestate_function_symbol_table), callable);
593  } catch(const wfl::formula_error& e) {
594  lg::log_to_chat() << "Formula error in ability or weapon special: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
595  ERR_WML << "Formula error in ability or weapon special: " << e.type << " at " << e.filename << ':' << e.line << ")";
596  return def;
597  }
598  }));
599 }
600 }
601 
602 template<typename TComp>
603 std::pair<int,map_location> unit_ability_list::get_extremum(const std::string& key, int def, const TComp& comp) const
604 {
605  if ( cfgs_.empty() ) {
606  return std::pair(def, map_location());
607  }
608  // The returned location is the best non-cumulative one, if any,
609  // the best absolute cumulative one otherwise.
610  map_location best_loc;
611  bool only_cumulative = true;
612  int abs_max = 0;
613  int flat = 0;
614  int stack = 0;
615  for (const unit_ability& p : cfgs_)
616  {
617  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) {
618  return formula.evaluate(callable).as_int();
619  });
620 
621  if ((*p.ability_cfg)["cumulative"].to_bool()) {
622  stack += value;
623  if (value < 0) value = -value;
624  if (only_cumulative && !comp(value, abs_max)) {
625  abs_max = value;
626  best_loc = p.teacher_loc;
627  }
628  } else if (only_cumulative || comp(flat, value)) {
629  only_cumulative = false;
630  flat = value;
631  best_loc = p.teacher_loc;
632  }
633  }
634  return std::pair(flat + stack, best_loc);
635 }
636 
637 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;
638 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;
639 
640 /*
641  *
642  * [special]
643  * [swarm]
644  * name= _ "swarm"
645  * name_inactive= _ ""
646  * description= _ ""
647  * description_inactive= _ ""
648  * cumulative=no
649  * apply_to=self #self,opponent,defender,attacker,both
650  * #active_on=defense # or offense; omitting this means "both"
651  *
652  * swarm_attacks_max=4
653  * swarm_attacks_min=2
654  *
655  * [filter_self] // SUF
656  * ...
657  * [/filter_self]
658  * [filter_opponent] // SUF
659  * [filter_attacker] // SUF
660  * [filter_defender] // SUF
661  * [filter_adjacent] // SAUF
662  * [filter_adjacent_location] // SAUF + locs
663  * [/swarm]
664  * [/special]
665  *
666  */
667 
668 namespace {
669 
670  struct special_match
671  {
672  std::string tag_name;
673  const config* cfg;
674  };
675 
676  /**
677  * Gets the children of @parent (which should be the specials for an
678  * attack_type) and places the ones whose tag or id= matches @a id into
679  * @a tag_result and @a id_result.
680  *
681  * If @a just_peeking is set to true, then @a tag_result and @a id_result
682  * are not touched; instead the return value is used to indicate if any
683  * matching children were found.
684  *
685  * @returns true if @a just_peeking is true and a match was found;
686  * false otherwise.
687  */
688  bool get_special_children(std::vector<special_match>& tag_result,
689  std::vector<special_match>& id_result,
690  const config& parent, const std::string& id,
691  bool just_peeking=false) {
692  for (const config::any_child sp : parent.all_children_range())
693  {
694  if (just_peeking && (sp.key == id || sp.cfg["id"] == id)) {
695  return true; // peek succeeded; done
696  }
697 
698  if(sp.key == id) {
699  special_match special = { sp.key, &sp.cfg };
700  tag_result.push_back(special);
701  }
702  if(sp.cfg["id"] == id) {
703  special_match special = { sp.key, &sp.cfg };
704  id_result.push_back(special);
705  }
706  }
707  return false;
708  }
709 
710  bool get_special_children_id(std::vector<special_match>& id_result,
711  const config& parent, const std::string& id,
712  bool just_peeking=false) {
713  for (const config::any_child sp : parent.all_children_range())
714  {
715  if (just_peeking && (sp.cfg["id"] == id)) {
716  return true; // peek succeeded; done
717  }
718 
719  if(sp.cfg["id"] == id) {
720  special_match special = { sp.key, &sp.cfg };
721  id_result.push_back(special);
722  }
723  }
724  return false;
725  }
726 
727  bool get_special_children_tags(std::vector<special_match>& tag_result,
728  const config& parent, const std::string& id,
729  bool just_peeking=false) {
730  for (const config::any_child sp : parent.all_children_range())
731  {
732  if (just_peeking && (sp.key == id)) {
733  return true; // peek succeeded; done
734  }
735 
736  if(sp.key == id) {
737  special_match special = { sp.key, &sp.cfg };
738  tag_result.push_back(special);
739  }
740  }
741  return false;
742  }
743 }
744 
745 /**
746  * Returns whether or not @a *this has a special with a tag or id equal to
747  * @a special. If @a simple_check is set to true, then the check is merely
748  * for being present. Otherwise (the default), the check is for a special
749  * active in the current context (see set_specials_context), including
750  * specials obtained from the opponent's attack.
751  */
752 bool attack_type::has_special(const std::string& special, bool simple_check, bool special_id, bool special_tags) const
753 {
754  {
755  std::vector<special_match> special_tag_matches;
756  std::vector<special_match> special_id_matches;
757  if(special_id && special_tags){
758  if ( get_special_children(special_tag_matches, special_id_matches, specials_, special, simple_check) ) {
759  return true;
760  }
761  } else if(special_id && !special_tags){
762  if ( get_special_children_id(special_id_matches, specials_, special, simple_check) ) {
763  return true;
764  }
765  } else if(!special_id && special_tags){
766  if ( get_special_children_tags(special_tag_matches, specials_, special, simple_check) ) {
767  return true;
768  }
769  }
770  // If we make it to here, then either list.empty() or !simple_check.
771  // So if the list is not empty, then this is not a simple check and
772  // we need to check each special in the list to see if any are active.
773  if(special_tags){
774  for(const special_match& entry : special_tag_matches) {
775  if ( special_active(*entry.cfg, AFFECT_SELF, entry.tag_name) ) {
776  return true;
777  }
778  }
779  }
780  if(special_id){
781  for(const special_match& entry : special_id_matches) {
782  if ( special_active(*entry.cfg, AFFECT_SELF, entry.tag_name) ) {
783  return true;
784  }
785  }
786  }
787  }
788 
789  // Skip checking the opponent's attack?
790  if ( simple_check || !other_attack_ ) {
791  return false;
792  }
793 
794  std::vector<special_match> special_tag_matches;
795  std::vector<special_match> special_id_matches;
796  if(special_id && special_tags){
797  get_special_children(special_tag_matches, special_id_matches, other_attack_->specials_, special);
798  } else if(special_id && !special_tags){
799  get_special_children_id(special_id_matches, other_attack_->specials_, special);
800  } else if(!special_id && special_tags){
801  get_special_children_tags(special_tag_matches, other_attack_->specials_, special);
802  }
803  if(special_tags){
804  for(const special_match& entry : special_tag_matches) {
805  if ( other_attack_->special_active(*entry.cfg, AFFECT_OTHER, entry.tag_name) ) {
806  return true;
807  }
808  }
809  }
810  if(special_id){
811  for(const special_match& entry : special_id_matches) {
812  if ( other_attack_->special_active(*entry.cfg, AFFECT_OTHER, entry.tag_name) ) {
813  return true;
814  }
815  }
816  }
817  return false;
818 }
819 
820 /**
821  * Returns the currently active specials as an ability list, given the current
822  * context (see set_specials_context).
823  */
824 unit_ability_list attack_type::get_specials(const std::string& special) const
825 {
826  //log_scope("get_specials");
827  const map_location loc = self_ ? self_->get_location() : self_loc_;
828  unit_ability_list res(loc);
829 
830  for(const config& i : specials_.child_range(special)) {
831  if(special_active(i, AFFECT_SELF, special)) {
832  res.emplace_back(&i, loc, loc);
833  }
834  }
835 
836  if(!other_attack_) {
837  return res;
838  }
839 
840  for(const config& i : other_attack_->specials_.child_range(special)) {
841  if(other_attack_->special_active(i, AFFECT_OTHER, special)) {
843  }
844  }
845  return res;
846 }
847 
848 /**
849  * Returns a vector of names and descriptions for the specials of *this.
850  * Each std::pair in the vector has first = name and second = description.
851  *
852  * This uses either the active or inactive name/description for each special,
853  * based on the current context (see set_specials_context), provided
854  * @a active_list is not nullptr. Otherwise specials are assumed active.
855  * If the appropriate name is empty, the special is skipped.
856  */
857 std::vector<std::pair<t_string, t_string>> attack_type::special_tooltips(
858  boost::dynamic_bitset<>* active_list) const
859 {
860  //log_scope("special_tooltips");
861  std::vector<std::pair<t_string, t_string>> res;
862  if ( active_list )
863  active_list->clear();
864 
866  {
867  if ( !active_list || special_active(sp.cfg, AFFECT_EITHER, sp.key) ) {
868  const t_string &name = sp.cfg["name"];
869  if (!name.empty()) {
870  res.emplace_back(name, sp.cfg["description"].t_str() );
871  if ( active_list )
872  active_list->push_back(true);
873  }
874  } else {
875  const t_string& name = sp.cfg.get_or("name_inactive", "name").t_str();
876  if (!name.empty()) {
877  res.emplace_back(name, sp.cfg.get_or("description_inactive", "description").t_str() );
878  active_list->push_back(false);
879  }
880  }
881  }
882  return res;
883 }
884 
885 /**
886  * static used in weapon_specials (bool only_active) and
887  * @return a string and a set_string for the weapon_specials function below.
888  * @param[in,out] temp_string the string modified and returned
889  * @param[in] active the boolean for determine if @name can be added or not
890  * @param[in] name string who must be or not added
891  * @param[in,out] checking_name the reference for checking if @name already added
892  */
893 static void add_name(std::string& temp_string, bool active, const std::string name, std::set<std::string>& checking_name)
894 {
895  if (active) {
896  if (!name.empty() && checking_name.count(name) == 0) {
897  checking_name.insert(name);
898  if (!temp_string.empty()) temp_string += ", ";
899  temp_string += font::span_color(font::BUTTON_COLOR, name);
900  }
901  }
902 }
903 
904 /**
905  * Returns a comma-separated string of active names for the specials of *this.
906  * Empty names are skipped.
907  *
908  * Whether or not a special is active depends
909  * on the current context (see set_specials_context)
910  */
911 std::string attack_type::weapon_specials() const
912 {
913  //log_scope("weapon_specials");
914  std::string res;
916  {
917  const bool active = special_active(sp.cfg, AFFECT_EITHER, sp.key);
918 
919  const std::string& name =
920  active
921  ? sp.cfg["name"].str()
922  : sp.cfg.get_or("name_inactive", "name").str();
923  if (!name.empty()) {
924  if (!res.empty()) res += ", ";
925  if (!active) res += font::span_color(font::inactive_details_color);
926  res += name;
927  if (!active) res += "</span>";
928  }
929  }
930  std::string temp_string;
931  std::set<std::string> checking_name;
932  weapon_specials_impl_self(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name);
933  weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, {}, "affect_allies");
934  if(!temp_string.empty() && !res.empty()) {
935  temp_string = ", \n" + temp_string;
936  res += temp_string;
937  } else if (!temp_string.empty()){
938  res = temp_string;
939  }
940  return res;
941 }
942 
943 static void add_name_list(std::string& temp_string, std::string& weapon_abilities, std::set<std::string>& checking_name, const std::string from_str)
944 {
945  if(!temp_string.empty()){
946  temp_string = from_str.c_str() + temp_string;
947  weapon_abilities += (!weapon_abilities.empty() && !temp_string.empty()) ? "\n" : "";
948  weapon_abilities += temp_string;
949  temp_string.clear();
950  checking_name.clear();
951  }
952 }
953 
954 std::string attack_type::weapon_specials_value(const std::set<std::string> checking_tags) const
955 {
956  //log_scope("weapon_specials_value");
957  std::string temp_string, weapon_abilities;
958  std::set<std::string> checking_name;
960  if((checking_tags.count(sp.key) != 0)){
961  const bool active = special_active(sp.cfg, AFFECT_SELF, sp.key);
962  add_name(temp_string, active, sp.cfg["name"].str(), checking_name);
963  }
964  }
965  add_name_list(temp_string, weapon_abilities, checking_name, "");
966 
967  weapon_specials_impl_self(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, checking_tags, true);
968  add_name_list(temp_string, weapon_abilities, checking_name, _("Owned: "));
969 
970  weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, checking_tags, "affect_allies", true);
971  // TRANSLATORS: Past-participle of "teach", used for an ability similar to leadership
972  add_name_list(temp_string, weapon_abilities, checking_name, _("Taught: "));
973 
974  weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, checking_tags, "affect_enemies", true);
975  // TRANSLATORS: Past-participle of "teach", used for an ability similar to leadership
976  add_name_list(temp_string, weapon_abilities, checking_name, _("Taught: (by an enemy): "));
977 
978 
979  if(other_attack_) {
980  for (const config::any_child sp : other_attack_->specials_.all_children_range()) {
981  if((checking_tags.count(sp.key) != 0)){
982  const bool active = other_attack_->special_active(sp.cfg, AFFECT_OTHER, sp.key);
983  add_name(temp_string, active, sp.cfg["name"].str(), checking_name);
984  }
985  }
986  }
987  weapon_specials_impl_self(temp_string, other_, other_attack_, shared_from_this(), other_loc_, AFFECT_OTHER, checking_name, checking_tags);
988  weapon_specials_impl_adj(temp_string, other_, other_attack_, shared_from_this(), other_loc_, AFFECT_OTHER, checking_name, checking_tags);
989  add_name_list(temp_string, weapon_abilities, checking_name, _("Used by opponent: "));
990 
991  return weapon_abilities;
992 }
993 
995  std::string& temp_string,
996  unit_const_ptr self,
997  const_attack_ptr self_attack,
998  const_attack_ptr other_attack,
999  const map_location& self_loc,
1000  AFFECTS whom,
1001  std::set<std::string>& checking_name,
1002  const std::set<std::string>& checking_tags,
1003  bool leader_bool)
1004 {
1005  if(self){
1006  for (const config::any_child sp : self->abilities().all_children_range()){
1007  bool tag_checked = (!checking_tags.empty()) ? (checking_tags.count(sp.key) != 0) : true;
1008  const bool active = tag_checked && check_self_abilities_impl(self_attack, other_attack, sp.cfg, self, self_loc, whom, sp.key, leader_bool);
1009  add_name(temp_string, active, sp.cfg["name"].str(), checking_name);
1010  }
1011  }
1012 }
1013 
1015  std::string& temp_string,
1016  unit_const_ptr self,
1017  const_attack_ptr self_attack,
1018  const_attack_ptr other_attack,
1019  const map_location& self_loc,
1020  AFFECTS whom,
1021  std::set<std::string>& checking_name,
1022  const std::set<std::string>& checking_tags,
1023  const std::string& affect_adjacents,
1024  bool leader_bool)
1025 {
1026  const unit_map& units = get_unit_map();
1027  if(self){
1028  const auto adjacent = get_adjacent_tiles(self_loc);
1029  for(unsigned i = 0; i < adjacent.size(); ++i) {
1030  const unit_map::const_iterator it = units.find(adjacent[i]);
1031  if (it == units.end() || it->incapacitated())
1032  continue;
1033  if(&*it == self.get())
1034  continue;
1035  for (const config::any_child sp : it->abilities().all_children_range()){
1036  bool tag_checked = (!checking_tags.empty()) ? (checking_tags.count(sp.key) != 0) : true;
1037  bool default_bool = (affect_adjacents == "affect_allies") ? true : false;
1038  bool affect_allies = (!affect_adjacents.empty()) ? sp.cfg[affect_adjacents].to_bool(default_bool) : true;
1039  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;
1040  add_name(temp_string, active, sp.cfg["name"].str(), checking_name);
1041  }
1042  }
1043  }
1044 }
1045 
1046 
1047 /**
1048  * Sets the context under which specials will be checked for being active.
1049  * This version is appropriate if both units in a combat are known.
1050  * @param[in] weapon The weapon being considered.
1051  * @param[in] self A reference to the unit with this weapon.
1052  * @param[in] other A reference to the other unit in the combat.
1053  * @param[in] unit_loc The location of the unit with this weapon.
1054  * @param[in] other_loc The location of the other unit in the combat.
1055  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
1056  * @param[in] other_attack The attack used by the other unit.
1057  */
1059  const attack_type& weapon,
1060  const_attack_ptr other_attack,
1061  unit_const_ptr self,
1062  unit_const_ptr other,
1063  const map_location& unit_loc,
1064  const map_location& other_loc,
1065  bool attacking)
1066  : parent(weapon.shared_from_this())
1067 {
1068  weapon.self_ = self;
1069  weapon.other_ = other;
1070  weapon.self_loc_ = unit_loc;
1071  weapon.other_loc_ = other_loc;
1072  weapon.is_attacker_ = attacking;
1073  weapon.other_attack_ = other_attack;
1074  weapon.is_for_listing_ = false;
1075 }
1076 
1077 /**
1078  * Sets the context under which specials will be checked for being active.
1079  * This version is appropriate if there is no specific combat being considered.
1080  * @param[in] weapon The weapon being considered.
1081  * @param[in] self A reference to the unit with this weapon.
1082  * @param[in] loc The location of the unit with this weapon.
1083  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
1084  */
1086  : parent(weapon.shared_from_this())
1087 {
1088  weapon.self_ = self;
1089  weapon.other_ = unit_ptr();
1090  weapon.self_loc_ = loc;
1092  weapon.is_attacker_ = attacking;
1093  weapon.other_attack_ = nullptr;
1094  weapon.is_for_listing_ = false;
1095 }
1096 
1097 /**
1098  * Sets the context under which specials will be checked for being active.
1099  * This version is appropriate for theoretical units of a particular type.
1100  * @param[in] weapon The weapon being considered.
1101  * @param[in] self_type A reference to the type of the unit with this weapon.
1102  * @param[in] loc The location of the unit with this weapon.
1103  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
1104  */
1105 attack_type::specials_context_t::specials_context_t(const attack_type& weapon, const unit_type& self_type, const map_location& loc, bool attacking)
1106  : parent(weapon.shared_from_this())
1107 {
1108  UNUSED(self_type);
1109  weapon.self_ = unit_ptr();
1110  weapon.other_ = unit_ptr();
1111  weapon.self_loc_ = loc;
1113  weapon.is_attacker_ = attacking;
1114  weapon.other_attack_ = nullptr;
1115  weapon.is_for_listing_ = false;
1116 }
1117 
1119  : parent(weapon.shared_from_this())
1120 {
1121  weapon.is_for_listing_ = true;
1122  weapon.is_attacker_ = attacking;
1123 }
1124 
1126 {
1127  if(was_moved) return;
1128  parent->self_ = unit_ptr();
1129  parent->other_ = unit_ptr();
1130  parent->self_loc_ = map_location::null_location();
1131  parent->other_loc_ = map_location::null_location();
1132  parent->is_attacker_ = false;
1133  parent->other_attack_ = nullptr;
1134  parent->is_for_listing_ = false;
1135 }
1136 
1138  : parent(other.parent)
1139 {
1140  other.was_moved = true;
1141 }
1142 
1143 /**
1144  * Calculates the number of attacks this weapon has, considering specials.
1145  * This returns two numbers because of the swarm special. The actual number of
1146  * attacks depends on the unit's health and should be:
1147  * min_attacks + (max_attacks - min_attacks) * (current hp) / (max hp)
1148  * c.f. swarm_blows()
1149  */
1150 void attack_type::modified_attacks(unsigned & min_attacks,
1151  unsigned & max_attacks) const
1152 {
1153  // Apply [attacks].
1154  int attacks_value = composite_value(get_specials_and_abilities("attacks"), num_attacks());
1155 
1156  if ( attacks_value < 0 ) {
1157  attacks_value = num_attacks();
1158  ERR_NG << "negative number of strikes after applying weapon specials";
1159  }
1160 
1161  // Apply [swarm].
1162  unit_ability_list swarm_specials = get_specials_and_abilities("swarm");
1163  if ( !swarm_specials.empty() ) {
1164  min_attacks = std::max<int>(0, swarm_specials.highest("swarm_attacks_min").first);
1165  max_attacks = std::max<int>(0, swarm_specials.highest("swarm_attacks_max", attacks_value).first);
1166  } else {
1167  min_attacks = max_attacks = attacks_value;
1168  }
1169 }
1170 
1171 static std::string select_replacement_type(const unit_ability_list& damage_type_list)
1172 {
1173  std::map<std::string, unsigned int> type_count;
1174  unsigned int max = 0;
1175  for(auto& i : damage_type_list) {
1176  const config& c = *i.ability_cfg;
1177  if(c.has_attribute("replacement_type")) {
1178  std::string type = c["replacement_type"].str();
1179  unsigned int count = ++type_count[type];
1180  if((count > max)) {
1181  max = count;
1182  }
1183  }
1184  }
1185 
1186  if (type_count.empty()) return "";
1187 
1188  std::vector<std::string> type_list;
1189  for(auto& i : type_count){
1190  if(i.second == max){
1191  type_list.push_back(i.first);
1192  }
1193  }
1194 
1195  if(type_list.empty()) return "";
1196 
1197  return type_list.front();
1198 }
1199 
1200 static std::string select_alternative_type(const unit_ability_list& damage_type_list, unit_ability_list resistance_list, const unit& u)
1201 {
1202  std::map<std::string, int> type_res;
1203  int max_res = INT_MIN;
1204  for(auto& i : damage_type_list) {
1205  const config& c = *i.ability_cfg;
1206  if(c.has_attribute("alternative_type")) {
1207  std::string type = c["alternative_type"].str();
1208  if(type_res.count(type) == 0){
1209  type_res[type] = u.resistance_value(resistance_list, type);
1210  max_res = std::max(max_res, type_res[type]);
1211  }
1212  }
1213  }
1214 
1215  if (type_res.empty()) return "";
1216 
1217  std::vector<std::string> type_list;
1218  for(auto& i : type_res){
1219  if(i.second == max_res){
1220  type_list.push_back(i.first);
1221  }
1222  }
1223  if(type_list.empty()) return "";
1224 
1225  return type_list.front();
1226 }
1227 
1228 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
1229 {
1230  bool is_alternative = (key_name == "alternative_type");
1231  if(is_alternative && other_){
1232  return select_alternative_type(damage_type_list, resistance_list, (*other_));
1233  } else if(!is_alternative){
1234  return select_replacement_type(damage_type_list);
1235  }
1236  return "";
1237 }
1238 
1239 /**
1240  * Returns the type of damage inflicted.
1241  */
1242 std::pair<std::string, std::string> attack_type::damage_type() const
1243 {
1244  unit_ability_list damage_type_list = get_specials_and_abilities("damage_type");
1245  if(damage_type_list.empty()){
1246  return {type(), ""};
1247  }
1248 
1249  unit_ability_list resistance_list;
1250  if(other_){
1251  resistance_list = (*other_).get_abilities_weapons("resistance", other_loc_, other_attack_, shared_from_this());
1252  }
1253  std::string replacement_type = select_damage_type(damage_type_list, "replacement_type", resistance_list);
1254  std::string alternative_type = select_damage_type(damage_type_list, "alternative_type", resistance_list);
1255  std::string type_damage = replacement_type.empty() ? type() : replacement_type;
1256  if(!alternative_type.empty() && type_damage != alternative_type){
1257  return {type_damage, alternative_type};
1258  }
1259  return {type_damage, ""};
1260 }
1261 
1262 std::set<std::string> attack_type::alternative_damage_types() const
1263 {
1264  unit_ability_list damage_alternative_type_list = get_specials_and_abilities("damage_type");
1265  if(damage_alternative_type_list.empty()){
1266  return {};
1267  }
1268  std::set<std::string> damage_types;
1269  for(auto& i : damage_alternative_type_list) {
1270  const config& c = *i.ability_cfg;
1271  damage_types.insert(c["alternative_type"].str());
1272  }
1273 
1274  return damage_types;
1275 }
1276 
1277 
1278 /**
1279  * Returns the damage per attack of this weapon, considering specials.
1280  */
1282 {
1283  int damage_value = composite_value(get_specials_and_abilities("damage"), damage());
1284  return damage_value;
1285 }
1286 
1287 
1288 namespace { // Helpers for attack_type::special_active()
1289 
1290  /**
1291  * Returns whether or not the given special affects the opponent of the unit
1292  * with the special.
1293  * @param[in] special a weapon special WML structure
1294  * @param[in] is_attacker whether or not the unit with the special is the attacker
1295  */
1296  bool special_affects_opponent(const config& special, bool is_attacker)
1297  {
1298  //log_scope("special_affects_opponent");
1299  const std::string& apply_to = special["apply_to"];
1300  if ( apply_to.empty() )
1301  return false;
1302  if ( apply_to == "both" )
1303  return true;
1304  if ( apply_to == "opponent" )
1305  return true;
1306  if ( is_attacker && apply_to == "defender" )
1307  return true;
1308  if ( !is_attacker && apply_to == "attacker" )
1309  return true;
1310  return false;
1311  }
1312 
1313  /**
1314  * Returns whether or not the given special affects the unit with the special.
1315  * @param[in] special a weapon special WML structure
1316  * @param[in] is_attacker whether or not the unit with the special is the attacker
1317  */
1318  bool special_affects_self(const config& special, bool is_attacker)
1319  {
1320  //log_scope("special_affects_self");
1321  const std::string& apply_to = special["apply_to"];
1322  if ( apply_to.empty() )
1323  return true;
1324  if ( apply_to == "both" )
1325  return true;
1326  if ( apply_to == "self" )
1327  return true;
1328  if ( is_attacker && apply_to == "attacker" )
1329  return true;
1330  if ( !is_attacker && apply_to == "defender")
1331  return true;
1332  return false;
1333  }
1334 
1335  /**
1336  * Determines if a unit/weapon combination matches the specified child
1337  * (normally a [filter_*] child) of the provided filter.
1338  * @param[in] u A unit to filter.
1339  * @param[in] u2 Another unit to filter.
1340  * @param[in] loc The presumed location of @a unit.
1341  * @param[in] weapon The attack_type to filter.
1342  * @param[in] filter The filter containing the child filter to use.
1343  * @param[in] for_listing
1344  * @param[in] child_tag The tag of the child filter to use.
1345  * @param[in] tag_name Parameter used for don't have infinite recusion for some filter attribute.
1346  */
1347  static bool special_unit_matches(unit_const_ptr & u,
1348  unit_const_ptr & u2,
1349  const map_location & loc,
1350  const_attack_ptr weapon,
1351  const config & filter,
1352  const bool for_listing,
1353  const std::string & child_tag, const std::string& tag_name)
1354  {
1355  if (for_listing && !loc.valid())
1356  // The special's context was set to ignore this unit, so assume we pass.
1357  // (This is used by reports.cpp to show active specials when the
1358  // opponent is not known. From a player's perspective, the special
1359  // is active, in that it can be used, even though the player might
1360  // need to select an appropriate opponent.)
1361  return true;
1362 
1363  auto filter_child = filter.optional_child(child_tag);
1364  if ( !filter_child )
1365  // The special does not filter on this unit, so we pass.
1366  return true;
1367 
1368  // If the primary unit doesn't exist, there's nothing to match
1369  if (!u) {
1370  return false;
1371  }
1372 
1373  unit_filter ufilt{vconfig(*filter_child)};
1374 
1375  // If the other unit doesn't exist, try matching without it
1376 
1377 
1378  // Check for a weapon match.
1379  if (auto filter_weapon = filter_child->optional_child("filter_weapon") ) {
1380  if ( !weapon || !weapon->matches_filter(*filter_weapon, tag_name) )
1381  return false;
1382  }
1383 
1384  // Passed.
1385  // If the other unit doesn't exist, try matching without it
1386  if (!u2) {
1387  return ufilt.matches(*u, loc);
1388  }
1389  return ufilt.matches(*u, loc, *u2);
1390  }
1391 
1392 }//anonymous namespace
1393 
1394 
1395 //The following functions are intended to allow the use in combat of capacities
1396 //identical to special weapons and therefore to be able to use them on adjacent
1397 //units (abilities of type 'aura') or else on all types of weapons even if the
1398 //beneficiary unit does not have a corresponding weapon
1399 //(defense against ranged weapons abilities for a unit that only has melee attacks)
1400 
1401 unit_ability_list attack_type::get_weapon_ability(const std::string& ability) const
1402 {
1403  const map_location loc = self_ ? self_->get_location() : self_loc_;
1404  unit_ability_list abil_list(loc);
1405  if(self_) {
1406  abil_list.append_if((*self_).get_abilities(ability, self_loc_), [&](const unit_ability& i) {
1407  return special_active(*i.ability_cfg, AFFECT_SELF, ability, "filter_student");
1408  });
1409  }
1410 
1411  if(other_) {
1412  abil_list.append_if((*other_).get_abilities(ability, other_loc_), [&](const unit_ability& i) {
1413  return special_active_impl(other_attack_, shared_from_this(), *i.ability_cfg, AFFECT_OTHER, ability, "filter_student");
1414  });
1415  }
1416 
1417  return abil_list;
1418 }
1419 
1421 {
1422  // get all weapon specials of the provided type
1423  unit_ability_list abil_list = get_specials(special);
1424  // append all such weapon specials as abilities as well
1425  abil_list.append(get_weapon_ability(special));
1426  // get a list of specials/"specials as abilities" that may potentially overwrite others
1427  unit_ability_list overwriters = overwrite_special_overwriter(abil_list, special);
1428  if(!abil_list.empty() && !overwriters.empty()){
1429  // remove all abilities that would be overwritten
1430  utils::erase_if(abil_list, [&](const unit_ability& j) {
1431  return (overwrite_special_checking(overwriters, *j.ability_cfg, special));
1432  });
1433  }
1434  return abil_list;
1435 }
1436 
1437 int attack_type::composite_value(const unit_ability_list& abil_list, int base_value) const
1438 {
1439  return unit_abilities::effect(abil_list, base_value, shared_from_this()).get_composite_value();
1440 }
1441 
1442 static bool overwrite_special_affects(const config& special)
1443 {
1444  const std::string& apply_to = special["overwrite_specials"];
1445  return (apply_to == "one_side" || apply_to == "both_sides");
1446 }
1447 
1449 {
1450  //remove element without overwrite_specials key, if list empty after check return empty list.
1451  utils::erase_if(overwriters, [&](const unit_ability& i) {
1452  return (!overwrite_special_affects(*i.ability_cfg));
1453  });
1454 
1455  // if empty, nothing is doing any overwriting
1456  if(overwriters.empty()){
1457  return overwriters;
1458  }
1459 
1460  // if there are specials/"specials as abilities" that could potentially overwrite each other
1461  if(overwriters.size() >= 2){
1462  // sort them by overwrite priority from highest to lowest (default priority is 0)
1463  utils::sort_if(overwriters,[](const unit_ability& i, const unit_ability& j){
1464  auto oi = (*i.ability_cfg).optional_child("overwrite");
1465  double l = 0;
1466  if(oi && !oi["priority"].empty()){
1467  l = oi["priority"].to_double(0);
1468  }
1469  auto oj = (*j.ability_cfg).optional_child("overwrite");
1470  double r = 0;
1471  if(oj && !oj["priority"].empty()){
1472  r = oj["priority"].to_double(0);
1473  }
1474  return l > r;
1475  });
1476  // remove any that need to be overwritten
1477  utils::erase_if(overwriters, [&](const unit_ability& i) {
1478  return (overwrite_special_checking(overwriters, *i.ability_cfg, tag_name));
1479  });
1480  }
1481  return overwriters;
1482 }
1483 
1484 bool attack_type::overwrite_special_checking(unit_ability_list& overwriters, const config& cfg, const std::string& tag_name) const
1485 {
1486  if(overwriters.empty()){
1487  return false;
1488  }
1489 
1490  for(const auto& j : overwriters) {
1491  // whether the overwriter affects a single side
1492  bool affect_side = ((*j.ability_cfg)["overwrite_specials"] == "one_side");
1493  // the overwriter's priority, default of 0
1494  auto overwrite_specials = (*j.ability_cfg).optional_child("overwrite");
1495  double priority = overwrite_specials ? overwrite_specials["priority"].to_double(0) : 0.00;
1496  // the cfg being checked for whether it will be overwritten
1497  auto has_overwrite_specials = cfg.optional_child("overwrite");
1498  // if the overwriter's priority is greater than 0, then true if the cfg being checked has a higher priority
1499  // else true
1500  bool prior = (priority > 0) ? (has_overwrite_specials && has_overwrite_specials["priority"].to_double(0) >= priority) : true;
1501  // 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
1502  // aka whether the cfg being checked can potentially be overwritten by the current overwriter
1503  bool is_overwritable = (overwrite_special_affects(cfg) && !prior) || !overwrite_special_affects(cfg);
1504  bool one_side_overwritable = true;
1505 
1506  // if the current overwriter affects one side and the cfg being checked can be overwritten by this overwriter
1507  // then check that the current overwriter and the cfg being checked both affect either this unit or its opponent
1508  if(affect_side && is_overwritable){
1509  if(special_affects_self(*j.ability_cfg, is_attacker_)){
1510  one_side_overwritable = special_affects_self(cfg, is_attacker_);
1511  }
1512  else if(special_affects_opponent(*j.ability_cfg, !is_attacker_)){
1513  one_side_overwritable = special_affects_opponent(cfg, !is_attacker_);
1514  }
1515  }
1516 
1517  // check whether the current overwriter is disabled due to a filter
1518  bool special_matches = true;
1519  if(overwrite_specials){
1520  auto overwrite_filter = (*overwrite_specials).optional_child("experimental_filter_specials");
1521  if(overwrite_filter && is_overwritable && one_side_overwritable){
1522  if(self_){
1523  special_matches = (*self_).ability_matches_filter(cfg, tag_name, *overwrite_filter);
1524  }
1525  }
1526  }
1527 
1528  // if the cfg being checked should be overwritten
1529  // and either this unit or its opponent are affected
1530  // and the current overwriter is not disabled due to a filter
1531  if(is_overwritable && one_side_overwritable && special_matches){
1532  return true;
1533  }
1534  }
1535  return false;
1536 }
1537 
1538  /**
1539  * Gets the children of parent (which should be the abilities for an
1540  * attack_type) and places the ones whose tag or id= matches @a id into
1541  * @a tag_result and @a id_result.
1542  * @param tag_result receive the children whose tag matches @a id
1543  * @param id_result receive the children whose id matches @a id
1544  * @param parent the tags whose contain children (abilities here)
1545  * @param id tag or id of child tested
1546  * @param special_id if true, children check by id
1547  * @param special_tags if true, children check by tags
1548  */
1549 static void get_ability_children(std::vector<special_match>& tag_result,
1550  std::vector<special_match>& id_result,
1551  const config& parent, const std::string& id,
1552  bool special_id=true, bool special_tags=true) {
1553  if(special_id && special_tags){
1554  get_special_children(tag_result, id_result, parent, id);
1555  } else if(special_id && !special_tags){
1556  get_special_children_id(id_result, parent, id);
1557  } else if(!special_id && special_tags){
1558  get_special_children_tags(tag_result, parent, id);
1559  }
1560 }
1561 
1562 bool unit::get_self_ability_bool(const config& special, const std::string& tag_name, const map_location& loc) const
1563 {
1564  return (ability_active(tag_name, special, loc) && ability_affects_self(tag_name, special, loc));
1565 }
1566 
1567 bool unit::get_adj_ability_bool(const config& special, const std::string& tag_name, int dir, const map_location& loc, const unit& from) const
1568 {
1569  const auto adjacent = get_adjacent_tiles(loc);
1570  return (affects_side(special, side(), from.side()) && from.ability_active(tag_name, special, adjacent[dir]) && ability_affects_adjacent(tag_name, special, dir, loc, from));
1571 }
1572 
1573 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
1574 {
1575  return (get_self_ability_bool(special, tag_name, loc) && ability_affects_weapon(special, weapon, false) && ability_affects_weapon(special, opp_weapon, true));
1576 }
1577 
1578 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
1579 {
1580  return (get_adj_ability_bool(special, tag_name, dir, loc, from) && ability_affects_weapon(special, weapon, false) && ability_affects_weapon(special, opp_weapon, true));
1581 }
1582 
1583 bool attack_type::check_self_abilities(const config& cfg, const std::string& special) const
1584 {
1585  return check_self_abilities_impl(shared_from_this(), other_attack_, cfg, self_, self_loc_, AFFECT_SELF, special, true);
1586 }
1587 
1588 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)
1589 {
1590  if(tag_name == "leadership" && leader_bool){
1591  if((*u).get_self_ability_bool_weapon(special, tag_name, loc, self_attack, other_attack)) {
1592  return true;
1593  }
1594  }
1595  if((*u).checking_tags().count(tag_name) != 0){
1596  if((*u).get_self_ability_bool(special, tag_name, loc) && special_active_impl(self_attack, other_attack, special, whom, tag_name, "filter_student")) {
1597  return true;
1598  }
1599  }
1600  return false;
1601 }
1602 
1603 bool attack_type::check_adj_abilities(const config& cfg, const std::string& special, int dir, const unit& from) const
1604 {
1605  return check_adj_abilities_impl(shared_from_this(), other_attack_, cfg, self_, from, dir, self_loc_, AFFECT_SELF, special, true);
1606 }
1607 
1608 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)
1609 {
1610  if(tag_name == "leadership" && leader_bool){
1611  if((*u).get_adj_ability_bool_weapon(special, tag_name, dir, loc, from, self_attack, other_attack)) {
1612  return true;
1613  }
1614  }
1615  if((*u).checking_tags().count(tag_name) != 0){
1616  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")) {
1617  return true;
1618  }
1619  }
1620  return false;
1621 }
1622 /**
1623  * Returns whether or not @a *this has a special ability with a tag or id equal to
1624  * @a special. the Check is for a special ability
1625  * active in the current context (see set_specials_context), including
1626  * specials obtained from the opponent's attack.
1627  */
1628 bool attack_type::has_weapon_ability(const std::string& special, bool special_id, bool special_tags) const
1629 {
1630  const unit_map& units = get_unit_map();
1631  if(self_){
1632  std::vector<special_match> special_tag_matches_self;
1633  std::vector<special_match> special_id_matches_self;
1634  get_ability_children(special_tag_matches_self, special_id_matches_self, (*self_).abilities(), special, special_id , special_tags);
1635  if(special_tags){
1636  for(const special_match& entry : special_tag_matches_self) {
1637  if(check_self_abilities(*entry.cfg, entry.tag_name)){
1638  return true;
1639  }
1640  }
1641  }
1642  if(special_id){
1643  for(const special_match& entry : special_id_matches_self) {
1644  if(check_self_abilities(*entry.cfg, entry.tag_name)){
1645  return true;
1646  }
1647  }
1648  }
1649 
1650  const auto adjacent = get_adjacent_tiles(self_loc_);
1651  for(unsigned i = 0; i < adjacent.size(); ++i) {
1652  const unit_map::const_iterator it = units.find(adjacent[i]);
1653  if (it == units.end() || it->incapacitated())
1654  continue;
1655  if ( &*it == self_.get() )
1656  continue;
1657 
1658  std::vector<special_match> special_tag_matches_adj;
1659  std::vector<special_match> special_id_matches_adj;
1660  get_ability_children(special_tag_matches_adj, special_id_matches_adj, it->abilities(), special, special_id , special_tags);
1661  if(special_tags){
1662  for(const special_match& entry : special_tag_matches_adj) {
1663  if(check_adj_abilities(*entry.cfg, entry.tag_name, i , *it)){
1664  return true;
1665  }
1666  }
1667  }
1668  if(special_id){
1669  for(const special_match& entry : special_id_matches_adj) {
1670  if(check_adj_abilities(*entry.cfg, entry.tag_name, i , *it)){
1671  return true;
1672  }
1673  }
1674  }
1675  }
1676  }
1677 
1678  if(other_){
1679  std::vector<special_match> special_tag_matches_other;
1680  std::vector<special_match> special_id_matches_other;
1681  get_ability_children(special_tag_matches_other, special_id_matches_other, (*other_).abilities(), special, special_id , special_tags);
1682  if(special_tags){
1683  for(const special_match& entry : special_tag_matches_other) {
1684  if(check_self_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, other_loc_, AFFECT_OTHER, entry.tag_name)){
1685  return true;
1686  }
1687  }
1688  }
1689 
1690  if(special_id){
1691  for(const special_match& entry : special_id_matches_other) {
1692  if(check_self_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, other_loc_, AFFECT_OTHER, entry.tag_name)){
1693  return true;
1694  }
1695  }
1696  }
1697 
1698  const auto adjacent = get_adjacent_tiles(other_loc_);
1699  for(unsigned i = 0; i < adjacent.size(); ++i) {
1700  const unit_map::const_iterator it = units.find(adjacent[i]);
1701  if (it == units.end() || it->incapacitated())
1702  continue;
1703  if ( &*it == other_.get() )
1704  continue;
1705 
1706  std::vector<special_match> special_tag_matches_oadj;
1707  std::vector<special_match> special_id_matches_oadj;
1708  get_ability_children(special_tag_matches_oadj, special_id_matches_oadj, it->abilities(), special, special_id , special_tags);
1709  if(special_tags){
1710  for(const special_match& entry : special_tag_matches_oadj) {
1711  if(check_adj_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, *it, i, other_loc_, AFFECT_OTHER, entry.tag_name)){
1712  return true;
1713  }
1714  }
1715  }
1716 
1717  if(special_id){
1718  for(const special_match& entry : special_id_matches_oadj) {
1719  if(check_adj_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, *it, i, other_loc_, AFFECT_OTHER, entry.tag_name)){
1720  return true;
1721  }
1722  }
1723  }
1724  }
1725  }
1726  return false;
1727 }
1728 
1729 bool attack_type::has_special_or_ability(const std::string& special, bool special_id, bool special_tags) const
1730 {
1731  //Now that filter_(second)attack in event supports special_id/type_active, including abilities used as weapons,
1732  //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.
1733  //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.
1734  if(range().empty()){
1735  return false;
1736  }
1737  return (has_special(special, false, special_id, special_tags) || has_weapon_ability(special, special_id, special_tags));
1738 }
1739 //end of emulate weapon special functions.
1740 
1741 bool attack_type::special_active(const config& special, AFFECTS whom, const std::string& tag_name,
1742  const std::string& filter_self) const
1743 {
1744  return special_active_impl(shared_from_this(), other_attack_, special, whom, tag_name, filter_self);
1745 }
1746 
1747 /**
1748  * Returns whether or not the given special is active for the specified unit,
1749  * based on the current context (see set_specials_context).
1750  * @param self_attack this unit's attack
1751  * @param other_attack the other unit's attack
1752  * @param special a weapon special WML structure
1753  * @param whom specifies which combatant we care about
1754  * @param tag_name tag name of the special config
1755  * @param filter_self the filter to use
1756  */
1758  const_attack_ptr self_attack,
1759  const_attack_ptr other_attack,
1760  const config& special,
1761  AFFECTS whom,
1762  const std::string& tag_name,
1763  const std::string& filter_self)
1764 {
1765  assert(self_attack || other_attack);
1766  bool is_attacker = self_attack ? self_attack->is_attacker_ : !other_attack->is_attacker_;
1767  bool is_for_listing = self_attack ? self_attack->is_for_listing_ : other_attack->is_for_listing_;
1768  //log_scope("special_active");
1769 
1770 
1771  // Does this affect the specified unit?
1772  if ( whom == AFFECT_SELF ) {
1773  if ( !special_affects_self(special, is_attacker) )
1774  return false;
1775  }
1776  if ( whom == AFFECT_OTHER ) {
1777  if ( !special_affects_opponent(special, is_attacker) )
1778  return false;
1779  }
1780 
1781  // Is this active on attack/defense?
1782  const std::string & active_on = special["active_on"];
1783  if ( !active_on.empty() ) {
1784  if ( is_attacker && active_on != "offense" )
1785  return false;
1786  if ( !is_attacker && active_on != "defense" )
1787  return false;
1788  }
1789 
1790  // Get the units involved.
1791  const unit_map& units = get_unit_map();
1792 
1793  unit_const_ptr self = self_attack ? self_attack->self_ : other_attack->other_;
1794  unit_const_ptr other = self_attack ? self_attack->other_ : other_attack->self_;
1795  map_location self_loc = self_attack ? self_attack->self_loc_ : other_attack->other_loc_;
1796  map_location other_loc = self_attack ? self_attack->other_loc_ : other_attack->self_loc_;
1797  //TODO: why is this needed?
1798  if(self == nullptr) {
1799  unit_map::const_iterator it = units.find(self_loc);
1800  if(it.valid()) {
1801  self = it.get_shared_ptr();
1802  }
1803  }
1804  if(other == nullptr) {
1805  unit_map::const_iterator it = units.find(other_loc);
1806  if(it.valid()) {
1807  other = it.get_shared_ptr();
1808  }
1809  }
1810 
1811  // Make sure they're facing each other.
1812  temporary_facing self_facing(self, self_loc.get_relative_dir(other_loc));
1813  temporary_facing other_facing(other, other_loc.get_relative_dir(self_loc));
1814 
1815  // Filter poison, plague, drain, slow, petrifies
1816  // True if "whom" corresponds to "self", false if "whom" is "other"
1817  bool whom_is_self = ((whom == AFFECT_SELF) || ((whom == AFFECT_EITHER) && special_affects_self(special, is_attacker)));
1818  unit_const_ptr them = whom_is_self ? other : self;
1819  map_location their_loc = whom_is_self ? other_loc : self_loc;
1820 
1821  if (tag_name == "drains" && them && them->get_state("undrainable")) {
1822  return false;
1823  }
1824  if (tag_name == "plague" && them &&
1825  (them->get_state("unplagueable") ||
1826  resources::gameboard->map().is_village(their_loc))) {
1827  return false;
1828  }
1829  if (tag_name == "poison" && them &&
1830  (them->get_state("unpoisonable") || them->get_state(unit::STATE_POISONED))) {
1831  return false;
1832  }
1833  if (tag_name == "slow" && them &&
1834  (them->get_state("unslowable") || them->get_state(unit::STATE_SLOWED))) {
1835  return false;
1836  }
1837  if (tag_name == "petrifies" && them &&
1838  them->get_state("unpetrifiable")) {
1839  return false;
1840  }
1841 
1842 
1843  // Translate our context into terms of "attacker" and "defender".
1844  unit_const_ptr & att = is_attacker ? self : other;
1845  unit_const_ptr & def = is_attacker ? other : self;
1846  const map_location & att_loc = is_attacker ? self_loc : other_loc;
1847  const map_location & def_loc = is_attacker ? other_loc : self_loc;
1848  const_attack_ptr att_weapon = is_attacker ? self_attack : other_attack;
1849  const_attack_ptr def_weapon = is_attacker ? other_attack : self_attack;
1850 
1851  // Filter firststrike here, if both units have first strike then the effects cancel out. Only check
1852  // the opponent if "whom" is the defender, otherwise this leads to infinite recursion.
1853  if (tag_name == "firststrike") {
1854  bool whom_is_defender = whom_is_self ? !is_attacker : is_attacker;
1855  if (whom_is_defender && att_weapon && att_weapon->has_special_or_ability("firststrike"))
1856  return false;
1857  }
1858 
1859  //Add wml filter if "backstab" attribute used.
1860  if (!special["backstab"].blank()) {
1861  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.");
1862  }
1863  config cfg = special;
1864  if(special["backstab"].to_bool()){
1865  const std::string& backstab_formula = "enemy_of(self, flanker) and not flanker.petrified where flanker = unit_at(direction_from(loc, other.facing))";
1866  config& filter_child = cfg.child_or_add("filter_opponent");
1867  if(!special.has_child("filter_opponent")){
1868  filter_child["formula"] = backstab_formula;
1869  } else {
1870  config filter;
1871  filter["formula"] = backstab_formula;
1872  filter_child.add_child("and", filter);
1873  }
1874  }
1875  const config& special_backstab = special["backstab"].to_bool() ? cfg : special;
1876 
1877  // Filter the units involved.
1878  //If filter concerns the unit on which special is applied,
1879  //then the type of special must be entered to avoid calling
1880  //the function of this special in matches_filter()
1881  std::string self_tag_name = whom_is_self ? tag_name : "";
1882  if (!special_unit_matches(self, other, self_loc, self_attack, special, is_for_listing, filter_self, self_tag_name))
1883  return false;
1884  std::string opp_tag_name = !whom_is_self ? tag_name : "";
1885  if (!special_unit_matches(other, self, other_loc, other_attack, special_backstab, is_for_listing, "filter_opponent", opp_tag_name))
1886  return false;
1887  std::string att_tag_name = is_attacker ? tag_name : "";
1888  if (!special_unit_matches(att, def, att_loc, att_weapon, special, is_for_listing, "filter_attacker", att_tag_name))
1889  return false;
1890  std::string def_tag_name = !is_attacker ? tag_name : "";
1891  if (!special_unit_matches(def, att, def_loc, def_weapon, special, is_for_listing, "filter_defender", def_tag_name))
1892  return false;
1893 
1894  const auto adjacent = get_adjacent_tiles(self_loc);
1895 
1896  // Filter the adjacent units.
1897  for (const config &i : special.child_range("filter_adjacent"))
1898  {
1899  std::size_t count = 0;
1900  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
1901  unit_filter filter{ vconfig(i) };
1902  for (const map_location::DIRECTION index : dirs)
1903  {
1905  continue;
1906  unit_map::const_iterator unit = units.find(adjacent[index]);
1907  if (unit == units.end() || !filter.matches(*unit, adjacent[index], *self))
1908  return false;
1909  if (i.has_attribute("is_enemy")) {
1911  if (i["is_enemy"].to_bool() != dc.get_team(unit->side()).is_enemy(self->side())) {
1912  continue;
1913  }
1914  }
1915  count++;
1916  }
1917  if (i["count"].empty() && count != dirs.size()) {
1918  return false;
1919  }
1920  if (!in_ranges<int>(count, utils::parse_ranges_unsigned(i["count"].str()))) {
1921  return false;
1922  }
1923  }
1924 
1925  // Filter the adjacent locations.
1926  for (const config &i : special.child_range("filter_adjacent_location"))
1927  {
1928  std::size_t count = 0;
1929  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
1930  terrain_filter adj_filter(vconfig(i), resources::filter_con, false);
1931  for (const map_location::DIRECTION index : dirs)
1932  {
1934  continue;
1935  if(!adj_filter.match(adjacent[index])) {
1936  return false;
1937  }
1938  count++;
1939  }
1940  if (i["count"].empty() && count != dirs.size()) {
1941  return false;
1942  }
1943  if (!in_ranges<int>(count, utils::parse_ranges_unsigned(i["count"].str()))) {
1944  return false;
1945  }
1946  }
1947 
1948  return true;
1949 }
1950 
1951 
1952 
1954 {
1955 
1956 void individual_effect::set(value_modifier t, int val, const config *abil, const map_location &l)
1957 {
1958  type=t;
1959  value=val;
1960  ability=abil;
1961  loc=l;
1962 }
1963 
1964 bool filter_base_matches(const config& cfg, int def)
1965 {
1966  if (auto apply_filter = cfg.optional_child("filter_base_value")) {
1967  config::attribute_value cond_eq = apply_filter["equals"];
1968  config::attribute_value cond_ne = apply_filter["not_equals"];
1969  config::attribute_value cond_lt = apply_filter["less_than"];
1970  config::attribute_value cond_gt = apply_filter["greater_than"];
1971  config::attribute_value cond_ge = apply_filter["greater_than_equal_to"];
1972  config::attribute_value cond_le = apply_filter["less_than_equal_to"];
1973  return (cond_eq.empty() || def == cond_eq.to_int()) &&
1974  (cond_ne.empty() || def != cond_ne.to_int()) &&
1975  (cond_lt.empty() || def < cond_lt.to_int()) &&
1976  (cond_gt.empty() || def > cond_gt.to_int()) &&
1977  (cond_ge.empty() || def >= cond_ge.to_int()) &&
1978  (cond_le.empty() || def <= cond_le.to_int());
1979  }
1980  return true;
1981 }
1982 
1983 effect::effect(const unit_ability_list& list, int def, const_attack_ptr att, EFFECTS wham) :
1984  effect_list_(),
1985  composite_value_(0)
1986 {
1987 
1988  int value_set = (wham == EFFECT_CUMULABLE) ? std::max(list.highest("value").first, 0) + std::min(list.lowest("value").first, 0) : def;
1989  std::map<std::string,individual_effect> values_add;
1990  std::map<std::string,individual_effect> values_mul;
1991  std::map<std::string,individual_effect> values_div;
1992 
1993  individual_effect set_effect_max;
1994  individual_effect set_effect_min;
1995  std::optional<int> max_value = std::nullopt;
1996  std::optional<int> min_value = std::nullopt;
1997 
1998  for (const unit_ability & ability : list) {
1999  const config& cfg = *ability.ability_cfg;
2000  const std::string& effect_id = cfg[cfg["id"].empty() ? "name" : "id"];
2001 
2002  if (!filter_base_matches(cfg, def))
2003  continue;
2004 
2005  if(wham != EFFECT_CUMULABLE){
2006  if (const config::attribute_value *v = cfg.get("value")) {
2007  int value = get_single_ability_value(*v, def, ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2008  callable.add("base_value", wfl::variant(def));
2009  return formula.evaluate(callable).as_int();
2010  });
2011 
2012  int value_cum = cfg["cumulative"].to_bool() ? std::max(def, value) : value;
2013  assert((set_effect_min.type != NOT_USED) == (set_effect_max.type != NOT_USED));
2014  if(set_effect_min.type == NOT_USED) {
2015  set_effect_min.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2016  set_effect_max.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2017  }
2018  else {
2019  if(value_cum > set_effect_max.value) {
2020  set_effect_max.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2021  }
2022  if(value_cum < set_effect_min.value) {
2023  set_effect_min.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2024  }
2025  }
2026  }
2027  }
2028 
2029  if(wham == EFFECT_CLAMP_MIN_MAX){
2030  if(cfg.has_attribute("max_value")){
2031  max_value = max_value ? std::min(*max_value, cfg["max_value"].to_int()) : cfg["max_value"].to_int();
2032  }
2033  if(cfg.has_attribute("min_value")){
2034  min_value = min_value ? std::max(*min_value, cfg["min_value"].to_int()) : cfg["min_value"].to_int();
2035  }
2036  }
2037 
2038  if (const config::attribute_value *v = cfg.get("add")) {
2039  int add = get_single_ability_value(*v, def, ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2040  callable.add("base_value", wfl::variant(def));
2041  return formula.evaluate(callable).as_int();
2042  });
2043  std::map<std::string,individual_effect>::iterator add_effect = values_add.find(effect_id);
2044  if(add_effect == values_add.end() || add > add_effect->second.value) {
2045  values_add[effect_id].set(ADD, add, ability.ability_cfg, ability.teacher_loc);
2046  }
2047  }
2048  if (const config::attribute_value *v = cfg.get("sub")) {
2049  int sub = - get_single_ability_value(*v, def, ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2050  callable.add("base_value", wfl::variant(def));
2051  return formula.evaluate(callable).as_int();
2052  });
2053  std::map<std::string,individual_effect>::iterator sub_effect = values_add.find(effect_id);
2054  if(sub_effect == values_add.end() || sub < sub_effect->second.value) {
2055  values_add[effect_id].set(ADD, sub, ability.ability_cfg, ability.teacher_loc);
2056  }
2057  }
2058  if (const config::attribute_value *v = cfg.get("multiply")) {
2059  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) {
2060  callable.add("base_value", wfl::variant(def));
2061  return formula.evaluate(callable).as_decimal() / 1000.0 ;
2062  }) * 100);
2063  std::map<std::string,individual_effect>::iterator mul_effect = values_mul.find(effect_id);
2064  if(mul_effect == values_mul.end() || multiply > mul_effect->second.value) {
2065  values_mul[effect_id].set(MUL, multiply, ability.ability_cfg, ability.teacher_loc);
2066  }
2067  }
2068  if (const config::attribute_value *v = cfg.get("divide")) {
2069  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) {
2070  callable.add("base_value", wfl::variant(def));
2071  return formula.evaluate(callable).as_decimal() / 1000.0 ;
2072  }) * 100);
2073 
2074  if (divide == 0) {
2075  ERR_NG << "division by zero with divide= in ability/weapon special " << effect_id;
2076  }
2077  else {
2078  std::map<std::string,individual_effect>::iterator div_effect = values_div.find(effect_id);
2079  if(div_effect == values_div.end() || divide > div_effect->second.value) {
2080  values_div[effect_id].set(DIV, divide, ability.ability_cfg, ability.teacher_loc);
2081  }
2082  }
2083  }
2084  }
2085 
2086  if((wham != EFFECT_CUMULABLE) && set_effect_max.type != NOT_USED) {
2087  value_set = std::max(set_effect_max.value, 0) + std::min(set_effect_min.value, 0);
2088  if(set_effect_max.value > def) {
2089  effect_list_.push_back(set_effect_max);
2090  }
2091  if(set_effect_min.value < def) {
2092  effect_list_.push_back(set_effect_min);
2093  }
2094  }
2095 
2096  /* Do multiplication with floating point values rather than integers
2097  * We want two places of precision for each multiplier
2098  * Using integers multiplied by 100 to keep precision causes overflow
2099  * after 3-4 abilities for 32-bit values and ~8 for 64-bit
2100  * Avoiding the overflow by dividing after each step introduces rounding errors
2101  * that may vary depending on the order effects are applied
2102  * As the final values are likely <1000 (always true for mainline), loss of less significant digits is not an issue
2103  */
2104  double multiplier = 1.0;
2105  double divisor = 1.0;
2106 
2107  for(const auto& val : values_mul) {
2108  multiplier *= val.second.value/100.0;
2109  effect_list_.push_back(val.second);
2110  }
2111 
2112  for(const auto& val : values_div) {
2113  divisor *= val.second.value/100.0;
2114  effect_list_.push_back(val.second);
2115  }
2116 
2117  int addition = 0;
2118  for(const auto& val : values_add) {
2119  addition += val.second.value;
2120  effect_list_.push_back(val.second);
2121  }
2122 
2123  composite_value_ = static_cast<int>((value_set + addition) * multiplier / divisor);
2124  //clamp what if min_value < max_value or one attribute only used.
2125  if(max_value && min_value && *min_value < *max_value) {
2126  composite_value_ = std::clamp(*min_value, *max_value, composite_value_);
2127  } else if(max_value && !min_value) {
2128  composite_value_ = std::min(*max_value, composite_value_);
2129  } else if(min_value && !max_value) {
2130  composite_value_ = std::max(*min_value, composite_value_);
2131  }
2132 }
2133 
2134 } // end namespace unit_abilities
static std::string select_replacement_type(const unit_ability_list &damage_type_list)
Definition: abilities.cpp:1171
static void add_name(std::string &temp_string, bool active, const std::string name, std::set< std::string > &checking_name)
Definition: abilities.cpp:893
static std::string select_alternative_type(const unit_ability_list &damage_type_list, unit_ability_list resistance_list, const unit &u)
Definition: abilities.cpp:1200
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:479
#define ERR_WML
Definition: abilities.cpp:50
static bool overwrite_special_affects(const config &special)
Definition: abilities.cpp:1442
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:943
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:1549
double t
Definition: astarsearch.cpp:63
specials_context_t(const attack_type &weapon, bool attacking)
Initialize weapon specials context for listing.
Definition: abilities.cpp:1118
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:911
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:1583
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:752
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:1757
int composite_value(const unit_ability_list &abil_list, int base_value) const
Return the special weapon value, considering specials.
Definition: abilities.cpp:1437
const_attack_ptr other_attack_
void add_formula_context(wfl::map_formula_callable &) const
Definition: abilities.cpp:527
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:1401
std::string weapon_specials_value(const std::set< std::string > checking_tags) const
Definition: abilities.cpp:954
unit_ability_list get_specials_and_abilities(const std::string &special) const
Definition: abilities.cpp:1420
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:1484
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:1603
config specials_
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:1448
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:1608
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:1014
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:857
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:994
const t_string & name() const
Definition: attack_type.hpp:41
std::set< std::string > alternative_damage_types() const
Definition: abilities.cpp:1262
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:1588
int modified_damage() const
Returns the damage per attack of this weapon, considering specials.
Definition: abilities.cpp:1281
void modified_attacks(unsigned &min_attacks, unsigned &max_attacks) const
Calculates the number of attacks this weapon has, considering specials.
Definition: abilities.cpp:1150
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:1741
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:824
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:1228
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:1729
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:1628
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:1242
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
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)
Euivalent 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:149
const display_context & get_disp_context() const
Definition: display.hpp:190
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:102
virtual const display_context & get_disp_context() const =0
team & get_team(int i)
Definition: game_board.hpp:91
virtual const unit_map & units() const override
Definition: game_board.hpp:106
virtual const gamemap & map() const override
Definition: game_board.hpp:96
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
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
std::vector< unit_ability > cfgs_
Definition: unit.hpp:125
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:603
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
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:29
std::size_t i
Definition: function.cpp:968
Interfaces for manipulating version numbers of engine, add-ons, etc.
static std::string _(const char *str)
Definition: gettext.hpp:93
#define UNUSED(x)
Definition: global.hpp:31
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:1573
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:1578
bool ability_active(const std::string &ability, const config &cfg, const map_location &loc) const
Check if an ability is active.
Definition: abilities.cpp:352
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:451
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:424
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:2040
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:1976
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:459
bool has_ability_type(const std::string &ability) const
Check if the unit has an ability of a specific type.
Definition: abilities.cpp:472
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:1567
unit_race::GENDER gender_
Definition: unit.hpp:1978
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:1939
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:1562
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
@ 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:1806
const std::vector< std::string > halo_or_icon_abilities(const std::string &image_type) const
Definition: abilities.cpp:487
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:544
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:1964
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
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::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:684
config & cfg
Definition: config.hpp:685
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:1956
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