The Battle for Wesnoth  1.15.6+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 
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, 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, loc, adjacent[i]);
218  }
219  }
220  }
221 
222 
223  return res;
224 }
225 
227 {
228  unit_ability_list res = get_abilities(tag_name, loc);
229  for(unit_ability_list::iterator i = res.begin(); i != res.end();) {
230  if((!ability_affects_weapon(*i->ability_cfg, weapon, false) || !ability_affects_weapon(*i->ability_cfg, opp_weapon, true))) {
231  i = res.erase(i);
232  } else {
233  ++i;
234  }
235  }
236  return res;
237 }
238 
239 std::vector<std::string> unit::get_ability_list() const
240 {
241  std::vector<std::string> res;
242 
243  for (const config::any_child &ab : this->abilities_.all_children_range()) {
244  std::string id = ab.cfg["id"];
245  if (!id.empty())
246  res.push_back(std::move(id));
247  }
248  return res;
249 }
250 
251 
252 namespace {
253  // These functions might have wider usefulness than this file, but for now
254  // I'll make them local.
255 
256  /**
257  * Chooses a value from the given config. If the value specified by @a key is
258  * blank, then @a default_key is chosen instead.
259  */
260  inline const config::attribute_value & default_value(
261  const config & cfg, const std::string & key, const std::string & default_key)
262  {
263  const config::attribute_value & value = cfg[key];
264  return !value.blank() ? value : cfg[default_key];
265  }
266 
267  /**
268  * Chooses a value from the given config based on gender. If the value for
269  * the specified gender is blank, then @a default_key is chosen instead.
270  */
271  inline const config::attribute_value & gender_value(
272  const config & cfg, unit_race::GENDER gender, const std::string & male_key,
273  const std::string & female_key, const std::string & default_key)
274  {
275  return default_value(cfg,
276  gender == unit_race::MALE ? male_key : female_key,
277  default_key);
278  }
279 
280  /**
281  * Adds a quadruple consisting of (in order) id, base name,
282  * male or female name as appropriate for the unit, and description.
283  *
284  * @returns Whether name was resolved and quadruple added.
285  */
286  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)
287  {
288  if (active) {
289  const t_string& name = gender_value(ab.cfg, gender, "name", "female_name", "name").t_str();
290 
291  if (!name.empty()) {
292  res.emplace_back(
293  ab.cfg["id"],
294  ab.cfg["name"].t_str(),
295  name,
296  ab.cfg["description"].t_str() );
297  return true;
298  }
299  }
300  else
301  {
302  // See if an inactive name was specified.
303  const config::attribute_value& inactive_value =
304  gender_value(ab.cfg, gender, "name_inactive",
305  "female_name_inactive", "name_inactive");
306  const t_string& name = !inactive_value.blank() ? inactive_value.t_str() :
307  gender_value(ab.cfg, gender, "name", "female_name", "name").t_str();
308 
309  if (!name.empty()) {
310  res.emplace_back(
311  ab.cfg["id"],
312  default_value(ab.cfg, "name_inactive", "name").t_str(),
313  name,
314  default_value(ab.cfg, "description_inactive", "description").t_str() );
315  return true;
316  }
317  }
318 
319  return false;
320  }
321 }
322 
323 std::vector<std::tuple<std::string, t_string, t_string, t_string>> unit::ability_tooltips() const
324 {
325  std::vector<std::tuple<std::string, t_string,t_string,t_string>> res;
326 
327  for (const config::any_child &ab : this->abilities_.all_children_range())
328  {
329  add_ability_tooltip(ab, gender_, res, true);
330  }
331 
332  return res;
333 }
334 
335 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
336 {
337  std::vector<std::tuple<std::string, t_string,t_string,t_string>> res;
338  active_list.clear();
339 
340  for (const config::any_child &ab : this->abilities_.all_children_range())
341  {
342  bool active = ability_active(ab.key, ab.cfg, loc);
343  if (add_ability_tooltip(ab, gender_, res, active))
344  {
345  active_list.push_back(active);
346  }
347  }
348  return res;
349 }
350 
351 bool unit::ability_active(const std::string& ability,const config& cfg,const map_location& loc) const
352 {
353  bool illuminates = ability == "illuminates";
354 
355  if (const config &afilter = cfg.child("filter"))
356  if ( !unit_filter(vconfig(afilter)).set_use_flat_tod(illuminates).matches(*this, loc) )
357  return false;
358 
359  adjacent_loc_array_t adjacent;
360  get_adjacent_tiles(loc,adjacent.data());
361 
362  assert(display::get_singleton());
363  const unit_map& units = display::get_singleton()->get_units();
364 
365  for (const config &i : cfg.child_range("filter_adjacent"))
366  {
367  std::size_t count = 0;
368  unit_filter ufilt{ vconfig(i) };
369  ufilt.set_use_flat_tod(illuminates);
370  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
371  for (const map_location::DIRECTION index : dirs)
372  {
374  continue;
375  unit_map::const_iterator unit = units.find(adjacent[index]);
376  if (unit == units.end())
377  return false;
378  if (!ufilt(*unit, *this))
379  return false;
380  if (i.has_attribute("is_enemy")) {
382  if (i["is_enemy"].to_bool() != dc.get_team(unit->side()).is_enemy(side_)) {
383  continue;
384  }
385  }
386  count++;
387  }
388  if (i["count"].empty() && count != dirs.size()) {
389  return false;
390  }
391  if (!in_ranges<int>(count, utils::parse_ranges(i["count"].str()))) {
392  return false;
393  }
394  }
395 
396  for (const config &i : cfg.child_range("filter_adjacent_location"))
397  {
398  std::size_t count = 0;
399  terrain_filter adj_filter(vconfig(i), resources::filter_con);
400  adj_filter.flatten(illuminates);
401 
402  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
403  for (const map_location::DIRECTION index : dirs)
404  {
406  continue;
407  }
408  if(!adj_filter.match(adjacent[index])) {
409  return false;
410  }
411  count++;
412  }
413  if (i["count"].empty() && count != dirs.size()) {
414  return false;
415  }
416  if (!in_ranges<int>(count, utils::parse_ranges(i["count"].str()))) {
417  return false;
418  }
419  }
420  return true;
421 }
422 
423 bool unit::ability_affects_adjacent(const std::string& ability, const config& cfg,int dir,const map_location& loc,const unit& from) const
424 {
425  bool illuminates = ability == "illuminates";
426 
427  assert(dir >=0 && dir <= 5);
428  map_location::DIRECTION direction = static_cast<map_location::DIRECTION>(dir);
429 
430  for (const config &i : cfg.child_range("affect_adjacent"))
431  {
432  if (i.has_attribute("adjacent")) { //key adjacent defined
433  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
434  if (std::find(dirs.begin(), dirs.end(), direction) == dirs.end()) {
435  continue;
436  }
437  }
438  const config &filter = i.child("filter");
439  if (!filter || //filter tag given
440  unit_filter(vconfig(filter)).set_use_flat_tod(illuminates).matches(*this, loc, from) ) {
441  return true;
442  }
443  }
444  return false;
445 }
446 
447 bool unit::ability_affects_self(const std::string& ability,const config& cfg,const map_location& loc) const
448 {
449  const config &filter = cfg.child("filter_self");
450  bool affect_self = cfg["affect_self"].to_bool(true);
451  if (!filter || !affect_self) return affect_self;
452  return unit_filter(vconfig(filter)).set_use_flat_tod(ability == "illuminates").matches(*this, loc);
453 }
454 
455 bool unit::ability_affects_weapon(const config& cfg, const_attack_ptr weapon, bool is_opp) const
456 {
457  const std::string filter_tag_name = is_opp ? "filter_second_weapon" : "filter_weapon";
458  if(!cfg.has_child(filter_tag_name)) {
459  return true;
460  }
461  const config& filter = cfg.child(filter_tag_name);
462  if(!weapon) {
463  return false;
464  }
465  return weapon->matches_filter(filter);
466 }
467 
468 bool unit::has_ability_type(const std::string& ability) const
469 {
470  return !abilities_.child_range(ability).empty();
471 }
472 
474 {
475  if(unit_const_ptr & att = is_attacker_ ? self_ : other_) {
476  callable.add("attacker", wfl::variant(std::make_shared<wfl::unit_callable>(*att)));
477  }
478  if(unit_const_ptr & def = is_attacker_ ? self_ : other_) {
479  callable.add("defender", wfl::variant(std::make_shared<wfl::unit_callable>(*def)));
480  }
481 }
482 
483 namespace {
484 
485 
486 template<typename T, typename TFuncFormula>
487 class get_ability_value_visitor : public boost::static_visitor<T>
488 {
489 public:
490  // Constructor stores the default value.
491  get_ability_value_visitor(T def, const TFuncFormula& formula_handler) : def_(def), formula_handler_(formula_handler) {}
492 
493  T operator()(const boost::blank&) const { return def_; }
494  T operator()(bool) const { return def_; }
495  T operator()(int i) const { return static_cast<T>(i); }
496  T operator()(unsigned long long u) const { return static_cast<T>(u); }
497  T operator()(double d) const { return static_cast<T>(d); }
498  T operator()(const t_string&) const { return def_; }
499  T operator()(const std::string& s) const
500  {
501  if(s.size() >= 2 && s[0] == '(') {
502  return formula_handler_(s);
503  }
504  return lexical_cast_default<T>(s, def_);
505  }
506 
507 private:
508  const T def_;
509  const TFuncFormula& formula_handler_;
510 };
511 
512 
513 template<typename T, typename TFuncFormula>
514 get_ability_value_visitor<T, TFuncFormula> make_get_ability_value_visitor(T def, const TFuncFormula& formula_handler)
515 {
516  return get_ability_value_visitor<T, TFuncFormula>(def, formula_handler);
517 }
518 
519 template<typename T, typename TFuncFormula>
520 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)
521 {
522  return v.apply_visitor(make_get_ability_value_visitor(def, [&](const std::string& s) {
523 
524  try {
525  assert(display::get_singleton());
526  const unit_map& units = display::get_singleton()->get_units();
527 
528  auto u_itor = units.find(ability_info.teacher_loc);
529 
530  if(u_itor == units.end()) {
531  return def;
532  }
533  wfl::map_formula_callable callable(std::make_shared<wfl::unit_callable>(*u_itor));
534  if(att) {
535  att->add_formula_context(callable);
536  }
537  if (auto uptr = units.find_unit_ptr(ability_info.student_loc)) {
538  callable.add("student", wfl::variant(std::make_shared<wfl::unit_callable>(*uptr)));
539  }
540  if (auto uptr = units.find_unit_ptr(receiver_loc)) {
541  callable.add("other", wfl::variant(std::make_shared<wfl::unit_callable>(*uptr)));
542  }
543  return formula_handler(wfl::formula(s, new wfl::gamestate_function_symbol_table), callable);
544  } catch(const wfl::formula_error& e) {
545  lg::wml_error() << "Formula error in ability or weapon special: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
546  return def;
547  }
548  }));
549 }
550 }
551 
552 template<typename TComp>
553 std::pair<int,map_location> unit_ability_list::get_extremum(const std::string& key, int def, const TComp& comp) const
554 {
555  if ( cfgs_.empty() ) {
556  return std::make_pair(def, map_location());
557  }
558  // The returned location is the best non-cumulative one, if any,
559  // the best absolute cumulative one otherwise.
560  map_location best_loc;
561  bool only_cumulative = true;
562  int abs_max = 0;
563  int flat = 0;
564  int stack = 0;
565  for (const unit_ability& p : cfgs_)
566  {
567  int value = get_single_ability_value((*p.ability_cfg)[key], def, p, loc(), const_attack_ptr(), [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
568  return formula.evaluate(callable).as_int();
569  });
570 
571  if ((*p.ability_cfg)["cumulative"].to_bool()) {
572  stack += value;
573  if (value < 0) value = -value;
574  if (only_cumulative && !comp(value, abs_max)) {
575  abs_max = value;
576  best_loc = p.teacher_loc;
577  }
578  } else if (only_cumulative || comp(flat, value)) {
579  only_cumulative = false;
580  flat = value;
581  best_loc = p.teacher_loc;
582  }
583  }
584  return std::make_pair(flat + stack, best_loc);
585 }
586 
587 template std::pair<int, map_location> unit_ability_list::get_extremum<std::less<int>>(const std::string& key, int def, const std::less<int>& comp) const;
588 template std::pair<int, map_location> unit_ability_list::get_extremum<std::greater<int>>(const std::string& key, int def, const std::greater<int>& comp) const;
589 
590 /*
591  *
592  * [special]
593  * [swarm]
594  * name= _ "swarm"
595  * name_inactive= _ ""
596  * description= _ ""
597  * description_inactive= _ ""
598  * cumulative=no
599  * apply_to=self #self,opponent,defender,attacker,both
600  * #active_on=defense # or offense; omitting this means "both"
601  *
602  * swarm_attacks_max=4
603  * swarm_attacks_min=2
604  *
605  * [filter_self] // SUF
606  * ...
607  * [/filter_self]
608  * [filter_opponent] // SUF
609  * [filter_attacker] // SUF
610  * [filter_defender] // SUF
611  * [filter_adjacent] // SAUF
612  * [filter_adjacent_location] // SAUF + locs
613  * [/swarm]
614  * [/special]
615  *
616  */
617 
618 namespace {
619 
620  struct special_match
621  {
622  std::string tag_name;
623  const config* cfg;
624  };
625 
626  /**
627  * Gets the children of @parent (which should be the specials for an
628  * attack_type) and places the ones whose tag or id= matches @a id into
629  * @a tag_result and @a id_result.
630  *
631  * If @a just_peeking is set to true, then @a tag_result and @a id_result
632  * are not touched; instead the return value is used to indicate if any
633  * matching children were found.
634  *
635  * @returns true if @a just_peeking is true and a match was found;
636  * false otherwise.
637  */
638  bool get_special_children(std::vector<special_match>& tag_result,
639  std::vector<special_match>& id_result,
640  const config& parent, const std::string& id,
641  bool just_peeking=false) {
642  for (const config::any_child &sp : parent.all_children_range())
643  {
644  if (just_peeking && (sp.key == id || sp.cfg["id"] == id)) {
645  return true; // peek succeeded; done
646  }
647 
648  if(sp.key == id) {
649  special_match special = { sp.key, &sp.cfg };
650  tag_result.push_back(special);
651  }
652  if(sp.cfg["id"] == id) {
653  special_match special = { sp.key, &sp.cfg };
654  id_result.push_back(special);
655  }
656  }
657  return false;
658  }
659 
660  bool get_special_children_id(std::vector<special_match>& id_result,
661  const config& parent, const std::string& id,
662  bool just_peeking=false) {
663  for (const config::any_child &sp : parent.all_children_range())
664  {
665  if (just_peeking && (sp.cfg["id"] == id)) {
666  return true; // peek succeeded; done
667  }
668 
669  if(sp.cfg["id"] == id) {
670  special_match special = { sp.key, &sp.cfg };
671  id_result.push_back(special);
672  }
673  }
674  return false;
675  }
676 
677  bool get_special_children_tags(std::vector<special_match>& tag_result,
678  const config& parent, const std::string& id,
679  bool just_peeking=false) {
680  for (const config::any_child &sp : parent.all_children_range())
681  {
682  if (just_peeking && (sp.key == id)) {
683  return true; // peek succeeded; done
684  }
685 
686  if(sp.key == id) {
687  special_match special = { sp.key, &sp.cfg };
688  tag_result.push_back(special);
689  }
690  }
691  return false;
692  }
693 }
694 
695 /**
696  * Returns whether or not @a *this has a special with a tag or id equal to
697  * @a special. If @a simple_check is set to true, then the check is merely
698  * for being present. Otherwise (the default), the check is for a special
699  * active in the current context (see set_specials_context), including
700  * specials obtained from the opponent's attack.
701  */
702 bool attack_type::get_special_bool(const std::string& special, bool simple_check, bool special_id, bool special_tags) const
703 {
704  {
705  std::vector<special_match> special_tag_matches;
706  std::vector<special_match> special_id_matches;
707  if(special_id && special_tags){
708  if ( get_special_children(special_tag_matches, special_id_matches, specials_, special, simple_check) ) {
709  return true;
710  }
711  } else if(special_id && !special_tags){
712  if ( get_special_children_id(special_id_matches, specials_, special, simple_check) ) {
713  return true;
714  }
715  } else if(!special_id && special_tags){
716  if ( get_special_children_tags(special_tag_matches, specials_, special, simple_check) ) {
717  return true;
718  }
719  }
720  // If we make it to here, then either list.empty() or !simple_check.
721  // So if the list is not empty, then this is not a simple check and
722  // we need to check each special in the list to see if any are active.
723  if(special_tags){
724  for(const special_match& entry : special_tag_matches) {
725  if ( special_active(*entry.cfg, AFFECT_SELF, entry.tag_name) ) {
726  return true;
727  }
728  }
729  }
730  if(special_id){
731  for(const special_match& entry : special_id_matches) {
732  if ( special_active(*entry.cfg, AFFECT_SELF, entry.tag_name) ) {
733  return true;
734  }
735  }
736  }
737  }
738 
739  // Skip checking the opponent's attack?
740  if ( simple_check || !other_attack_ ) {
741  return false;
742  }
743 
744  std::vector<special_match> special_tag_matches;
745  std::vector<special_match> special_id_matches;
746  if(special_id && special_tags){
747  get_special_children(special_tag_matches, special_id_matches, other_attack_->specials_, special);
748  } else if(special_id && !special_tags){
749  get_special_children_id(special_id_matches, other_attack_->specials_, special);
750  } else if(!special_id && special_tags){
751  get_special_children_tags(special_tag_matches, other_attack_->specials_, special);
752  }
753  if(special_tags){
754  for(const special_match& entry : special_tag_matches) {
755  if ( other_attack_->special_active(*entry.cfg, AFFECT_OTHER, entry.tag_name) ) {
756  return true;
757  }
758  }
759  }
760  if(special_id){
761  for(const special_match& entry : special_id_matches) {
762  if ( other_attack_->special_active(*entry.cfg, AFFECT_OTHER, entry.tag_name) ) {
763  return true;
764  }
765  }
766  }
767  return false;
768 }
769 
770 /**
771  * Returns the currently active specials as an ability list, given the current
772  * context (see set_specials_context).
773  */
775 {
776  //log_scope("get_specials");
777  const map_location loc = self_ ? self_->get_location() : self_loc_;
778  unit_ability_list res(loc);
779 
780  for(const config& i : specials_.child_range(special)) {
781  if(special_active(i, AFFECT_SELF, special)) {
782  res.emplace_back(&i, loc, loc);
783  }
784  }
785 
786  if(!other_attack_) {
787  return res;
788  }
789 
790  for(const config& i : other_attack_->specials_.child_range(special)) {
791  if(other_attack_->special_active(i, AFFECT_OTHER, special)) {
792  res.emplace_back(&i, other_loc_, other_loc_);
793  }
794  }
795  return res;
796 }
797 
798 /**
799  * Returns a vector of names and descriptions for the specials of *this.
800  * Each std::pair in the vector has first = name and second = description.
801  *
802  * This uses either the active or inactive name/description for each special,
803  * based on the current context (see set_specials_context), provided
804  * @a active_list is not nullptr. Otherwise specials are assumed active.
805  * If the appropriate name is empty, the special is skipped.
806  */
807 std::vector<std::pair<t_string, t_string>> attack_type::special_tooltips(
808  boost::dynamic_bitset<>* active_list) const
809 {
810  //log_scope("special_tooltips");
811  std::vector<std::pair<t_string, t_string>> res;
812  if ( active_list )
813  active_list->clear();
814 
815  for (const config::any_child &sp : specials_.all_children_range())
816  {
817  if ( !active_list || special_active(sp.cfg, AFFECT_EITHER, sp.key) ) {
818  const t_string &name = sp.cfg["name"];
819  if (!name.empty()) {
820  res.emplace_back(name, sp.cfg["description"].t_str() );
821  if ( active_list )
822  active_list->push_back(true);
823  }
824  } else {
825  const t_string& name = default_value(sp.cfg, "name_inactive", "name").t_str();
826  if (!name.empty()) {
827  res.emplace_back(name, default_value(sp.cfg, "description_inactive", "description").t_str() );
828  active_list->push_back(false);
829  }
830  }
831  }
832  return res;
833 }
834 
835 /**
836  * Returns a comma-separated string of active names for the specials of *this.
837  * Empty names are skipped.
838  *
839  * This excludes inactive specials if only_active is true. Whether or not a
840  * special is active depends on the current context (see set_specials_context)
841  * and the @a is_backstab parameter.
842  */
843 std::string attack_type::weapon_specials(bool only_active, bool is_backstab) const
844 {
845  //log_scope("weapon_specials");
846  std::string res;
847  for (const config::any_child &sp : specials_.all_children_range())
848  {
849  const bool active = special_active(sp.cfg, AFFECT_EITHER, sp.key, is_backstab);
850 
851  const std::string& name =
852  active
853  ? sp.cfg["name"].str()
854  : default_value(sp.cfg, "name_inactive", "name").str();
855  if (!name.empty()) {
856  if (!res.empty()) res += ", ";
857  if (only_active && !active) res += font::span_color(font::inactive_details_color);
858  res += name;
859  if (only_active && !active) res += "</span>";
860  }
861  }
862 
863  return res;
864 }
865 
866 
867 /**
868  * Sets the context under which specials will be checked for being active.
869  * This version is appropriate if both units in a combat are known.
870  * @param[in] self A reference to the unit with this weapon.
871  * @param[in] other A reference to the other unit in the combat.
872  * @param[in] unit_loc The location of the unit with this weapon.
873  * @param[in] other_loc The location of the other unit in the combat.
874  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
875  * @param[in] other_attack The attack used by the other unit.
876  */
878  const_attack_ptr other_attack,
879  unit_const_ptr self,
880  unit_const_ptr other,
881  const map_location& unit_loc,
882  const map_location& other_loc,
883  bool attacking)
884  : parent(weapon.shared_from_this())
885 {
886  weapon.self_ = self;
887  weapon.other_ = other;
888  weapon.self_loc_ = unit_loc;
889  weapon.other_loc_ = other_loc;
890  weapon.is_attacker_ = attacking;
891  weapon.other_attack_ = other_attack;
892  weapon.is_for_listing_ = false;
893 }
894 
895 /**
896  * Sets the context under which specials will be checked for being active.
897  * This version is appropriate if there is no specific combat being considered.
898  * @param[in] self A reference to the unit with this weapon.
899  * @param[in] loc The location of the unit with this weapon.
900  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
901  */
903  : parent(weapon.shared_from_this())
904 {
905  weapon.self_ = self;
906  weapon.other_ = unit_ptr();
907  weapon.self_loc_ = loc;
909  weapon.is_attacker_ = attacking;
910  weapon.other_attack_ = nullptr;
911  weapon.is_for_listing_ = false;
912 }
913 
914 /**
915  * Sets the context under which specials will be checked for being active.
916  * This version is appropriate for theoretical units of a particular type.
917  * @param[in] self_type A reference to the type of the unit with this weapon.
918  * @param[in] loc The location of the unit with this weapon.
919  * @param[in] attacking Whether or not the unit with this weapon is the attacker.
920  */
921 attack_type::specials_context_t::specials_context_t(const attack_type& weapon, const unit_type& self_type, const map_location& loc, bool attacking)
922  : parent(weapon.shared_from_this())
923 {
924  UNUSED(self_type);
925  weapon.self_ = unit_ptr();
926  weapon.other_ = unit_ptr();
927  weapon.self_loc_ = loc;
929  weapon.is_attacker_ = attacking;
930  weapon.other_attack_ = nullptr;
931  weapon.is_for_listing_ = false;
932 }
933 
935  : parent(weapon.shared_from_this())
936 {
937  weapon.is_for_listing_ = true;
938  weapon.is_attacker_ = attacking;
939 }
940 
942 {
943  if(was_moved) return;
944  parent->self_ = unit_ptr();
945  parent->other_ = unit_ptr();
946  parent->self_loc_ = map_location::null_location();
947  parent->other_loc_ = map_location::null_location();
948  parent->is_attacker_ = false;
949  parent->other_attack_ = nullptr;
950  parent->is_for_listing_ = false;
951 }
952 
954  : parent(other.parent)
955 {
956  other.was_moved = true;
957 }
958 
959 /**
960  * Calculates the number of attacks this weapon has, considering specials.
961  * This returns two numbers because of the swarm special. The actual number of
962  * attacks depends on the unit's health and should be:
963  * min_attacks + (max_attacks - min_attacks) * (current hp) / (max hp)
964  * c.f. swarm_blows()
965  */
966 void attack_type::modified_attacks(bool is_backstab, unsigned & min_attacks,
967  unsigned & max_attacks) const
968 {
969  // Apply [attacks].
970  unit_abilities::effect attacks_effect(get_special_ability("attacks"),
971  num_attacks(), is_backstab, shared_from_this());
972  int attacks_value = attacks_effect.get_composite_value();
973 
974  if ( attacks_value < 0 ) {
975  attacks_value = num_attacks();
976  ERR_NG << "negative number of strikes after applying weapon specials" << std::endl;
977  }
978 
979  // Apply [swarm].
980  unit_ability_list swarm_specials = get_special_ability("swarm");
981  if ( !swarm_specials.empty() ) {
982  min_attacks = std::max<int>(0, swarm_specials.highest("swarm_attacks_min").first);
983  max_attacks = std::max<int>(0, swarm_specials.highest("swarm_attacks_max", attacks_value).first);
984  } else {
985  min_attacks = max_attacks = attacks_value;
986  }
987 }
988 
989 
990 /**
991  * Returns the damage per attack of this weapon, considering specials.
992  */
993 int attack_type::modified_damage(bool is_backstab) const
994 {
995  unit_abilities::effect dmg_effect(get_special_ability("damage"), damage(), is_backstab, shared_from_this());
996  int damage_value = dmg_effect.get_composite_value();
997  return damage_value;
998 }
999 
1000 
1001 namespace { // Helpers for attack_type::special_active()
1002 
1003  /**
1004  * Returns whether or not the given special affects the opponent of the unit
1005  * with the special.
1006  * @param[in] special a weapon special WML structure
1007  * @param[in] is_attacker whether or not the unit with the special is the attacker
1008  */
1009  bool special_affects_opponent(const config& special, bool is_attacker)
1010  {
1011  //log_scope("special_affects_opponent");
1012  const std::string& apply_to = special["apply_to"];
1013  if ( apply_to.empty() )
1014  return false;
1015  if ( apply_to == "both" )
1016  return true;
1017  if ( apply_to == "opponent" )
1018  return true;
1019  if ( is_attacker && apply_to == "defender" )
1020  return true;
1021  if ( !is_attacker && apply_to == "attacker" )
1022  return true;
1023  return false;
1024  }
1025 
1026  /**
1027  * Returns whether or not the given special affects the unit with the special.
1028  * @param[in] special a weapon special WML structure
1029  * @param[in] is_attacker whether or not the unit with the special is the attacker
1030  */
1031  bool special_affects_self(const config& special, bool is_attacker)
1032  {
1033  //log_scope("special_affects_self");
1034  const std::string& apply_to = special["apply_to"];
1035  if ( apply_to.empty() )
1036  return true;
1037  if ( apply_to == "both" )
1038  return true;
1039  if ( apply_to == "self" )
1040  return true;
1041  if ( is_attacker && apply_to == "attacker" )
1042  return true;
1043  if ( !is_attacker && apply_to == "defender")
1044  return true;
1045  return false;
1046  }
1047 
1048  /**
1049  * Determines if a unit/weapon combination matches the specified child
1050  * (normally a [filter_*] child) of the provided filter.
1051  * @param[in] u A unit to filter.
1052  * @param[in] u2 Another unit to filter.
1053  * @param[in] loc The presumed location of @a un_it.
1054  * @param[in] weapon The attack_type to filter.
1055  * @param[in] filter The filter containing the child filter to use.
1056  * @param[in] child_tag The tag of the child filter to use.
1057  */
1058  static bool special_unit_matches(unit_const_ptr & u,
1059  unit_const_ptr & u2,
1060  const map_location & loc,
1061  const_attack_ptr weapon,
1062  const config & filter,
1063  const bool for_listing,
1064  const std::string & child_tag)
1065  {
1066  if (for_listing && !loc.valid())
1067  // The special's context was set to ignore this unit, so assume we pass.
1068  // (This is used by reports.cpp to show active specials when the
1069  // opponent is not known. From a player's perspective, the special
1070  // is active, in that it can be used, even though the player might
1071  // need to select an appropriate opponent.)
1072  return true;
1073 
1074  const config & filter_child = filter.child(child_tag);
1075  if ( !filter_child )
1076  // The special does not filter on this unit, so we pass.
1077  return true;
1078 
1079  // If the primary unit doesn't exist, there's nothing to match
1080  if (!u) {
1081  return false;
1082  }
1083 
1084  unit_filter ufilt{vconfig(filter_child)};
1085 
1086  // If the other unit doesn't exist, try matching without it
1087 
1088 
1089  // Check for a weapon match.
1090  if ( const config & filter_weapon = filter_child.child("filter_weapon") ) {
1091  if ( !weapon || !weapon->matches_filter(filter_weapon) )
1092  return false;
1093  }
1094 
1095  // Passed.
1096  // If the other unit doesn't exist, try matching without it
1097  if (!u2) {
1098  return ufilt.matches(*u, loc);
1099  }
1100  return ufilt.matches(*u, loc, *u2);
1101  }
1102 
1103 }//anonymous namespace
1104 
1105 
1106 //The following functions are intended to allow the use in combat of capacities
1107 //identical to special weapons and therefore to be able to use them on adjacent
1108 //units (abilities of type 'aura') or else on all types of weapons even if the
1109 //beneficiary unit does not have a corresponding weapon
1110 //(defense against ranged weapons abilities for a unit that only has melee attacks)
1111 
1113 {
1114  const map_location loc = self_ ? self_->get_location() : self_loc_;
1115  unit_ability_list abil_list(loc);
1116  unit_ability_list abil_other_list(loc);
1117  if(self_) {
1118  abil_list.append((*self_).get_abilities(ability, self_loc_));
1119  for(unit_ability_list::iterator i = abil_list.begin(); i != abil_list.end();) {
1120  if(!special_active(*i->ability_cfg, AFFECT_SELF, ability, true, "filter_student")) {
1121  i = abil_list.erase(i);
1122  } else {
1123  ++i;
1124  }
1125  }
1126  }
1127 
1128  if(other_) {
1129  abil_other_list.append((*other_).get_abilities(ability, other_loc_));
1130  for(unit_ability_list::iterator i = abil_other_list.begin(); i != abil_other_list.end();) {
1131  if(!special_active_impl(other_attack_, shared_from_this(), *i->ability_cfg, AFFECT_OTHER, ability, true, "filter_student")) {
1132  i = abil_other_list.erase(i);
1133  } else {
1134  ++i;
1135  }
1136  }
1137  }
1138  abil_list.append(abil_other_list);
1139  return abil_list;
1140 }
1141 
1143 {
1144  unit_ability_list abil_list = list_ability(ability);
1145  abil_list.append(get_specials(ability));
1146  return abil_list;
1147 }
1148 
1149 bool attack_type::bool_ability(const std::string& ability) const
1150 {
1151  bool abil_bool = get_special_bool(ability);
1152  unit_ability_list abil = list_ability(ability);
1153  if(!abil.empty()) {
1154  abil_bool = true;
1155  }
1156  return abil_bool;
1157 }
1158 //end of emulate weapon special functions.
1159 
1160 bool attack_type::special_active(const config& special, AFFECTS whom, const std::string& tag_name,
1161  bool include_backstab, const std::string& filter_self) const
1162 {
1163  return special_active_impl(shared_from_this(), other_attack_, special, whom, tag_name, include_backstab, filter_self);
1164 }
1165 
1166 /**
1167  * Returns whether or not the given special is active for the specified unit,
1168  * based on the current context (see set_specials_context).
1169  * @param[in] special a weapon special WML structure
1170  * @param[in] whom specifies which combatant we care about
1171  * @param[in] tag_name tag name of the special config
1172  * @param[in] include_backstab false if backstab specials should not be active
1173  * (usually true since backstab is usually accounted
1174  * for elsewhere)
1175  */
1176 bool attack_type::special_active_impl(const_attack_ptr self_attack, const_attack_ptr other_attack, const config& special, AFFECTS whom, const std::string& tag_name,
1177  bool include_backstab, const std::string& filter_self)
1178 {
1179  assert(self_attack || other_attack);
1180  bool is_attacker = self_attack ? self_attack->is_attacker_ : !other_attack->is_attacker_;
1181  bool is_for_listing = self_attack ? self_attack->is_for_listing_ : other_attack->is_for_listing_;
1182  //log_scope("special_active");
1183 
1184  // Backstab check
1185  if ( !include_backstab )
1186  if ( special["backstab"].to_bool() )
1187  return false;
1188 
1189  // Does this affect the specified unit?
1190  if ( whom == AFFECT_SELF ) {
1191  if ( !special_affects_self(special, is_attacker) )
1192  return false;
1193  }
1194  if ( whom == AFFECT_OTHER ) {
1195  if ( !special_affects_opponent(special, is_attacker) )
1196  return false;
1197  }
1198 
1199  // Is this active on attack/defense?
1200  const std::string & active_on = special["active_on"];
1201  if ( !active_on.empty() ) {
1202  if ( is_attacker && active_on != "offense" )
1203  return false;
1204  if ( !is_attacker && active_on != "defense" )
1205  return false;
1206  }
1207 
1208  // Get the units involved.
1209  assert(display::get_singleton());
1210  const unit_map& units = display::get_singleton()->get_units();
1211 
1212  unit_const_ptr self = self_attack ? self_attack->self_ : other_attack->other_;
1213  unit_const_ptr other = self_attack ? self_attack->other_ : other_attack->self_;
1214  map_location self_loc = self_attack ? self_attack->self_loc_ : other_attack->other_loc_;
1215  map_location other_loc = self_attack ? self_attack->other_loc_ : other_attack->self_loc_;
1216  //TODO: why is this needed?
1217  if(self == nullptr) {
1218  unit_map::const_iterator it = units.find(self_loc);
1219  if(it.valid()) {
1220  self = it.get_shared_ptr();
1221  }
1222  }
1223  if(other == nullptr) {
1224  unit_map::const_iterator it = units.find(other_loc);
1225  if(it.valid()) {
1226  other = it.get_shared_ptr();
1227  }
1228  }
1229 
1230  // Make sure they're facing each other.
1231  temporary_facing self_facing(self, self_loc.get_relative_dir(other_loc));
1232  temporary_facing other_facing(other, other_loc.get_relative_dir(self_loc));
1233 
1234  // Filter poison, plague, drain, first strike
1235  if (tag_name == "drains" && other && other->get_state("undrainable")) {
1236  return false;
1237  }
1238  if (tag_name == "plague" && other &&
1239  (other->get_state("unplagueable") ||
1240  resources::gameboard->map().is_village(other_loc))) {
1241  return false;
1242  }
1243  if (tag_name == "poison" && other &&
1244  (other->get_state("unpoisonable") || other->get_state(unit::STATE_POISONED))) {
1245  return false;
1246  }
1247  if (tag_name == "firststrike" && !is_attacker && other_attack &&
1248  other_attack->get_special_bool("firststrike", false)) {
1249  return false;
1250  }
1251 
1252 
1253  // Translate our context into terms of "attacker" and "defender".
1254  unit_const_ptr & att = is_attacker ? self : other;
1255  unit_const_ptr & def = is_attacker ? other : self;
1256  const map_location & att_loc = is_attacker ? self_loc : other_loc;
1257  const map_location & def_loc = is_attacker ? other_loc : self_loc;
1258  const_attack_ptr att_weapon = is_attacker ? self_attack : other_attack;
1259  const_attack_ptr def_weapon = is_attacker ? other_attack : self_attack;
1260 
1261  // Filter the units involved.
1262  if (!special_unit_matches(self, other, self_loc, self_attack, special, is_for_listing, filter_self))
1263  return false;
1264  if (!special_unit_matches(other, self, other_loc, other_attack, special, is_for_listing, "filter_opponent"))
1265  return false;
1266  if (!special_unit_matches(att, def, att_loc, att_weapon, special, is_for_listing, "filter_attacker"))
1267  return false;
1268  if (!special_unit_matches(def, att, def_loc, def_weapon, special, is_for_listing, "filter_defender"))
1269  return false;
1270 
1271  adjacent_loc_array_t adjacent;
1272  get_adjacent_tiles(self_loc, adjacent.data());
1273 
1274  // Filter the adjacent units.
1275  for (const config &i : special.child_range("filter_adjacent"))
1276  {
1277  std::size_t count = 0;
1278  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
1279  unit_filter filter{ vconfig(i) };
1280  for (const map_location::DIRECTION index : dirs)
1281  {
1283  continue;
1284  unit_map::const_iterator unit = units.find(adjacent[index]);
1285  if (unit == units.end() || !filter.matches(*unit, adjacent[index], *self))
1286  return false;
1287  if (i.has_attribute("is_enemy")) {
1289  if (i["is_enemy"].to_bool() != dc.get_team(unit->side()).is_enemy(self->side())) {
1290  continue;
1291  }
1292  }
1293  count++;
1294  }
1295  if (i["count"].empty() && count != dirs.size()) {
1296  return false;
1297  }
1298  if (!in_ranges<int>(count, utils::parse_ranges(i["count"].str()))) {
1299  return false;
1300  }
1301  }
1302 
1303  // Filter the adjacent locations.
1304  for (const config &i : special.child_range("filter_adjacent_location"))
1305  {
1306  std::size_t count = 0;
1307  std::vector<map_location::DIRECTION> dirs = map_location::parse_directions(i["adjacent"]);
1308  terrain_filter adj_filter(vconfig(i), resources::filter_con);
1309  for (const map_location::DIRECTION index : dirs)
1310  {
1312  continue;
1313  if(!adj_filter.match(adjacent[index])) {
1314  return false;
1315  }
1316  count++;
1317  }
1318  if (i["count"].empty() && count != dirs.size()) {
1319  return false;
1320  }
1321  if (!in_ranges<int>(count, utils::parse_ranges(i["count"].str()))) {
1322  return false;
1323  }
1324  }
1325 
1326  return true;
1327 }
1328 
1329 
1330 
1332 {
1333 
1334 void individual_effect::set(value_modifier t, int val, const config *abil, const map_location &l)
1335 {
1336  type=t;
1337  value=val;
1338  ability=abil;
1339  loc=l;
1340 }
1341 
1342 bool filter_base_matches(const config& cfg, int def)
1343 {
1344  if (const config &apply_filter = cfg.child("filter_base_value")) {
1345  config::attribute_value cond_eq = apply_filter["equals"];
1346  config::attribute_value cond_ne = apply_filter["not_equals"];
1347  config::attribute_value cond_lt = apply_filter["less_than"];
1348  config::attribute_value cond_gt = apply_filter["greater_than"];
1349  config::attribute_value cond_ge = apply_filter["greater_than_equal_to"];
1350  config::attribute_value cond_le = apply_filter["less_than_equal_to"];
1351  return (cond_eq.empty() || def == cond_eq.to_int()) &&
1352  (cond_ne.empty() || def != cond_ne.to_int()) &&
1353  (cond_lt.empty() || def < cond_lt.to_int()) &&
1354  (cond_gt.empty() || def > cond_gt.to_int()) &&
1355  (cond_ge.empty() || def >= cond_ge.to_int()) &&
1356  (cond_le.empty() || def <= cond_le.to_int());
1357  }
1358  return true;
1359 }
1360 
1361 effect::effect(const unit_ability_list& list, int def, bool backstab, const_attack_ptr att) :
1362  effect_list_(),
1363  composite_value_(0)
1364 {
1365 
1366  int value_set = def;
1367  std::map<std::string,individual_effect> values_add;
1368  std::map<std::string,individual_effect> values_mul;
1369  std::map<std::string,individual_effect> values_div;
1370 
1371  individual_effect set_effect_max;
1372  individual_effect set_effect_min;
1373 
1374  for (const unit_ability & ability : list) {
1375  const config& cfg = *ability.ability_cfg;
1376  const std::string& effect_id = cfg[cfg["id"].empty() ? "name" : "id"];
1377 
1378  if (!cfg["backstab"].blank()) {
1379  deprecated_message("backstab= in weapon specials", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Use [filter_adjacent] instead.");
1380  }
1381 
1382  if (!backstab && cfg["backstab"].to_bool())
1383  continue;
1384  if (!filter_base_matches(cfg, def))
1385  continue;
1386 
1387  if (const config::attribute_value *v = cfg.get("value")) {
1388  int value = get_single_ability_value(*v, def, ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
1389  callable.add("base_value", wfl::variant(def));
1390  return formula.evaluate(callable).as_int();
1391  });
1392 
1393  int value_cum = cfg["cumulative"].to_bool() ? std::max(def, value) : value;
1394  assert((set_effect_min.type != NOT_USED) == (set_effect_max.type != NOT_USED));
1395  if(set_effect_min.type == NOT_USED) {
1396  set_effect_min.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
1397  set_effect_max.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
1398  }
1399  else {
1400  if(value_cum > set_effect_max.value) {
1401  set_effect_max.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
1402  }
1403  if(value_cum < set_effect_min.value) {
1404  set_effect_min.set(SET, value_cum, ability.ability_cfg, ability.teacher_loc);
1405  }
1406  }
1407  }
1408 
1409  if (const config::attribute_value *v = cfg.get("add")) {
1410  int add = get_single_ability_value(*v, def, ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
1411  callable.add("base_value", wfl::variant(def));
1412  return formula.evaluate(callable).as_int();
1413  });
1414  std::map<std::string,individual_effect>::iterator add_effect = values_add.find(effect_id);
1415  if(add_effect == values_add.end() || add > add_effect->second.value) {
1416  values_add[effect_id].set(ADD, add, ability.ability_cfg, ability.teacher_loc);
1417  }
1418  }
1419  if (const config::attribute_value *v = cfg.get("sub")) {
1420  int sub = - get_single_ability_value(*v, def, ability, list.loc(), att, [&](const wfl::formula& formula, wfl::map_formula_callable& callable) {
1421  callable.add("base_value", wfl::variant(def));
1422  return formula.evaluate(callable).as_int();
1423  });
1424  std::map<std::string,individual_effect>::iterator sub_effect = values_add.find(effect_id);
1425  if(sub_effect == values_add.end() || sub < sub_effect->second.value) {
1426  values_add[effect_id].set(ADD, sub, ability.ability_cfg, ability.teacher_loc);
1427  }
1428  }
1429  if (const config::attribute_value *v = cfg.get("multiply")) {
1430  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) {
1431  callable.add("base_value", wfl::variant(def));
1432  return formula.evaluate(callable).as_decimal() / 1000.0 ;
1433  }) * 100);
1434  std::map<std::string,individual_effect>::iterator mul_effect = values_mul.find(effect_id);
1435  if(mul_effect == values_mul.end() || multiply > mul_effect->second.value) {
1436  values_mul[effect_id].set(MUL, multiply, ability.ability_cfg, ability.teacher_loc);
1437  }
1438  }
1439  if (const config::attribute_value *v = cfg.get("divide")) {
1440  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) {
1441  callable.add("base_value", wfl::variant(def));
1442  return formula.evaluate(callable).as_decimal() / 1000.0 ;
1443  }) * 100);
1444 
1445  if (divide == 0) {
1446  ERR_NG << "division by zero with divide= in ability/weapon special " << effect_id << std::endl;
1447  }
1448  else {
1449  std::map<std::string,individual_effect>::iterator div_effect = values_div.find(effect_id);
1450  if(div_effect == values_div.end() || divide > div_effect->second.value) {
1451  values_div[effect_id].set(DIV, divide, ability.ability_cfg, ability.teacher_loc);
1452  }
1453  }
1454  }
1455  }
1456 
1457  if(set_effect_max.type != NOT_USED) {
1458  value_set = std::max(set_effect_max.value, 0) + std::min(set_effect_min.value, 0);
1459  if(set_effect_max.value > def) {
1460  effect_list_.push_back(set_effect_max);
1461  }
1462  if(set_effect_min.value < def) {
1463  effect_list_.push_back(set_effect_min);
1464  }
1465  }
1466 
1467  /* Do multiplication with floating point values rather than integers
1468  * We want two places of precision for each multiplier
1469  * Using integers multiplied by 100 to keep precision causes overflow
1470  * after 3-4 abilities for 32-bit values and ~8 for 64-bit
1471  * Avoiding the overflow by dividing after each step introduces rounding errors
1472  * that may vary depending on the order effects are applied
1473  * As the final values are likely <1000 (always true for mainline), loss of less significant digits is not an issue
1474  */
1475  double multiplier = 1.0;
1476  double divisor = 1.0;
1477 
1478  for(const auto& val : values_mul) {
1479  multiplier *= val.second.value/100.0;
1480  effect_list_.push_back(val.second);
1481  }
1482 
1483  for(const auto& val : values_div) {
1484  divisor *= val.second.value/100.0;
1485  effect_list_.push_back(val.second);
1486  }
1487 
1488  int addition = 0;
1489  for(const auto& val : values_add) {
1490  addition += val.second.value;
1491  effect_list_.push_back(val.second);
1492  }
1493 
1494  composite_value_ = static_cast<int>((value_set + addition) * multiplier / divisor);
1495 }
1496 
1497 } // end namespace unit_abilities
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:843
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:455
std::vector< individual_effect > effect_list_
Definition: abilities.hpp:55
bool empty() const
Definition: unit.hpp:101
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:429
#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
bool special_active(const config &special, AFFECTS whom, const std::string &tag_name, bool include_backstab=true, const std::string &filter_self="filter_self") const
Definition: abilities.cpp:1160
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
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:129
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:110
iterator erase(const iterator &erase_it)
Definition: unit.hpp:107
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:1334
child_itors child_range(config_key_type key)
Definition: config.cpp:362
map_location student_loc
Used by the formula in the ability.
Definition: unit.hpp:65
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:1112
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:966
The unit is poisoned - it loses health each turn.
Definition: unit.hpp:862
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:1142
bool has_ability_type(const std::string &ability) const
Check if the unit has an ability of a specific type.
Definition: abilities.cpp:468
unit_const_ptr other_
const std::string & type() const
Definition: attack_type.hpp:44
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
std::string str
Definition: statement.cpp:110
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
std::shared_ptr< unit > unit_ptr
Definition: ptr.hpp:28
int num_attacks() const
Definition: attack_type.hpp:53
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:323
A single unit type that the player may recruit.
Definition: types.hpp:44
bool filter_base_matches(const config &cfg, int def)
Definition: abilities.cpp:1342
std::vector< unit_ability >::iterator iterator
Definition: unit.hpp:92
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:29
bool bool_ability(const std::string &ability) const
return an boolean value for abilities like poison slow firstrike or petrifies
Definition: abilities.cpp:1149
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
Data typedef for unit_ability_list.
Definition: unit.hpp:52
map_location teacher_loc
The location of the teacher, that is the unit who owns the ability tags (differnt from student becaus...
Definition: unit.hpp:68
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
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:423
filter_context * filter_con
Definition: resources.cpp:23
bool valid() const
Definition: location.hpp:93
specials_context_t(const attack_type &weapon, bool attacking)
Initialize weapon specials context for listing.
Definition: abilities.cpp:934
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:807
map_display and display: classes which take care of displaying the map and game-data on the screen...
std::pair< int, map_location > get_extremum(const std::string &key, int def, const TComp &comp) const
Definition: abilities.cpp:553
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:774
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:40
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
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:291
bool is_for_listing_
int damage() const
Definition: attack_type.hpp:52
mock_party p
static map_location::DIRECTION s
iterator begin()
Definition: unit.hpp:95
std::string name
Definition: sdl_ttf.cpp:70
unit_const_ptr self_
int modified_damage(bool is_backstab) const
Returns the damage per attack of this weapon, considering specials.
Definition: abilities.cpp:993
const display_context & get_disp_context() const
Definition: display.hpp:168
int get_composite_value() const
Definition: abilities.hpp:48
void append(const unit_ability_list &other)
Appens the abilities from other to this, ignores other.loc()
Definition: unit.hpp:115
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
void add_formula_context(wfl::map_formula_callable &) const
Definition: abilities.cpp:473
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:447
config & cfg
Definition: config.hpp:525
static bool special_active_impl(const_attack_ptr self_attack, const_attack_ptr other_attack, const config &special, AFFECTS whom, const std::string &tag_name, bool include_backstab=true, const std::string &filter_self="filter_self")
Returns whether or not the given special is active for the specified unit, based on the current conte...
Definition: abilities.cpp:1176
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
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:702
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:239
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
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:226
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:351
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:36
bind set(MYSQL_TYPE_BLOB, nullptr, static_cast< long unsigned int >(size))
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:97
std::pair< int, map_location > highest(const std::string &key, int def=0) const
Definition: unit.hpp:79