The Battle for Wesnoth  1.17.21+dev
abilities.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2023
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 #include <boost/dynamic_bitset.hpp>
45 
46 #include <string_view>
47 
48 static lg::log_domain log_engine("engine");
49 #define ERR_NG LOG_STREAM(err, log_engine)
50 
51 static lg::log_domain log_wml("wml");
52 #define ERR_WML LOG_STREAM(err, log_wml)
53 
54 namespace {
55  class temporary_facing
56  {
57  map_location::DIRECTION save_dir_;
58  unit_const_ptr u_;
59  public:
60  temporary_facing(unit_const_ptr u, map_location::DIRECTION new_dir)
61  : save_dir_(u ? u->facing() : map_location::NDIRECTIONS)
62  , u_(u)
63  {
64  if (u_) {
65  u_->set_facing(new_dir);
66  }
67  }
68  ~temporary_facing()
69  {
70  if (u_) {
71  u_->set_facing(save_dir_);
72  }
73  }
74  };
75 }
76 
77 /*
78  *
79  * [abilities]
80  * ...
81  *
82  * [heals]
83  * value=4
84  * max_value=8
85  * cumulative=no
86  * affect_allies=yes
87  * name= _ "heals"
88  * female_name= _ "female^heals"
89  * name_inactive=null
90  * female_name_inactive=null
91  * description= _ "Heals:
92 Allows the unit to heal adjacent friendly units at the beginning of each turn.
93 
94 A unit cared for by a healer may heal up to 4 HP per turn.
95 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."
96  * description_inactive=null
97  *
98  * affect_self=yes
99  * [filter] // SUF
100  * ...
101  * [/filter]
102  * [filter_self] // SUF
103  * ...
104  * [/filter_self]
105  * [filter_adjacent] // SUF
106  * adjacent=n,ne,nw
107  * ...
108  * [/filter_adjacent]
109  * [filter_adjacent_location]
110  * adjacent=n,ne,nw
111  * ...
112  * [/filter_adjacent]
113  * [affect_adjacent]
114  * adjacent=n,ne,nw
115  * [filter] // SUF
116  * ...
117  * [/filter]
118  * [/affect_adjacent]
119  * [affect_adjacent]
120  * adjacent=s,se,sw
121  * [filter] // SUF
122  * ...
123  * [/filter]
124  * [/affect_adjacent]
125  *
126  * [/heals]
127  *
128  * ...
129  * [/abilities]
130  *
131  */
132 
133 
134 namespace {
135 
136 const unit_map& get_unit_map()
137 {
138  // Used if we're in the game, including during the construction of the display_context
140  return resources::gameboard->units();
141  }
142 
143  // If we get here, we're in the scenario editor
144  assert(display::get_singleton());
145  return display::get_singleton()->get_units();
146 }
147 
148 const team& get_team(std::size_t side)
149 {
150  // Used if we're in the game, including during the construction of the display_context
152  return resources::gameboard->get_team(side);
153  }
154 
155  // If we get here, we're in the scenario editor
156  assert(display::get_singleton());
158 }
159 
160 /**
161  * Common code for the question "some other unit has an ability, can that ability affect this
162  * unit" - it's not the full answer to that question, just a part of it.
163  *
164  * Although this is called while checking which units' "hides" abilities are active, that's only
165  * for the question "is this unit next to an ally that has a 'camoflages adjacent allies' ability";
166  * not the question "is this unit next to an enemy, therefore visible".
167  */
168 bool affects_side(const config& cfg, std::size_t side, std::size_t other_side)
169 {
170  const team& side_team = get_team(side);
171 
172  if(side == other_side)
173  return cfg["affect_allies"].to_bool(true);
174  if(side_team.is_enemy(other_side))
175  return cfg["affect_enemies"].to_bool();
176  else
177  return cfg["affect_allies"].to_bool();
178 }
179 
180 }
181 
182 bool unit::get_ability_bool(const std::string& tag_name, const map_location& loc) const
183 {
184  for (const config &i : this->abilities_.child_range(tag_name)) {
185  if (ability_active(tag_name, i, loc) &&
186  ability_affects_self(tag_name, i, loc))
187  {
188  return true;
189  }
190  }
191 
192  const unit_map& units = get_unit_map();
193 
194  const auto adjacent = get_adjacent_tiles(loc);
195  for(unsigned i = 0; i < adjacent.size(); ++i) {
196  const unit_map::const_iterator it = units.find(adjacent[i]);
197  if (it == units.end() || it->incapacitated())
198  continue;
199  // Abilities may be tested at locations other than the unit's current
200  // location. This is intentional to allow for less messing with the unit
201  // map during calculations, particularly with regards to movement.
202  // Thus, we need to make sure the adjacent unit (*it) is not actually
203  // ourself.
204  if ( &*it == this )
205  continue;
206  for (const config &j : it->abilities_.child_range(tag_name)) {
207  if (affects_side(j, side(), it->side()) &&
208  it->ability_active(tag_name, j, adjacent[i]) &&
209  ability_affects_adjacent(tag_name, j, i, loc, *it))
210  {
211  return true;
212  }
213  }
214  }
215 
216 
217  return false;
218 }
219 
220 unit_ability_list unit::get_abilities(const std::string& tag_name, const map_location& loc) const
221 {
222  unit_ability_list res(loc_);
223 
224  for(const config& i : this->abilities_.child_range(tag_name)) {
225  if(ability_active(tag_name, i, loc)
226  && ability_affects_self(tag_name, i, loc))
227  {
228  res.emplace_back(&i, loc, loc);
229  }
230  }
231 
232  const unit_map& units = get_unit_map();
233 
234  const auto adjacent = get_adjacent_tiles(loc);
235  for(unsigned i = 0; i < adjacent.size(); ++i) {
236  const unit_map::const_iterator it = units.find(adjacent[i]);
237  if (it == units.end() || it->incapacitated())
238  continue;
239  // Abilities may be tested at locations other than the unit's current
240  // location. This is intentional to allow for less messing with the unit
241  // map during calculations, particularly with regards to movement.
242  // Thus, we need to make sure the adjacent unit (*it) is not actually
243  // ourself.
244  if ( &*it == this )
245  continue;
246  for(const config& j : it->abilities_.child_range(tag_name)) {
247  if(affects_side(j, side(), it->side())
248  && it->ability_active(tag_name, j, adjacent[i])
249  && ability_affects_adjacent(tag_name, j, i, loc, *it))
250  {
251  res.emplace_back(&j, loc, adjacent[i]);
252  }
253  }
254  }
255 
256 
257  return res;
258 }
259 
260 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
261 {
262  unit_ability_list res = get_abilities(tag_name, loc);
263  utils::erase_if(res, [&](const unit_ability& i) {
264  return !ability_affects_weapon(*i.ability_cfg, weapon, false) || !ability_affects_weapon(*i.ability_cfg, opp_weapon, true);
265  });
266  return res;
267 }
268 
269 std::vector<std::string> unit::get_ability_list() const
270 {
271  std::vector<std::string> res;
272 
273  for (const config::any_child ab : this->abilities_.all_children_range()) {
274  std::string id = ab.cfg["id"];
275  if (!id.empty())
276  res.push_back(std::move(id));
277  }
278  return res;
279 }
280 
281 
282 namespace {
283  /**
284  * Adds a quadruple consisting of (in order) id, base name,
285  * male or female name as appropriate for the unit, and description.
286  *
287  * @returns Whether name was resolved and quadruple added.
288  */
289  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)
290  {
291  if (active) {
292  const t_string& name = gender_value(ab.cfg, gender, "name", "female_name", "name").t_str();
293 
294  if (!name.empty()) {
295  res.emplace_back(
296  ab.cfg["id"],
297  ab.cfg["name"].t_str(),
298  name,
299  ab.cfg["description"].t_str() );
300  return true;
301  }
302  }
303  else
304  {
305  // See if an inactive name was specified.
306  const config::attribute_value& inactive_value =
307  gender_value(ab.cfg, gender, "name_inactive",
308  "female_name_inactive", "name_inactive");
309  const t_string& name = !inactive_value.blank() ? inactive_value.t_str() :
310  gender_value(ab.cfg, gender, "name", "female_name", "name").t_str();
311 
312  if (!name.empty()) {
313  res.emplace_back(
314  ab.cfg["id"],
315  ab.cfg.get_or("name_inactive", "name").t_str(),
316  name,
317  ab.cfg.get_or("description_inactive", "description").t_str() );
318  return true;
319  }
320  }
321 
322  return false;
323  }
324 }
325 
326 std::vector<std::tuple<std::string, t_string, t_string, t_string>> unit::ability_tooltips() const
327 {
328  std::vector<std::tuple<std::string, t_string,t_string,t_string>> res;
329 
330  for (const config::any_child ab : this->abilities_.all_children_range())
331  {
332  add_ability_tooltip(ab, gender_, res, true);
333  }
334 
335  return res;
336 }
337 
338 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
339 {
340  std::vector<std::tuple<std::string, t_string,t_string,t_string>> res;
341  active_list.clear();
342 
343  for (const config::any_child ab : this->abilities_.all_children_range())
344  {
345  bool active = ability_active(ab.key, ab.cfg, loc);
346  if (add_ability_tooltip(ab, gender_, res, active))
347  {
348  active_list.push_back(active);
349  }
350  }
351  return res;
352 }
353 
354 bool unit::ability_active(const std::string& ability,const config& cfg,const map_location& loc) const
355 {
356  bool illuminates = ability == "illuminates";
357 
358  if (auto afilter = cfg.optional_child("filter"))
359  if ( !unit_filter(vconfig(*afilter)).set_use_flat_tod(illuminates).matches(*this, loc) )
360  return false;
361 
362  const auto adjacent = get_adjacent_tiles(loc);
363 
364  const unit_map& units = get_unit_map();
365 
366  for (const config &i : cfg.child_range("filter_adjacent"))
367  {
368  std::size_t count = 0;
369  unit_filter ufilt{ vconfig(i) };
370  ufilt.set_use_flat_tod(illuminates);
371  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
372  for (const map_location::DIRECTION index : dirs)
373  {
375  continue;
376  unit_map::const_iterator unit = units.find(adjacent[index]);
377  if (unit == units.end())
378  return false;
379  if (!ufilt(*unit, *this))
380  return false;
381  if((*this).id() == (*unit).id())
382  return false;
383  if (i.has_attribute("is_enemy")) {
385  if (i["is_enemy"].to_bool() != dc.get_team(unit->side()).is_enemy(side_)) {
386  continue;
387  }
388  }
389  count++;
390  }
391  if (i["count"].empty() && count != dirs.size()) {
392  return false;
393  }
394  if (!in_ranges<int>(count, utils::parse_ranges_unsigned(i["count"].str()))) {
395  return false;
396  }
397  }
398 
399  for (const config &i : cfg.child_range("filter_adjacent_location"))
400  {
401  std::size_t count = 0;
402  terrain_filter adj_filter(vconfig(i), resources::filter_con, false);
403  adj_filter.flatten(illuminates);
404 
405  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
406  for (const map_location::DIRECTION index : dirs)
407  {
409  continue;
410  }
411  if(!adj_filter.match(adjacent[index])) {
412  return false;
413  }
414  count++;
415  }
416  if (i["count"].empty() && count != dirs.size()) {
417  return false;
418  }
419  if (!in_ranges<int>(count, utils::parse_ranges_unsigned(i["count"].str()))) {
420  return false;
421  }
422  }
423  return true;
424 }
425 
426 bool unit::ability_affects_adjacent(const std::string& ability, const config& cfg,int dir,const map_location& loc,const unit& from) const
427 {
428  bool illuminates = ability == "illuminates";
429 
430  assert(dir >=0 && dir <= 5);
431  map_location::DIRECTION direction = static_cast<map_location::DIRECTION>(dir);
432 
433  for (const config &i : cfg.child_range("affect_adjacent"))
434  {
435  if (i.has_attribute("adjacent")) { //key adjacent defined
436  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
437  if (std::find(dirs.begin(), dirs.end(), direction) == dirs.end()) {
438  continue;
439  }
440  }
441  if((*this).id() == from.id()){
442  return false;
443  }
444  auto filter = i.optional_child("filter");
445  if (!filter || //filter tag given
446  unit_filter(vconfig(*filter)).set_use_flat_tod(illuminates).matches(*this, loc, from) ) {
447  return true;
448  }
449  }
450  return false;
451 }
452 
453 bool unit::ability_affects_self(const std::string& ability,const config& cfg,const map_location& loc) const
454 {
455  auto filter = cfg.optional_child("filter_self");
456  bool affect_self = cfg["affect_self"].to_bool(true);
457  if (!filter || !affect_self) return affect_self;
458  return unit_filter(vconfig(*filter)).set_use_flat_tod(ability == "illuminates").matches(*this, loc);
459 }
460 
461 bool unit::ability_affects_weapon(const config& cfg, const_attack_ptr weapon, bool is_opp) const
462 {
463  const std::string filter_tag_name = is_opp ? "filter_second_weapon" : "filter_weapon";
464  if(!cfg.has_child(filter_tag_name)) {
465  return true;
466  }
467  const config& filter = cfg.mandatory_child(filter_tag_name);
468  if(!weapon) {
469  return false;
470  }
471  return weapon->matches_filter(filter);
472 }
473 
474 bool unit::has_ability_type(const std::string& ability) const
475 {
476  return !abilities_.child_range(ability).empty();
477 }
478 
480 {
481  if(unit_const_ptr & att = is_attacker_ ? self_ : other_) {
482  callable.add("attacker", wfl::variant(std::make_shared<wfl::unit_callable>(*att)));
483  }
484  if(unit_const_ptr & def = is_attacker_ ? other_ : self_) {
485  callable.add("defender", wfl::variant(std::make_shared<wfl::unit_callable>(*def)));
486  }
487 }
488 
489 namespace {
490 
491 
492 template<typename T, typename TFuncFormula>
493 class get_ability_value_visitor
494 #ifdef USING_BOOST_VARIANT
495  : public boost::static_visitor<T>
496 #endif
497 {
498 public:
499  // Constructor stores the default value.
500  get_ability_value_visitor(T def, const TFuncFormula& formula_handler) : def_(def), formula_handler_(formula_handler) {}
501 
502  T operator()(const utils::monostate&) const { return def_; }
503  T operator()(bool) const { return def_; }
504  T operator()(int i) const { return static_cast<T>(i); }
505  T operator()(unsigned long long u) const { return static_cast<T>(u); }
506  T operator()(double d) const { return static_cast<T>(d); }
507  T operator()(const t_string&) const { return def_; }
508  T operator()(const std::string& s) const
509  {
510  if(s.size() >= 2 && s[0] == '(') {
511  return formula_handler_(s);
512  }
513  return lexical_cast_default<T>(s, def_);
514  }
515 
516 private:
517  const T def_;
518  const TFuncFormula& formula_handler_;
519 };
520 
521 template<typename T, typename TFuncFormula>
522 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)
523 {
524  return v.apply_visitor(get_ability_value_visitor(def, [&](const std::string& s) {
525 
526  try {
527  const unit_map& units = get_unit_map();
528 
529  auto u_itor = units.find(ability_info.teacher_loc);
530 
531  if(u_itor == units.end()) {
532  return def;
533  }
534  wfl::map_formula_callable callable(std::make_shared<wfl::unit_callable>(*u_itor));
535  if(att) {
536  att->add_formula_context(callable);
537  }
538  if (auto uptr = units.find_unit_ptr(ability_info.student_loc)) {
539  callable.add("student", wfl::variant(std::make_shared<wfl::unit_callable>(*uptr)));
540  }
541  if (auto uptr = units.find_unit_ptr(receiver_loc)) {
542  callable.add("other", wfl::variant(std::make_shared<wfl::unit_callable>(*uptr)));
543  }
544  return formula_handler(wfl::formula(s, new wfl::gamestate_function_symbol_table), callable);
545  } catch(const wfl::formula_error& e) {
546  lg::log_to_chat() << "Formula error in ability or weapon special: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
547  ERR_WML << "Formula error in ability or weapon special: " << e.type << " at " << e.filename << ':' << e.line << ")";
548  return def;
549  }
550  }));
551 }
552 }
553 
554 template<typename TComp>
555 std::pair<int,map_location> unit_ability_list::get_extremum(const std::string& key, int def, const TComp& comp) const
556 {
557  if ( cfgs_.empty() ) {
558  return std::pair(def, map_location());
559  }
560  // The returned location is the best non-cumulative one, if any,
561  // the best absolute cumulative one otherwise.
562  map_location best_loc;
563  bool only_cumulative = true;
564  int abs_max = 0;
565  int flat = 0;
566  int stack = 0;
567  for (const unit_ability& p : cfgs_)
568  {
569  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) {
570  return formula.evaluate(callable).as_int();
571  });
572 
573  if ((*p.ability_cfg)["cumulative"].to_bool()) {
574  stack += value;
575  if (value < 0) value = -value;
576  if (only_cumulative && !comp(value, abs_max)) {
577  abs_max = value;
578  best_loc = p.teacher_loc;
579  }
580  } else if (only_cumulative || comp(flat, value)) {
581  only_cumulative = false;
582  flat = value;
583  best_loc = p.teacher_loc;
584  }
585  }
586  return std::pair(flat + stack, best_loc);
587 }
588 
589 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;
590 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;
591 
592 /*
593  *
594  * [special]
595  * [swarm]
596  * name= _ "swarm"
597  * name_inactive= _ ""
598  * description= _ ""
599  * description_inactive= _ ""
600  * cumulative=no
601  * apply_to=self #self,opponent,defender,attacker,both
602  * #active_on=defense # or offense; omitting this means "both"
603  *
604  * swarm_attacks_max=4
605  * swarm_attacks_min=2
606  *
607  * [filter_self] // SUF
608  * ...
609  * [/filter_self]
610  * [filter_opponent] // SUF
611  * [filter_attacker] // SUF
612  * [filter_defender] // SUF
613  * [filter_adjacent] // SAUF
614  * [filter_adjacent_location] // SAUF + locs
615  * [/swarm]
616  * [/special]
617  *
618  */
619 
620 namespace {
621 
622  struct special_match
623  {
624  std::string tag_name;
625  const config* cfg;
626  };
627 
628  /**
629  * Gets the children of @parent (which should be the specials for an
630  * attack_type) and places the ones whose tag or id= matches @a id into
631  * @a tag_result and @a id_result.
632  *
633  * If @a just_peeking is set to true, then @a tag_result and @a id_result
634  * are not touched; instead the return value is used to indicate if any
635  * matching children were found.
636  *
637  * @returns true if @a just_peeking is true and a match was found;
638  * false otherwise.
639  */
640  bool get_special_children(std::vector<special_match>& tag_result,
641  std::vector<special_match>& id_result,
642  const config& parent, const std::string& id,
643  bool just_peeking=false) {
644  for (const config::any_child sp : parent.all_children_range())
645  {
646  if (just_peeking && (sp.key == id || sp.cfg["id"] == id)) {
647  return true; // peek succeeded; done
648  }
649 
650  if(sp.key == id) {
651  special_match special = { sp.key, &sp.cfg };
652  tag_result.push_back(special);
653  }
654  if(sp.cfg["id"] == id) {
655  special_match special = { sp.key, &sp.cfg };
656  id_result.push_back(special);
657  }
658  }
659  return false;
660  }
661 
662  bool get_special_children_id(std::vector<special_match>& id_result,
663  const config& parent, const std::string& id,
664  bool just_peeking=false) {
665  for (const config::any_child sp : parent.all_children_range())
666  {
667  if (just_peeking && (sp.cfg["id"] == id)) {
668  return true; // peek succeeded; done
669  }
670 
671  if(sp.cfg["id"] == id) {
672  special_match special = { sp.key, &sp.cfg };
673  id_result.push_back(special);
674  }
675  }
676  return false;
677  }
678 
679  bool get_special_children_tags(std::vector<special_match>& tag_result,
680  const config& parent, const std::string& id,
681  bool just_peeking=false) {
682  for (const config::any_child sp : parent.all_children_range())
683  {
684  if (just_peeking && (sp.key == id)) {
685  return true; // peek succeeded; done
686  }
687 
688  if(sp.key == id) {
689  special_match special = { sp.key, &sp.cfg };
690  tag_result.push_back(special);
691  }
692  }
693  return false;
694  }
695 }
696 
697 /**
698  * Returns whether or not @a *this has a special with a tag or id equal to
699  * @a special. If @a simple_check is set to true, then the check is merely
700  * for being present. Otherwise (the default), the check is for a special
701  * active in the current context (see set_specials_context), including
702  * specials obtained from the opponent's attack.
703  */
704 bool attack_type::has_special(const std::string& special, bool simple_check, bool special_id, bool special_tags) const
705 {
706  {
707  std::vector<special_match> special_tag_matches;
708  std::vector<special_match> special_id_matches;
709  if(special_id && special_tags){
710  if ( get_special_children(special_tag_matches, special_id_matches, specials_, special, simple_check) ) {
711  return true;
712  }
713  } else if(special_id && !special_tags){
714  if ( get_special_children_id(special_id_matches, specials_, special, simple_check) ) {
715  return true;
716  }
717  } else if(!special_id && special_tags){
718  if ( get_special_children_tags(special_tag_matches, specials_, special, simple_check) ) {
719  return true;
720  }
721  }
722  // If we make it to here, then either list.empty() or !simple_check.
723  // So if the list is not empty, then this is not a simple check and
724  // we need to check each special in the list to see if any are active.
725  if(special_tags){
726  for(const special_match& entry : special_tag_matches) {
727  if ( special_active(*entry.cfg, AFFECT_SELF, entry.tag_name) ) {
728  return true;
729  }
730  }
731  }
732  if(special_id){
733  for(const special_match& entry : special_id_matches) {
734  if ( special_active(*entry.cfg, AFFECT_SELF, entry.tag_name) ) {
735  return true;
736  }
737  }
738  }
739  }
740 
741  // Skip checking the opponent's attack?
742  if ( simple_check || !other_attack_ ) {
743  return false;
744  }
745 
746  std::vector<special_match> special_tag_matches;
747  std::vector<special_match> special_id_matches;
748  if(special_id && special_tags){
749  get_special_children(special_tag_matches, special_id_matches, other_attack_->specials_, special);
750  } else if(special_id && !special_tags){
751  get_special_children_id(special_id_matches, other_attack_->specials_, special);
752  } else if(!special_id && special_tags){
753  get_special_children_tags(special_tag_matches, other_attack_->specials_, special);
754  }
755  if(special_tags){
756  for(const special_match& entry : special_tag_matches) {
757  if ( other_attack_->special_active(*entry.cfg, AFFECT_OTHER, entry.tag_name) ) {
758  return true;
759  }
760  }
761  }
762  if(special_id){
763  for(const special_match& entry : special_id_matches) {
764  if ( other_attack_->special_active(*entry.cfg, AFFECT_OTHER, entry.tag_name) ) {
765  return true;
766  }
767  }
768  }
769  return false;
770 }
771 
772 /**
773  * Returns the currently active specials as an ability list, given the current
774  * context (see set_specials_context).
775  */
776 unit_ability_list attack_type::get_specials(const std::string& special) const
777 {
778  //log_scope("get_specials");
779  const map_location loc = self_ ? self_->get_location() : self_loc_;
780  unit_ability_list res(loc);
781 
782  for(const config& i : specials_.child_range(special)) {
783  if(special_active(i, AFFECT_SELF, special)) {
784  res.emplace_back(&i, loc, loc);
785  }
786  }
787 
788  if(!other_attack_) {
789  return res;
790  }
791 
792  for(const config& i : other_attack_->specials_.child_range(special)) {
793  if(other_attack_->special_active(i, AFFECT_OTHER, special)) {
795  }
796  }
797  return res;
798 }
799 
800 /**
801  * Returns a vector of names and descriptions for the specials of *this.
802  * Each std::pair in the vector has first = name and second = description.
803  *
804  * This uses either the active or inactive name/description for each special,
805  * based on the current context (see set_specials_context), provided
806  * @a active_list is not nullptr. Otherwise specials are assumed active.
807  * If the appropriate name is empty, the special is skipped.
808  */
809 std::vector<std::pair<t_string, t_string>> attack_type::special_tooltips(
810  boost::dynamic_bitset<>* active_list) const
811 {
812  //log_scope("special_tooltips");
813  std::vector<std::pair<t_string, t_string>> res;
814  if ( active_list )
815  active_list->clear();
816 
818  {
819  if ( !active_list || special_active(sp.cfg, AFFECT_EITHER, sp.key) ) {
820  const t_string &name = sp.cfg["name"];
821  if (!name.empty()) {
822  res.emplace_back(name, sp.cfg["description"].t_str() );
823  if ( active_list )
824  active_list->push_back(true);
825  }
826  } else {
827  const t_string& name = sp.cfg.get_or("name_inactive", "name").t_str();
828  if (!name.empty()) {
829  res.emplace_back(name, sp.cfg.get_or("description_inactive", "description").t_str() );
830  active_list->push_back(false);
831  }
832  }
833  }
834  return res;
835 }
836 
837 /**
838  * static used in weapon_specials (bool only_active) and
839  * @return a string and a set_string for the weapon_specials function below.
840  * @param[in,out] temp_string the string modified and returned
841  * @param[in] active the boolean for determine if @name can be added or not
842  * @param[in] name string who must be or not added
843  * @param[in,out] checking_name the reference for checking if @name already added
844  */
845 static void add_name(std::string& temp_string, bool active, const std::string name, std::set<std::string>& checking_name)
846 {
847  if (active) {
848  if (!name.empty() && checking_name.count(name) == 0) {
849  checking_name.insert(name);
850  if (!temp_string.empty()) temp_string += ", ";
851  temp_string += font::span_color(font::BUTTON_COLOR, name);
852  }
853  }
854 }
855 
856 /**
857  * Returns a comma-separated string of active names for the specials of *this.
858  * Empty names are skipped.
859  *
860  * Whether or not a special is active depends
861  * on the current context (see set_specials_context)
862  */
863 std::string attack_type::weapon_specials() const
864 {
865  //log_scope("weapon_specials");
866  std::string res;
868  {
869  const bool active = special_active(sp.cfg, AFFECT_EITHER, sp.key);
870 
871  const std::string& name =
872  active
873  ? sp.cfg["name"].str()
874  : sp.cfg.get_or("name_inactive", "name").str();
875  if (!name.empty()) {
876  if (!res.empty()) res += ", ";
877  if (!active) res += font::span_color(font::inactive_details_color);
878  res += name;
879  if (!active) res += "</span>";
880  }
881  }
882  std::string temp_string;
883  std::set<std::string> checking_name;
884  weapon_specials_impl_self(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name);
885  weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, {}, "affect_allies");
886  if(!temp_string.empty() && !res.empty()) {
887  temp_string = ", \n" + temp_string;
888  res += temp_string;
889  } else if (!temp_string.empty()){
890  res = temp_string;
891  }
892  return res;
893 }
894 
895 static void add_name_list(std::string& temp_string, std::string& weapon_abilities, std::set<std::string>& checking_name, const std::string from_str)
896 {
897  if(!temp_string.empty()){
898  temp_string = from_str.c_str() + temp_string;
899  weapon_abilities += (!weapon_abilities.empty() && !temp_string.empty()) ? "\n" : "";
900  weapon_abilities += temp_string;
901  temp_string.clear();
902  checking_name.clear();
903  }
904 }
905 
906 std::string attack_type::weapon_specials_value(const std::set<std::string> checking_tags) const
907 {
908  //log_scope("weapon_specials_value");
909  std::string temp_string, weapon_abilities;
910  std::set<std::string> checking_name;
912  if((checking_tags.count(sp.key) != 0)){
913  const bool active = special_active(sp.cfg, AFFECT_SELF, sp.key);
914  add_name(temp_string, active, sp.cfg["name"].str(), checking_name);
915  }
916  }
917  add_name_list(temp_string, weapon_abilities, checking_name, "");
918 
919  weapon_specials_impl_self(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, checking_tags, true);
920  add_name_list(temp_string, weapon_abilities, checking_name, _("Owned: "));
921 
922  weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, checking_tags, "affect_allies", true);
923  add_name_list(temp_string, weapon_abilities, checking_name, _("Taught: "));
924 
925  weapon_specials_impl_adj(temp_string, self_, shared_from_this(), other_attack_, self_loc_, AFFECT_SELF, checking_name, checking_tags, "affect_enemies", true);
926  add_name_list(temp_string, weapon_abilities, checking_name, _("Taught: (by an enemy): "));
927 
928 
929  if(other_attack_) {
930  for (const config::any_child sp : other_attack_->specials_.all_children_range()) {
931  if((checking_tags.count(sp.key) != 0)){
932  const bool active = other_attack_->special_active(sp.cfg, AFFECT_OTHER, sp.key);
933  add_name(temp_string, active, sp.cfg["name"].str(), checking_name);
934  }
935  }
936  }
937  weapon_specials_impl_self(temp_string, other_, other_attack_, shared_from_this(), other_loc_, AFFECT_OTHER, checking_name, checking_tags);
938  weapon_specials_impl_adj(temp_string, other_, other_attack_, shared_from_this(), other_loc_, AFFECT_OTHER, checking_name, checking_tags);
939  add_name_list(temp_string, weapon_abilities, checking_name, _("Used by opponent: "));
940 
941  return weapon_abilities;
942 }
943 
945  std::string& temp_string,
946  unit_const_ptr self,
947  const_attack_ptr self_attack,
948  const_attack_ptr other_attack,
949  const map_location& self_loc,
950  AFFECTS whom,
951  std::set<std::string>& checking_name,
952  const std::set<std::string>& checking_tags,
953  bool leader_bool)
954 {
955  if(self){
956  for (const config::any_child sp : self->abilities().all_children_range()){
957  bool tag_checked = (!checking_tags.empty()) ? (checking_tags.count(sp.key) != 0) : true;
958  const bool active = tag_checked && check_self_abilities_impl(self_attack, other_attack, sp.cfg, self, self_loc, whom, sp.key, leader_bool);
959  add_name(temp_string, active, sp.cfg["name"].str(), checking_name);
960  }
961  }
962 }
963 
965  std::string& temp_string,
966  unit_const_ptr self,
967  const_attack_ptr self_attack,
968  const_attack_ptr other_attack,
969  const map_location& self_loc,
970  AFFECTS whom,
971  std::set<std::string>& checking_name,
972  const std::set<std::string>& checking_tags,
973  const std::string& affect_adjacents,
974  bool leader_bool)
975 {
976  const unit_map& units = get_unit_map();
977  if(self){
978  const auto adjacent = get_adjacent_tiles(self_loc);
979  for(unsigned i = 0; i < adjacent.size(); ++i) {
980  const unit_map::const_iterator it = units.find(adjacent[i]);
981  if (it == units.end() || it->incapacitated())
982  continue;
983  if(&*it == self.get())
984  continue;
985  for (const config::any_child sp : it->abilities().all_children_range()){
986  bool tag_checked = (!checking_tags.empty()) ? (checking_tags.count(sp.key) != 0) : true;
987  bool default_bool = (affect_adjacents == "affect_allies") ? true : false;
988  bool affect_allies = (!affect_adjacents.empty()) ? sp.cfg[affect_adjacents].to_bool(default_bool) : true;
989  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;
990  add_name(temp_string, active, sp.cfg["name"].str(), checking_name);
991  }
992  }
993  }
994 }
995 
996 
997 /**
998  * Sets the context under which specials will be checked for being active.
999  * This version is appropriate if both units in a combat are known.
1000  * @param[in] weapon The weapon being considered.
1001  * @param[in] self A reference to the unit with this weapon.
1002  * @param[in] other A reference to the other unit in the combat.
1003  * @param[in] unit_loc The location of the unit with this weapon.
1004  * @param[in] other_loc The location of the other unit in the combat.
1005  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
1006  * @param[in] other_attack The attack used by the other unit.
1007  */
1009  const attack_type& weapon,
1010  const_attack_ptr other_attack,
1011  unit_const_ptr self,
1012  unit_const_ptr other,
1013  const map_location& unit_loc,
1014  const map_location& other_loc,
1015  bool attacking)
1016  : parent(weapon.shared_from_this())
1017 {
1018  weapon.self_ = self;
1019  weapon.other_ = other;
1020  weapon.self_loc_ = unit_loc;
1021  weapon.other_loc_ = other_loc;
1022  weapon.is_attacker_ = attacking;
1023  weapon.other_attack_ = other_attack;
1024  weapon.is_for_listing_ = false;
1025 }
1026 
1027 /**
1028  * Sets the context under which specials will be checked for being active.
1029  * This version is appropriate if there is no specific combat being considered.
1030  * @param[in] weapon The weapon being considered.
1031  * @param[in] self A reference to the unit with this weapon.
1032  * @param[in] loc The location of the unit with this weapon.
1033  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
1034  */
1036  : parent(weapon.shared_from_this())
1037 {
1038  weapon.self_ = self;
1039  weapon.other_ = unit_ptr();
1040  weapon.self_loc_ = loc;
1042  weapon.is_attacker_ = attacking;
1043  weapon.other_attack_ = nullptr;
1044  weapon.is_for_listing_ = false;
1045 }
1046 
1047 /**
1048  * Sets the context under which specials will be checked for being active.
1049  * This version is appropriate for theoretical units of a particular type.
1050  * @param[in] weapon The weapon being considered.
1051  * @param[in] self_type A reference to the type of the unit with this weapon.
1052  * @param[in] loc The location of the unit with this weapon.
1053  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
1054  */
1055 attack_type::specials_context_t::specials_context_t(const attack_type& weapon, const unit_type& self_type, const map_location& loc, bool attacking)
1056  : parent(weapon.shared_from_this())
1057 {
1058  UNUSED(self_type);
1059  weapon.self_ = unit_ptr();
1060  weapon.other_ = unit_ptr();
1061  weapon.self_loc_ = loc;
1063  weapon.is_attacker_ = attacking;
1064  weapon.other_attack_ = nullptr;
1065  weapon.is_for_listing_ = false;
1066 }
1067 
1069  : parent(weapon.shared_from_this())
1070 {
1071  weapon.is_for_listing_ = true;
1072  weapon.is_attacker_ = attacking;
1073 }
1074 
1076 {
1077  if(was_moved) return;
1078  parent->self_ = unit_ptr();
1079  parent->other_ = unit_ptr();
1080  parent->self_loc_ = map_location::null_location();
1081  parent->other_loc_ = map_location::null_location();
1082  parent->is_attacker_ = false;
1083  parent->other_attack_ = nullptr;
1084  parent->is_for_listing_ = false;
1085 }
1086 
1088  : parent(other.parent)
1089 {
1090  other.was_moved = true;
1091 }
1092 
1093 /**
1094  * Calculates the number of attacks this weapon has, considering specials.
1095  * This returns two numbers because of the swarm special. The actual number of
1096  * attacks depends on the unit's health and should be:
1097  * min_attacks + (max_attacks - min_attacks) * (current hp) / (max hp)
1098  * c.f. swarm_blows()
1099  */
1100 void attack_type::modified_attacks(unsigned & min_attacks,
1101  unsigned & max_attacks) const
1102 {
1103  // Apply [attacks].
1104  int attacks_value = composite_value(get_specials_and_abilities("attacks"), num_attacks());
1105 
1106  if ( attacks_value < 0 ) {
1107  attacks_value = num_attacks();
1108  ERR_NG << "negative number of strikes after applying weapon specials";
1109  }
1110 
1111  // Apply [swarm].
1112  unit_ability_list swarm_specials = get_specials_and_abilities("swarm");
1113  if ( !swarm_specials.empty() ) {
1114  min_attacks = std::max<int>(0, swarm_specials.highest("swarm_attacks_min").first);
1115  max_attacks = std::max<int>(0, swarm_specials.highest("swarm_attacks_max", attacks_value).first);
1116  } else {
1117  min_attacks = max_attacks = attacks_value;
1118  }
1119 }
1120 
1121 
1122 /**
1123  * Returns the damage per attack of this weapon, considering specials.
1124  */
1126 {
1127  int damage_value = composite_value(get_specials_and_abilities("damage"), damage());
1128  return damage_value;
1129 }
1130 
1131 
1132 namespace { // Helpers for attack_type::special_active()
1133 
1134  /**
1135  * Returns whether or not the given special affects the opponent of the unit
1136  * with the special.
1137  * @param[in] special a weapon special WML structure
1138  * @param[in] is_attacker whether or not the unit with the special is the attacker
1139  */
1140  bool special_affects_opponent(const config& special, bool is_attacker)
1141  {
1142  //log_scope("special_affects_opponent");
1143  const std::string& apply_to = special["apply_to"];
1144  if ( apply_to.empty() )
1145  return false;
1146  if ( apply_to == "both" )
1147  return true;
1148  if ( apply_to == "opponent" )
1149  return true;
1150  if ( is_attacker && apply_to == "defender" )
1151  return true;
1152  if ( !is_attacker && apply_to == "attacker" )
1153  return true;
1154  return false;
1155  }
1156 
1157  /**
1158  * Returns whether or not the given special affects the unit with the special.
1159  * @param[in] special a weapon special WML structure
1160  * @param[in] is_attacker whether or not the unit with the special is the attacker
1161  */
1162  bool special_affects_self(const config& special, bool is_attacker)
1163  {
1164  //log_scope("special_affects_self");
1165  const std::string& apply_to = special["apply_to"];
1166  if ( apply_to.empty() )
1167  return true;
1168  if ( apply_to == "both" )
1169  return true;
1170  if ( apply_to == "self" )
1171  return true;
1172  if ( is_attacker && apply_to == "attacker" )
1173  return true;
1174  if ( !is_attacker && apply_to == "defender")
1175  return true;
1176  return false;
1177  }
1178 
1179  /**
1180  * Determines if a unit/weapon combination matches the specified child
1181  * (normally a [filter_*] child) of the provided filter.
1182  * @param[in] u A unit to filter.
1183  * @param[in] u2 Another unit to filter.
1184  * @param[in] loc The presumed location of @a un_it.
1185  * @param[in] weapon The attack_type to filter.
1186  * @param[in] filter The filter containing the child filter to use.
1187  * @param[in] for_listing
1188  * @param[in] child_tag The tag of the child filter to use.
1189  */
1190  static bool special_unit_matches(unit_const_ptr & u,
1191  unit_const_ptr & u2,
1192  const map_location & loc,
1193  const_attack_ptr weapon,
1194  const config & filter,
1195  const bool for_listing,
1196  const std::string & child_tag)
1197  {
1198  if (for_listing && !loc.valid())
1199  // The special's context was set to ignore this unit, so assume we pass.
1200  // (This is used by reports.cpp to show active specials when the
1201  // opponent is not known. From a player's perspective, the special
1202  // is active, in that it can be used, even though the player might
1203  // need to select an appropriate opponent.)
1204  return true;
1205 
1206  auto filter_child = filter.optional_child(child_tag);
1207  if ( !filter_child )
1208  // The special does not filter on this unit, so we pass.
1209  return true;
1210 
1211  // If the primary unit doesn't exist, there's nothing to match
1212  if (!u) {
1213  return false;
1214  }
1215 
1216  unit_filter ufilt{vconfig(*filter_child)};
1217 
1218  // If the other unit doesn't exist, try matching without it
1219 
1220 
1221  // Check for a weapon match.
1222  if (auto filter_weapon = filter_child->optional_child("filter_weapon") ) {
1223  if ( !weapon || !weapon->matches_filter(*filter_weapon) )
1224  return false;
1225  }
1226 
1227  // Passed.
1228  // If the other unit doesn't exist, try matching without it
1229  if (!u2) {
1230  return ufilt.matches(*u, loc);
1231  }
1232  return ufilt.matches(*u, loc, *u2);
1233  }
1234 
1235 }//anonymous namespace
1236 
1237 
1238 //The following functions are intended to allow the use in combat of capacities
1239 //identical to special weapons and therefore to be able to use them on adjacent
1240 //units (abilities of type 'aura') or else on all types of weapons even if the
1241 //beneficiary unit does not have a corresponding weapon
1242 //(defense against ranged weapons abilities for a unit that only has melee attacks)
1243 
1244 unit_ability_list attack_type::get_weapon_ability(const std::string& ability) const
1245 {
1246  const map_location loc = self_ ? self_->get_location() : self_loc_;
1247  unit_ability_list abil_list(loc);
1248  if(self_) {
1249  abil_list.append_if((*self_).get_abilities(ability, self_loc_), [&](const unit_ability& i) {
1250  return special_active(*i.ability_cfg, AFFECT_SELF, ability, "filter_student");
1251  });
1252  }
1253 
1254  if(other_) {
1255  abil_list.append_if((*other_).get_abilities(ability, other_loc_), [&](const unit_ability& i) {
1256  return special_active_impl(other_attack_, shared_from_this(), *i.ability_cfg, AFFECT_OTHER, ability, "filter_student");
1257  });
1258  }
1259 
1260  return abil_list;
1261 }
1262 
1264 {
1265  unit_ability_list abil_list = get_weapon_ability(special);
1266  if(!abil_list.empty()){
1267  abil_list = overwrite_special_checking(abil_list, abil_list, false);
1268  }
1269  unit_ability_list spe_list = get_specials(special);
1270  if(!spe_list.empty()){
1271  spe_list = overwrite_special_checking(spe_list, abil_list, true);
1272  if(special == "plague" && !spe_list.empty()){
1273  return spe_list;
1274  }
1275  abil_list.append(spe_list);
1276  }
1277  return abil_list;
1278 }
1279 
1280 int attack_type::composite_value(const unit_ability_list& abil_list, int base_value) const
1281 {
1282  return unit_abilities::effect(abil_list, base_value, shared_from_this()).get_composite_value();
1283 }
1284 
1285 static bool overwrite_special_affects(const config& special)
1286 {
1287  const std::string& apply_to = special["overwrite_specials"];
1288  return (apply_to == "one_side" || apply_to == "both_sides");
1289 }
1290 
1292 {
1293  for(unit_ability_list::iterator i = overwriters.begin(); i != overwriters.end();) {
1294  if(!overwrite_special_affects(*i->ability_cfg)) {
1295  i = overwriters.erase(i);
1296  } else {
1297  ++i;
1298  }
1299  }
1300  if(overwriters.empty()){
1301  return input;
1302  }
1303 
1304  for(const auto& i : overwriters) {
1305  bool affect_side = ((*i.ability_cfg)["overwrite_specials"] == "one_side");
1306  utils::erase_if(input, [&](const unit_ability& j) {
1307  bool is_overwritable = (is_special || !overwrite_special_affects(*j.ability_cfg));
1308  bool one_side_overwritable = true;
1309  if(affect_side && is_overwritable){
1310  if(special_affects_self(*i.ability_cfg, is_attacker_)){
1311  one_side_overwritable = special_affects_self(*j.ability_cfg, is_attacker_);
1312  }
1313  else if(special_affects_opponent(*i.ability_cfg, !is_attacker_)){
1314  one_side_overwritable = special_affects_opponent(*j.ability_cfg, !is_attacker_);
1315  }
1316  }
1317  return (is_overwritable && one_side_overwritable);
1318  });
1319  }
1320  return input;
1321 }
1322 
1323  /**
1324  * Gets the children of parent (which should be the abilities for an
1325  * attack_type) and places the ones whose tag or id= matches @a id into
1326  * @a tag_result and @a id_result.
1327  * @param tag_result receive the children whose tag matches @a id
1328  * @param id_result receive the children whose id matches @a id
1329  * @param parent the tags whose contain children (abilities here)
1330  * @param id tag or id of child tested
1331  * @param special_id if true, children check by id
1332  * @param special_tags if true, children check by tags
1333  */
1334 static void get_ability_children(std::vector<special_match>& tag_result,
1335  std::vector<special_match>& id_result,
1336  const config& parent, const std::string& id,
1337  bool special_id=true, bool special_tags=true) {
1338  if(special_id && special_tags){
1339  get_special_children(tag_result, id_result, parent, id);
1340  } else if(special_id && !special_tags){
1341  get_special_children_id(id_result, parent, id);
1342  } else if(!special_id && special_tags){
1343  get_special_children_tags(tag_result, parent, id);
1344  }
1345 }
1346 
1347 bool unit::get_self_ability_bool(const config& special, const std::string& tag_name, const map_location& loc) const
1348 {
1349  return (ability_active(tag_name, special, loc) && ability_affects_self(tag_name, special, loc));
1350 }
1351 
1352 bool unit::get_adj_ability_bool(const config& special, const std::string& tag_name, int dir, const map_location& loc, const unit& from) const
1353 {
1354  const auto adjacent = get_adjacent_tiles(loc);
1355  return (affects_side(special, side(), from.side()) && from.ability_active(tag_name, special, adjacent[dir]) && ability_affects_adjacent(tag_name, special, dir, loc, from));
1356 }
1357 
1358 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
1359 {
1360  return (get_self_ability_bool(special, tag_name, loc) && ability_affects_weapon(special, weapon, false) && ability_affects_weapon(special, opp_weapon, true));
1361 }
1362 
1363 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
1364 {
1365  return (get_adj_ability_bool(special, tag_name, dir, loc, from) && ability_affects_weapon(special, weapon, false) && ability_affects_weapon(special, opp_weapon, true));
1366 }
1367 
1368 bool attack_type::check_self_abilities(const config& cfg, const std::string& special) const
1369 {
1370  return check_self_abilities_impl(shared_from_this(), other_attack_, cfg, self_, self_loc_, AFFECT_SELF, special, true);
1371 }
1372 
1373 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)
1374 {
1375  if(tag_name == "leadership" && leader_bool){
1376  if((*u).get_self_ability_bool_weapon(special, tag_name, loc, self_attack, other_attack)) {
1377  return true;
1378  }
1379  }
1380  if((*u).checking_tags().count(tag_name) != 0){
1381  if((*u).get_self_ability_bool(special, tag_name, loc) && special_active_impl(self_attack, other_attack, special, whom, tag_name, "filter_student")) {
1382  return true;
1383  }
1384  }
1385  return false;
1386 }
1387 
1388 bool attack_type::check_adj_abilities(const config& cfg, const std::string& special, int dir, const unit& from) const
1389 {
1390  return check_adj_abilities_impl(shared_from_this(), other_attack_, cfg, self_, from, dir, self_loc_, AFFECT_SELF, special, true);
1391 }
1392 
1393 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)
1394 {
1395  if(tag_name == "leadership" && leader_bool){
1396  if((*u).get_adj_ability_bool_weapon(special, tag_name, dir, loc, from, self_attack, other_attack)) {
1397  return true;
1398  }
1399  }
1400  if((*u).checking_tags().count(tag_name) != 0){
1401  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")) {
1402  return true;
1403  }
1404  }
1405  return false;
1406 }
1407 /**
1408  * Returns whether or not @a *this has a special ability with a tag or id equal to
1409  * @a special. the Check is for a special ability
1410  * active in the current context (see set_specials_context), including
1411  * specials obtained from the opponent's attack.
1412  */
1413 bool attack_type::has_weapon_ability(const std::string& special, bool special_id, bool special_tags) const
1414 {
1415  const unit_map& units = get_unit_map();
1416  if(self_){
1417  std::vector<special_match> special_tag_matches_self;
1418  std::vector<special_match> special_id_matches_self;
1419  get_ability_children(special_tag_matches_self, special_id_matches_self, (*self_).abilities(), special, special_id , special_tags);
1420  if(special_tags){
1421  for(const special_match& entry : special_tag_matches_self) {
1422  if(check_self_abilities(*entry.cfg, entry.tag_name)){
1423  return true;
1424  }
1425  }
1426  }
1427  if(special_id){
1428  for(const special_match& entry : special_id_matches_self) {
1429  if(check_self_abilities(*entry.cfg, entry.tag_name)){
1430  return true;
1431  }
1432  }
1433  }
1434 
1435  const auto adjacent = get_adjacent_tiles(self_loc_);
1436  for(unsigned i = 0; i < adjacent.size(); ++i) {
1437  const unit_map::const_iterator it = units.find(adjacent[i]);
1438  if (it == units.end() || it->incapacitated())
1439  continue;
1440  if ( &*it == self_.get() )
1441  continue;
1442 
1443  std::vector<special_match> special_tag_matches_adj;
1444  std::vector<special_match> special_id_matches_adj;
1445  get_ability_children(special_tag_matches_adj, special_id_matches_adj, it->abilities(), special, special_id , special_tags);
1446  if(special_tags){
1447  for(const special_match& entry : special_tag_matches_adj) {
1448  if(check_adj_abilities(*entry.cfg, entry.tag_name, i , *it)){
1449  return true;
1450  }
1451  }
1452  }
1453  if(special_id){
1454  for(const special_match& entry : special_id_matches_adj) {
1455  if(check_adj_abilities(*entry.cfg, entry.tag_name, i , *it)){
1456  return true;
1457  }
1458  }
1459  }
1460  }
1461  }
1462 
1463  if(other_){
1464  std::vector<special_match> special_tag_matches_other;
1465  std::vector<special_match> special_id_matches_other;
1466  get_ability_children(special_tag_matches_other, special_id_matches_other, (*other_).abilities(), special, special_id , special_tags);
1467  if(special_tags){
1468  for(const special_match& entry : special_tag_matches_other) {
1469  if(check_self_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, other_loc_, AFFECT_OTHER, entry.tag_name)){
1470  return true;
1471  }
1472  }
1473  }
1474 
1475  if(special_id){
1476  for(const special_match& entry : special_id_matches_other) {
1477  if(check_self_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, other_loc_, AFFECT_OTHER, entry.tag_name)){
1478  return true;
1479  }
1480  }
1481  }
1482 
1483  const auto adjacent = get_adjacent_tiles(other_loc_);
1484  for(unsigned i = 0; i < adjacent.size(); ++i) {
1485  const unit_map::const_iterator it = units.find(adjacent[i]);
1486  if (it == units.end() || it->incapacitated())
1487  continue;
1488  if ( &*it == other_.get() )
1489  continue;
1490 
1491  std::vector<special_match> special_tag_matches_oadj;
1492  std::vector<special_match> special_id_matches_oadj;
1493  get_ability_children(special_tag_matches_oadj, special_id_matches_oadj, it->abilities(), special, special_id , special_tags);
1494  if(special_tags){
1495  for(const special_match& entry : special_tag_matches_oadj) {
1496  if(check_adj_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, *it, i, other_loc_, AFFECT_OTHER, entry.tag_name)){
1497  return true;
1498  }
1499  }
1500  }
1501 
1502  if(special_id){
1503  for(const special_match& entry : special_id_matches_oadj) {
1504  if(check_adj_abilities_impl(other_attack_, shared_from_this(), *entry.cfg, other_, *it, i, other_loc_, AFFECT_OTHER, entry.tag_name)){
1505  return true;
1506  }
1507  }
1508  }
1509  }
1510  }
1511  return false;
1512 }
1513 
1514 bool attack_type::has_special_or_ability(const std::string& special, bool special_id, bool special_tags) const
1515 {
1516  //Now that filter_(second)attack in event supports special_id/type_active, including abilities used as weapons,
1517  //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.
1518  //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.
1519  if(range().empty()){
1520  return false;
1521  }
1522  return (has_special(special, false, special_id, special_tags) || has_weapon_ability(special, special_id, special_tags));
1523 }
1524 //end of emulate weapon special functions.
1525 
1526 bool attack_type::special_active(const config& special, AFFECTS whom, const std::string& tag_name,
1527  const std::string& filter_self) const
1528 {
1529  return special_active_impl(shared_from_this(), other_attack_, special, whom, tag_name, filter_self);
1530 }
1531 
1532 /**
1533  * Returns whether or not the given special is active for the specified unit,
1534  * based on the current context (see set_specials_context).
1535  * @param self_attack this unit's attack
1536  * @param other_attack the other unit's attack
1537  * @param special a weapon special WML structure
1538  * @param whom specifies which combatant we care about
1539  * @param tag_name tag name of the special config
1540  * @param filter_self the filter to use
1541  */
1543  const_attack_ptr self_attack,
1544  const_attack_ptr other_attack,
1545  const config& special,
1546  AFFECTS whom,
1547  const std::string& tag_name,
1548  const std::string& filter_self)
1549 {
1550  assert(self_attack || other_attack);
1551  bool is_attacker = self_attack ? self_attack->is_attacker_ : !other_attack->is_attacker_;
1552  bool is_for_listing = self_attack ? self_attack->is_for_listing_ : other_attack->is_for_listing_;
1553  //log_scope("special_active");
1554 
1555 
1556  // Does this affect the specified unit?
1557  if ( whom == AFFECT_SELF ) {
1558  if ( !special_affects_self(special, is_attacker) )
1559  return false;
1560  }
1561  if ( whom == AFFECT_OTHER ) {
1562  if ( !special_affects_opponent(special, is_attacker) )
1563  return false;
1564  }
1565 
1566  // Is this active on attack/defense?
1567  const std::string & active_on = special["active_on"];
1568  if ( !active_on.empty() ) {
1569  if ( is_attacker && active_on != "offense" )
1570  return false;
1571  if ( !is_attacker && active_on != "defense" )
1572  return false;
1573  }
1574 
1575  // Get the units involved.
1576  const unit_map& units = get_unit_map();
1577 
1578  unit_const_ptr self = self_attack ? self_attack->self_ : other_attack->other_;
1579  unit_const_ptr other = self_attack ? self_attack->other_ : other_attack->self_;
1580  map_location self_loc = self_attack ? self_attack->self_loc_ : other_attack->other_loc_;
1581  map_location other_loc = self_attack ? self_attack->other_loc_ : other_attack->self_loc_;
1582  //TODO: why is this needed?
1583  if(self == nullptr) {
1584  unit_map::const_iterator it = units.find(self_loc);
1585  if(it.valid()) {
1586  self = it.get_shared_ptr();
1587  }
1588  }
1589  if(other == nullptr) {
1590  unit_map::const_iterator it = units.find(other_loc);
1591  if(it.valid()) {
1592  other = it.get_shared_ptr();
1593  }
1594  }
1595 
1596  // Make sure they're facing each other.
1597  temporary_facing self_facing(self, self_loc.get_relative_dir(other_loc));
1598  temporary_facing other_facing(other, other_loc.get_relative_dir(self_loc));
1599 
1600  // Filter poison, plague, drain, slow, petrifies
1601  // True if "whom" corresponds to "self", false if "whom" is "other"
1602  bool whom_is_self = ((whom == AFFECT_SELF) || ((whom == AFFECT_EITHER) && special_affects_self(special, is_attacker)));
1603  unit_const_ptr them = whom_is_self ? other : self;
1604  map_location their_loc = whom_is_self ? other_loc : self_loc;
1605 
1606  if (tag_name == "drains" && them && them->get_state("undrainable")) {
1607  return false;
1608  }
1609  if (tag_name == "plague" && them &&
1610  (them->get_state("unplagueable") ||
1611  resources::gameboard->map().is_village(their_loc))) {
1612  return false;
1613  }
1614  if (tag_name == "poison" && them &&
1615  (them->get_state("unpoisonable") || them->get_state(unit::STATE_POISONED))) {
1616  return false;
1617  }
1618  if (tag_name == "slow" && them &&
1619  (them->get_state("unslowable") || them->get_state(unit::STATE_SLOWED))) {
1620  return false;
1621  }
1622  if (tag_name == "petrifies" && them &&
1623  them->get_state("unpetrifiable")) {
1624  return false;
1625  }
1626 
1627 
1628  // Translate our context into terms of "attacker" and "defender".
1629  unit_const_ptr & att = is_attacker ? self : other;
1630  unit_const_ptr & def = is_attacker ? other : self;
1631  const map_location & att_loc = is_attacker ? self_loc : other_loc;
1632  const map_location & def_loc = is_attacker ? other_loc : self_loc;
1633  const_attack_ptr att_weapon = is_attacker ? self_attack : other_attack;
1634  const_attack_ptr def_weapon = is_attacker ? other_attack : self_attack;
1635 
1636  // Filter firststrike here, if both units have first strike then the effects cancel out. Only check
1637  // the opponent if "whom" is the defender, otherwise this leads to infinite recursion.
1638  if (tag_name == "firststrike") {
1639  bool whom_is_defender = whom_is_self ? !is_attacker : is_attacker;
1640  if (whom_is_defender && att_weapon && att_weapon->has_special_or_ability("firststrike"))
1641  return false;
1642  }
1643 
1644  //Add wml filter if "backstab" attribute used.
1645  if (!special["backstab"].blank()) {
1646  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.");
1647  }
1648  config cfg = special;
1649  if(special["backstab"].to_bool()){
1650  const std::string& backstab_formula = "enemy_of(self, flanker) and not flanker.petrified where flanker = unit_at(direction_from(loc, other.facing))";
1651  config& filter_child = cfg.child_or_add("filter_opponent");
1652  if(!special.has_child("filter_opponent")){
1653  filter_child["formula"] = backstab_formula;
1654  } else {
1655  config filter;
1656  filter["formula"] = backstab_formula;
1657  filter_child.add_child("and", filter);
1658  }
1659  }
1660  const config& special_backstab = special["backstab"].to_bool() ? cfg : special;
1661 
1662  // Filter the units involved.
1663  if (!special_unit_matches(self, other, self_loc, self_attack, special, is_for_listing, filter_self))
1664  return false;
1665  if (!special_unit_matches(other, self, other_loc, other_attack, special_backstab, is_for_listing, "filter_opponent"))
1666  return false;
1667  if (!special_unit_matches(att, def, att_loc, att_weapon, special, is_for_listing, "filter_attacker"))
1668  return false;
1669  if (!special_unit_matches(def, att, def_loc, def_weapon, special, is_for_listing, "filter_defender"))
1670  return false;
1671 
1672  const auto adjacent = get_adjacent_tiles(self_loc);
1673 
1674  // Filter the adjacent units.
1675  for (const config &i : special.child_range("filter_adjacent"))
1676  {
1677  std::size_t count = 0;
1678  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
1679  unit_filter filter{ vconfig(i) };
1680  for (const map_location::DIRECTION index : dirs)
1681  {
1683  continue;
1684  unit_map::const_iterator unit = units.find(adjacent[index]);
1685  if (unit == units.end() || !filter.matches(*unit, adjacent[index], *self))
1686  return false;
1687  if (i.has_attribute("is_enemy")) {
1689  if (i["is_enemy"].to_bool() != dc.get_team(unit->side()).is_enemy(self->side())) {
1690  continue;
1691  }
1692  }
1693  count++;
1694  }
1695  if (i["count"].empty() && count != dirs.size()) {
1696  return false;
1697  }
1698  if (!in_ranges<int>(count, utils::parse_ranges_unsigned(i["count"].str()))) {
1699  return false;
1700  }
1701  }
1702 
1703  // Filter the adjacent locations.
1704  for (const config &i : special.child_range("filter_adjacent_location"))
1705  {
1706  std::size_t count = 0;
1707  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
1708  terrain_filter adj_filter(vconfig(i), resources::filter_con, false);
1709  for (const map_location::DIRECTION index : dirs)
1710  {
1712  continue;
1713  if(!adj_filter.match(adjacent[index])) {
1714  return false;
1715  }
1716  count++;
1717  }
1718  if (i["count"].empty() && count != dirs.size()) {
1719  return false;
1720  }
1721  if (!in_ranges<int>(count, utils::parse_ranges_unsigned(i["count"].str()))) {
1722  return false;
1723  }
1724  }
1725 
1726  return true;
1727 }
1728 
1729 
1730 
1732 {
1733 
1734 void individual_effect::set(value_modifier t, int val, const config *abil, const map_location &l)
1735 {
1736  type=t;
1737  value=val;
1738  ability=abil;
1739  loc=l;
1740 }
1741 
1742 bool filter_base_matches(const config& cfg, int def)
1743 {
1744  if (auto apply_filter = cfg.optional_child("filter_base_value")) {
1745  config::attribute_value cond_eq = apply_filter["equals"];
1746  config::attribute_value cond_ne = apply_filter["not_equals"];
1747  config::attribute_value cond_lt = apply_filter["less_than"];
1748  config::attribute_value cond_gt = apply_filter["greater_than"];
1749  config::attribute_value cond_ge = apply_filter["greater_than_equal_to"];
1750  config::attribute_value cond_le = apply_filter["less_than_equal_to"];
1751  return (cond_eq.empty() || def == cond_eq.to_int()) &&
1752  (cond_ne.empty() || def != cond_ne.to_int()) &&
1753  (cond_lt.empty() || def < cond_lt.to_int()) &&
1754  (cond_gt.empty() || def > cond_gt.to_int()) &&
1755  (cond_ge.empty() || def >= cond_ge.to_int()) &&
1756  (cond_le.empty() || def <= cond_le.to_int());
1757  }
1758  return true;
1759 }
1760 
1761 effect::effect(const unit_ability_list& list, int def, const_attack_ptr att, bool is_cumulable) :
1762  effect_list_(),
1763  composite_value_(0)
1764 {
1765 
1766  int value_set = is_cumulable ? std::max(list.highest("value").first, 0) + std::min(list.lowest("value").first, 0) : def;
1767  std::map<std::string,individual_effect> values_add;
1768  std::map<std::string,individual_effect> values_mul;
1769  std::map<std::string,individual_effect> values_div;
1770 
1771  individual_effect set_effect_max;
1772  individual_effect set_effect_min;
1773 
1774  for (const unit_ability & ability : list) {
1775  const config& cfg = *ability.ability_cfg;
1776  const std::string& effect_id = cfg[cfg["id"].empty() ? "name" : "id"];
1777 
1778  if (!filter_base_matches(cfg, def))
1779  continue;
1780 
1781  if(!is_cumulable){
1782  if (const config::attribute_value *v = cfg.get("value")) {
1783  int value = get_single_ability_value(*v, def, ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
1784  callable.add("base_value", wfl::variant(def));
1785  return formula.evaluate(callable).as_int();
1786  });
1787 
1788  int value_cum = cfg["cumulative"].to_bool() ? std::max(def, value) : value;
1789  assert((set_effect_min.type != NOT_USED) == (set_effect_max.type != NOT_USED));
1790  if(set_effect_min.type == NOT_USED) {
1791  set_effect_min.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
1792  set_effect_max.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
1793  }
1794  else {
1795  if(value_cum > set_effect_max.value) {
1796  set_effect_max.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
1797  }
1798  if(value_cum < set_effect_min.value) {
1799  set_effect_min.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
1800  }
1801  }
1802  }
1803  }
1804 
1805  if (const config::attribute_value *v = cfg.get("add")) {
1806  int add = get_single_ability_value(*v, def, ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
1807  callable.add("base_value", wfl::variant(def));
1808  return formula.evaluate(callable).as_int();
1809  });
1810  std::map<std::string,individual_effect>::iterator add_effect = values_add.find(effect_id);
1811  if(add_effect == values_add.end() || add > add_effect->second.value) {
1812  values_add[effect_id].set(ADD, add, ability.ability_cfg, ability.teacher_loc);
1813  }
1814  }
1815  if (const config::attribute_value *v = cfg.get("sub")) {
1816  int sub = - get_single_ability_value(*v, def, ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
1817  callable.add("base_value", wfl::variant(def));
1818  return formula.evaluate(callable).as_int();
1819  });
1820  std::map<std::string,individual_effect>::iterator sub_effect = values_add.find(effect_id);
1821  if(sub_effect == values_add.end() || sub < sub_effect->second.value) {
1822  values_add[effect_id].set(ADD, sub, ability.ability_cfg, ability.teacher_loc);
1823  }
1824  }
1825  if (const config::attribute_value *v = cfg.get("multiply")) {
1826  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) {
1827  callable.add("base_value", wfl::variant(def));
1828  return formula.evaluate(callable).as_decimal() / 1000.0 ;
1829  }) * 100);
1830  std::map<std::string,individual_effect>::iterator mul_effect = values_mul.find(effect_id);
1831  if(mul_effect == values_mul.end() || multiply > mul_effect->second.value) {
1832  values_mul[effect_id].set(MUL, multiply, ability.ability_cfg, ability.teacher_loc);
1833  }
1834  }
1835  if (const config::attribute_value *v = cfg.get("divide")) {
1836  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) {
1837  callable.add("base_value", wfl::variant(def));
1838  return formula.evaluate(callable).as_decimal() / 1000.0 ;
1839  }) * 100);
1840 
1841  if (divide == 0) {
1842  ERR_NG << "division by zero with divide= in ability/weapon special " << effect_id;
1843  }
1844  else {
1845  std::map<std::string,individual_effect>::iterator div_effect = values_div.find(effect_id);
1846  if(div_effect == values_div.end() || divide > div_effect->second.value) {
1847  values_div[effect_id].set(DIV, divide, ability.ability_cfg, ability.teacher_loc);
1848  }
1849  }
1850  }
1851  }
1852 
1853  if(!is_cumulable && set_effect_max.type != NOT_USED) {
1854  value_set = std::max(set_effect_max.value, 0) + std::min(set_effect_min.value, 0);
1855  if(set_effect_max.value > def) {
1856  effect_list_.push_back(set_effect_max);
1857  }
1858  if(set_effect_min.value < def) {
1859  effect_list_.push_back(set_effect_min);
1860  }
1861  }
1862 
1863  /* Do multiplication with floating point values rather than integers
1864  * We want two places of precision for each multiplier
1865  * Using integers multiplied by 100 to keep precision causes overflow
1866  * after 3-4 abilities for 32-bit values and ~8 for 64-bit
1867  * Avoiding the overflow by dividing after each step introduces rounding errors
1868  * that may vary depending on the order effects are applied
1869  * As the final values are likely <1000 (always true for mainline), loss of less significant digits is not an issue
1870  */
1871  double multiplier = 1.0;
1872  double divisor = 1.0;
1873 
1874  for(const auto& val : values_mul) {
1875  multiplier *= val.second.value/100.0;
1876  effect_list_.push_back(val.second);
1877  }
1878 
1879  for(const auto& val : values_div) {
1880  divisor *= val.second.value/100.0;
1881  effect_list_.push_back(val.second);
1882  }
1883 
1884  int addition = 0;
1885  for(const auto& val : values_add) {
1886  addition += val.second.value;
1887  effect_list_.push_back(val.second);
1888  }
1889 
1890  composite_value_ = static_cast<int>((value_set + addition) * multiplier / divisor);
1891 }
1892 
1893 } // end namespace unit_abilities
static void add_name(std::string &temp_string, bool active, const std::string name, std::set< std::string > &checking_name)
Definition: abilities.cpp:845
static lg::log_domain log_engine("engine")
#define ERR_NG
Definition: abilities.cpp:49
#define ERR_WML
Definition: abilities.cpp:52
static bool overwrite_special_affects(const config &special)
Definition: abilities.cpp:1285
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:895
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:1334
double t
Definition: astarsearch.cpp:65
specials_context_t(const attack_type &weapon, bool attacking)
Initialize weapon specials context for listing.
Definition: abilities.cpp:1068
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:863
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:1368
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:704
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:1542
int composite_value(const unit_ability_list &abil_list, int base_value) const
Return the special weapon value, considering specials.
Definition: abilities.cpp:1280
const_attack_ptr other_attack_
void add_formula_context(wfl::map_formula_callable &) const
Definition: abilities.cpp:479
const std::string & range() const
Definition: attack_type.hpp:47
map_location self_loc_
unit_ability_list overwrite_special_checking(unit_ability_list input, unit_ability_list overwriters, bool is_special) const
Filter a list of abilities or weapon specials, removing any entries that are overridden by the overwr...
Definition: abilities.cpp:1291
const std::string & type() const
Definition: attack_type.hpp:45
unit_ability_list get_weapon_ability(const std::string &ability) const
Returns list for weapon like abilities for each ability type.
Definition: abilities.cpp:1244
std::string weapon_specials_value(const std::set< std::string > checking_tags) const
Definition: abilities.cpp:906
unit_ability_list get_specials_and_abilities(const std::string &special) const
Returns list who contains get_weapon_ability and get_specials list for each ability type.
Definition: abilities.cpp:1263
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:1388
config specials_
int num_attacks() const
Definition: attack_type.hpp:54
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:1393
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:964
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:809
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:944
const t_string & name() const
Definition: attack_type.hpp:43
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:1373
int modified_damage() const
Returns the damage per attack of this weapon, considering specials.
Definition: abilities.cpp:1125
void modified_attacks(unsigned &min_attacks, unsigned &max_attacks) const
Calculates the number of attacks this weapon has, considering specials.
Definition: abilities.cpp:1100
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:1526
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:776
int damage() const
Definition: attack_type.hpp:53
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:1514
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:1413
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:161
const attribute_value & get_or(const config_key_type key, const config_key_type default_key) const
Chooses a value.
Definition: config.cpp:697
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:371
bool matches(const config &filter) const
Definition: config.cpp:1202
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:321
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:891
child_itors child_range(config_key_type key)
Definition: config.cpp:277
config & child_or_add(config_key_type key)
Returns a reference to the first child with the given key.
Definition: config.cpp:410
bool empty() const
Definition: config.cpp:856
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:691
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:389
config & add_child(config_key_type key)
Definition: config.cpp:445
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:148
const display_context & get_disp_context() const
Definition: display.hpp:189
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:101
virtual const display_context & get_disp_context() const =0
team & get_team(int i)
Definition: game_board.hpp:98
virtual const unit_map & units() const override
Definition: game_board.hpp:113
virtual const gamemap & map() const override
Definition: game_board.hpp:103
bool is_village(const map_location &loc) const
Definition: map.cpp:66
bool empty() const
Definition: tstring.hpp:187
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:76
bool is_enemy(int n) const
Definition: team.hpp:231
int get_composite_value() const
Definition: abilities.hpp:47
std::vector< individual_effect > effect_list_
Definition: abilities.hpp:54
void append(const unit_ability_list &other)
Appends the abilities from other to this, ignores other.loc()
Definition: unit.hpp:107
iterator erase(const iterator &erase_it)
Definition: unit.hpp:98
std::pair< int, map_location > lowest(const std::string &key, int def=0) const
Definition: unit.hpp:74
std::vector< unit_ability > cfgs_
Definition: unit.hpp:126
iterator begin()
Definition: unit.hpp:86
iterator end()
Definition: unit.hpp:88
const map_location & loc() const
Definition: unit.hpp:104
std::vector< unit_ability >::iterator iterator
Definition: unit.hpp:83
void emplace_back(T &&... args)
Definition: unit.hpp:102
std::pair< int, map_location > highest(const std::string &key, int def=0) const
Definition: unit.hpp:70
bool empty() const
Definition: unit.hpp:92
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:119
std::pair< int, map_location > get_extremum(const std::string &key, int def, const TComp &comp) const
Definition: abilities.cpp:555
bool matches(const unit &u, const map_location &loc) const
Determine if *this matches filter at a specified location.
Definition: filter.hpp:127
unit_filter & set_use_flat_tod(bool value)
Definition: filter.hpp:117
Container associating units to locations.
Definition: map.hpp:99
unit_iterator end()
Definition: map.hpp:429
unit_ptr find_unit_ptr(const T &val)
Definition: map.hpp:388
unit_iterator find(std::size_t id)
Definition: map.cpp:301
A single unit type that the player may recruit.
Definition: types.hpp:46
This class represents a single unit of a specific type.
Definition: unit.hpp:134
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:30
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:1358
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:1363
bool ability_active(const std::string &ability, const config &cfg, const map_location &loc) const
Check if an ability is active.
Definition: abilities.cpp:354
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:453
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:182
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:426
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:326
config abilities_
Definition: unit.hpp:1965
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:260
int side_
Definition: unit.hpp:1902
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:461
bool has_ability_type(const std::string &ability) const
Check if the unit has an ability of a specific type.
Definition: abilities.cpp:474
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:220
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:1352
unit_race::GENDER gender_
Definition: unit.hpp:1904
std::vector< std::string > get_ability_list() const
Get a list of all abilities by ID.
Definition: abilities.cpp:269
map_location loc_
Definition: unit.hpp:1865
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:1347
const std::string & id() const
Gets this unit's id.
Definition: unit.hpp:381
int side() const
The side this unit belongs to.
Definition: unit.hpp:344
@ STATE_SLOWED
Definition: unit.hpp:861
@ STATE_POISONED
The unit is slowed - it moves slower and does less damage.
Definition: unit.hpp:862
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:475
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.
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:468
game_board * gameboard
Definition: resources.cpp:21
filter_context * filter_con
Definition: resources.cpp:24
bool filter_base_matches(const config &cfg, int def)
Definition: abilities.cpp:1742
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:72
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:104
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:161
const child_map::key_type & key
Definition: config.hpp:686
config & cfg
Definition: config.hpp:687
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:125
DIRECTION get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition: location.cpp:227
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:1734
Data typedef for unit_ability_list.
Definition: unit.hpp:40
map_location student_loc
Used by the formula in the ability.
Definition: unit.hpp:54
const config * ability_cfg
The contents of the ability tag, never nullptr.
Definition: unit.hpp:61
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:59
bool valid() const
Definition: map.hpp:274
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:218
mock_party p
static map_location::DIRECTION s
#define d
#define e