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_type_list = get_specials_and_abilities("damage_type");
1265  if(damage_type_list.empty()){
1266  return {};
1267  }
1268  std::set<std::string> damage_types;
1269  for(auto& i : damage_type_list) {
1270  const config& c = *i.ability_cfg;
1271  if(c.has_attribute("alternative_type")){
1272  damage_types.insert(c["alternative_type"].str());
1273  }
1274  }
1275 
1276  return damage_types;
1277 }
1278 
1279 
1280 /**
1281  * Returns the damage per attack of this weapon, considering specials.
1282  */
1284 {
1285  int damage_value = composite_value(get_specials_and_abilities("damage"), damage());
1286  return damage_value;
1287 }
1288 
1289 
1290 namespace { // Helpers for attack_type::special_active()
1291 
1292  /**
1293  * Returns whether or not the given special affects the opponent of the unit
1294  * with the special.
1295  * @param[in] special a weapon special WML structure
1296  * @param[in] is_attacker whether or not the unit with the special is the attacker
1297  */
1298  bool special_affects_opponent(const config& special, bool is_attacker)
1299  {
1300  //log_scope("special_affects_opponent");
1301  const std::string& apply_to = special["apply_to"];
1302  if ( apply_to.empty() )
1303  return false;
1304  if ( apply_to == "both" )
1305  return true;
1306  if ( apply_to == "opponent" )
1307  return true;
1308  if ( is_attacker && apply_to == "defender" )
1309  return true;
1310  if ( !is_attacker && apply_to == "attacker" )
1311  return true;
1312  return false;
1313  }
1314 
1315  /**
1316  * Returns whether or not the given special affects the unit with the special.
1317  * @param[in] special a weapon special WML structure
1318  * @param[in] is_attacker whether or not the unit with the special is the attacker
1319  */
1320  bool special_affects_self(const config& special, bool is_attacker)
1321  {
1322  //log_scope("special_affects_self");
1323  const std::string& apply_to = special["apply_to"];
1324  if ( apply_to.empty() )
1325  return true;
1326  if ( apply_to == "both" )
1327  return true;
1328  if ( apply_to == "self" )
1329  return true;
1330  if ( is_attacker && apply_to == "attacker" )
1331  return true;
1332  if ( !is_attacker && apply_to == "defender")
1333  return true;
1334  return false;
1335  }
1336 
1337  /**
1338  * Determines if a unit/weapon combination matches the specified child
1339  * (normally a [filter_*] child) of the provided filter.
1340  * @param[in] u A unit to filter.
1341  * @param[in] u2 Another unit to filter.
1342  * @param[in] loc The presumed location of @a unit.
1343  * @param[in] weapon The attack_type to filter.
1344  * @param[in] filter The filter containing the child filter to use.
1345  * @param[in] for_listing
1346  * @param[in] child_tag The tag of the child filter to use.
1347  * @param[in] tag_name Parameter used for don't have infinite recusion for some filter attribute.
1348  */
1349  static bool special_unit_matches(unit_const_ptr & u,
1350  unit_const_ptr & u2,
1351  const map_location & loc,
1352  const_attack_ptr weapon,
1353  const config & filter,
1354  const bool for_listing,
1355  const std::string & child_tag, const std::string& tag_name)
1356  {
1357  if (for_listing && !loc.valid())
1358  // The special's context was set to ignore this unit, so assume we pass.
1359  // (This is used by reports.cpp to show active specials when the
1360  // opponent is not known. From a player's perspective, the special
1361  // is active, in that it can be used, even though the player might
1362  // need to select an appropriate opponent.)
1363  return true;
1364 
1365  auto filter_child = filter.optional_child(child_tag);
1366  if ( !filter_child )
1367  // The special does not filter on this unit, so we pass.
1368  return true;
1369 
1370  // If the primary unit doesn't exist, there's nothing to match
1371  if (!u) {
1372  return false;
1373  }
1374 
1375  unit_filter ufilt{vconfig(*filter_child)};
1376 
1377  // If the other unit doesn't exist, try matching without it
1378 
1379 
1380  // Check for a weapon match.
1381  if (auto filter_weapon = filter_child->optional_child("filter_weapon") ) {
1382  if ( !weapon || !weapon->matches_filter(*filter_weapon, tag_name) )
1383  return false;
1384  }
1385 
1386  // Passed.
1387  // If the other unit doesn't exist, try matching without it
1388  if (!u2) {
1389  return ufilt.matches(*u, loc);
1390  }
1391  return ufilt.matches(*u, loc, *u2);
1392  }
1393 
1394 }//anonymous namespace
1395 
1396 
1397 //The following functions are intended to allow the use in combat of capacities
1398 //identical to special weapons and therefore to be able to use them on adjacent
1399 //units (abilities of type 'aura') or else on all types of weapons even if the
1400 //beneficiary unit does not have a corresponding weapon
1401 //(defense against ranged weapons abilities for a unit that only has melee attacks)
1402 
1403 unit_ability_list attack_type::get_weapon_ability(const std::string& ability) const
1404 {
1405  const map_location loc = self_ ? self_->get_location() : self_loc_;
1406  unit_ability_list abil_list(loc);
1407  if(self_) {
1408  abil_list.append_if((*self_).get_abilities(ability, self_loc_), [&](const unit_ability& i) {
1409  return special_active(*i.ability_cfg, AFFECT_SELF, ability, "filter_student");
1410  });
1411  }
1412 
1413  if(other_) {
1414  abil_list.append_if((*other_).get_abilities(ability, other_loc_), [&](const unit_ability& i) {
1415  return special_active_impl(other_attack_, shared_from_this(), *i.ability_cfg, AFFECT_OTHER, ability, "filter_student");
1416  });
1417  }
1418 
1419  return abil_list;
1420 }
1421 
1423 {
1424  // get all weapon specials of the provided type
1425  unit_ability_list abil_list = get_specials(special);
1426  // append all such weapon specials as abilities as well
1427  abil_list.append(get_weapon_ability(special));
1428  // get a list of specials/"specials as abilities" that may potentially overwrite others
1429  unit_ability_list overwriters = overwrite_special_overwriter(abil_list, special);
1430  if(!abil_list.empty() && !overwriters.empty()){
1431  // remove all abilities that would be overwritten
1432  utils::erase_if(abil_list, [&](const unit_ability& j) {
1433  return (overwrite_special_checking(overwriters, *j.ability_cfg, special));
1434  });
1435  }
1436  return abil_list;
1437 }
1438 
1439 int attack_type::composite_value(const unit_ability_list& abil_list, int base_value) const
1440 {
1441  return unit_abilities::effect(abil_list, base_value, shared_from_this()).get_composite_value();
1442 }
1443 
1444 static bool overwrite_special_affects(const config& special)
1445 {
1446  const std::string& apply_to = special["overwrite_specials"];
1447  return (apply_to == "one_side" || apply_to == "both_sides");
1448 }
1449 
1451 {
1452  //remove element without overwrite_specials key, if list empty after check return empty list.
1453  utils::erase_if(overwriters, [&](const unit_ability& i) {
1454  return (!overwrite_special_affects(*i.ability_cfg));
1455  });
1456 
1457  // if empty, nothing is doing any overwriting
1458  if(overwriters.empty()){
1459  return overwriters;
1460  }
1461 
1462  // if there are specials/"specials as abilities" that could potentially overwrite each other
1463  if(overwriters.size() >= 2){
1464  // sort them by overwrite priority from highest to lowest (default priority is 0)
1465  utils::sort_if(overwriters,[](const unit_ability& i, const unit_ability& j){
1466  auto oi = (*i.ability_cfg).optional_child("overwrite");
1467  double l = 0;
1468  if(oi && !oi["priority"].empty()){
1469  l = oi["priority"].to_double(0);
1470  }
1471  auto oj = (*j.ability_cfg).optional_child("overwrite");
1472  double r = 0;
1473  if(oj && !oj["priority"].empty()){
1474  r = oj["priority"].to_double(0);
1475  }
1476  return l > r;
1477  });
1478  // remove any that need to be overwritten
1479  utils::erase_if(overwriters, [&](const unit_ability& i) {
1480  return (overwrite_special_checking(overwriters, *i.ability_cfg, tag_name));
1481  });
1482  }
1483  return overwriters;
1484 }
1485 
1486 bool attack_type::overwrite_special_checking(unit_ability_list& overwriters, const config& cfg, const std::string& tag_name) const
1487 {
1488  if(overwriters.empty()){
1489  return false;
1490  }
1491 
1492  for(const auto& j : overwriters) {
1493  // whether the overwriter affects a single side
1494  bool affect_side = ((*j.ability_cfg)["overwrite_specials"] == "one_side");
1495  // the overwriter's priority, default of 0
1496  auto overwrite_specials = (*j.ability_cfg).optional_child("overwrite");
1497  double priority = overwrite_specials ? overwrite_specials["priority"].to_double(0) : 0.00;
1498  // the cfg being checked for whether it will be overwritten
1499  auto has_overwrite_specials = cfg.optional_child("overwrite");
1500  // if the overwriter's priority is greater than 0, then true if the cfg being checked has a higher priority
1501  // else true
1502  bool prior = (priority > 0) ? (has_overwrite_specials && has_overwrite_specials["priority"].to_double(0) >= priority) : true;
1503  // 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
1504  // aka whether the cfg being checked can potentially be overwritten by the current overwriter
1505  bool is_overwritable = (overwrite_special_affects(cfg) && !prior) || !overwrite_special_affects(cfg);
1506  bool one_side_overwritable = true;
1507 
1508  // if the current overwriter affects one side and the cfg being checked can be overwritten by this overwriter
1509  // then check that the current overwriter and the cfg being checked both affect either this unit or its opponent
1510  if(affect_side && is_overwritable){
1511  if(special_affects_self(*j.ability_cfg, is_attacker_)){
1512  one_side_overwritable = special_affects_self(cfg, is_attacker_);
1513  }
1514  else if(special_affects_opponent(*j.ability_cfg, !is_attacker_)){
1515  one_side_overwritable = special_affects_opponent(cfg, !is_attacker_);
1516  }
1517  }
1518 
1519  // check whether the current overwriter is disabled due to a filter
1520  bool special_matches = true;
1521  if(overwrite_specials){
1522  auto overwrite_filter = (*overwrite_specials).optional_child("experimental_filter_specials");
1523  if(overwrite_filter && is_overwritable && one_side_overwritable){
1524  if(self_){
1525  special_matches = (*self_).ability_matches_filter(cfg, tag_name, *overwrite_filter);
1526  }
1527  }
1528  }
1529 
1530  // if the cfg being checked should be overwritten
1531  // and either this unit or its opponent are affected
1532  // and the current overwriter is not disabled due to a filter
1533  if(is_overwritable && one_side_overwritable && special_matches){
1534  return true;
1535  }
1536  }
1537  return false;
1538 }
1539 
1540  /**
1541  * Gets the children of parent (which should be the abilities for an
1542  * attack_type) and places the ones whose tag or id= matches @a id into
1543  * @a tag_result and @a id_result.
1544  * @param tag_result receive the children whose tag matches @a id
1545  * @param id_result receive the children whose id matches @a id
1546  * @param parent the tags whose contain children (abilities here)
1547  * @param id tag or id of child tested
1548  * @param special_id if true, children check by id
1549  * @param special_tags if true, children check by tags
1550  */
1551 static void get_ability_children(std::vector<special_match>& tag_result,
1552  std::vector<special_match>& id_result,
1553  const config& parent, const std::string& id,
1554  bool special_id=true, bool special_tags=true) {
1555  if(special_id && special_tags){
1556  get_special_children(tag_result, id_result, parent, id);
1557  } else if(special_id && !special_tags){
1558  get_special_children_id(id_result, parent, id);
1559  } else if(!special_id && special_tags){
1560  get_special_children_tags(tag_result, parent, id);
1561  }
1562 }
1563 
1564 bool unit::get_self_ability_bool(const config& special, const std::string& tag_name, const map_location& loc) const
1565 {
1566  return (ability_active(tag_name, special, loc) && ability_affects_self(tag_name, special, loc));
1567 }
1568 
1569 bool unit::get_adj_ability_bool(const config& special, const std::string& tag_name, int dir, const map_location& loc, const unit& from) const
1570 {
1571  const auto adjacent = get_adjacent_tiles(loc);
1572  return (affects_side(special, side(), from.side()) && from.ability_active(tag_name, special, adjacent[dir]) && ability_affects_adjacent(tag_name, special, dir, loc, from));
1573 }
1574 
1575 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
1576 {
1577  return (get_self_ability_bool(special, tag_name, loc) && ability_affects_weapon(special, weapon, false) && ability_affects_weapon(special, opp_weapon, true));
1578 }
1579 
1580 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
1581 {
1582  return (get_adj_ability_bool(special, tag_name, dir, loc, from) && ability_affects_weapon(special, weapon, false) && ability_affects_weapon(special, opp_weapon, true));
1583 }
1584 
1585 bool attack_type::check_self_abilities(const config& cfg, const std::string& special) const
1586 {
1587  return check_self_abilities_impl(shared_from_this(), other_attack_, cfg, self_, self_loc_, AFFECT_SELF, special, true);
1588 }
1589 
1590 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)
1591 {
1592  if(tag_name == "leadership" && leader_bool){
1593  if((*u).get_self_ability_bool_weapon(special, tag_name, loc, self_attack, other_attack)) {
1594  return true;
1595  }
1596  }
1597  if((*u).checking_tags().count(tag_name) != 0){
1598  if((*u).get_self_ability_bool(special, tag_name, loc) && special_active_impl(self_attack, other_attack, special, whom, tag_name, "filter_student")) {
1599  return true;
1600  }
1601  }
1602  return false;
1603 }
1604 
1605 bool attack_type::check_adj_abilities(const config& cfg, const std::string& special, int dir, const unit& from) const
1606 {
1607  return check_adj_abilities_impl(shared_from_this(), other_attack_, cfg, self_, from, dir, self_loc_, AFFECT_SELF, special, true);
1608 }
1609 
1610 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)
1611 {
1612  if(tag_name == "leadership" && leader_bool){
1613  if((*u).get_adj_ability_bool_weapon(special, tag_name, dir, loc, from, self_attack, other_attack)) {
1614  return true;
1615  }
1616  }
1617  if((*u).checking_tags().count(tag_name) != 0){
1618  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")) {
1619  return true;
1620  }
1621  }
1622  return false;
1623 }
1624 /**
1625  * Returns whether or not @a *this has a special ability with a tag or id equal to
1626  * @a special. the Check is for a special ability
1627  * active in the current context (see set_specials_context), including
1628  * specials obtained from the opponent's attack.
1629  */
1630 bool attack_type::has_weapon_ability(const std::string& special, bool special_id, bool special_tags) const
1631 {
1632  const unit_map& units = get_unit_map();
1633  if(self_){
1634  std::vector<special_match> special_tag_matches_self;
1635  std::vector<special_match> special_id_matches_self;
1636  get_ability_children(special_tag_matches_self, special_id_matches_self, (*self_).abilities(), special, special_id , special_tags);
1637  if(special_tags){
1638  for(const special_match& entry : special_tag_matches_self) {
1639  if(check_self_abilities(*entry.cfg, entry.tag_name)){
1640  return true;
1641  }
1642  }
1643  }
1644  if(special_id){
1645  for(const special_match& entry : special_id_matches_self) {
1646  if(check_self_abilities(*entry.cfg, entry.tag_name)){
1647  return true;
1648  }
1649  }
1650  }
1651 
1652  const auto adjacent = get_adjacent_tiles(self_loc_);
1653  for(unsigned i = 0; i < adjacent.size(); ++i) {
1654  const unit_map::const_iterator it = units.find(adjacent[i]);
1655  if (it == units.end() || it->incapacitated())
1656  continue;
1657  if ( &*it == self_.get() )
1658  continue;
1659 
1660  std::vector<special_match> special_tag_matches_adj;
1661  std::vector<special_match> special_id_matches_adj;
1662  get_ability_children(special_tag_matches_adj, special_id_matches_adj, it->abilities(), special, special_id , special_tags);
1663  if(special_tags){
1664  for(const special_match& entry : special_tag_matches_adj) {
1665  if(check_adj_abilities(*entry.cfg, entry.tag_name, i , *it)){
1666  return true;
1667  }
1668  }
1669  }
1670  if(special_id){
1671  for(const special_match& entry : special_id_matches_adj) {
1672  if(check_adj_abilities(*entry.cfg, entry.tag_name, i , *it)){
1673  return true;
1674  }
1675  }
1676  }
1677  }
1678  }
1679 
1680  if(other_){
1681  std::vector<special_match> special_tag_matches_other;
1682  std::vector<special_match> special_id_matches_other;
1683  get_ability_children(special_tag_matches_other, special_id_matches_other, (*other_).abilities(), special, special_id , special_tags);
1684  if(special_tags){
1685  for(const special_match& entry : special_tag_matches_other) {
1686  if(check_self_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, other_loc_, AFFECT_OTHER, entry.tag_name)){
1687  return true;
1688  }
1689  }
1690  }
1691 
1692  if(special_id){
1693  for(const special_match& entry : special_id_matches_other) {
1694  if(check_self_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, other_loc_, AFFECT_OTHER, entry.tag_name)){
1695  return true;
1696  }
1697  }
1698  }
1699 
1700  const auto adjacent = get_adjacent_tiles(other_loc_);
1701  for(unsigned i = 0; i < adjacent.size(); ++i) {
1702  const unit_map::const_iterator it = units.find(adjacent[i]);
1703  if (it == units.end() || it->incapacitated())
1704  continue;
1705  if ( &*it == other_.get() )
1706  continue;
1707 
1708  std::vector<special_match> special_tag_matches_oadj;
1709  std::vector<special_match> special_id_matches_oadj;
1710  get_ability_children(special_tag_matches_oadj, special_id_matches_oadj, it->abilities(), special, special_id , special_tags);
1711  if(special_tags){
1712  for(const special_match& entry : special_tag_matches_oadj) {
1713  if(check_adj_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, *it, i, other_loc_, AFFECT_OTHER, entry.tag_name)){
1714  return true;
1715  }
1716  }
1717  }
1718 
1719  if(special_id){
1720  for(const special_match& entry : special_id_matches_oadj) {
1721  if(check_adj_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, *it, i, other_loc_, AFFECT_OTHER, entry.tag_name)){
1722  return true;
1723  }
1724  }
1725  }
1726  }
1727  }
1728  return false;
1729 }
1730 
1731 bool attack_type::has_special_or_ability(const std::string& special, bool special_id, bool special_tags) const
1732 {
1733  //Now that filter_(second)attack in event supports special_id/type_active, including abilities used as weapons,
1734  //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.
1735  //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.
1736  if(range().empty()){
1737  return false;
1738  }
1739  return (has_special(special, false, special_id, special_tags) || has_weapon_ability(special, special_id, special_tags));
1740 }
1741 //end of emulate weapon special functions.
1742 
1743 bool attack_type::special_active(const config& special, AFFECTS whom, const std::string& tag_name,
1744  const std::string& filter_self) const
1745 {
1746  return special_active_impl(shared_from_this(), other_attack_, special, whom, tag_name, filter_self);
1747 }
1748 
1749 /**
1750  * Returns whether or not the given special is active for the specified unit,
1751  * based on the current context (see set_specials_context).
1752  * @param self_attack this unit's attack
1753  * @param other_attack the other unit's attack
1754  * @param special a weapon special WML structure
1755  * @param whom specifies which combatant we care about
1756  * @param tag_name tag name of the special config
1757  * @param filter_self the filter to use
1758  */
1760  const_attack_ptr self_attack,
1761  const_attack_ptr other_attack,
1762  const config& special,
1763  AFFECTS whom,
1764  const std::string& tag_name,
1765  const std::string& filter_self)
1766 {
1767  assert(self_attack || other_attack);
1768  bool is_attacker = self_attack ? self_attack->is_attacker_ : !other_attack->is_attacker_;
1769  bool is_for_listing = self_attack ? self_attack->is_for_listing_ : other_attack->is_for_listing_;
1770  //log_scope("special_active");
1771 
1772 
1773  // Does this affect the specified unit?
1774  if ( whom == AFFECT_SELF ) {
1775  if ( !special_affects_self(special, is_attacker) )
1776  return false;
1777  }
1778  if ( whom == AFFECT_OTHER ) {
1779  if ( !special_affects_opponent(special, is_attacker) )
1780  return false;
1781  }
1782 
1783  // Is this active on attack/defense?
1784  const std::string & active_on = special["active_on"];
1785  if ( !active_on.empty() ) {
1786  if ( is_attacker && active_on != "offense" )
1787  return false;
1788  if ( !is_attacker && active_on != "defense" )
1789  return false;
1790  }
1791 
1792  // Get the units involved.
1793  const unit_map& units = get_unit_map();
1794 
1795  unit_const_ptr self = self_attack ? self_attack->self_ : other_attack->other_;
1796  unit_const_ptr other = self_attack ? self_attack->other_ : other_attack->self_;
1797  map_location self_loc = self_attack ? self_attack->self_loc_ : other_attack->other_loc_;
1798  map_location other_loc = self_attack ? self_attack->other_loc_ : other_attack->self_loc_;
1799  //TODO: why is this needed?
1800  if(self == nullptr) {
1801  unit_map::const_iterator it = units.find(self_loc);
1802  if(it.valid()) {
1803  self = it.get_shared_ptr();
1804  }
1805  }
1806  if(other == nullptr) {
1807  unit_map::const_iterator it = units.find(other_loc);
1808  if(it.valid()) {
1809  other = it.get_shared_ptr();
1810  }
1811  }
1812 
1813  // Make sure they're facing each other.
1814  temporary_facing self_facing(self, self_loc.get_relative_dir(other_loc));
1815  temporary_facing other_facing(other, other_loc.get_relative_dir(self_loc));
1816 
1817  // Filter poison, plague, drain, slow, petrifies
1818  // True if "whom" corresponds to "self", false if "whom" is "other"
1819  bool whom_is_self = ((whom == AFFECT_SELF) || ((whom == AFFECT_EITHER) && special_affects_self(special, is_attacker)));
1820  unit_const_ptr them = whom_is_self ? other : self;
1821  map_location their_loc = whom_is_self ? other_loc : self_loc;
1822 
1823  if (tag_name == "drains" && them && them->get_state("undrainable")) {
1824  return false;
1825  }
1826  if (tag_name == "plague" && them &&
1827  (them->get_state("unplagueable") ||
1828  resources::gameboard->map().is_village(their_loc))) {
1829  return false;
1830  }
1831  if (tag_name == "poison" && them &&
1832  (them->get_state("unpoisonable") || them->get_state(unit::STATE_POISONED))) {
1833  return false;
1834  }
1835  if (tag_name == "slow" && them &&
1836  (them->get_state("unslowable") || them->get_state(unit::STATE_SLOWED))) {
1837  return false;
1838  }
1839  if (tag_name == "petrifies" && them &&
1840  them->get_state("unpetrifiable")) {
1841  return false;
1842  }
1843 
1844 
1845  // Translate our context into terms of "attacker" and "defender".
1846  unit_const_ptr & att = is_attacker ? self : other;
1847  unit_const_ptr & def = is_attacker ? other : self;
1848  const map_location & att_loc = is_attacker ? self_loc : other_loc;
1849  const map_location & def_loc = is_attacker ? other_loc : self_loc;
1850  const_attack_ptr att_weapon = is_attacker ? self_attack : other_attack;
1851  const_attack_ptr def_weapon = is_attacker ? other_attack : self_attack;
1852 
1853  // Filter firststrike here, if both units have first strike then the effects cancel out. Only check
1854  // the opponent if "whom" is the defender, otherwise this leads to infinite recursion.
1855  if (tag_name == "firststrike") {
1856  bool whom_is_defender = whom_is_self ? !is_attacker : is_attacker;
1857  if (whom_is_defender && att_weapon && att_weapon->has_special_or_ability("firststrike"))
1858  return false;
1859  }
1860 
1861  //Add wml filter if "backstab" attribute used.
1862  if (!special["backstab"].blank()) {
1863  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.");
1864  }
1865  config cfg = special;
1866  if(special["backstab"].to_bool()){
1867  const std::string& backstab_formula = "enemy_of(self, flanker) and not flanker.petrified where flanker = unit_at(direction_from(loc, other.facing))";
1868  config& filter_child = cfg.child_or_add("filter_opponent");
1869  if(!special.has_child("filter_opponent")){
1870  filter_child["formula"] = backstab_formula;
1871  } else {
1872  config filter;
1873  filter["formula"] = backstab_formula;
1874  filter_child.add_child("and", filter);
1875  }
1876  }
1877  const config& special_backstab = special["backstab"].to_bool() ? cfg : special;
1878 
1879  // Filter the units involved.
1880  //If filter concerns the unit on which special is applied,
1881  //then the type of special must be entered to avoid calling
1882  //the function of this special in matches_filter()
1883  std::string self_tag_name = whom_is_self ? tag_name : "";
1884  if (!special_unit_matches(self, other, self_loc, self_attack, special, is_for_listing, filter_self, self_tag_name))
1885  return false;
1886  std::string opp_tag_name = !whom_is_self ? tag_name : "";
1887  if (!special_unit_matches(other, self, other_loc, other_attack, special_backstab, is_for_listing, "filter_opponent", opp_tag_name))
1888  return false;
1889  std::string att_tag_name = is_attacker ? tag_name : "";
1890  if (!special_unit_matches(att, def, att_loc, att_weapon, special, is_for_listing, "filter_attacker", att_tag_name))
1891  return false;
1892  std::string def_tag_name = !is_attacker ? tag_name : "";
1893  if (!special_unit_matches(def, att, def_loc, def_weapon, special, is_for_listing, "filter_defender", def_tag_name))
1894  return false;
1895 
1896  const auto adjacent = get_adjacent_tiles(self_loc);
1897 
1898  // Filter the adjacent units.
1899  for (const config &i : special.child_range("filter_adjacent"))
1900  {
1901  std::size_t count = 0;
1902  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
1903  unit_filter filter{ vconfig(i) };
1904  for (const map_location::DIRECTION index : dirs)
1905  {
1907  continue;
1908  unit_map::const_iterator unit = units.find(adjacent[index]);
1909  if (unit == units.end() || !filter.matches(*unit, adjacent[index], *self))
1910  return false;
1911  if (i.has_attribute("is_enemy")) {
1913  if (i["is_enemy"].to_bool() != dc.get_team(unit->side()).is_enemy(self->side())) {
1914  continue;
1915  }
1916  }
1917  count++;
1918  }
1919  if (i["count"].empty() && count != dirs.size()) {
1920  return false;
1921  }
1922  if (!in_ranges<int>(count, utils::parse_ranges_unsigned(i["count"].str()))) {
1923  return false;
1924  }
1925  }
1926 
1927  // Filter the adjacent locations.
1928  for (const config &i : special.child_range("filter_adjacent_location"))
1929  {
1930  std::size_t count = 0;
1931  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
1932  terrain_filter adj_filter(vconfig(i), resources::filter_con, false);
1933  for (const map_location::DIRECTION index : dirs)
1934  {
1936  continue;
1937  if(!adj_filter.match(adjacent[index])) {
1938  return false;
1939  }
1940  count++;
1941  }
1942  if (i["count"].empty() && count != dirs.size()) {
1943  return false;
1944  }
1945  if (!in_ranges<int>(count, utils::parse_ranges_unsigned(i["count"].str()))) {
1946  return false;
1947  }
1948  }
1949 
1950  return true;
1951 }
1952 
1953 
1954 
1956 {
1957 
1958 void individual_effect::set(value_modifier t, int val, const config *abil, const map_location &l)
1959 {
1960  type=t;
1961  value=val;
1962  ability=abil;
1963  loc=l;
1964 }
1965 
1966 bool filter_base_matches(const config& cfg, int def)
1967 {
1968  if (auto apply_filter = cfg.optional_child("filter_base_value")) {
1969  config::attribute_value cond_eq = apply_filter["equals"];
1970  config::attribute_value cond_ne = apply_filter["not_equals"];
1971  config::attribute_value cond_lt = apply_filter["less_than"];
1972  config::attribute_value cond_gt = apply_filter["greater_than"];
1973  config::attribute_value cond_ge = apply_filter["greater_than_equal_to"];
1974  config::attribute_value cond_le = apply_filter["less_than_equal_to"];
1975  return (cond_eq.empty() || def == cond_eq.to_int()) &&
1976  (cond_ne.empty() || def != cond_ne.to_int()) &&
1977  (cond_lt.empty() || def < cond_lt.to_int()) &&
1978  (cond_gt.empty() || def > cond_gt.to_int()) &&
1979  (cond_ge.empty() || def >= cond_ge.to_int()) &&
1980  (cond_le.empty() || def <= cond_le.to_int());
1981  }
1982  return true;
1983 }
1984 
1985 effect::effect(const unit_ability_list& list, int def, const_attack_ptr att, EFFECTS wham) :
1986  effect_list_(),
1987  composite_value_(0)
1988 {
1989 
1990  int value_set = (wham == EFFECT_CUMULABLE) ? std::max(list.highest("value").first, 0) + std::min(list.lowest("value").first, 0) : def;
1991  std::map<std::string,individual_effect> values_add;
1992  std::map<std::string,individual_effect> values_mul;
1993  std::map<std::string,individual_effect> values_div;
1994 
1995  individual_effect set_effect_max;
1996  individual_effect set_effect_min;
1997  std::optional<int> max_value = std::nullopt;
1998  std::optional<int> min_value = std::nullopt;
1999 
2000  for (const unit_ability & ability : list) {
2001  const config& cfg = *ability.ability_cfg;
2002  const std::string& effect_id = cfg[cfg["id"].empty() ? "name" : "id"];
2003 
2004  if (!filter_base_matches(cfg, def))
2005  continue;
2006 
2007  if(wham != EFFECT_CUMULABLE){
2008  if (const config::attribute_value *v = cfg.get("value")) {
2009  int value = get_single_ability_value(*v, def, ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2010  callable.add("base_value", wfl::variant(def));
2011  return formula.evaluate(callable).as_int();
2012  });
2013 
2014  int value_cum = cfg["cumulative"].to_bool() ? std::max(def, value) : value;
2015  assert((set_effect_min.type != NOT_USED) == (set_effect_max.type != NOT_USED));
2016  if(set_effect_min.type == NOT_USED) {
2017  set_effect_min.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2018  set_effect_max.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2019  }
2020  else {
2021  if(value_cum > set_effect_max.value) {
2022  set_effect_max.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2023  }
2024  if(value_cum < set_effect_min.value) {
2025  set_effect_min.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
2026  }
2027  }
2028  }
2029  }
2030 
2031  if(wham == EFFECT_CLAMP_MIN_MAX){
2032  if(cfg.has_attribute("max_value")){
2033  max_value = max_value ? std::min(*max_value, cfg["max_value"].to_int()) : cfg["max_value"].to_int();
2034  }
2035  if(cfg.has_attribute("min_value")){
2036  min_value = min_value ? std::max(*min_value, cfg["min_value"].to_int()) : cfg["min_value"].to_int();
2037  }
2038  }
2039 
2040  if (const config::attribute_value *v = cfg.get("add")) {
2041  int add = get_single_ability_value(*v, def, ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2042  callable.add("base_value", wfl::variant(def));
2043  return formula.evaluate(callable).as_int();
2044  });
2045  std::map<std::string,individual_effect>::iterator add_effect = values_add.find(effect_id);
2046  if(add_effect == values_add.end() || add > add_effect->second.value) {
2047  values_add[effect_id].set(ADD, add, ability.ability_cfg, ability.teacher_loc);
2048  }
2049  }
2050  if (const config::attribute_value *v = cfg.get("sub")) {
2051  int sub = - get_single_ability_value(*v, def, ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
2052  callable.add("base_value", wfl::variant(def));
2053  return formula.evaluate(callable).as_int();
2054  });
2055  std::map<std::string,individual_effect>::iterator sub_effect = values_add.find(effect_id);
2056  if(sub_effect == values_add.end() || sub < sub_effect->second.value) {
2057  values_add[effect_id].set(ADD, sub, ability.ability_cfg, ability.teacher_loc);
2058  }
2059  }
2060  if (const config::attribute_value *v = cfg.get("multiply")) {
2061  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) {
2062  callable.add("base_value", wfl::variant(def));
2063  return formula.evaluate(callable).as_decimal() / 1000.0 ;
2064  }) * 100);
2065  std::map<std::string,individual_effect>::iterator mul_effect = values_mul.find(effect_id);
2066  if(mul_effect == values_mul.end() || multiply > mul_effect->second.value) {
2067  values_mul[effect_id].set(MUL, multiply, ability.ability_cfg, ability.teacher_loc);
2068  }
2069  }
2070  if (const config::attribute_value *v = cfg.get("divide")) {
2071  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) {
2072  callable.add("base_value", wfl::variant(def));
2073  return formula.evaluate(callable).as_decimal() / 1000.0 ;
2074  }) * 100);
2075 
2076  if (divide == 0) {
2077  ERR_NG << "division by zero with divide= in ability/weapon special " << effect_id;
2078  }
2079  else {
2080  std::map<std::string,individual_effect>::iterator div_effect = values_div.find(effect_id);
2081  if(div_effect == values_div.end() || divide > div_effect->second.value) {
2082  values_div[effect_id].set(DIV, divide, ability.ability_cfg, ability.teacher_loc);
2083  }
2084  }
2085  }
2086  }
2087 
2088  if((wham != EFFECT_CUMULABLE) && set_effect_max.type != NOT_USED) {
2089  value_set = std::max(set_effect_max.value, 0) + std::min(set_effect_min.value, 0);
2090  if(set_effect_max.value > def) {
2091  effect_list_.push_back(set_effect_max);
2092  }
2093  if(set_effect_min.value < def) {
2094  effect_list_.push_back(set_effect_min);
2095  }
2096  }
2097 
2098  /* Do multiplication with floating point values rather than integers
2099  * We want two places of precision for each multiplier
2100  * Using integers multiplied by 100 to keep precision causes overflow
2101  * after 3-4 abilities for 32-bit values and ~8 for 64-bit
2102  * Avoiding the overflow by dividing after each step introduces rounding errors
2103  * that may vary depending on the order effects are applied
2104  * As the final values are likely <1000 (always true for mainline), loss of less significant digits is not an issue
2105  */
2106  double multiplier = 1.0;
2107  double divisor = 1.0;
2108 
2109  for(const auto& val : values_mul) {
2110  multiplier *= val.second.value/100.0;
2111  effect_list_.push_back(val.second);
2112  }
2113 
2114  for(const auto& val : values_div) {
2115  divisor *= val.second.value/100.0;
2116  effect_list_.push_back(val.second);
2117  }
2118 
2119  int addition = 0;
2120  for(const auto& val : values_add) {
2121  addition += val.second.value;
2122  effect_list_.push_back(val.second);
2123  }
2124 
2125  composite_value_ = static_cast<int>((value_set + addition) * multiplier / divisor);
2126  //clamp what if min_value < max_value or one attribute only used.
2127  if(max_value && min_value && *min_value < *max_value) {
2128  composite_value_ = std::clamp(*min_value, *max_value, composite_value_);
2129  } else if(max_value && !min_value) {
2130  composite_value_ = std::min(*max_value, composite_value_);
2131  } else if(min_value && !max_value) {
2132  composite_value_ = std::max(*min_value, composite_value_);
2133  }
2134 }
2135 
2136 } // 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:1444
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:1551
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:1585
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:1759
int composite_value(const unit_ability_list &abil_list, int base_value) const
Return the special weapon value, considering specials.
Definition: abilities.cpp:1439
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:1403
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:1422
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:1486
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:1605
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:1450
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:1610
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:1590
int modified_damage() const
Returns the damage per attack of this weapon, considering specials.
Definition: abilities.cpp:1283
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:1743
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:1731
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:1630
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
map_display and display: classes which take care of displaying the map and game-data on the screen.
std::size_t i
Definition: function.cpp: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:1575
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:1580
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:1569
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:1564
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:1823
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:1966
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:1958
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