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