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