The Battle for Wesnoth  1.15.2+dev
abilities.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2018 by Dominic Bolin <dominic.bolin@exong.net>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 /**
16  * @file
17  * Manage unit-abilities, like heal, cure, and weapon_specials.
18  */
19 
20 #include "display.hpp"
21 #include "display_context.hpp"
22 #include "font/text_formatting.hpp"
23 #include "game_board.hpp"
24 #include "lexical_cast.hpp"
25 #include "log.hpp"
26 #include "map/map.hpp"
27 #include "resources.hpp"
28 #include "team.hpp"
29 #include "terrain/filter.hpp"
30 #include "units/unit.hpp"
31 #include "units/abilities.hpp"
32 #include "units/filter.hpp"
33 #include "units/map.hpp"
34 #include "filter_context.hpp"
35 #include "formula/callable_objects.hpp"
36 #include "formula/formula.hpp"
39 #include "deprecation.hpp"
40 
41 #include <boost/dynamic_bitset.hpp>
42 #include <boost/algorithm/string/predicate.hpp>
43 
44 static lg::log_domain log_engine("engine");
45 #define ERR_NG LOG_STREAM(err, log_engine)
46 
47 namespace {
48  class temporary_facing
49  {
50  map_location::DIRECTION save_dir_;
51  unit_const_ptr u_;
52  public:
53  temporary_facing(unit_const_ptr u, map_location::DIRECTION new_dir)
54  : save_dir_(u ? u->facing() : map_location::NDIRECTIONS)
55  , u_(u)
56  {
57  if (u_) {
58  u_->set_facing(new_dir);
59  }
60  }
61  ~temporary_facing()
62  {
63  if (u_) {
64  u_->set_facing(save_dir_);
65  }
66  }
67  };
68 }
69 
70 /*
71  *
72  * [abilities]
73  * ...
74  *
75  * [heals]
76  * value=4
77  * max_value=8
78  * cumulative=no
79  * affect_allies=yes
80  * name= _ "heals"
81  * female_name= _ "female^heals"
82  * name_inactive=null
83  * female_name_inactive=null
84  * description= _ "Heals:
85 Allows the unit to heal adjacent friendly units at the beginning of each turn.
86 
87 A unit cared for by a healer may heal up to 4 HP per turn.
88 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."
89  * description_inactive=null
90  *
91  * affect_self=yes
92  * [filter] // SUF
93  * ...
94  * [/filter]
95  * [filter_self] // SUF
96  * ...
97  * [/filter_self]
98  * [filter_adjacent] // SUF
99  * adjacent=n,ne,nw
100  * ...
101  * [/filter_adjacent]
102  * [filter_adjacent_location]
103  * adjacent=n,ne,nw
104  * ...
105  * [/filter_adjacent]
106  * [affect_adjacent]
107  * adjacent=n,ne,nw
108  * [filter] // SUF
109  * ...
110  * [/filter]
111  * [/affect_adjacent]
112  * [affect_adjacent]
113  * adjacent=s,se,sw
114  * [filter] // SUF
115  * ...
116  * [/filter]
117  * [/affect_adjacent]
118  *
119  * [/heals]
120  *
121  * ...
122  * [/abilities]
123  *
124  */
125 
126 
127 namespace {
128 
129 bool affects_side(const config& cfg, std::size_t side, std::size_t other_side)
130 {
131  // display::get_singleton() has already been confirmed valid by both callers.
132  const team& side_team = display::get_singleton()->get_disp_context().get_team(side);
133 
134  if(side == other_side)
135  return cfg["affect_allies"].to_bool(true);
136  if(side_team.is_enemy(other_side))
137  return cfg["affect_enemies"].to_bool();
138  else
139  return cfg["affect_allies"].to_bool();
140 }
141 
142 }
143 
144 bool unit::get_ability_bool(const std::string& tag_name, const map_location& loc) const
145 {
146  for (const config &i : this->abilities_.child_range(tag_name)) {
147  if (ability_active(tag_name, i, loc) &&
148  ability_affects_self(tag_name, i, loc))
149  {
150  return true;
151  }
152  }
153 
154  assert(display::get_singleton());
155  const unit_map& units = display::get_singleton()->get_units();
156 
157  adjacent_loc_array_t adjacent;
158  get_adjacent_tiles(loc,adjacent.data());
159  for(unsigned i = 0; i < adjacent.size(); ++i) {
160  const unit_map::const_iterator it = units.find(adjacent[i]);
161  if (it == units.end() || it->incapacitated())
162  continue;
163  // Abilities may be tested at locations other than the unit's current
164  // location. This is intentional to allow for less messing with the unit
165  // map during calculations, particularly with regards to movement.
166  // Thus, we need to make sure the adjacent unit (*it) is not actually
167  // ourself.
168  if ( &*it == this )
169  continue;
170  for (const config &j : it->abilities_.child_range(tag_name)) {
171  if (affects_side(j, side(), it->side()) &&
172  it->ability_active(tag_name, j, adjacent[i]) &&
173  ability_affects_adjacent(tag_name, j, i, loc, *it))
174  {
175  return true;
176  }
177  }
178  }
179 
180 
181  return false;
182 }
183 
184 unit_ability_list unit::get_abilities(const std::string& tag_name, const map_location& loc) const
185 {
186  unit_ability_list res(loc_);
187 
188  for(const config& i : this->abilities_.child_range(tag_name)) {
189  if(ability_active(tag_name, i, loc)
190  && ability_affects_self(tag_name, i, loc))
191  {
192  res.emplace_back(&i, loc);
193  }
194  }
195 
196  assert(display::get_singleton());
197  const unit_map& units = display::get_singleton()->get_units();
198 
199  adjacent_loc_array_t adjacent;
200  get_adjacent_tiles(loc,adjacent.data());
201  for(unsigned i = 0; i < adjacent.size(); ++i) {
202  const unit_map::const_iterator it = units.find(adjacent[i]);
203  if (it == units.end() || it->incapacitated())
204  continue;
205  // Abilities may be tested at locations other than the unit's current
206  // location. This is intentional to allow for less messing with the unit
207  // map during calculations, particularly with regards to movement.
208  // Thus, we need to make sure the adjacent unit (*it) is not actually
209  // ourself.
210  if ( &*it == this )
211  continue;
212  for(const config& j : it->abilities_.child_range(tag_name)) {
213  if(affects_side(j, side(), it->side())
214  && it->ability_active(tag_name, j, adjacent[i])
215  && ability_affects_adjacent(tag_name, j, i, loc, *it))
216  {
217  res.emplace_back(&j, adjacent[i]);
218  }
219  }
220  }
221 
222 
223  return res;
224 }
225 
226 std::vector<std::string> unit::get_ability_list() const
227 {
228  std::vector<std::string> res;
229 
230  for (const config::any_child &ab : this->abilities_.all_children_range()) {
231  std::string id = ab.cfg["id"];
232  if (!id.empty())
233  res.push_back(std::move(id));
234  }
235  return res;
236 }
237 
238 
239 namespace {
240  // These functions might have wider usefulness than this file, but for now
241  // I'll make them local.
242 
243  /**
244  * Chooses a value from the given config. If the value specified by @a key is
245  * blank, then @a default_key is chosen instead.
246  */
247  inline const config::attribute_value & default_value(
248  const config & cfg, const std::string & key, const std::string & default_key)
249  {
250  const config::attribute_value & value = cfg[key];
251  return !value.blank() ? value : cfg[default_key];
252  }
253 
254  /**
255  * Chooses a value from the given config based on gender. If the value for
256  * the specified gender is blank, then @a default_key is chosen instead.
257  */
258  inline const config::attribute_value & gender_value(
259  const config & cfg, unit_race::GENDER gender, const std::string & male_key,
260  const std::string & female_key, const std::string & default_key)
261  {
262  return default_value(cfg,
263  gender == unit_race::MALE ? male_key : female_key,
264  default_key);
265  }
266 
267  /**
268  * Adds a quadruple consisting of (in order) id, base name,
269  * male or female name as appropriate for the unit, and description.
270  *
271  * @returns Whether name was resolved and quadruple added.
272  */
273  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)
274  {
275  if (active) {
276  const t_string& name = gender_value(ab.cfg, gender, "name", "female_name", "name").t_str();
277 
278  if (!name.empty()) {
279  res.emplace_back(
280  ab.cfg["id"],
281  ab.cfg["name"].t_str(),
282  name,
283  ab.cfg["description"].t_str() );
284  return true;
285  }
286  }
287  else
288  {
289  // See if an inactive name was specified.
290  const config::attribute_value& inactive_value =
291  gender_value(ab.cfg, gender, "name_inactive",
292  "female_name_inactive", "name_inactive");
293  const t_string& name = !inactive_value.blank() ? inactive_value.t_str() :
294  gender_value(ab.cfg, gender, "name", "female_name", "name").t_str();
295 
296  if (!name.empty()) {
297  res.emplace_back(
298  ab.cfg["id"],
299  default_value(ab.cfg, "name_inactive", "name").t_str(),
300  name,
301  default_value(ab.cfg, "description_inactive", "description").t_str() );
302  return true;
303  }
304  }
305 
306  return false;
307  }
308 }
309 
310 std::vector<std::tuple<std::string, t_string, t_string, t_string>> unit::ability_tooltips() const
311 {
312  std::vector<std::tuple<std::string, t_string,t_string,t_string>> res;
313 
314  for (const config::any_child &ab : this->abilities_.all_children_range())
315  {
316  add_ability_tooltip(ab, gender_, res, true);
317  }
318 
319  return res;
320 }
321 
322 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
323 {
324  std::vector<std::tuple<std::string, t_string,t_string,t_string>> res;
325  active_list.clear();
326 
327  for (const config::any_child &ab : this->abilities_.all_children_range())
328  {
329  bool active = ability_active(ab.key, ab.cfg, loc);
330  if (add_ability_tooltip(ab, gender_, res, active))
331  {
332  active_list.push_back(active);
333  }
334  }
335  return res;
336 }
337 
338 bool unit::ability_active(const std::string& ability,const config& cfg,const map_location& loc) const
339 {
340  bool illuminates = ability == "illuminates";
341 
342  if (const config &afilter = cfg.child("filter"))
343  if ( !unit_filter(vconfig(afilter)).set_use_flat_tod(illuminates).matches(*this, loc) )
344  return false;
345 
346  adjacent_loc_array_t adjacent;
347  get_adjacent_tiles(loc,adjacent.data());
348 
349  assert(display::get_singleton());
350  const unit_map& units = display::get_singleton()->get_units();
351 
352  for (const config &i : cfg.child_range("filter_adjacent"))
353  {
354  std::size_t count = 0;
355  unit_filter ufilt{ vconfig(i) };
356  ufilt.set_use_flat_tod(illuminates);
357  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
358  for (const map_location::DIRECTION index : dirs)
359  {
361  continue;
362  unit_map::const_iterator unit = units.find(adjacent[index]);
363  if (unit == units.end())
364  return false;
365  if (!ufilt(*unit, *this))
366  return false;
367  if (i.has_attribute("is_enemy")) {
369  if (i["is_enemy"].to_bool() != dc.get_team(unit->side()).is_enemy(side_)) {
370  continue;
371  }
372  }
373  count++;
374  }
375  if (i["count"].empty() && count != dirs.size()) {
376  return false;
377  }
378  if (!in_ranges<int>(count, utils::parse_ranges(i["count"].str()))) {
379  return false;
380  }
381  }
382 
383  for (const config &i : cfg.child_range("filter_adjacent_location"))
384  {
385  std::size_t count = 0;
386  terrain_filter adj_filter(vconfig(i), resources::filter_con);
387  adj_filter.flatten(illuminates);
388 
389  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
390  for (const map_location::DIRECTION index : dirs)
391  {
393  continue;
394  }
395  if(!adj_filter.match(adjacent[index])) {
396  return false;
397  }
398  count++;
399  }
400  if (i["count"].empty() && count != dirs.size()) {
401  return false;
402  }
403  if (!in_ranges<int>(count, utils::parse_ranges(i["count"].str()))) {
404  return false;
405  }
406  }
407  return true;
408 }
409 
410 bool unit::ability_affects_adjacent(const std::string& ability, const config& cfg,int dir,const map_location& loc,const unit& from) const
411 {
412  bool illuminates = ability == "illuminates";
413 
414  assert(dir >=0 && dir <= 5);
415  map_location::DIRECTION direction = static_cast<map_location::DIRECTION>(dir);
416 
417  for (const config &i : cfg.child_range("affect_adjacent"))
418  {
419  if (i.has_attribute("adjacent")) { //key adjacent defined
420  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
421  if (std::find(dirs.begin(), dirs.end(), direction) == dirs.end()) {
422  continue;
423  }
424  }
425  const config &filter = i.child("filter");
426  if (!filter || //filter tag given
427  unit_filter(vconfig(filter)).set_use_flat_tod(illuminates).matches(*this, loc, from) ) {
428  return true;
429  }
430  }
431  return false;
432 }
433 
434 bool unit::ability_affects_self(const std::string& ability,const config& cfg,const map_location& loc) const
435 {
436  const config &filter = cfg.child("filter_self");
437  bool affect_self = cfg["affect_self"].to_bool(true);
438  if (!filter || !affect_self) return affect_self;
439  return unit_filter(vconfig(filter)).set_use_flat_tod(ability == "illuminates").matches(*this, loc);
440 }
441 
442 bool unit::ability_affects_weapon(const config& cfg, const_attack_ptr weapon, bool is_opp) const
443 {
444  const std::string filter_tag_name = is_opp ? "filter_second_weapon" : "filter_weapon";
445  if(!cfg.has_child(filter_tag_name)) {
446  return true;
447  }
448  const config& filter = cfg.child(filter_tag_name);
449  if(!weapon) {
450  return false;
451  }
452  return weapon->matches_filter(filter);
453 }
454 
455 bool unit::has_ability_type(const std::string& ability) const
456 {
457  return !abilities_.child_range(ability).empty();
458 }
459 
460 namespace {
461 
462 
463 template<typename T, typename TFuncFormula>
464 class get_ability_value_visitor : public boost::static_visitor<T>
465 {
466 public:
467  // Constructor stores the default value.
468  get_ability_value_visitor(T def, const TFuncFormula& formula_handler) : def_(def), formula_handler_(formula_handler) {}
469 
470  T operator()(const boost::blank&) const { return def_; }
471  T operator()(bool) const { return def_; }
472  T operator()(int i) const { return static_cast<T>(i); }
473  T operator()(unsigned long long u) const { return static_cast<T>(u); }
474  T operator()(double d) const { return static_cast<T>(d); }
475  T operator()(const t_string&) const { return def_; }
476  T operator()(const std::string& s) const
477  {
478  if(s.size() >= 2 && s[0] == '(') {
479  return formula_handler_(s);
480  }
481  return lexical_cast_default<T>(s, def_);
482  }
483 
484 private:
485  const T def_;
486  const TFuncFormula& formula_handler_;
487 };
488 template<typename T, typename TFuncFormula>
489 get_ability_value_visitor<T, TFuncFormula> make_get_ability_value_visitor(T def, const TFuncFormula& formula_handler)
490 {
491  return get_ability_value_visitor<T, TFuncFormula>(def, formula_handler);
492 }
493 template<typename T, typename TFuncFormula>
494 T get_single_ability_value(const config::attribute_value& v, T def, const map_location& sender_loc, const map_location& receiver_loc, const TFuncFormula& formula_handler)
495 {
496  return v.apply_visitor(make_get_ability_value_visitor(def, [&](const std::string& s) {
497 
498  try {
499  assert(display::get_singleton());
500  const unit_map& units = display::get_singleton()->get_units();
501 
502  auto u_itor = units.find(sender_loc);
503 
504  if(u_itor == units.end()) {
505  return def;
506  }
507  wfl::map_formula_callable callable(std::make_shared<wfl::unit_callable>(*u_itor));
508  u_itor = units.find(receiver_loc);
509  if(u_itor != units.end()) {
510  callable.add("other", wfl::variant(std::make_shared<wfl::unit_callable>(*u_itor)));
511  }
512  return formula_handler(wfl::formula(s, new wfl::gamestate_function_symbol_table), callable);
513  } catch(const wfl::formula_error& e) {
514  lg::wml_error() << "Formula error in ability or weapon special: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
515  return def;
516  }
517  }));
518 }
519 }
520 
521 template<typename TComp>
522 std::pair<int,map_location> unit_ability_list::get_extremum(const std::string& key, int def, const TComp& comp) const
523 {
524  if ( cfgs_.empty() ) {
525  return std::make_pair(def, map_location());
526  }
527  // The returned location is the best non-cumulative one, if any,
528  // the best absolute cumulative one otherwise.
529  map_location best_loc;
530  bool only_cumulative = true;
531  int abs_max = 0;
532  int flat = 0;
533  int stack = 0;
534  for (const unit_ability& p : cfgs_)
535  {
536  int value = get_single_ability_value((*p.first)[key], def, p.second, loc(),[&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
537  return formula.evaluate(callable).as_int();
538  });
539 
540  if ((*p.first)["cumulative"].to_bool()) {
541  stack += value;
542  if (value < 0) value = -value;
543  if (only_cumulative && !comp(value, abs_max)) {
544  abs_max = value;
545  best_loc = p.second;
546  }
547  } else if (only_cumulative || comp(flat, value)) {
548  only_cumulative = false;
549  flat = value;
550  best_loc = p.second;
551  }
552  }
553  return std::make_pair(flat + stack, best_loc);
554 }
555 
556 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;
557 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;
558 
559 /*
560  *
561  * [special]
562  * [swarm]
563  * name= _ "swarm"
564  * name_inactive= _ ""
565  * description= _ ""
566  * description_inactive= _ ""
567  * cumulative=no
568  * apply_to=self #self,opponent,defender,attacker,both
569  * #active_on=defense # or offense; omitting this means "both"
570  *
571  * swarm_attacks_max=4
572  * swarm_attacks_min=2
573  *
574  * [filter_self] // SUF
575  * ...
576  * [/filter_self]
577  * [filter_opponent] // SUF
578  * [filter_attacker] // SUF
579  * [filter_defender] // SUF
580  * [filter_adjacent] // SAUF
581  * [filter_adjacent_location] // SAUF + locs
582  * [/swarm]
583  * [/special]
584  *
585  */
586 
587 namespace {
588 
589  struct special_match
590  {
591  std::string tag_name;
592  const config* cfg;
593  };
594 
595  /**
596  * Gets the children of @parent (which should be the specials for an
597  * attack_type) and places the ones whose tag or id= matches @a id into
598  * @a tag_result and @a id_result.
599  *
600  * If @a just_peeking is set to true, then @a tag_result and @a id_result
601  * are not touched; instead the return value is used to indicate if any
602  * matching children were found.
603  *
604  * @returns true if @a just_peeking is true and a match was found;
605  * false otherwise.
606  */
607  bool get_special_children(std::vector<special_match>& tag_result,
608  std::vector<special_match>& id_result,
609  const config& parent, const std::string& id,
610  bool just_peeking=false) {
611  for (const config::any_child &sp : parent.all_children_range())
612  {
613  if (just_peeking && (sp.key == id || sp.cfg["id"] == id)) {
614  return true; // peek succeeded; done
615  }
616 
617  if(sp.key == id) {
618  special_match special = { sp.key, &sp.cfg };
619  tag_result.push_back(special);
620  }
621  if(sp.cfg["id"] == id) {
622  special_match special = { sp.key, &sp.cfg };
623  id_result.push_back(special);
624  }
625  }
626  return false;
627  }
628 
629  bool get_special_children_id(std::vector<special_match>& id_result,
630  const config& parent, const std::string& id,
631  bool just_peeking=false) {
632  for (const config::any_child &sp : parent.all_children_range())
633  {
634  if (just_peeking && (sp.cfg["id"] == id)) {
635  return true; // peek succeeded; done
636  }
637 
638  if(sp.cfg["id"] == id) {
639  special_match special = { sp.key, &sp.cfg };
640  id_result.push_back(special);
641  }
642  }
643  return false;
644  }
645 
646  bool get_special_children_tags(std::vector<special_match>& tag_result,
647  const config& parent, const std::string& id,
648  bool just_peeking=false) {
649  for (const config::any_child &sp : parent.all_children_range())
650  {
651  if (just_peeking && (sp.key == id)) {
652  return true; // peek succeeded; done
653  }
654 
655  if(sp.key == id) {
656  special_match special = { sp.key, &sp.cfg };
657  tag_result.push_back(special);
658  }
659  }
660  return false;
661  }
662 }
663 
664 /**
665  * Returns whether or not @a *this has a special with a tag or id equal to
666  * @a special. If @a simple_check is set to true, then the check is merely
667  * for being present. Otherwise (the default), the check is for a special
668  * active in the current context (see set_specials_context), including
669  * specials obtained from the opponent's attack.
670  */
671 bool attack_type::get_special_bool(const std::string& special, bool simple_check, bool special_id, bool special_tags) const
672 {
673  {
674  std::vector<special_match> special_tag_matches;
675  std::vector<special_match> special_id_matches;
676  if(special_id && special_tags){
677  if ( get_special_children(special_tag_matches, special_id_matches, specials_, special, simple_check) ) {
678  return true;
679  }
680  } else if(special_id && !special_tags){
681  if ( get_special_children_id(special_id_matches, specials_, special, simple_check) ) {
682  return true;
683  }
684  } else if(!special_id && special_tags){
685  if ( get_special_children_tags(special_tag_matches, specials_, special, simple_check) ) {
686  return true;
687  }
688  }
689  // If we make it to here, then either list.empty() or !simple_check.
690  // So if the list is not empty, then this is not a simple check and
691  // we need to check each special in the list to see if any are active.
692  if(special_tags){
693  for(const special_match& entry : special_tag_matches) {
694  if ( special_active(*entry.cfg, AFFECT_SELF, entry.tag_name) ) {
695  return true;
696  }
697  }
698  }
699  if(special_id){
700  for(const special_match& entry : special_id_matches) {
701  if ( special_active(*entry.cfg, AFFECT_SELF, entry.tag_name) ) {
702  return true;
703  }
704  }
705  }
706  }
707 
708  // Skip checking the opponent's attack?
709  if ( simple_check || !other_attack_ ) {
710  return false;
711  }
712 
713  std::vector<special_match> special_tag_matches;
714  std::vector<special_match> special_id_matches;
715  if(special_id && special_tags){
716  get_special_children(special_tag_matches, special_id_matches, other_attack_->specials_, special);
717  } else if(special_id && !special_tags){
718  get_special_children_id(special_id_matches, other_attack_->specials_, special);
719  } else if(!special_id && special_tags){
720  get_special_children_tags(special_tag_matches, other_attack_->specials_, special);
721  }
722  if(special_tags){
723  for(const special_match& entry : special_tag_matches) {
724  if ( other_attack_->special_active(*entry.cfg, AFFECT_OTHER, entry.tag_name) ) {
725  return true;
726  }
727  }
728  }
729  if(special_id){
730  for(const special_match& entry : special_id_matches) {
731  if ( other_attack_->special_active(*entry.cfg, AFFECT_OTHER, entry.tag_name) ) {
732  return true;
733  }
734  }
735  }
736  return false;
737 }
738 
739 /**
740  * Returns the currently active specials as an ability list, given the current
741  * context (see set_specials_context).
742  */
743 unit_ability_list attack_type::get_specials(const std::string& special) const
744 {
745  //log_scope("get_specials");
746  const map_location loc = self_ ? self_->get_location() : self_loc_;
747  unit_ability_list res(loc);
748 
749  for(const config& i : specials_.child_range(special)) {
750  if(special_active(i, AFFECT_SELF, special)) {
751  res.emplace_back(&i, loc);
752  }
753  }
754 
755  if(!other_attack_) {
756  return res;
757  }
758 
759  for(const config& i : other_attack_->specials_.child_range(special)) {
760  if(other_attack_->special_active(i, AFFECT_OTHER, special)) {
761  res.emplace_back(&i, other_loc_);
762  }
763  }
764  return res;
765 }
766 
767 /**
768  * Returns a vector of names and descriptions for the specials of *this.
769  * Each std::pair in the vector has first = name and second = description.
770  *
771  * This uses either the active or inactive name/description for each special,
772  * based on the current context (see set_specials_context), provided
773  * @a active_list is not nullptr. Otherwise specials are assumed active.
774  * If the appropriate name is empty, the special is skipped.
775  */
776 std::vector<std::pair<t_string, t_string>> attack_type::special_tooltips(
777  boost::dynamic_bitset<>* active_list) const
778 {
779  //log_scope("special_tooltips");
780  std::vector<std::pair<t_string, t_string>> res;
781  if ( active_list )
782  active_list->clear();
783 
784  for (const config::any_child &sp : specials_.all_children_range())
785  {
786  if ( !active_list || special_active(sp.cfg, AFFECT_EITHER, sp.key) ) {
787  const t_string &name = sp.cfg["name"];
788  if (!name.empty()) {
789  res.emplace_back(name, sp.cfg["description"].t_str() );
790  if ( active_list )
791  active_list->push_back(true);
792  }
793  } else {
794  const t_string& name = default_value(sp.cfg, "name_inactive", "name").t_str();
795  if (!name.empty()) {
796  res.emplace_back(name, default_value(sp.cfg, "description_inactive", "description").t_str() );
797  active_list->push_back(false);
798  }
799  }
800  }
801  return res;
802 }
803 
804 /**
805  * Returns a comma-separated string of active names for the specials of *this.
806  * Empty names are skipped.
807  *
808  * This excludes inactive specials if only_active is true. Whether or not a
809  * special is active depends on the current context (see set_specials_context)
810  * and the @a is_backstab parameter.
811  */
812 std::string attack_type::weapon_specials(bool only_active, bool is_backstab) const
813 {
814  //log_scope("weapon_specials");
815  std::string res;
816  for (const config::any_child &sp : specials_.all_children_range())
817  {
818  const bool active = special_active(sp.cfg, AFFECT_EITHER, sp.key, is_backstab);
819 
820  const std::string& name =
821  active
822  ? sp.cfg["name"].str()
823  : default_value(sp.cfg, "name_inactive", "name").str();
824  if (!name.empty()) {
825  if (!res.empty()) res += ", ";
826  if (only_active && !active) res += font::span_color(font::inactive_details_color);
827  res += name;
828  if (only_active && !active) res += "</span>";
829  }
830  }
831 
832  return res;
833 }
834 
835 
836 /**
837  * Sets the context under which specials will be checked for being active.
838  * This version is appropriate if both units in a combat are known.
839  * @param[in] self A reference to the unit with this weapon.
840  * @param[in] other A reference to the other unit in the combat.
841  * @param[in] unit_loc The location of the unit with this weapon.
842  * @param[in] other_loc The location of the other unit in the combat.
843  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
844  * @param[in] other_attack The attack used by the other unit.
845  */
847  const_attack_ptr other_attack,
848  unit_const_ptr self,
849  unit_const_ptr other,
850  const map_location& unit_loc,
851  const map_location& other_loc,
852  bool attacking)
853  : parent(weapon.shared_from_this())
854 {
855  weapon.self_ = self;
856  weapon.other_ = other;
857  weapon.self_loc_ = unit_loc;
858  weapon.other_loc_ = other_loc;
859  weapon.is_attacker_ = attacking;
860  weapon.other_attack_ = other_attack;
861  weapon.is_for_listing_ = false;
862 }
863 
864 /**
865  * Sets the context under which specials will be checked for being active.
866  * This version is appropriate if there is no specific combat being considered.
867  * @param[in] self A reference to the unit with this weapon.
868  * @param[in] loc The location of the unit with this weapon.
869  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
870  */
872  : parent(weapon.shared_from_this())
873 {
874  weapon.self_ = self;
875  weapon.other_ = nullptr;
876  weapon.self_loc_ = loc;
878  weapon.is_attacker_ = attacking;
879  weapon.other_attack_ = nullptr;
880  weapon.is_for_listing_ = false;
881 }
882 
883 /**
884  * Sets the context under which specials will be checked for being active.
885  * This version is appropriate for theoretical units of a particular type.
886  * @param[in] self_type A reference to the type of the unit with this weapon.
887  * @param[in] loc The location of the unit with this weapon.
888  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
889  */
890 attack_type::specials_context_t::specials_context_t(const attack_type& weapon, const unit_type& self_type, const map_location& loc, bool attacking)
891  : parent(weapon.shared_from_this())
892 {
893  UNUSED(self_type);
894  weapon.self_ = nullptr;
895  weapon.other_ = nullptr;
896  weapon.self_loc_ = loc;
898  weapon.is_attacker_ = attacking;
899  weapon.other_attack_ = nullptr;
900  weapon.is_for_listing_ = false;
901 }
902 
904  : parent(weapon.shared_from_this())
905 {
906  weapon.is_for_listing_ = true;
907  weapon.is_attacker_ = attacking;
908 }
909 
911 {
912  if(was_moved) return;
913  parent->self_ = nullptr;
914  parent->other_ = nullptr;
915  parent->self_loc_ = map_location::null_location();
916  parent->other_loc_ = map_location::null_location();
917  parent->is_attacker_ = false;
918  parent->other_attack_ = nullptr;
919  parent->is_for_listing_ = false;
920 }
921 
923  : parent(other.parent)
924 {
925  other.was_moved = true;
926 }
927 
928 /**
929  * Calculates the number of attacks this weapon has, considering specials.
930  * This returns two numbers because of the swarm special. The actual number of
931  * attacks depends on the unit's health and should be:
932  * min_attacks + (max_attacks - min_attacks) * (current hp) / (max hp)
933  * c.f. swarm_blows()
934  */
935 void attack_type::modified_attacks(bool is_backstab, unsigned & min_attacks,
936  unsigned & max_attacks) const
937 {
938  // Apply [attacks].
939  unit_abilities::effect attacks_effect(get_special_ability("attacks"),
940  num_attacks(), is_backstab);
941  int attacks_value = attacks_effect.get_composite_value();
942 
943  if ( attacks_value < 0 ) {
944  attacks_value = num_attacks();
945  ERR_NG << "negative number of strikes after applying weapon specials" << std::endl;
946  }
947 
948  // Apply [swarm].
949  unit_ability_list swarm_specials = get_special_ability("swarm");
950  if ( !swarm_specials.empty() ) {
951  min_attacks = std::max<int>(0, swarm_specials.highest("swarm_attacks_min").first);
952  max_attacks = std::max<int>(0, swarm_specials.highest("swarm_attacks_max", attacks_value).first);
953  } else {
954  min_attacks = max_attacks = attacks_value;
955  }
956 }
957 
958 
959 /**
960  * Returns the damage per attack of this weapon, considering specials.
961  */
962 int attack_type::modified_damage(bool is_backstab) const
963 {
964  unit_abilities::effect dmg_effect(get_special_ability("damage"), damage(), is_backstab);
965  int damage_value = dmg_effect.get_composite_value();
966  return damage_value;
967 }
968 
969 
970 namespace { // Helpers for attack_type::special_active()
971 
972  /**
973  * Returns whether or not the given special affects the opponent of the unit
974  * with the special.
975  * @param[in] special a weapon special WML structure
976  * @param[in] is_attacker whether or not the unit with the special is the attacker
977  */
978  bool special_affects_opponent(const config& special, bool is_attacker)
979  {
980  //log_scope("special_affects_opponent");
981  const std::string& apply_to = special["apply_to"];
982  if ( apply_to.empty() )
983  return false;
984  if ( apply_to == "both" )
985  return true;
986  if ( apply_to == "opponent" )
987  return true;
988  if ( is_attacker && apply_to == "defender" )
989  return true;
990  if ( !is_attacker && apply_to == "attacker" )
991  return true;
992  return false;
993  }
994 
995  /**
996  * Returns whether or not the given special affects the unit with the special.
997  * @param[in] special a weapon special WML structure
998  * @param[in] is_attacker whether or not the unit with the special is the attacker
999  */
1000  bool special_affects_self(const config& special, bool is_attacker)
1001  {
1002  //log_scope("special_affects_self");
1003  const std::string& apply_to = special["apply_to"];
1004  if ( apply_to.empty() )
1005  return true;
1006  if ( apply_to == "both" )
1007  return true;
1008  if ( apply_to == "self" )
1009  return true;
1010  if ( is_attacker && apply_to == "attacker" )
1011  return true;
1012  if ( !is_attacker && apply_to == "defender")
1013  return true;
1014  return false;
1015  }
1016 
1017  /**
1018  * Determines if a unit/weapon combination matches the specified child
1019  * (normally a [filter_*] child) of the provided filter.
1020  * @param[in] u A unit to filter.
1021  * @param[in] u2 Another unit to filter.
1022  * @param[in] loc The presumed location of @a un_it.
1023  * @param[in] weapon The attack_type to filter.
1024  * @param[in] filter The filter containing the child filter to use.
1025  * @param[in] child_tag The tag of the child filter to use.
1026  */
1027  static bool special_unit_matches(unit_const_ptr & u,
1028  unit_const_ptr & u2,
1029  const map_location & loc,
1030  const_attack_ptr weapon,
1031  const config & filter,
1032  const bool for_listing,
1033  const std::string & child_tag)
1034  {
1035  if (for_listing && !loc.valid())
1036  // The special's context was set to ignore this unit, so assume we pass.
1037  // (This is used by reports.cpp to show active specials when the
1038  // opponent is not known. From a player's perspective, the special
1039  // is active, in that it can be used, even though the player might
1040  // need to select an appropriate opponent.)
1041  return true;
1042 
1043  const config & filter_child = filter.child(child_tag);
1044  if ( !filter_child )
1045  // The special does not filter on this unit, so we pass.
1046  return true;
1047 
1048  // If the primary unit doesn't exist, there's nothing to match
1049  if (!u) {
1050  return false;
1051  }
1052 
1053  unit_filter ufilt{vconfig(filter_child)};
1054 
1055  // If the other unit doesn't exist, try matching without it
1056  if (!u2) {
1057  return ufilt.matches(*u, loc);
1058  }
1059 
1060  // Check for a unit match.
1061  if (!ufilt.matches(*u, loc, *u2)) {
1062  return false;
1063  }
1064 
1065  // Check for a weapon match.
1066  if ( const config & filter_weapon = filter_child.child("filter_weapon") ) {
1067  if ( !weapon || !weapon->matches_filter(filter_weapon) )
1068  return false;
1069  }
1070 
1071  // Passed.
1072  return true;
1073  }
1074 
1075 }//anonymous namespace
1076 
1077 
1078 //The following functions are intended to allow the use in combat of capacities
1079 //identical to special weapons and therefore to be able to use them on adjacent
1080 //units (abilities of type 'aura') or else on all types of weapons even if the
1081 //beneficiary unit does not have a corresponding weapon
1082 //(defense against ranged weapons abilities for a unit that only has melee attacks)
1083 
1084 /**
1085  * returns whether @a u matches @a cfg .child( @a filter_attacker ). But also check for [filter_weapon] subtags writ the current attack context.
1086  *
1087  * @param[in] ability The tagname of the ability, needed because some tagnames need special handling
1088  * @param[in] loc The location where we assume the filtered unit to be at
1089  * @param[in] u2 the 'other unit' in the filter
1090  * @param[in] weapon used for [filter_weapon]
1091 *
1092  * @returns true if the unit passed the filter.
1093  */
1094 static bool ability_filter_fighter(const std::string& ability,
1095  const std::string & filter_attacker,
1096  const config& cfg, const map_location & loc,
1097  unit_const_ptr & u,
1098  unit_const_ptr & u2,
1099  const_attack_ptr weapon)
1100 {
1101 
1102  const config &filter = cfg.child(filter_attacker);
1103  if(!filter) {
1104  return true;
1105  }
1106 
1107  if (!u) {
1108  return false;
1109  }
1110 
1111  if ( const config & filter_weapon = filter.child("filter_weapon") ) {
1112  if ( !weapon || !weapon->matches_filter(filter_weapon) )
1113  return false;
1114  }
1115 
1116  if (!u2) {
1117  return unit_filter(vconfig(filter)).set_use_flat_tod(ability == "illuminates").matches(*u, loc);
1118  }
1119  return unit_filter(vconfig(filter)).set_use_flat_tod(ability == "illuminates").matches(*u, loc, *u2);
1120 }
1121 
1122 /**
1123 * checks whether a single ability passes attack related filters, in particular for example [filter_student][filter_weapon]
1124  *
1125  * @param[in] un, up The units filtered(in that example un is 'self and up 'other' unit)
1126  * @param[in] ability The tagname of the ability, needed because some tagnames need special handling
1127  * @param[in] loc, opp_loc The locations where we assume the filtered units un and up to be at(here loc is used)
1128  * @param[in] weapon, opp_weapon used for [filter_weapon](weapon in that example)
1129 *
1130  * @returns true if the units passed all filters.
1131  */
1132 static bool ability_apply_filter(unit_const_ptr un, unit_const_ptr up, const std::string& ability, const config& cfg, const map_location& loc, const map_location& opp_loc, bool attacker, const_attack_ptr weapon, const_attack_ptr opp_weapon)
1133 {
1134  bool filter_opponent = ability_filter_fighter(ability, "filter_opponent", cfg, opp_loc, up, un, opp_weapon);
1135  bool filter_student = ability_filter_fighter(ability, "filter_student", cfg, loc, un, up, weapon);
1136  bool filter_attacker = (attacker && ability_filter_fighter(ability, "filter_attacker", cfg, loc, un, up, weapon)) || (!attacker && ability_filter_fighter(ability, "filter_attacker", cfg, opp_loc, up, un, opp_weapon));
1137  bool filter_defender = (!attacker && ability_filter_fighter(ability, "filter_defender", cfg, loc,un, up, weapon)) || (attacker && ability_filter_fighter(ability, "filter_defender", cfg, opp_loc, up, un, opp_weapon));
1138  if(filter_student && filter_opponent && filter_attacker && filter_defender){
1139  return true;
1140  }
1141  return false;
1142 }
1143 
1144 /**
1145  * like to un->get_abilities() this returns the active abilities on this unit. That is. abilities of this unit and abilities of adjacent units that affect this unit. Unlike un->get_abilities() this also checks for attack related filters.
1146  * @param[in] un The current unit
1147  * @param[in] ability The tagname of the ability, for filtered the abilities type
1148  * @param[in] loc The location where we assume the un unit to be at
1149  * @param[in] up The opponent
1150  * @param[in] opp_loc The location where we assume the opponent to be at
1151  * @param[in] attacker the condition for matches 'active_on=offense/defense' and @a special_affects_self/opponent
1152  * @param[in] weapon, opp_weapon used for [filter_weapon] in @a ability_filter_fighter
1153  * @param[in] affect_other if true we will only return abilities that affect the opponent otherwise we will return abilities that affect @a un
1154  *
1155  * @returns an unit_ability_list containing all abilities active in the current attack context.
1156  */
1157 static unit_ability_list list_leadership(const std::string& ability, unit_const_ptr un, unit_const_ptr up, const map_location& loc, const map_location& opp_loc, bool attacker, const_attack_ptr weapon, const_attack_ptr opp_weapon, bool affect_other)
1158 {
1159  unit_ability_list abil = (*un).get_abilities(ability, loc);
1160  for(unit_ability_list::iterator i = abil.begin(); i != abil.end();) {
1161  bool abil_affect = false;//used for determine if abilities with special_affects_self or opponent must be erased of list
1162  if(affect_other){
1163  abil_affect = !special_affects_opponent((*i->first), attacker);
1164  } else {
1165  abil_affect = !special_affects_self((*i->first), attacker);
1166  }
1167  bool fighter_filter = !ability_apply_filter(un, up, ability, *i->first, loc, opp_loc, attacker, weapon, opp_weapon);
1168  const std::string& active_on = (*i->first)["active_on"];
1169  bool active_on_bool = !(active_on.empty() || (attacker && active_on == "offense") || (!attacker && active_on == "defense"));
1170  if(active_on_bool || fighter_filter || abil_affect) {
1171  i = abil.erase(i);
1172  } else {
1173  ++i;
1174  }
1175  }
1176  return abil;
1177 }
1178 
1179 unit_ability_list attack_type::list_ability(const std::string& ability) const
1180 {
1181  const unit_map& units = display::get_singleton()->get_units();
1182 
1183  unit_const_ptr self = self_;
1184  unit_const_ptr other = other_;
1185 
1186  if(self == nullptr) {
1188  if(it.valid()) {
1189  self = it.get_shared_ptr().get();
1190  }
1191  }
1192  if(other == nullptr) {
1194  if(it.valid()) {
1195  other = it.get_shared_ptr().get();
1196  }
1197  }
1198 
1199  // Make sure they're facing each other.
1200  temporary_facing self_facing(self, self_loc_.get_relative_dir(other_loc_));
1201  temporary_facing other_facing(other, other_loc_.get_relative_dir(self_loc_));
1202  const map_location loc = self_ ? self_->get_location() : self_loc_;
1203  unit_ability_list abil_list(loc);
1204  if(self){
1205  abil_list.append(list_leadership(ability, self, other, self_loc_, other_loc_, is_attacker_, shared_from_this(), other_attack_, false));
1206  }
1207 
1208  if(other && other_attack_) {
1209  abil_list.append(list_leadership(ability, other, self, other_loc_, self_loc_, !is_attacker_, other_attack_, shared_from_this(), true));
1210  }
1211  return abil_list;
1212 }
1213 
1214 unit_ability_list attack_type::get_special_ability(const std::string& ability) const
1215 {
1216  unit_ability_list abil_list = list_ability(ability);
1217  abil_list.append(get_specials(ability));
1218  return abil_list;
1219 }
1220 
1221 bool attack_type::bool_ability(const std::string& ability) const
1222 {
1223  bool abil_bool = get_special_bool(ability);
1224  unit_ability_list abil = list_ability(ability);
1225  if(!abil.empty()) {
1226  abil_bool = true;
1227  }
1228  return abil_bool;
1229 }
1230 //end of emulate weapon special functions.
1231 
1232 /**
1233  * Returns whether or not the given special is active for the specified unit,
1234  * based on the current context (see set_specials_context).
1235  * @param[in] special a weapon special WML structure
1236  * @param[in] whom specifies which combatant we care about
1237  * @param[in] tag_name tag name of the special config
1238  * @param[in] include_backstab false if backstab specials should not be active
1239  * (usually true since backstab is usually accounted
1240  * for elsewhere)
1241  */
1242 bool attack_type::special_active(const config& special, AFFECTS whom, const std::string& tag_name,
1243  bool include_backstab) const
1244 {
1245  //log_scope("special_active");
1246 
1247  // Backstab check
1248  if ( !include_backstab )
1249  if ( special["backstab"].to_bool() )
1250  return false;
1251 
1252  // Does this affect the specified unit?
1253  if ( whom == AFFECT_SELF ) {
1254  if ( !special_affects_self(special, is_attacker_) )
1255  return false;
1256  }
1257  if ( whom == AFFECT_OTHER ) {
1258  if ( !special_affects_opponent(special, is_attacker_) )
1259  return false;
1260  }
1261 
1262  // Is this active on attack/defense?
1263  const std::string & active_on = special["active_on"];
1264  if ( !active_on.empty() ) {
1265  if ( is_attacker_ && active_on != "offense" )
1266  return false;
1267  if ( !is_attacker_ && active_on != "defense" )
1268  return false;
1269  }
1270 
1271  // Get the units involved.
1272  assert(display::get_singleton());
1273  const unit_map& units = display::get_singleton()->get_units();
1274 
1275  unit_const_ptr self = self_;
1276  unit_const_ptr other = other_;
1277 
1278  if(self == nullptr) {
1280  if(it.valid()) {
1281  self = it.get_shared_ptr().get();
1282  }
1283  }
1284  if(other == nullptr) {
1286  if(it.valid()) {
1287  other = it.get_shared_ptr().get();
1288  }
1289  }
1290 
1291  // Make sure they're facing each other.
1292  temporary_facing self_facing(self, self_loc_.get_relative_dir(other_loc_));
1293  temporary_facing other_facing(other, other_loc_.get_relative_dir(self_loc_));
1294 
1295  // Filter poison, plague, drain, first strike
1296  if (tag_name == "drains" && other && other->get_state("undrainable")) {
1297  return false;
1298  }
1299  if (tag_name == "plague" && other &&
1300  (other->get_state("unplagueable") ||
1302  return false;
1303  }
1304  if (tag_name == "poison" && other &&
1305  (other->get_state("unpoisonable") || other->get_state(unit::STATE_POISONED))) {
1306  return false;
1307  }
1308  if (tag_name == "firststrike" && !is_attacker_ && other_attack_ &&
1309  other_attack_->get_special_bool("firststrike", false)) {
1310  return false;
1311  }
1312 
1313 
1314  // Translate our context into terms of "attacker" and "defender".
1315  unit_const_ptr & att = is_attacker_ ? self : other;
1316  unit_const_ptr & def = is_attacker_ ? other : self;
1317  const map_location & att_loc = is_attacker_ ? self_loc_ : other_loc_;
1318  const map_location & def_loc = is_attacker_ ? other_loc_ : self_loc_;
1319  const_attack_ptr att_weapon = is_attacker_ ? shared_from_this() : other_attack_;
1320  const_attack_ptr def_weapon = is_attacker_ ? other_attack_ : shared_from_this();
1321 
1322  // Filter the units involved.
1323  if (!special_unit_matches(self, other, self_loc_, shared_from_this(), special, is_for_listing_, "filter_self"))
1324  return false;
1325  if (!special_unit_matches(other, self, other_loc_, other_attack_, special, is_for_listing_, "filter_opponent"))
1326  return false;
1327  if (!special_unit_matches(att, def, att_loc, att_weapon, special, is_for_listing_, "filter_attacker"))
1328  return false;
1329  if (!special_unit_matches(def, att, def_loc, def_weapon, special, is_for_listing_, "filter_defender"))
1330  return false;
1331 
1332  adjacent_loc_array_t adjacent;
1333  get_adjacent_tiles(self_loc_, adjacent.data());
1334 
1335  // Filter the adjacent units.
1336  for (const config &i : special.child_range("filter_adjacent"))
1337  {
1338  std::size_t count = 0;
1339  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
1340  unit_filter filter{ vconfig(i) };
1341  for (const map_location::DIRECTION index : dirs)
1342  {
1344  continue;
1345  unit_map::const_iterator unit = units.find(adjacent[index]);
1346  if (unit == units.end() || !filter.matches(*unit, adjacent[index], *self))
1347  return false;
1348  if (i.has_attribute("is_enemy")) {
1350  if (i["is_enemy"].to_bool() != dc.get_team(unit->side()).is_enemy(self->side())) {
1351  continue;
1352  }
1353  }
1354  count++;
1355  }
1356  if (i["count"].empty() && count != dirs.size()) {
1357  return false;
1358  }
1359  if (!in_ranges<int>(count, utils::parse_ranges(i["count"].str()))) {
1360  return false;
1361  }
1362  }
1363 
1364  // Filter the adjacent locations.
1365  for (const config &i : special.child_range("filter_adjacent_location"))
1366  {
1367  std::size_t count = 0;
1368  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
1369  terrain_filter adj_filter(vconfig(i), resources::filter_con);
1370  for (const map_location::DIRECTION index : dirs)
1371  {
1373  continue;
1374  if(!adj_filter.match(adjacent[index])) {
1375  return false;
1376  }
1377  count++;
1378  }
1379  if (i["count"].empty() && count != dirs.size()) {
1380  return false;
1381  }
1382  if (!in_ranges<int>(count, utils::parse_ranges(i["count"].str()))) {
1383  return false;
1384  }
1385  }
1386 
1387  return true;
1388 }
1389 
1390 
1391 
1393 {
1394 
1395 void individual_effect::set(value_modifier t, int val, const config *abil, const map_location &l)
1396 {
1397  type=t;
1398  value=val;
1399  ability=abil;
1400  loc=l;
1401 }
1402 
1403 bool filter_base_matches(const config& cfg, int def)
1404 {
1405  if (const config &apply_filter = cfg.child("filter_base_value")) {
1406  config::attribute_value cond_eq = apply_filter["equals"];
1407  config::attribute_value cond_ne = apply_filter["not_equals"];
1408  config::attribute_value cond_lt = apply_filter["less_than"];
1409  config::attribute_value cond_gt = apply_filter["greater_than"];
1410  config::attribute_value cond_ge = apply_filter["greater_than_equal_to"];
1411  config::attribute_value cond_le = apply_filter["less_than_equal_to"];
1412  return (cond_eq.empty() || def == cond_eq.to_int()) &&
1413  (cond_ne.empty() || def != cond_ne.to_int()) &&
1414  (cond_lt.empty() || def < cond_lt.to_int()) &&
1415  (cond_gt.empty() || def > cond_gt.to_int()) &&
1416  (cond_ge.empty() || def >= cond_ge.to_int()) &&
1417  (cond_le.empty() || def <= cond_le.to_int());
1418  }
1419  return true;
1420 }
1421 
1422 effect::effect(const unit_ability_list& list, int def, bool backstab) :
1423  effect_list_(),
1424  composite_value_(0)
1425 {
1426 
1427  int value_set = def;
1428  std::map<std::string,individual_effect> values_add;
1429  std::map<std::string,individual_effect> values_mul;
1430  std::map<std::string,individual_effect> values_div;
1431 
1432  individual_effect set_effect_max;
1433  individual_effect set_effect_min;
1434 
1435  for (const unit_ability & ability : list) {
1436  const config& cfg = *ability.first;
1437  const std::string& effect_id = cfg[cfg["id"].empty() ? "name" : "id"];
1438 
1439  if (!cfg["backstab"].blank()) {
1440  deprecated_message("backstab= in weapon specials", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Use [filter_adjacent] instead.");
1441  }
1442 
1443  if (!backstab && cfg["backstab"].to_bool())
1444  continue;
1445  if (!filter_base_matches(cfg, def))
1446  continue;
1447 
1448  if (const config::attribute_value *v = cfg.get("value")) {
1449  int value = get_single_ability_value(*v, def, ability.second, list.loc(),[&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
1450  callable.add("base_value", wfl::variant(def));
1451  return formula.evaluate(callable).as_int();
1452  });
1453 
1454  int value_cum = cfg["cumulative"].to_bool() ? std::max(def, value) : value;
1455  assert((set_effect_min.type != NOT_USED) == (set_effect_max.type != NOT_USED));
1456  if(set_effect_min.type == NOT_USED) {
1457  set_effect_min.set(SET, value_cum, ability.first, ability.second);
1458  set_effect_max.set(SET, value_cum, ability.first, ability.second);
1459  }
1460  else {
1461  if(value_cum > set_effect_max.value) {
1462  set_effect_max.set(SET, value_cum, ability.first, ability.second);
1463  }
1464  if(value_cum < set_effect_min.value) {
1465  set_effect_min.set(SET, value_cum, ability.first, ability.second);
1466  }
1467  }
1468  }
1469 
1470  if (const config::attribute_value *v = cfg.get("add")) {
1471  int add = get_single_ability_value(*v, def, ability.second, list.loc(),[&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
1472  callable.add("base_value", wfl::variant(def));
1473  return formula.evaluate(callable).as_int();
1474  });
1475  std::map<std::string,individual_effect>::iterator add_effect = values_add.find(effect_id);
1476  if(add_effect == values_add.end() || add > add_effect->second.value) {
1477  values_add[effect_id].set(ADD, add, ability.first, ability.second);
1478  }
1479  }
1480  if (const config::attribute_value *v = cfg.get("sub")) {
1481  int sub = - get_single_ability_value(*v, def, ability.second, list.loc(),[&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
1482  callable.add("base_value", wfl::variant(def));
1483  return formula.evaluate(callable).as_int();
1484  });
1485  std::map<std::string,individual_effect>::iterator sub_effect = values_add.find(effect_id);
1486  if(sub_effect == values_add.end() || sub < sub_effect->second.value) {
1487  values_add[effect_id].set(ADD, sub, ability.first, ability.second);
1488  }
1489  }
1490  if (const config::attribute_value *v = cfg.get("multiply")) {
1491  int multiply = static_cast<int>(get_single_ability_value(*v, static_cast<double>(def), ability.second, list.loc(),[&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
1492  callable.add("base_value", wfl::variant(def));
1493  return formula.evaluate(callable).as_decimal() / 1000.0 ;
1494  }) * 100);
1495  std::map<std::string,individual_effect>::iterator mul_effect = values_mul.find(effect_id);
1496  if(mul_effect == values_mul.end() || multiply > mul_effect->second.value) {
1497  values_mul[effect_id].set(MUL, multiply, ability.first, ability.second);
1498  }
1499  }
1500  if (const config::attribute_value *v = cfg.get("divide")) {
1501  int divide = static_cast<int>(get_single_ability_value(*v, static_cast<double>(def), ability.second, list.loc(),[&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
1502  callable.add("base_value", wfl::variant(def));
1503  return formula.evaluate(callable).as_decimal() / 1000.0 ;
1504  }) * 100);
1505 
1506  if (divide == 0) {
1507  ERR_NG << "division by zero with divide= in ability/weapon special " << effect_id << std::endl;
1508  }
1509  else {
1510  std::map<std::string,individual_effect>::iterator div_effect = values_div.find(effect_id);
1511  if(div_effect == values_div.end() || divide > div_effect->second.value) {
1512  values_div[effect_id].set(DIV, divide, ability.first, ability.second);
1513  }
1514  }
1515  }
1516  }
1517 
1518  if(set_effect_max.type != NOT_USED) {
1519  value_set = std::max(set_effect_max.value, 0) + std::min(set_effect_min.value, 0);
1520  if(set_effect_max.value > def) {
1521  effect_list_.push_back(set_effect_max);
1522  }
1523  if(set_effect_min.value < def) {
1524  effect_list_.push_back(set_effect_min);
1525  }
1526  }
1527 
1528  /* Do multiplication with floating point values rather than integers
1529  * We want two places of precision for each multiplier
1530  * Using integers multiplied by 100 to keep precision causes overflow
1531  * after 3-4 abilities for 32-bit values and ~8 for 64-bit
1532  * Avoiding the overflow by dividing after each step introduces rounding errors
1533  * that may vary depending on the order effects are applied
1534  * As the final values are likely <1000 (always true for mainline), loss of less significant digits is not an issue
1535  */
1536  double multiplier = 1.0;
1537  double divisor = 1.0;
1538 
1539  for(const auto& val : values_mul) {
1540  multiplier *= val.second.value/100.0;
1541  effect_list_.push_back(val.second);
1542  }
1543 
1544  for(const auto& val : values_div) {
1545  divisor *= val.second.value/100.0;
1546  effect_list_.push_back(val.second);
1547  }
1548 
1549  int addition = 0;
1550  for(const auto& val : values_add) {
1551  addition += val.second.value;
1552  effect_list_.push_back(val.second);
1553  }
1554 
1555  composite_value_ = static_cast<int>((value_set + addition) * multiplier / divisor);
1556 }
1557 
1558 } // end namespace unit_abilities
boost::intrusive_ptr< const unit > unit_const_ptr
Definition: ptr.hpp:30
bool empty() const
Tests for an attribute that either was never set or was set to "".
std::string weapon_specials(bool only_active=false, bool is_backstab=false) const
Returns a comma-separated string of active names for the specials of *this.
Definition: abilities.cpp:812
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:442
std::vector< individual_effect > effect_list_
Definition: abilities.hpp:56
bool empty() const
Definition: unit.hpp:78
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:420
unit_iterator end()
Definition: map.hpp:415
#define ERR_NG
Definition: abilities.cpp:45
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:88
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:921
const team & get_team(int side) const
virtual const display_context & get_disp_context() const =0
void get_adjacent_tiles(const map_location &a, map_location *res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:474
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
Definition: cursor.cpp:175
DIRECTION get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition: location.cpp:226
This class represents a single unit of a specific type.
Definition: unit.hpp:106
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:39
unit_filter & set_use_flat_tod(bool value)
Definition: filter.hpp:121
void emplace_back(T &&... args)
Definition: unit.hpp:87
iterator erase(const iterator &erase_it)
Definition: unit.hpp:84
Variant for storing WML attributes.
std::string filename
Definition: formula.hpp:107
New lexcical_cast header.
int as_int() const
Definition: variant.cpp:292
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:412
void set(value_modifier t, int val, const config *abil, const map_location &l)
Definition: abilities.cpp:1395
child_itors child_range(config_key_type key)
Definition: config.cpp:362
map_location other_loc_
virtual const gamemap & map() const override
Definition: game_board.hpp:109
unit_ability_list list_ability(const std::string &ability) const
Returns list for weapon like abilitiesfor each ability type.
Definition: abilities.cpp:1179
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:935
The unit is poisoned - it loses health each turn.
Definition: unit.hpp:839
const unit_map & get_units() const
Definition: display.hpp:121
unit_ability_list get_special_ability(const std::string &ability) const
Returns list who contains list_ability and get_specials list for each ability type.
Definition: abilities.cpp:1214
bool has_ability_type(const std::string &ability) const
Check if the unit has an ability of a specific type.
Definition: abilities.cpp:455
unit_const_ptr other_
const std::string & type() const
Definition: attack_type.hpp:42
bool matches(const unit &u, const map_location &loc) const
Determine if *this matches filter at a specified location.
Definition: filter.hpp:129
#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:124
-file sdl_utils.hpp
int num_attacks() const
Definition: attack_type.hpp:51
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:310
A single unit type that the player may recruit.
Definition: types.hpp:42
bool filter_base_matches(const config &cfg, int def)
Definition: abilities.cpp:1403
std::vector< unit_ability >::iterator iterator
Definition: unit.hpp:69
map_location loc_
bool bool_ability(const std::string &ability) const
return an boolean value for abilities like poison slow firstrike or petrifies
Definition: abilities.cpp:1221
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:44
int as_decimal() const
Returns variant&#39;s internal representation of decimal number: ie, 1.234 is represented as 1234...
Definition: variant.cpp:301
static bool ability_apply_filter(unit_const_ptr un, unit_const_ptr up, const std::string &ability, const config &cfg, const map_location &loc, const map_location &opp_loc, bool attacker, const_attack_ptr weapon, const_attack_ptr opp_weapon)
checks whether a single ability passes attack related filters, in particular for example [filter_stud...
Definition: abilities.cpp:1132
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:29
std::pair< const config *, map_location > unit_ability
Definition: unit.hpp:48
map_formula_callable & add(const std::string &key, const variant &value)
Definition: callable.hpp:252
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:410
filter_context * filter_con
Definition: resources.cpp:23
bool valid() const
Definition: location.hpp:93
static bool ability_filter_fighter(const std::string &ability, const std::string &filter_attacker, const config &cfg, const map_location &loc, unit_const_ptr &u, unit_const_ptr &u2, const_attack_ptr weapon)
returns whether u matches cfg .child( filter_attacker ).
Definition: abilities.cpp:1094
specials_context_t(const attack_type &weapon, bool attacking)
Initialize weapon specials context for listing.
Definition: abilities.cpp:903
std::string type
Definition: formula.hpp:105
bool blank() const
Tests for an attribute that was never set.
game_board * gameboard
Definition: resources.cpp:20
bool is_enemy(int n) const
Definition: team.hpp:243
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:776
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:522
std::array< map_location, 6 > adjacent_loc_array_t
Definition: location.hpp:170
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:743
Encapsulates the map of the game.
Definition: location.hpp:42
unit_iterator find(std::size_t id)
Definition: map.cpp:311
#define UNUSED(x)
Definition: global.hpp:34
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:144
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:220
bool special_active(const config &special, AFFECTS whom, const std::string &tag_name, bool include_backstab=true) const
Returns whether or not the given special is active for the specified unit, based on the current conte...
Definition: abilities.cpp:1242
std::size_t i
Definition: function.cpp:933
std::stringstream & wml_error()
Use this logger to send errors due to deprecated WML.
Definition: log.cpp:269
bool is_for_listing_
int damage() const
Definition: attack_type.hpp:50
mock_party p
static map_location::DIRECTION s
iterator begin()
Definition: unit.hpp:72
unit_const_ptr self_
int modified_damage(bool is_backstab) const
Returns the damage per attack of this weapon, considering specials.
Definition: abilities.cpp:962
const display_context & get_disp_context() const
Definition: display.hpp:168
int get_composite_value() const
Definition: abilities.hpp:49
void append(const unit_ability_list &other)
Appens the abilities from other to this, ignores other.loc()
Definition: unit.hpp:92
DIRECTION
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:44
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:71
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:434
config & cfg
Definition: config.hpp:522
bool is_village(const map_location &loc) const
Definition: map.cpp:65
bool empty() const
Definition: tstring.hpp:182
double t
Definition: astarsearch.cpp:64
bool find(E event, F functor)
Tests whether an event handler is available.
const_attack_ptr other_attack_
A variable-expanding proxy for the config class.
Definition: variable.hpp:44
Standard logging facilities (interface).
V::result_type apply_visitor(const V &visitor) const
Applies a visitor to the underlying variant.
static const map_location & null_location()
Definition: location.hpp:85
bool get_special_bool(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:671
Container associating units to locations.
Definition: map.hpp:99
#define e
std::vector< std::string > get_ability_list() const
Get a list of all abilities by ID.
Definition: abilities.cpp:226
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:184
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:338
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:68
bool valid() const
Definition: map.hpp:276
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:37
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
bool empty() const
Definition: config.cpp:884
map_location self_loc_
static lg::log_domain log_engine("engine")
iterator end()
Definition: unit.hpp:74
std::pair< int, map_location > highest(const std::string &key, int def=0) const
Definition: unit.hpp:56
static unit_ability_list list_leadership(const std::string &ability, unit_const_ptr un, unit_const_ptr up, const map_location &loc, const map_location &opp_loc, bool attacker, const_attack_ptr weapon, const_attack_ptr opp_weapon, bool affect_other)
like to un->get_abilities() this returns the active abilities on this unit.
Definition: abilities.cpp:1157